[med-svn] [artemis] 01/02: Imported Upstream version 16.0.0+dfsg
Afif Elghraoui
afif-guest at moszumanska.debian.org
Mon Sep 21 07:34:26 UTC 2015
This is an automated email from the git hooks/post-receive script.
afif-guest pushed a commit to branch master
in repository artemis.
commit e4831b5e2f83bc88a422d2578be64f8c7e8d797a
Author: Afif Elghraoui <afif at ghraoui.name>
Date: Sun Sep 20 19:49:16 2015 -0700
Imported Upstream version 16.0.0+dfsg
---
.classpath | 27 +
.gitignore | 1 +
.project | 17 +
ChangeLog | 418 +
INSTALL | 33 +
META-INF/MANIFEST.MF | 2 +
Makefile | 337 +
README | 55 +
act | 148 +
act.command | 103 +
art | 167 +
art.bat | 9 +
artemis_sqlmap/Analysis.xml | 47 +
artemis_sqlmap/AnalysisFeature.xml | 61 +
artemis_sqlmap/Cv.xml | 32 +
artemis_sqlmap/CvTerm.xml | 75 +
artemis_sqlmap/Db.xml | 40 +
artemis_sqlmap/DbXRef.xml | 72 +
artemis_sqlmap/Feature.xml | 593 +
artemis_sqlmap/FeatureCvTerm.xml | 167 +
artemis_sqlmap/FeatureCvTermDbXRef.xml | 67 +
artemis_sqlmap/FeatureCvTermProp.xml | 29 +
artemis_sqlmap/FeatureCvTermPub.xml | 55 +
artemis_sqlmap/FeatureDbXRef.xml | 91 +
artemis_sqlmap/FeatureLoc.xml | 121 +
artemis_sqlmap/FeatureProp.xml | 106 +
artemis_sqlmap/FeaturePub.xml | 82 +
artemis_sqlmap/FeatureRelationship.xml | 170 +
artemis_sqlmap/FeatureSynonym.xml | 150 +
artemis_sqlmap/Graph.xml | 50 +
artemis_sqlmap/Organism.xml | 94 +
artemis_sqlmap/OrganismProp.xml | 20 +
artemis_sqlmap/Pub.xml | 70 +
artemis_sqlmap/PubDbXRef.xml | 39 +
artemis_sqlmap/Synonym.xml | 51 +
artemis_sqlmap/chado_iBatis_config.properties | 9 +
artemis_sqlmap/chado_iBatis_config.xml | 104 +
build.xml | 166 +
corba/apollo.idl | 206 +
corba/ensembl.idl | 53 +
corba/nsdb.idl | 560 +
corba/nsdb_write.idl | 260 +
corba/seqdb.idl | 131 +
corba/types.idl | 334 +
dnaplotter | 79 +
docs/Makefile | 76 +
docs/acknowledgments.sgml | 17 +
docs/act_display_menu.sgml | 23 +
docs/act_file_menu.sgml | 166 +
docs/act_intro_chapter.sgml | 128 +
docs/act_main_window.sgml | 338 +
docs/act_main_window_comp.gif | Bin 0 -> 17492 bytes
docs/act_main_window_menu.gif | Bin 0 -> 5666 bytes
docs/act_main_window_query.gif | Bin 0 -> 11234 bytes
docs/act_main_window_subject.gif | Bin 0 -> 11806 bytes
docs/act_manual.sgml | 85 +
docs/act_start_chapter.sgml | 230 +
docs/act_user_manual_screen_shot.gif | Bin 0 -> 12942 bytes
docs/artemis.dsl | 145 +
docs/chado/ACT.html | 27 +
docs/chado/ActSelection.gif | Bin 0 -> 20917 bytes
docs/chado/ActSelection2seqs.gif | Bin 0 -> 14691 bytes
docs/chado/AddCV.gif | Bin 0 -> 10593 bytes
docs/chado/Artemis.gif | Bin 0 -> 69331 bytes
docs/chado/ArtemisLogin.gif | Bin 0 -> 12538 bytes
docs/chado/CV.gif | Bin 0 -> 37748 bytes
docs/chado/GMOD2009SummerSchool.shtml | 342 +
docs/chado/Pf10_Pk6.gif | Bin 0 -> 82454 bytes
docs/chado/Pk6_Pf10_Pk8.gif | Bin 0 -> 88416 bytes
docs/chado/Properties.gif | Bin 0 -> 8695 bytes
docs/chado/TAT.gif | Bin 0 -> 38113 bytes
docs/chado/addterm.gif | Bin 0 -> 12851 bytes
docs/chado/admin.shtml | 191 +
docs/chado/chado_gene_model.gif | Bin 0 -> 12291 bytes
docs/chado/commit.gif | Bin 0 -> 1829 bytes
docs/chado/databasemanager.gif | Bin 0 -> 23062 bytes
docs/chado/databasemanager_example.gif | Bin 0 -> 23274 bytes
docs/chado/dbloading.shtml | 93 +
docs/chado/editor.gif | Bin 0 -> 110242 bytes
docs/chado/genebuilder.gif | Bin 0 -> 52396 bytes
docs/chado/genebuilder2.gif | Bin 0 -> 37438 bytes
docs/chado/index.shtml | 30 +
docs/chado/login.gif | Bin 0 -> 15218 bytes
docs/chado/logviewer.gif | Bin 0 -> 70712 bytes
docs/chado/overview.shtml | 324 +
docs/chado/prompt1.png | Bin 0 -> 49752 bytes
docs/chado/prompt2.png | Bin 0 -> 38503 bytes
docs/chado/storage.html | 296 +
docs/chado/writedb_entry.html | 137 +
docs/concepts.sgml | 150 +
docs/copyright.sgml | 284 +
docs/display_menu.sgml | 32 +
docs/display_menu_common.sgml | 160 +
docs/feature_edit.eps | 27141 +++++++++++++++++++
docs/feature_edit.gif | Bin 0 -> 30763 bytes
docs/feature_list.sgml | 119 +
docs/file_menu.sgml | 274 +
docs/filetypes.sgml | 100 +
docs/fm.gif | Bin 0 -> 19925 bytes
docs/fm_login.gif | Bin 0 -> 20114 bytes
docs/fm_popup.gif | Bin 0 -> 48931 bytes
docs/getting_java.sgml | 32 +
docs/intro_chapter.sgml | 137 +
docs/jvm_options.sgml | 132 +
docs/login.gif | Bin 0 -> 15114 bytes
docs/mac_osx_dmg.gif | Bin 0 -> 12720 bytes
docs/main_window_1.eps | 1464 +
docs/main_window_1.gif | Bin 0 -> 812 bytes
docs/main_window_1.png | Bin 0 -> 4576 bytes
docs/main_window_2.eps | 900 +
docs/main_window_2.gif | Bin 0 -> 766 bytes
docs/main_window_2.png | Bin 0 -> 4816 bytes
docs/main_window_3.eps | 1746 ++
docs/main_window_3.gif | Bin 0 -> 1107 bytes
docs/main_window_3.png | Bin 0 -> 2460 bytes
docs/main_window_4.eps | 14624 ++++++++++
docs/main_window_4.gif | Bin 0 -> 18900 bytes
docs/main_window_4.png | Bin 0 -> 9725 bytes
docs/main_window_5.eps | 8185 ++++++
docs/main_window_5.gif | Bin 0 -> 5878 bytes
docs/main_window_5.png | Bin 0 -> 13558 bytes
docs/main_window_6.eps | 8138 ++++++
docs/main_window_6.gif | Bin 0 -> 6371 bytes
docs/main_window_6.png | Bin 0 -> 42812 bytes
docs/main_window_chapter.sgml | 269 +
docs/manual.sgml | 91 +
docs/menus.sgml | 1894 ++
docs/mousebuttons.sgml | 21 +
docs/next_gen.sgml | 97 +
docs/options.sgml | 510 +
docs/options_menu.sgml | 84 +
docs/projMgr.png | Bin 0 -> 40664 bytes
docs/project_manager.sgml | 59 +
docs/psu.css | 12 +
docs/putty1.gif | Bin 0 -> 42181 bytes
docs/putty2.gif | Bin 0 -> 46186 bytes
docs/putty3.gif | Bin 0 -> 54999 bytes
docs/putty4.gif | Bin 0 -> 35744 bytes
docs/putty5.gif | Bin 0 -> 47923 bytes
docs/putty6.gif | Bin 0 -> 47242 bytes
docs/putty7.gif | Bin 0 -> 19748 bytes
docs/requirements.sgml | 10 +
docs/selector.eps | 804 +
docs/selector.gif | Bin 0 -> 25812 bytes
docs/ssh_chapter.sgml | 346 +
docs/start_chapter.sgml | 179 +
docs/unix_args.sgml | 31 +
docs/user_manual_screen_shot.gif | Bin 0 -> 113596 bytes
docs/vcf.png | Bin 0 -> 86299 bytes
docs/views_directedit.sgml | 12 +
docs/views_popup.sgml | 196 +
docs/views_scale.sgml | 16 +
docs/views_scrolling.sgml | 10 +
docs/views_selection.sgml | 28 +
etc/af063097.embl | 1903 ++
etc/af063097.fasta | 561 +
etc/af063097_v_b132222.crunch | 199 +
etc/b132222.embl | 1512 ++
etc/b132222.fasta | 512 +
etc/c1215.blastn.crunch | 126 +
etc/c1215.blastn.tab | 630 +
etc/c1215.embl | 258 +
etc/c1215.genbank | 275 +
etc/c1215.seq | 110 +
etc/chado_extra.sql | 65 +
etc/feature_keys | 68 +
etc/feature_keys_gff | 81 +
etc/gene_builder | 103 +
etc/go_associations.pl | 114 +
etc/key_mapping | 26 +
etc/log4j.properties | 86 +
etc/log4j.properties.txt | 31 +
etc/options | 898 +
etc/pombe.usage | 20 +
etc/project.properties | 19 +
etc/qualifier_mapping | 37 +
etc/qualifier_types | 120 +
etc/qualifier_types_gff | 144 +
etc/readmeDNADraw.html | 77 +
etc/results_to_netscape | 272 +
etc/run_blastn | 98 +
etc/run_blastn.sanger | 122 +
etc/run_blastp | 99 +
etc/run_blastp.sanger | 437 +
etc/run_blastp.sanger.linux | 420 +
etc/run_blastx | 98 +
etc/run_blastx.sanger | 121 +
etc/run_clustalx | 41 +
etc/run_clustalx.sanger | 59 +
etc/run_fasta | 121 +
etc/run_fasta.sanger | 470 +
etc/run_fasta.sanger.linux | 447 +
etc/run_fastx | 89 +
etc/run_fastx.sanger | 103 +
etc/run_hth | 148 +
etc/run_jalview | 250 +
etc/run_netblastp | 93 +
etc/run_pepstats | 77 +
etc/run_sigcleave | 78 +
etc/run_smart | 95 +
etc/run_tblastn | 115 +
etc/run_tblastx | 116 +
etc/run_tblastx.sanger | 132 +
etc/versions | 4 +
etc/writedb_entry | 71 +
images/PSUlogo.gif | Bin 0 -> 8763 bytes
images/act.gif | Bin 0 -> 595 bytes
images/helix.gif | Bin 0 -> 2172 bytes
images/icon.gif | Bin 0 -> 392 bytes
images/sanger-centre.gif | Bin 0 -> 775 bytes
org/gmod/schema/analysis/Analysis.java | 242 +
org/gmod/schema/analysis/AnalysisFeature.java | 176 +
org/gmod/schema/analysis/AnalysisProp.java | 104 +
org/gmod/schema/cv/Cv.java | 111 +
org/gmod/schema/cv/CvTerm.java | 648 +
org/gmod/schema/cv/CvTermDbXRef.java | 103 +
org/gmod/schema/cv/CvTermPath.java | 139 +
org/gmod/schema/cv/CvTermProp.java | 118 +
org/gmod/schema/cv/CvTermRelationship.java | 113 +
org/gmod/schema/cv/CvTermSynonym.java | 101 +
org/gmod/schema/cv/DbXRefProp.java | 120 +
org/gmod/schema/dao/BaseDaoI.java | 51 +
org/gmod/schema/dao/CvDaoI.java | 117 +
org/gmod/schema/dao/GeneralDaoI.java | 55 +
org/gmod/schema/dao/OrganismDaoI.java | 58 +
org/gmod/schema/dao/PhylogenyDaoI.java | 45 +
org/gmod/schema/dao/PubDaoI.java | 65 +
org/gmod/schema/dao/SchemaDaoI.java | 55 +
org/gmod/schema/dao/SequenceDaoI.java | 259 +
org/gmod/schema/general/Db.java | 136 +
org/gmod/schema/general/DbXRef.java | 292 +
org/gmod/schema/general/Project.java | 59 +
org/gmod/schema/general/Tableinfo.java | 148 +
org/gmod/schema/organism/Organism.java | 207 +
org/gmod/schema/organism/OrganismDbXRef.java | 85 +
org/gmod/schema/organism/OrganismProp.java | 124 +
org/gmod/schema/phylogeny/Phylonode.java | 230 +
org/gmod/schema/phylogeny/PhylonodeDbXRef.java | 80 +
org/gmod/schema/phylogeny/PhylonodeOrganism.java | 80 +
org/gmod/schema/phylogeny/PhylonodeProp.java | 104 +
org/gmod/schema/phylogeny/PhylonodePub.java | 80 +
.../schema/phylogeny/PhylonodeRelationship.java | 110 +
org/gmod/schema/phylogeny/Phylotree.java | 131 +
org/gmod/schema/phylogeny/PhylotreePub.java | 80 +
org/gmod/schema/pub/Pub.java | 555 +
org/gmod/schema/pub/PubAuthor.java | 155 +
org/gmod/schema/pub/PubDbXRef.java | 117 +
org/gmod/schema/pub/PubProp.java | 145 +
org/gmod/schema/pub/PubRelationship.java | 104 +
org/gmod/schema/sequence/Feature.java | 509 +
org/gmod/schema/sequence/FeatureCvTerm.java | 169 +
org/gmod/schema/sequence/FeatureCvTermDbXRef.java | 76 +
org/gmod/schema/sequence/FeatureCvTermProp.java | 175 +
org/gmod/schema/sequence/FeatureCvTermPub.java | 78 +
org/gmod/schema/sequence/FeatureDbXRef.java | 93 +
org/gmod/schema/sequence/FeatureLoc.java | 251 +
org/gmod/schema/sequence/FeatureLocPub.java | 62 +
org/gmod/schema/sequence/FeatureProp.java | 122 +
org/gmod/schema/sequence/FeaturePropPub.java | 65 +
org/gmod/schema/sequence/FeaturePub.java | 77 +
org/gmod/schema/sequence/FeatureRelationship.java | 160 +
.../schema/sequence/FeatureRelationshipProp.java | 78 +
.../sequence/FeatureRelationshipPropPub.java | 63 +
.../schema/sequence/FeatureRelationshipPub.java | 63 +
org/gmod/schema/sequence/FeatureSynonym.java | 127 +
org/gmod/schema/sequence/Synonym.java | 113 +
org/gmod/schema/utils/CollectionUtils.java | 27 +
org/gmod/schema/utils/CountedName.java | 55 +
org/gmod/schema/utils/PeptideProperties.java | 76 +
org/gmod/schema/utils/Rankable.java | 16 +
org/gmod/schema/utils/propinterface/PropertyI.java | 13 +
test/build-test.xml | 117 +
test/data/MAL1.embl.gz | Bin 0 -> 253555 bytes
test/data/MAL1_8_16_24h.raw.bcf | Bin 0 -> 4364 bytes
test/data/MAL1_8_16_24h.raw.bcf.bci | Bin 0 -> 199 bytes
test/data/Pf3D7_01_02_v3.gff.gz | Bin 0 -> 464782 bytes
test/data/foo.restrict | 41 +
test/data/plot/base_position.plot.gz | Bin 0 -> 191 bytes
test/data/plot/base_position_labels.plot.gz | Bin 0 -> 215 bytes
test/data/plot/index_tab_sorted.plot.gz | Bin 0 -> 195 bytes
test/data/plot/index_tab_sorted.plot.gz.tbi | Bin 0 -> 88 bytes
test/data/project.properties | 20 +
test/data/test.embl.gz | Bin 0 -> 974620 bytes
test/data/test.gff.gz | Bin 0 -> 60671 bytes
test/data/test.vcf.gz | Bin 0 -> 37250 bytes
test/data/test.vcf.gz.tbi | Bin 0 -> 744 bytes
test/data/test1.vcf.gz | Bin 0 -> 551 bytes
test/data/test1.vcf.gz.tbi | Bin 0 -> 139 bytes
test/data/test2.vcf.gz | Bin 0 -> 393 bytes
test/data/test2.vcf.gz.tbi | Bin 0 -> 146 bytes
test/data/test3.vcf.gz | Bin 0 -> 396 bytes
test/data/test3.vcf.gz.tbi | Bin 0 -> 140 bytes
test/data/test4.vcf.gz | Bin 0 -> 450 bytes
test/data/test4.vcf.gz.tbi | Bin 0 -> 165 bytes
test/data/test_boundary.gff.gz | Bin 0 -> 14408 bytes
.../digest/CircularGenomeControllerTest.java | 47 +
.../circular/digest/EmbossTableParserTest.java | 61 +
.../sanger/artemis/circular/digest/UtilsTest.java | 61 +
.../ac/sanger/artemis/components/EditMenuTest.java | 71 +
.../artemis/components/ProjectPropertyTest.java | 168 +
.../components/TransferAnnotationToolTest.java | 243 +
.../artemis/components/variant/WriteVCFTest.java | 561 +
.../components/variant/WriteVariantSitesTest.java | 166 +
test/uk/ac/sanger/artemis/io/GFFTest.java | 122 +
test/uk/ac/sanger/artemis/io/GffToEMBLTest.java | 67 +
test/uk/ac/sanger/artemis/io/Utils.java | 170 +
.../ac/sanger/artemis/io/ValidateFeatureTest.java | 333 +
test/uk/ac/sanger/artemis/plot/UserPlotTest.java | 129 +
uk/ac/sanger/artemis/.project | 11 +
uk/ac/sanger/artemis/Action.java | 105 +
uk/ac/sanger/artemis/ActionController.java | 372 +
uk/ac/sanger/artemis/ActionVector.java | 87 +
uk/ac/sanger/artemis/AlignMatch.java | 225 +
uk/ac/sanger/artemis/AlignMatchVector.java | 126 +
.../artemis/AlignmentSelectionChangeEvent.java | 52 +
.../artemis/AlignmentSelectionChangeListener.java | 39 +
uk/ac/sanger/artemis/BlastM8ComparisonData.java | 153 +
uk/ac/sanger/artemis/ChangeEvent.java | 46 +
uk/ac/sanger/artemis/ChangeEventVector.java | 70 +
uk/ac/sanger/artemis/ChangeListener.java | 39 +
uk/ac/sanger/artemis/ClipBoard.java | 68 +
uk/ac/sanger/artemis/ComparisonData.java | 81 +
uk/ac/sanger/artemis/ComparisonDataFactory.java | 85 +
.../artemis/ComparisonDataParseException.java | 46 +
uk/ac/sanger/artemis/Entry.java | 1256 +
uk/ac/sanger/artemis/EntryChangeEvent.java | 161 +
uk/ac/sanger/artemis/EntryChangeListener.java | 43 +
uk/ac/sanger/artemis/EntryGroup.java | 344 +
uk/ac/sanger/artemis/EntryGroupChangeEvent.java | 115 +
uk/ac/sanger/artemis/EntryGroupChangeListener.java | 43 +
uk/ac/sanger/artemis/EntrySource.java | 98 +
uk/ac/sanger/artemis/EntrySourceVector.java | 123 +
uk/ac/sanger/artemis/EntryVector.java | 131 +
uk/ac/sanger/artemis/ExternalProgram.java | 863 +
uk/ac/sanger/artemis/ExternalProgramEvent.java | 125 +
uk/ac/sanger/artemis/ExternalProgramException.java | 45 +
uk/ac/sanger/artemis/ExternalProgramListener.java | 41 +
uk/ac/sanger/artemis/ExternalProgramMonitor.java | 42 +
uk/ac/sanger/artemis/ExternalProgramThread.java | 42 +
uk/ac/sanger/artemis/ExternalProgramVector.java | 90 +
uk/ac/sanger/artemis/Feature.java | 3372 +++
uk/ac/sanger/artemis/FeatureChangeEvent.java | 234 +
uk/ac/sanger/artemis/FeatureChangeListener.java | 44 +
uk/ac/sanger/artemis/FeatureEnumeration.java | 58 +
.../sanger/artemis/FeatureFromVectorPredicate.java | 63 +
uk/ac/sanger/artemis/FeatureKeyPredicate.java | 62 +
.../artemis/FeatureKeyQualifierPredicate.java | 237 +
uk/ac/sanger/artemis/FeaturePatternPredicate.java | 61 +
uk/ac/sanger/artemis/FeaturePredicate.java | 46 +
.../artemis/FeaturePredicateConjunction.java | 122 +
uk/ac/sanger/artemis/FeaturePredicateVector.java | 74 +
uk/ac/sanger/artemis/FeatureSegment.java | 506 +
uk/ac/sanger/artemis/FeatureSegmentVector.java | 171 +
uk/ac/sanger/artemis/FeatureVector.java | 166 +
uk/ac/sanger/artemis/FilteredEntryGroup.java | 768 +
uk/ac/sanger/artemis/GotoEvent.java | 62 +
uk/ac/sanger/artemis/GotoEventSource.java | 89 +
uk/ac/sanger/artemis/GotoListener.java | 43 +
uk/ac/sanger/artemis/LastSegmentException.java | 45 +
uk/ac/sanger/artemis/Logger.java | 45 +
uk/ac/sanger/artemis/MSPcrunchComparisonData.java | 150 +
uk/ac/sanger/artemis/MUMmerComparisonData.java | 130 +
uk/ac/sanger/artemis/MegaBlastComparisonData.java | 160 +
uk/ac/sanger/artemis/OptionChangeEvent.java | 59 +
uk/ac/sanger/artemis/OptionChangeListener.java | 41 +
uk/ac/sanger/artemis/Options.java | 1496 +
uk/ac/sanger/artemis/ProcessMonitor.java | 138 +
uk/ac/sanger/artemis/SSAHAComparisonData.java | 138 +
uk/ac/sanger/artemis/Selectable.java | 41 +
uk/ac/sanger/artemis/Selection.java | 895 +
uk/ac/sanger/artemis/SelectionChangeEvent.java | 73 +
uk/ac/sanger/artemis/SelectionChangeListener.java | 44 +
uk/ac/sanger/artemis/SimpleComparisonData.java | 359 +
uk/ac/sanger/artemis/SimpleEntryGroup.java | 1047 +
.../artemis/SimpleExternalProgramMonitor.java | 97 +
uk/ac/sanger/artemis/SimpleGotoEventSource.java | 173 +
uk/ac/sanger/artemis/chado/ArtemisUtils.java | 723 +
uk/ac/sanger/artemis/chado/ChadoCvTermView.java | 469 +
uk/ac/sanger/artemis/chado/ChadoDAO.java | 202 +
uk/ac/sanger/artemis/chado/ChadoDemo.java | 884 +
uk/ac/sanger/artemis/chado/ChadoTransaction.java | 204 +
.../artemis/chado/ChadoTransactionManager.java | 2979 ++
.../artemis/chado/ClusterLazyQualifierValue.java | 445 +
uk/ac/sanger/artemis/chado/CommitFrame.java | 178 +
uk/ac/sanger/artemis/chado/DbSqlConfig.java | 117 +
.../artemis/chado/FeatureForUpdatingResidues.java | 87 +
.../chado/FeatureLocLazyQualifierValue.java | 431 +
uk/ac/sanger/artemis/chado/GmodDAO.java | 599 +
uk/ac/sanger/artemis/chado/Graph.java | 94 +
uk/ac/sanger/artemis/chado/IBatisDAO.java | 1401 +
uk/ac/sanger/artemis/chado/JdbcDAO.java | 2334 ++
.../sanger/artemis/chado/SqlMapClientWrapper.java | 154 +
uk/ac/sanger/artemis/circular/Block.java | 953 +
uk/ac/sanger/artemis/circular/BlockComparator.java | 40 +
uk/ac/sanger/artemis/circular/DNADraw.java | 1964 ++
.../artemis/circular/EmbossCirdnaReader.java | 225 +
uk/ac/sanger/artemis/circular/Feature.java | 117 +
uk/ac/sanger/artemis/circular/GCGraph.java | 64 +
uk/ac/sanger/artemis/circular/GCSkewGraph.java | 71 +
uk/ac/sanger/artemis/circular/GeneticMarker.java | 541 +
uk/ac/sanger/artemis/circular/Graph.java | 580 +
uk/ac/sanger/artemis/circular/LineAttribute.java | 245 +
uk/ac/sanger/artemis/circular/MemoryComboBox.java | 146 +
uk/ac/sanger/artemis/circular/PrintDNAImage.java | 405 +
uk/ac/sanger/artemis/circular/ProgressFrame.java | 61 +
.../sanger/artemis/circular/RestrictionEnzyme.java | 449 +
uk/ac/sanger/artemis/circular/ScrollPanel.java | 77 +
uk/ac/sanger/artemis/circular/SequenceData.java | 122 +
uk/ac/sanger/artemis/circular/TextFieldFloat.java | 104 +
uk/ac/sanger/artemis/circular/TextFieldInt.java | 107 +
uk/ac/sanger/artemis/circular/TextFieldSink.java | 155 +
uk/ac/sanger/artemis/circular/Ticks.java | 202 +
uk/ac/sanger/artemis/circular/Track.java | 370 +
uk/ac/sanger/artemis/circular/TrackManager.java | 675 +
uk/ac/sanger/artemis/circular/UserGraph.java | 158 +
uk/ac/sanger/artemis/circular/Wizard.java | 1009 +
.../circular/digest/CircularGenomeController.java | 694 +
uk/ac/sanger/artemis/circular/digest/CutSite.java | 87 +
.../circular/digest/EmbossDigestParser.java | 72 +
.../artemis/circular/digest/EmbossTableParser.java | 76 +
.../circular/digest/FileSelectionPanel.java | 254 +
.../artemis/circular/digest/FragmentBand.java | 26 +
.../artemis/circular/digest/InSilicoGelPanel.java | 351 +
uk/ac/sanger/artemis/circular/digest/Utils.java | 266 +
uk/ac/sanger/artemis/components/ActMain.java | 379 +
.../sanger/artemis/components/ActPanelResizer.java | 181 +
uk/ac/sanger/artemis/components/AddMenu.java | 1793 ++
.../artemis/components/AlignMatchViewer.java | 473 +
.../sanger/artemis/components/AlignmentEvent.java | 57 +
.../artemis/components/AlignmentListener.java | 43 +
.../sanger/artemis/components/AlignmentViewer.java | 2472 ++
uk/ac/sanger/artemis/components/ArtemisMain.java | 795 +
uk/ac/sanger/artemis/components/BasePlot.java | 1059 +
uk/ac/sanger/artemis/components/BasePlotGroup.java | 415 +
.../artemis/components/BioJavaEntrySource.java | 154 +
uk/ac/sanger/artemis/components/CanvasPanel.java | 115 +
uk/ac/sanger/artemis/components/ChoiceFrame.java | 109 +
.../artemis/components/ComparatorDialog.java | 333 +
.../sanger/artemis/components/ComparatorGlue.java | 510 +
uk/ac/sanger/artemis/components/ContigTool.java | 693 +
.../artemis/components/CorbaEntrySource.java | 90 +
.../artemis/components/DbfetchEntrySource.java | 203 +
.../artemis/components/DisplayAdjustmentEvent.java | 215 +
.../components/DisplayAdjustmentListener.java | 45 +
.../artemis/components/DisplayComponent.java | 51 +
.../artemis/components/EMBLCorbaEntrySource.java | 318 +
uk/ac/sanger/artemis/components/EditMenu.java | 3756 +++
.../artemis/components/EntryActionListener.java | 63 +
uk/ac/sanger/artemis/components/EntryEdit.java | 2615 ++
.../sanger/artemis/components/EntryEditVector.java | 77 +
.../sanger/artemis/components/EntryFileDialog.java | 595 +
.../artemis/components/EntryGroupDisplay.java | 301 +
.../artemis/components/EntryGroupInfoDisplay.java | 629 +
.../sanger/artemis/components/EntryGroupMenu.java | 336 +
.../sanger/artemis/components/EntryGroupPanel.java | 410 +
.../sanger/artemis/components/EntryHeaderEdit.java | 205 +
.../artemis/components/ExternalProgramOptions.java | 64 +
.../artemis/components/FeatureAminoAcidViewer.java | 166 +
.../artemis/components/FeatureBaseViewer.java | 177 +
.../sanger/artemis/components/FeatureDisplay.java | 5656 ++++
uk/ac/sanger/artemis/components/FeatureEdit.java | 1809 ++
uk/ac/sanger/artemis/components/FeatureInfo.java | 658 +
uk/ac/sanger/artemis/components/FeatureList.java | 1080 +
.../artemis/components/FeatureListFrame.java | 206 +
uk/ac/sanger/artemis/components/FeaturePlot.java | 393 +
.../artemis/components/FeaturePlotGroup.java | 337 +
uk/ac/sanger/artemis/components/FeaturePopup.java | 970 +
uk/ac/sanger/artemis/components/FeatureViewer.java | 169 +
.../artemis/components/FileDialogEntrySource.java | 242 +
uk/ac/sanger/artemis/components/FileViewer.java | 488 +
.../sanger/artemis/components/FindAndReplace.java | 707 +
uk/ac/sanger/artemis/components/GotoMenu.java | 430 +
uk/ac/sanger/artemis/components/GraphMenu.java | 652 +
.../artemis/components/IndexReferenceEvent.java | 14 +
.../artemis/components/IndexReferenceListener.java | 9 +
.../components/InputStreamProgressDialog.java | 144 +
uk/ac/sanger/artemis/components/KeyChoice.java | 171 +
uk/ac/sanger/artemis/components/KeyChooser.java | 130 +
uk/ac/sanger/artemis/components/ListDialog.java | 128 +
.../artemis/components/ListSelectionPanel.java | 188 +
.../sanger/artemis/components/LogReadListener.java | 77 +
uk/ac/sanger/artemis/components/LogViewer.java | 149 +
.../artemis/components/MarkerRangeRequester.java | 106 +
.../components/MarkerRangeRequesterEvent.java | 213 +
.../components/MarkerRangeRequesterListener.java | 42 +
uk/ac/sanger/artemis/components/MessageDialog.java | 167 +
uk/ac/sanger/artemis/components/MessageFrame.java | 87 +
.../sanger/artemis/components/MultiComparator.java | 1184 +
uk/ac/sanger/artemis/components/Navigator.java | 969 +
.../sanger/artemis/components/NonModalDialog.java | 95 +
uk/ac/sanger/artemis/components/OSXAdapter.java | 209 +
uk/ac/sanger/artemis/components/Plot.java | 1151 +
.../artemis/components/PlotMouseListener.java | 64 +
uk/ac/sanger/artemis/components/PrintACT.java | 562 +
uk/ac/sanger/artemis/components/PrintArtemis.java | 724 +
.../sanger/artemis/components/ProcessWatcher.java | 194 +
.../artemis/components/ProcessWatcherEvent.java | 70 +
.../artemis/components/ProcessWatcherListener.java | 42 +
.../artemis/components/ProgressBarFrame.java | 69 +
.../sanger/artemis/components/ProgressThread.java | 69 +
.../sanger/artemis/components/ProjectProperty.java | 938 +
.../sanger/artemis/components/QualifierChoice.java | 185 +
.../sanger/artemis/components/QualifierEditor.java | 361 +
.../artemis/components/QualifierTextArea.java | 457 +
.../sanger/artemis/components/RunBlastAtNCBI.java | 261 +
uk/ac/sanger/artemis/components/RunMenu.java | 391 +
.../artemis/components/RunPfamSearchThread.java | 143 +
.../artemis/components/ScoreChangeEvent.java | 58 +
.../artemis/components/ScoreChangeListener.java | 40 +
uk/ac/sanger/artemis/components/ScoreChanger.java | 222 +
.../sanger/artemis/components/ScoreScrollbar.java | 120 +
.../artemis/components/SearchResultViewer.java | 131 +
uk/ac/sanger/artemis/components/SegmentBorder.java | 113 +
uk/ac/sanger/artemis/components/SelectMenu.java | 1051 +
.../artemis/components/SelectionInfoDisplay.java | 487 +
uk/ac/sanger/artemis/components/SelectionMenu.java | 551 +
.../sanger/artemis/components/SelectionViewer.java | 352 +
uk/ac/sanger/artemis/components/Selector.java | 1050 +
.../artemis/components/SequenceComboBox.java | 107 +
.../sanger/artemis/components/SequenceViewer.java | 333 +
uk/ac/sanger/artemis/components/ShortCut.java | 135 +
uk/ac/sanger/artemis/components/Splash.java | 1204 +
.../artemis/components/StickyFileChooser.java | 95 +
uk/ac/sanger/artemis/components/SwingWorker.java | 150 +
uk/ac/sanger/artemis/components/TextDialog.java | 119 +
uk/ac/sanger/artemis/components/TextFieldSink.java | 179 +
uk/ac/sanger/artemis/components/TextRequester.java | 158 +
.../artemis/components/TextRequesterEvent.java | 87 +
.../artemis/components/TextRequesterListener.java | 44 +
.../artemis/components/TransferAnnotationTool.java | 1149 +
.../artemis/components/TransferableContig.java | 82 +
.../artemis/components/UserDefinedQualifiers.java | 868 +
uk/ac/sanger/artemis/components/Utilities.java | 186 +
.../sanger/artemis/components/ValidateViewer.java | 281 +
uk/ac/sanger/artemis/components/ViewMenu.java | 1959 ++
.../components/WritableEMBLCorbaEntrySource.java | 226 +
uk/ac/sanger/artemis/components/WriteMenu.java | 1254 +
uk/ac/sanger/artemis/components/YesNoDialog.java | 86 +
uk/ac/sanger/artemis/components/ZoomScrollBar.java | 127 +
.../components/alignment/AbstractGraphPanel.java | 248 +
.../artemis/components/alignment/BamFrame.java | 75 +
.../components/alignment/BamOSXAdapter.java | 209 +
.../artemis/components/alignment/BamUtils.java | 230 +
.../artemis/components/alignment/BamView.java | 4443 +++
.../components/alignment/BamViewRecord.java | 15 +
.../alignment/CRAMReferenceSequenceFile.java | 111 +
.../components/alignment/CoveragePanel.java | 675 +
.../components/alignment/FileSelectionDialog.java | 358 +
.../components/alignment/GroupBamFrame.java | 320 +
.../components/alignment/LineAttributes.java | 397 +
.../artemis/components/alignment/LookSeqPanel.java | 628 +
.../artemis/components/alignment/MappedReads.java | 916 +
.../components/alignment/PairedReadComparator.java | 51 +
.../components/alignment/PopupMessageFrame.java | 177 +
.../artemis/components/alignment/PrintBamView.java | 195 +
.../components/alignment/ReadCountDialog.java | 188 +
.../components/alignment/ReadGroupsFrame.java | 178 +
.../artemis/components/alignment/RunSamTools.java | 336 +
.../components/alignment/SAMRecordComparator.java | 58 +
.../components/alignment/SAMRecordFilter.java | 208 +
.../SAMRecordFlagConjunctionPredicate.java | 73 +
.../alignment/SAMRecordFlagPredicate.java | 115 +
.../components/alignment/SAMRecordList.java | 272 +
.../alignment/SAMRecordMapQPredicate.java | 52 +
.../alignment/SAMRecordPositionComparator.java | 54 +
.../components/alignment/SAMRecordPredicate.java | 41 +
.../artemis/components/alignment/SnpPanel.java | 224 +
.../components/database/DatabaseEntrySource.java | 661 +
.../components/database/DatabaseJPanel.java | 790 +
.../artemis/components/database/DatabaseJTree.java | 172 +
.../components/database/DatabaseTreeNode.java | 327 +
.../components/filetree/AbstractCellEditor.java | 100 +
.../filetree/DatabaseEntryFilterPanel.java | 183 +
.../artemis/components/filetree/FileList.java | 218 +
.../artemis/components/filetree/FileManager.java | 306 +
.../artemis/components/filetree/FileNode.java | 160 +
.../components/filetree/FileSystemModel.java | 251 +
.../artemis/components/filetree/JTreeTable.java | 1027 +
.../filetree/LocalAndRemoteFileManager.java | 766 +
.../components/filetree/RemoteFileNode.java | 372 +
.../artemis/components/filetree/SshJTreeTable.java | 1009 +
.../filetree/TransferableFileNodeList.java | 77 +
.../components/filetree/TreeTableModel.java | 76 +
.../components/filetree/TreeTableModelAdapter.java | 103 +
.../genebuilder/AutoCompleteComboDocument.java | 231 +
.../genebuilder/BasicGeneBuilderFrame.java | 1114 +
.../genebuilder/BasicGeneViewerPanel.java | 823 +
.../genebuilder/BasicProteinMapPanel.java | 148 +
.../components/genebuilder/ButtonPanel.java | 445 +
.../components/genebuilder/GeneBuilderFrame.java | 727 +
.../components/genebuilder/GeneComponentTree.java | 371 +
.../artemis/components/genebuilder/GeneEdit.java | 462 +
.../components/genebuilder/GeneEditorPanel.java | 205 +
.../artemis/components/genebuilder/GeneUtils.java | 1559 ++
.../components/genebuilder/GeneViewerPanel.java | 1716 ++
.../components/genebuilder/JExtendedComboBox.java | 258 +
.../artemis/components/genebuilder/MapPanel.java | 123 +
.../components/genebuilder/OpenSectionButton.java | 85 +
.../components/genebuilder/ProteinMapPanel.java | 569 +
.../components/genebuilder/ReferencesPanel.java | 188 +
.../genebuilder/TextAreaDocumentListener.java | 148 +
.../components/genebuilder/cv/AbstractCvBox.java | 177 +
.../artemis/components/genebuilder/cv/CVPanel.java | 815 +
.../genebuilder/cv/ControlledCurationBox.java | 298 +
.../components/genebuilder/cv/CvTermSelector.java | 489 +
.../genebuilder/cv/CvTermsComparator.java | 38 +
.../components/genebuilder/cv/DatePanel.java | 134 +
.../artemis/components/genebuilder/cv/GoBox.java | 521 +
.../components/genebuilder/cv/HistoryBox.java | 334 +
.../components/genebuilder/cv/ProductBox.java | 261 +
.../components/genebuilder/cv/WrapTextArea.java | 101 +
.../components/genebuilder/gff/AddButton.java | 57 +
.../genebuilder/gff/BasicPropertiesPanel.java | 125 +
.../genebuilder/gff/PropertiesPanel.java | 993 +
.../components/genebuilder/gff/RemoveButton.java | 55 +
.../genebuilder/ortholog/AbstractMatchTable.java | 774 +
.../genebuilder/ortholog/MatchPanel.java | 845 +
.../genebuilder/ortholog/OrthoParalogTable.java | 1106 +
.../genebuilder/ortholog/SimilarityTable.java | 671 +
.../components/variant/AbstractVCFReader.java | 400 +
.../artemis/components/variant/BCFReader.java | 632 +
.../artemis/components/variant/CDSFeature.java | 64 +
.../components/variant/FeatureContigPredicate.java | 48 +
.../artemis/components/variant/FilteredPanel.java | 197 +
.../artemis/components/variant/GraphPanel.java | 420 +
.../artemis/components/variant/HeaderLine.java | 146 +
.../sanger/artemis/components/variant/IOUtils.java | 1507 +
.../components/variant/MultipleAlleleVariant.java | 116 +
.../artemis/components/variant/PrintVCFview.java | 299 +
.../artemis/components/variant/RecordFilter.java | 187 +
.../artemis/components/variant/TabixReader.java | 440 +
.../artemis/components/variant/TableViewer.java | 153 +
.../artemis/components/variant/VCFFilter.java | 1042 +
.../artemis/components/variant/VCFRecord.java | 606 +
.../sanger/artemis/components/variant/VCFview.java | 2210 ++
.../artemis/components/variant/VariantBase.java | 120 +
uk/ac/sanger/artemis/editor/Annotation.java | 634 +
uk/ac/sanger/artemis/editor/BigPane.java | 692 +
uk/ac/sanger/artemis/editor/BrowserControl.java | 168 +
uk/ac/sanger/artemis/editor/CommonMenu.java | 60 +
uk/ac/sanger/artemis/editor/DBViewer.java | 412 +
.../sanger/artemis/editor/DataCollectionPane.java | 946 +
.../artemis/editor/DataViewInternalFrame.java | 464 +
uk/ac/sanger/artemis/editor/EvidenceViewer.java | 301 +
.../sanger/artemis/editor/ExternalApplication.java | 313 +
uk/ac/sanger/artemis/editor/FastaListener.java | 31 +
uk/ac/sanger/artemis/editor/FastaTextPane.java | 1288 +
uk/ac/sanger/artemis/editor/HitInfo.java | 739 +
uk/ac/sanger/artemis/editor/MouseOverButton.java | 105 +
.../sanger/artemis/editor/MultiLineToolTipUI.java | 212 +
uk/ac/sanger/artemis/editor/PlafMacros.java | 96 +
uk/ac/sanger/artemis/editor/ScrollPanel.java | 83 +
uk/ac/sanger/artemis/io/BetweenRange.java | 85 +
uk/ac/sanger/artemis/io/BioJavaEntry.java | 441 +
uk/ac/sanger/artemis/io/BioJavaFeature.java | 456 +
uk/ac/sanger/artemis/io/BioJavaSequence.java | 169 +
uk/ac/sanger/artemis/io/BlastDocumentEntry.java | 116 +
uk/ac/sanger/artemis/io/BlastEntryInformation.java | 37 +
uk/ac/sanger/artemis/io/BlastStreamFeature.java | 283 +
uk/ac/sanger/artemis/io/ChadoCanonicalGene.java | 1343 +
uk/ac/sanger/artemis/io/ComparableFeature.java | 51 +
uk/ac/sanger/artemis/io/CorbaEntry.java | 243 +
uk/ac/sanger/artemis/io/CorbaFeature.java | 389 +
uk/ac/sanger/artemis/io/CorbaSequence.java | 155 +
uk/ac/sanger/artemis/io/DatabaseDocumentEntry.java | 138 +
.../sanger/artemis/io/DatabaseInferredFeature.java | 466 +
uk/ac/sanger/artemis/io/DatabaseStreamFeature.java | 65 +
uk/ac/sanger/artemis/io/DateStampFeature.java | 104 +
uk/ac/sanger/artemis/io/DocumentEntry.java | 88 +
.../artemis/io/DocumentEntryAutosaveThread.java | 155 +
uk/ac/sanger/artemis/io/DocumentEntryFactory.java | 227 +
uk/ac/sanger/artemis/io/DocumentFeature.java | 37 +
uk/ac/sanger/artemis/io/EMBLObject.java | 72 +
uk/ac/sanger/artemis/io/EmblDocumentEntry.java | 124 +
uk/ac/sanger/artemis/io/EmblMisc.java | 57 +
uk/ac/sanger/artemis/io/EmblStreamFeature.java | 111 +
uk/ac/sanger/artemis/io/EmblStreamSequence.java | 321 +
uk/ac/sanger/artemis/io/Entry.java | 222 +
uk/ac/sanger/artemis/io/EntryInformation.java | 150 +
.../artemis/io/EntryInformationException.java | 48 +
uk/ac/sanger/artemis/io/EntryStreamEvent.java | 42 +
uk/ac/sanger/artemis/io/FastaStreamSequence.java | 126 +
uk/ac/sanger/artemis/io/Feature.java | 176 +
uk/ac/sanger/artemis/io/FeatureComparator.java | 173 +
uk/ac/sanger/artemis/io/FeatureEnumeration.java | 56 +
uk/ac/sanger/artemis/io/FeatureHeader.java | 74 +
uk/ac/sanger/artemis/io/FeatureTable.java | 233 +
uk/ac/sanger/artemis/io/FeatureTree.java | 272 +
uk/ac/sanger/artemis/io/FeatureVector.java | 54 +
uk/ac/sanger/artemis/io/FuzzyRange.java | 339 +
uk/ac/sanger/artemis/io/GFFDocumentEntry.java | 1009 +
uk/ac/sanger/artemis/io/GFFEntryInformation.java | 208 +
uk/ac/sanger/artemis/io/GFFMisc.java | 48 +
uk/ac/sanger/artemis/io/GFFStreamFeature.java | 1359 +
uk/ac/sanger/artemis/io/GFFUtils.java | 125 +
uk/ac/sanger/artemis/io/GenbankDocumentEntry.java | 110 +
uk/ac/sanger/artemis/io/GenbankMisc.java | 76 +
uk/ac/sanger/artemis/io/GenbankStreamFeature.java | 114 +
uk/ac/sanger/artemis/io/GenbankStreamSequence.java | 315 +
.../sanger/artemis/io/GenbankTblOutputStream.java | 184 +
uk/ac/sanger/artemis/io/GffToEMBL.java | 286 +
uk/ac/sanger/artemis/io/IndexFastaStream.java | 360 +
.../sanger/artemis/io/IndexedGFFDocumentEntry.java | 1391 +
uk/ac/sanger/artemis/io/InvalidKeyException.java | 58 +
.../artemis/io/InvalidQualifierException.java | 64 +
.../artemis/io/InvalidRelationException.java | 89 +
uk/ac/sanger/artemis/io/Key.java | 130 +
uk/ac/sanger/artemis/io/KeyVector.java | 92 +
uk/ac/sanger/artemis/io/LazyQualifierValue.java | 8 +
uk/ac/sanger/artemis/io/LineGroup.java | 470 +
uk/ac/sanger/artemis/io/Location.java | 989 +
uk/ac/sanger/artemis/io/LocationLexer.java | 316 +
.../sanger/artemis/io/LocationParseException.java | 68 +
uk/ac/sanger/artemis/io/LocationParseNode.java | 960 +
.../sanger/artemis/io/LocationParseNodeVector.java | 47 +
uk/ac/sanger/artemis/io/LowerInteger.java | 71 +
.../sanger/artemis/io/MSPcrunchDocumentEntry.java | 115 +
.../artemis/io/MSPcrunchEntryInformation.java | 37 +
.../sanger/artemis/io/MSPcrunchStreamFeature.java | 445 +
uk/ac/sanger/artemis/io/MiscLineGroup.java | 94 +
uk/ac/sanger/artemis/io/OutOfDateException.java | 44 +
uk/ac/sanger/artemis/io/Packing.java | 83 +
uk/ac/sanger/artemis/io/PartialSequence.java | 219 +
uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java | 862 +
uk/ac/sanger/artemis/io/PublicDBStreamFeature.java | 689 +
uk/ac/sanger/artemis/io/Qualifier.java | 195 +
uk/ac/sanger/artemis/io/QualifierInfo.java | 233 +
.../sanger/artemis/io/QualifierInfoException.java | 47 +
uk/ac/sanger/artemis/io/QualifierInfoHash.java | 109 +
uk/ac/sanger/artemis/io/QualifierInfoVector.java | 98 +
uk/ac/sanger/artemis/io/QualifierLazyLoading.java | 224 +
.../sanger/artemis/io/QualifierParseException.java | 47 +
uk/ac/sanger/artemis/io/QualifierVector.java | 169 +
uk/ac/sanger/artemis/io/RWCorbaEntry.java | 539 +
uk/ac/sanger/artemis/io/RWCorbaFeature.java | 733 +
uk/ac/sanger/artemis/io/Range.java | 246 +
uk/ac/sanger/artemis/io/RangeVector.java | 83 +
uk/ac/sanger/artemis/io/RawStreamSequence.java | 390 +
uk/ac/sanger/artemis/io/ReadAndWriteEntry.java | 494 +
uk/ac/sanger/artemis/io/ReadEvent.java | 56 +
uk/ac/sanger/artemis/io/ReadFormatException.java | 76 +
uk/ac/sanger/artemis/io/ReadListener.java | 37 +
.../artemis/io/ReadOnlyEmblStreamFeature.java | 183 +
uk/ac/sanger/artemis/io/ReadOnlyEntry.java | 109 +
uk/ac/sanger/artemis/io/Sequence.java | 89 +
uk/ac/sanger/artemis/io/SimpleDocumentEntry.java | 1265 +
uk/ac/sanger/artemis/io/SimpleDocumentFeature.java | 534 +
.../sanger/artemis/io/SimpleEntryInformation.java | 511 +
uk/ac/sanger/artemis/io/StreamFeature.java | 47 +
uk/ac/sanger/artemis/io/StreamFeatureTable.java | 76 +
uk/ac/sanger/artemis/io/StreamQualifier.java | 474 +
uk/ac/sanger/artemis/io/StreamSequence.java | 469 +
uk/ac/sanger/artemis/io/StreamSequenceFactory.java | 117 +
uk/ac/sanger/artemis/io/UI.java | 161 +
uk/ac/sanger/artemis/io/UIEraserThread.java | 34 +
uk/ac/sanger/artemis/io/UpperInteger.java | 66 +
uk/ac/sanger/artemis/io/ValidateFeature.java | 1106 +
uk/ac/sanger/artemis/j2ssh/FTProgress.java | 71 +
.../artemis/j2ssh/FileTransferProgressMonitor.java | 104 +
uk/ac/sanger/artemis/j2ssh/SshFileManager.java | 447 +
uk/ac/sanger/artemis/j2ssh/SshLogin.java | 279 +
uk/ac/sanger/artemis/j2ssh/SshPSUClient.java | 743 +
uk/ac/sanger/artemis/plot/AGWindowAlgorithm.java | 176 +
.../sanger/artemis/plot/ATDeviationAlgorithm.java | 198 +
uk/ac/sanger/artemis/plot/Algorithm.java | 311 +
uk/ac/sanger/artemis/plot/BaseAlgorithm.java | 222 +
uk/ac/sanger/artemis/plot/CSCSAlgorithm.java | 432 +
.../artemis/plot/Codon12CorrelationAlgorithm.java | 313 +
uk/ac/sanger/artemis/plot/CodonUsageAlgorithm.java | 299 +
.../artemis/plot/CodonUsageFormatException.java | 46 +
uk/ac/sanger/artemis/plot/CodonUsageWeight.java | 366 +
uk/ac/sanger/artemis/plot/CodonWeight.java | 50 +
.../sanger/artemis/plot/CoilFeatureAlgorithm.java | 243 +
.../artemis/plot/CumulativeATSkewAlgorithm.java | 208 +
.../artemis/plot/CumulativeGCSkewAlgorithm.java | 206 +
uk/ac/sanger/artemis/plot/EntropyAlgorithm.java | 231 +
uk/ac/sanger/artemis/plot/FeatureAlgorithm.java | 81 +
.../sanger/artemis/plot/GCDeviationAlgorithm.java | 195 +
uk/ac/sanger/artemis/plot/GCFrameAlgorithm.java | 266 +
uk/ac/sanger/artemis/plot/GCSDWindowAlgorithm.java | 285 +
uk/ac/sanger/artemis/plot/GCWindowAlgorithm.java | 176 +
uk/ac/sanger/artemis/plot/HydroAlgorithm.java | 179 +
.../artemis/plot/HydrophilicityAlgorithm.java | 80 +
.../artemis/plot/HydrophobicityAlgorithm.java | 84 +
uk/ac/sanger/artemis/plot/ICDIAlgorithm.java | 489 +
uk/ac/sanger/artemis/plot/KarlinSigAlgorithm.java | 300 +
uk/ac/sanger/artemis/plot/LineAttributes.java | 449 +
uk/ac/sanger/artemis/plot/MRIAlgorithm.java | 555 +
uk/ac/sanger/artemis/plot/NcAlgorithm.java | 563 +
.../artemis/plot/PositionalAsymmetryAlgorithm.java | 283 +
uk/ac/sanger/artemis/plot/ScaledChiAlgorithm.java | 410 +
uk/ac/sanger/artemis/plot/UserDataAlgorithm.java | 1022 +
.../sanger/artemis/sequence/AminoAcidSequence.java | 1086 +
uk/ac/sanger/artemis/sequence/BasePattern.java | 454 +
.../sequence/BasePatternFormatException.java | 46 +
uk/ac/sanger/artemis/sequence/Bases.java | 1550 ++
uk/ac/sanger/artemis/sequence/Marker.java | 362 +
.../sanger/artemis/sequence/MarkerChangeEvent.java | 82 +
.../artemis/sequence/MarkerChangeListener.java | 43 +
uk/ac/sanger/artemis/sequence/MarkerRange.java | 293 +
.../sanger/artemis/sequence/MarkerRangeVector.java | 90 +
.../artemis/sequence/NoSequenceException.java | 43 +
.../artemis/sequence/SequenceChangeEvent.java | 173 +
.../artemis/sequence/SequenceChangeListener.java | 42 +
uk/ac/sanger/artemis/sequence/Strand.java | 693 +
uk/ac/sanger/artemis/util/ByteBuffer.java | 119 +
uk/ac/sanger/artemis/util/CacheHashMap.java | 92 +
uk/ac/sanger/artemis/util/DatabaseDocument.java | 3434 +++
.../artemis/util/DatabaseLocationParser.java | 374 +
uk/ac/sanger/artemis/util/Document.java | 209 +
uk/ac/sanger/artemis/util/DocumentFactory.java | 74 +
uk/ac/sanger/artemis/util/FTPSeekableStream.java | 331 +
uk/ac/sanger/artemis/util/FastVector.java | 101 +
uk/ac/sanger/artemis/util/FileDocument.java | 176 +
.../artemis/util/InputStreamProgressEvent.java | 64 +
.../artemis/util/InputStreamProgressListener.java | 41 +
.../util/InputStreamProgressListenerVector.java | 70 +
uk/ac/sanger/artemis/util/LargeObjectDocument.java | 121 +
uk/ac/sanger/artemis/util/LinePushBackReader.java | 126 +
uk/ac/sanger/artemis/util/OutOfRangeException.java | 51 +
uk/ac/sanger/artemis/util/ProgressInputStream.java | 170 +
uk/ac/sanger/artemis/util/PushBackException.java | 46 +
uk/ac/sanger/artemis/util/ReadOnlyException.java | 54 +
uk/ac/sanger/artemis/util/RemoteFileDocument.java | 204 +
uk/ac/sanger/artemis/util/StringVector.java | 225 +
uk/ac/sanger/artemis/util/TextDocument.java | 175 +
uk/ac/sanger/artemis/util/URLDocument.java | 146 +
.../artemis/util/WorkingGZIPInputStream.java | 78 +
uk/ac/sanger/artemis/util/ZipFileDocument.java | 193 +
829 files changed, 293823 insertions(+)
diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..3d63d38
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="corba"/>
+ <classpathentry including="nsdb/|seqdb/|type/" kind="src" path="ant-build/src/main"/>
+ <classpathentry excluding="ant-build/src/main/|uk/ac/sanger/artemis/ExternalProgramUtils.java" including="nsdb/|org/|seqdb/|type/|uk/" kind="src" path=""/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="lib" path="lib/jemAlign.jar"/>
+ <classpathentry kind="lib" path="lib/jakarta-regexp-1.2.jar"/>
+ <classpathentry kind="lib" path="lib/biojava.jar"/>
+ <classpathentry kind="lib" path="lib/j2ssh/j2ssh-core.jar"/>
+ <classpathentry kind="lib" path="lib/j2ssh/j2ssh-artemis-plugin.jar"/>
+ <classpathentry kind="lib" path="lib/j2ssh/commons-logging.jar"/>
+ <classpathentry kind="lib" path="lib/ibatis/ibatis-2.3.4.726.jar"/>
+ <classpathentry kind="lib" path="lib/ibatis/log4j-1.2.14.jar"/>
+ <classpathentry kind="lib" path="lib/postgresql-8.4-701.jdbc3.jar"/>
+ <classpathentry kind="lib" path="lib/picard/picard.jar"/>
+ <classpathentry kind="lib" path="lib/picard/sam.jar"/>
+ <classpathentry kind="lib" path="lib/commons-net-2.2.jar"/>
+ <classpathentry kind="lib" path="lib/batik/batik-awt-util.jar"/>
+ <classpathentry kind="lib" path="lib/batik/batik-codec.jar"/>
+ <classpathentry kind="lib" path="lib/batik/batik-dom.jar"/>
+ <classpathentry kind="lib" path="lib/batik/batik-ext.jar"/>
+ <classpathentry kind="lib" path="lib/batik/batik-svggen.jar"/>
+ <classpathentry kind="lib" path="lib/batik/batik-util.jar"/>
+ <classpathentry kind="lib" path="lib/batik/batik-xml.jar"/>
+ <classpathentry kind="output" path="ant-build/classes/main"/>
+</classpath>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6b468b6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.class
diff --git a/.project b/.project
new file mode 100644
index 0000000..8daa858
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>artemis</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..ac4dd04
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,418 @@
+Version XX
+ Add 'Features Within Selection' option to the 'Select' menu to select
+ features that are contained by a selected base range.
+
+ Saving an entry as EMBL submission has an option now to remove products
+ from CDS features with a pseudogene qualifier.
+
+ From the 'View' menu added 'Adjust panel heights...' option in ACT
+ for adjusting their heights (BAM, VCF, plots, comparisons) by giving
+ them different weights in order to distribute the space between each
+ component.
+
+ Added ability to hide graph lines. Right click on the graph to get the
+ popup menu and select 'Configure...'. Then use the 'Line size' slider
+ to reduce the size of the line(s) that you want to hide to zero.
+
+ Labels can optionally be added to the header of base position plots
+ and these are used in the legend, e.g.
+ # colour 5:150:55 255:0:0 0:255:0 0:0:255 100:100:100 50:150:50
+ # label lab1 lab2 lab3 lab4 lab5 lab6
+ 176 2204.8 848.23 0 0 0 536.04
+ ...
+
+ In the Feature Editor there is now a button ('User Qualifiers') which
+ opens a tool for maintaining user defined lists of qualifiers (i.e.
+ qualifiers in the form tag = value pairs on separate lines) and the
+ option to read qualifiers from OBO formatted files or URLs. These can
+ be saved between sessions in the file '.artemis.qualifiers' in the
+ home directory.
+
+ Automatic addition of history qualifier in chado database mode.
+
+ Added RNASeq strand specific option to the BAM popup menu ('Colour By' ->
+ 'RNASeq Strand Specific Tag (XS)'). Reads colours are based on the XS
+ tag (used by TopHat). The RNA strand is then used in the coverage plots
+ and in calculation of read counts and RPKM values.
+
+ Added the following flags:
+ -Dshow_snps Show SNP marks in BamView
+ -Dshow_snp_plot Open SNP plot in BamView
+ -Dshow_cov_plot Open coverage plot in BamView
+
+ Add validation checks. This will check the following:
+ All file types:
+ - CDS have no internal stop codon
+ - CDS have valid stop codon
+ GFF / Chado:
+ - check complete gene model
+ - check boundaries are valid
+ - check all features are on the same strand
+ - check CDS features have a phase
+ - check attribute column
+ - qualifiers have a value (not empty)
+ - only reserved tags start with uppercase
+
+ Validation can be run in two ways:
+ 1. using the option in View->Feature Filters->Validation checks... which shows
+ 'failed' features in feature list windows
+ 2. selecting 'Validation report ...' option in the popup menu when right clicking
+ on the feature display. This produces a report with an option to auto-fix gene
+ boundaries and stop codons.
+
+Version 15
+
+ Multiple BAM panels can be opened using the bamClone flag this is used with
+ the -Dbam flag:
+ art -Dbam='/pathToFile/file1.bam,/pathToFile/file2.bam' -DbamClone=n
+ (where n is an integer greater than 1). All BAM files are then shown in each
+ panel. Alternatively the following will open BAM files in separate panels
+ (using -Dbam[1,2,3...]):
+ art -Dbam1=fileA.bam -Dbam2=fileB.bam
+
+ Add SVG (scalable vector graphics) support for Artemis, ACT and DNAPlotter.
+
+ Option added to adjust VCF row height.
+
+ Add support for indexed user graphs using tabix. For example file.plot is a tab
+ delimited file with column 1 containing the sequence name and column 2 the
+ positions:
+ (grep ^"#" file.plot; grep -v ^"#" file.plot | sort -k1,1 -k2,2n) | bgzip > sorted.plot.gz ;
+ tabix -s 1 -b 2 -e 2 sorted.plot.gz
+
+ Option added to show or hide the average line in the graphs.
+
+ BAM coverage heatmap view added.
+
+ Add Rfam sequence search from the RUN menu.
+
+ Base similarity graph for each VCF added to the VCF view.
+
+ Sense and anti-sense read counts and RPKM values are now reported.
+
+ Option added to create features from BAM peaks, i.e. above a threshold of the
+ number aligned reads.
+
+ Option included for defining groups of BAM files so that they can be
+ switched on and off by their group.
+
+ Coverage plots from read alignments (BAM) can be plotted by their strand.
+
+ Addition of a Project File Manager used to group files together for
+ launching in Artemis. When a project has been added or updated the details
+ are saved at the end of each session in '.artemis.project.properties'
+ in the user's home directory.
+
+ Fix for saving user defined shortcuts between sessions on windows.
+
+ Add preliminary support for the CRAM format:
+ http://www.ebi.ac.uk/ena/about/cram_toolkit
+ This requires the cramtools.jar to be added to the start of the CLASSPATH.
+
+ Improved support for matching GFF feature coordinates to the correct
+ contig in a multiple FASTA sequences. This works now whether
+ the sequence is part of the GFF file or a separate multiple-FASTA file.
+
+ Add 'Feature Stack View' to visualise overlapping gene features.
+
+ Add support for read-only indexed GFF. Features in GFF format are
+ sorted and indexed with tabix:
+ http://samtools.sourceforge.net/tabix.shtml
+
+Version 14
+ Add options in the navigator for searching the forward and
+ reverse strands individually for base / amino acid patterns.
+
+ Add an option to the navigator for searching for matches that
+ overlap a selected region or feature.
+
+ New translation table 24 (Pterobranchia mitochondrial) added.
+
+ If the BAM index file is missing then Artemis uses the picard library to
+ attempt to create the index.
+
+ Variant (VCF / BCF) filtering now uses the meta-data in the header to
+ enable filtering based on INFO, FILTER and FORMAT columns.
+
+ Shortcut changes made in the 'Preferences' menu are saved between sessions (the
+ shortcut_cache flag in the options file can be used to turn this on/off).
+
+ More support for GTF format to show CDS and exons as joined features.
+
+ BAM record list option added to display as a list the reads and their
+ properties.
+
+ Add options for loading graph, BAM and VCF files into ACT from the
+ command line. Numbers are used to associate the file with a particular
+ sequence. e.g. add a BAM to the first (top) sequence in ACT:
+ act -Dbam1=/pathToFile/file.bam
+ or, to add a userplot to the second sequence:
+ act -Duserplot2=/pathToFile/userPlot
+
+Version 13.2.0
+ Added option to provide overview of the variation sites.
+
+ Change BamView filter to enable filtering in and out based on
+ reads flag.
+
+ Add read count and RPKM calculations to BamView.
+
+ Add option in BamView to clone the alignment panel.
+
+ Create features option for VCF records.
+
+ Unit tests added for writing VCF/BCF sequences.
+
+ Add new Coverage view to BamView. It automatically switches to this view
+ on zooming out.
+
+ Add options to write out or view FASTA sequences from VCF/BVF variation
+ data.
+
+Version 13
+ Add support for reading in indexed BCF (Binary VCF) files.
+
+ Memory optimisation of codon caching, reducing the memory footprint.
+
+ It is now possible to add BAM, VCF and BCF files from the command line using the
+ JVM bam option, e.g. art -Dbam=/pathToFile/file.bam
+ and for multiple BAM's/VCF's this is comma separated
+ art -Dbam='/pathToFile/file1.bam,/pathToFile/file2.bam', it
+ can also read the BAM's from URL's.
+
+ Added option to display orientation of reads in BamView.
+
+ In Bamview, display reads that are split over introns so that the exon boundaries can
+ be identified by colouring the line between the aligned blocks grey.
+
+ Add VCF panel to ACT.
+
+ Support added to read indexed fasta sequence files. An index file is created
+ using SAMtools.
+
+ Add support for VCF v3.3 and v4.0.
+
+ Add option to show combined coverage plots for multiple BAMs.
+
+ Add show_forward_lines and show_reverse_lines as options for switching
+ frame lines on and off.
+
+ Add an option to delete qualifiers in the Find/Replace tool.
+
+ Artemis can read in a set of zipped search results (e.g. blastp/blastp.zip).
+
+ Add BamView panel to ACT.
+
+ Fix for writing EMBL files out from GFF entries.
+
+ An option has been added in BamView to colour reads by the colour
+ used in the coverage plot - useful when looking at multiple BAM files.
+
+ Plot multiple coverage plots in BamView when multiple BAM files are
+ loaded in.
+
+Version 12
+ It is now possible to add user plots from the command line using the
+ JVM userplot option, e.g. art -Duserplot=/pathToFile/userPlot
+ and for multiple plots this is comma separated
+ art -Duserplot='/pathToFile/userPlot1,/pathToFile/userPlot2', it
+ can also read the plots from URL's.
+
+ Added support to be able to read BAM files to display read alignments.
+ It uses picard (http://picard.sourceforge.net/) to read from the BAM file
+ and so requires Artemis to be run with Java 1.6.
+
+ All graphs can now be configured to be plotted as line graphs
+ or heat maps.
+
+ Added support for BLAST tabular format (-m 8 option) and MSPcrunch
+ format to plot scores.
+
+ Added support for wiggle (variableStep/fixedStep) plots that can be
+ displayed as histograms or heat maps.
+
+ Added a new user plot file format with the first column specifying
+ a base position.
+
+ More configure options have been added to the graphs to enable
+ configuration of the graph line style and size.
+
+ GoTo directory option added to file manager to assist navigation.
+
+ The database manager is cached between sessions (this can is on by default
+ and can be switched off with -Ddatabase_manager_cache_off). There is an
+ option under the File menu to clear this cache.
+
+ A checkbox has been added to the window for adding ortholog/paralog links
+ in the Gene Builder. If this is selected it adds links between existing
+ ortholog/paralogs and the new ortholog/paralog. By default this is off.
+
+ An option has been added to the database manager to display polypeptide
+ domains in the feature display (as well as the protein map in the Gene
+ Builder).
+
+ An option has been added to the Run menu for doing a search of the Pfam
+ database.
+
+ Added an option to the Write menu for writing a combination of upstream
+ + feature + downstream bases for selected features.
+
+ Option added in the View->Filter Features menu to search for Duplicate
+ Systematic Name Qualifier.
+
+ LookSeq analysis panel can be displayed by setting the lookseq
+ value in the options file. An option under the Display menu then
+ is used to shows the LookSeq read alignment panel in Artemis.
+
+ Added options to set the minimum and maximum values of the plots.
+
+ Transfer Annotation Tool (TAT) added to feature editor and Gene
+ Builder.
+
+ New graph popup menu option to show the values and average for
+ a selected range.
+
+ Added product_cv database option to define if the product is
+ stored as a controlled vocabulary or as a feature property (featureprop).
+
+Version 11
+
+ Use black_belt_mode to suppress warnings when opening Artemis.
+
+ Script (writedb_entry) added to make it easier to write out
+ multiple entries as EMBL / GFF files from chado. This uses Artemis
+ read-write libraries and does not require each sequence to be
+ launched from the database. For command line help run:
+ etc/writedb_entry -help
+
+ On MacOSX - enable dropping files on Artemis application to open sequences
+ with file extensions: gff, embl, EMBL, genbank, gbk, fasta, seq, art and
+ dna (defined in Artemis.app/Contents/Info.plist).
+
+ Add NCBI search link to run menu. This transfers the sequence automatically
+ to the NCBI web page.
+
+ Add support for writing Sequin table format.
+
+ Added print to PostScript option in Artemis and ACT.
+
+ Optimisation of reading in user graphs.
+
+ New Edit->Selected Feature(s)->Convert Keys option to convert
+ keys of selected features.
+
+ New Edit menu option for finding and replacing qualifier text. This
+ has an option for boolean searches (e.g. and, or, &, |) of qualifier
+ text. This includes an option to search for duplicate qualifiers.
+
+ Implemented a commit manager for the database mode. This
+ highlights transactions that produce an error.
+
+ Make the chado transaction log messages more human readable.
+
+ Add ability to write file formats from Artemis in database mode.
+ With option to collapse the gene hierarchy (gene, transcript, exon)
+ into a CDS feature.
+
+ Graphs are now added to a split pane. So that their size can be
+ defined by dragging the divider at the bottom of the graphs.
+
+ Provide option to log transform user data plot.
+
+ Improved error reporting for contig reordering.
+
+ Fix for creating intergenic features for overlapping CDS's. Also
+ add note based on which one of the 4 cases with respect to the
+ flanking CDS it belongs to, i.e.:
+ IGR-F (forward): cds> IGR cds>
+ IGR-R (reverse): <cds IGR <cds
+ IGR-B (both): <cds IGR cds>
+ IGR-X: cds> IGR <cds
+
+ Add option to preferences for defining contig ordering features.
+
+ Added option to "Create features from graph peaks". For a graph
+ this creates features in regions above a given cut-off and above
+ a given feature size.
+
+ Add -Dread_only option for read only databases.
+
+ Add option to lazy load feature data from database.
+
+Version 10
+
+ Add redo function to 'Edit' menu. Also enable/disable undo and
+ redo menu items when available/not available.
+
+ Add option to replace selected bases in 'Edit' menu.
+
+ Option to create features in intergenic regions added.
+
+ Feature editor now marks hyperlinks to SWALL, EMBL, UniProt,
+ PMID, PubMed, InterPro, OrthoMCLDB, Pfam that are opened in
+ the browser. Now configured in the options file.
+
+ Added "Convert Qualifier of Selected..." option to the Edit menu.
+ This allows the user to change the names of qualifiers for all
+ selected features.
+
+ Now using new release of j2ssh (0.2.9). This requires Java1.5+.
+
+ Implemented option for ORF creation to take into account boundaries
+ of multiple fasta sequences, so that they do not cross them.
+
+ Implemented the ability to run and store fasta and blast search
+ results for multiple databases... E.g. fasta searches on uniprot
+ and on user's own database, stored in multiple fasta_file qualifiers.
+
+ "Set Score Cutoffs" in Artemis popup menu uses existing /scores as
+ the initial min and max values (rather than just 0 and 100).
+
+ Added cache to store the entries retrieved for the object editor.
+
+ Implement log4j logging to be displayed in log viewer. Using colour
+ coding depending on level of logging.
+
+ Added -Doffset so that Artemis can be opened at a given base.
+
+Version 9
+
+ Feature selector can be used to look for features with introns
+ that do no contain the GT/GC start or AG end.
+
+ Contig tool now checks for contigs that contain features that
+ span the boundaries of the contigs. These features have to be
+ removed or restricted to the contig boundary before it can
+ carry out contig reordering.
+
+ Fix for converting files from other file formats to
+ genbank format.
+
+ Fix rounding problem for long sequences when writting
+ out all bases in FASTA or raw format.
+
+ Cache the start codons (as per stop codon caching),
+ to speed their display.
+
+ Combine the extend to next exon and the fix stop codons
+ into one option.
+
+ The feature types that appear on the frame lines can be defined
+ by the user via an option ("Frame Line Features...") in the
+ feature display popup menu.
+
+ Added to File -> Preferences a user defined selection for display names
+ and systematic names. Also extended popup menu option in feature lists
+ to allow the user to be able to select multiple qualifiers to display.
+
+ For entries opened from the remote side of an SSH connection will search
+ for results on the remote file system if they are not found locally. They
+ are transferred via SSH and then stored locally.
+
+ For Mac users, the option to send search results to the browser will
+ display the results in the default browser.
+
+ When automatically generating gene names (under the Edit menu), the user
+ can specify the number of zeros to pad the numbering with. e.g if 5 digits are
+ selected the format will look like : 00001, 00002 etc.
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..0240478
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,33 @@
+
+Installation instructions for UNIX
+
+ 1. change to /some/directory
+
+ 2. uncompress and untar the artemis_compiled.tar.gz file
+ On UNIX the command is: gzip -d < artemis_compiled.tar.gz | tar xf -
+
+
+This will create a "artemis" directory containing all the java classes. On
+Unix the easiest way to run the program is to run the script called art in the
+diana directory:
+
+ /some/directory/artemis/art
+
+If all goes well you will be presented with a small window with two
+menus. From the File menu you can open a flat file containing an
+entry. If it reads the entry successfully a new window will open,
+which shows the sequence and features for the entry.
+
+====================================================================
+
+Installation instructions for GitHub download
+
+ 1. Download:
+ git clone http://github.com/sanger-pathogens/Artemis.git
+ 2. Compile the code:
+ cd Artemis
+ make
+ 3. Run Artemis:
+ ./art
+ or ACT:
+ ./act
\ No newline at end of file
diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..b41c283
--- /dev/null
+++ b/META-INF/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: uk.ac.sanger.artemis.components.ArtemisMain
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8dc99b0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,337 @@
+# This is a GNU Makefile for Artemis
+
+# $Header: //tmp/pathsoft/artemis/Makefile,v 1.47 2009-09-21 15:32:03 tjc Exp $
+
+SHELL=/bin/sh
+
+#OPT_FLAGS = -g -deprecation
+
+JAVAC := javac -source 1.5 -target 1.5 $(OPT_FLAGS) $(EXTRA_FLAGS)
+
+REAL_CLASSPATH := CLASSPATH=lib/biojava.jar:lib/jemAlign.jar:lib/j2ssh/j2ssh-core.jar:lib/ibatis/ibatis-2.3.4.726.jar:lib/ibatis/log4j-1.2.14.jar:lib/postgresql-8.4-701.jdbc3.jar:lib/picard/picard.jar:lib/picard/sam.jar:lib/commons-net-2.2.jar:lib/batik/batik-awt-util.jar:lib/batik/batik-dom.jar:lib/batik/batik-ext.jar:lib/batik/batik-svggen.jar:lib/batik/batik-util.jar:lib/batik/batik-xml.jar:.
+
+# NAMES:= \
+# uk/ac/sanger/artemis/OptionChangeListener \
+# uk/ac/sanger/artemis/OptionChangeEvent \
+# uk/ac/sanger/artemis/Options \
+# uk/ac/sanger/artemis/Selection \
+# uk/ac/sanger/artemis/components/ArtemisMain \
+# uk/ac/sanger/artemis/components/ActMain \
+# uk/ac/sanger/artemis/components/Splash \
+# uk/ac/sanger/artemis/components/ListDialog \
+# uk/ac/sanger/artemis/components/FeatureListFrame \
+# uk/ac/sanger/artemis/components/FeatureList \
+# uk/ac/sanger/artemis/components/FeatureDisplay \
+# uk/ac/sanger/artemis/components/CanvasPanel \
+# uk/ac/sanger/artemis/components/EntryGroupDisplay \
+# uk/ac/sanger/artemis/components/EntryHeaderEdit \
+# uk/ac/sanger/artemis/components/BasePlotGroup \
+# uk/ac/sanger/artemis/components/EntryEditVector \
+# uk/ac/sanger/artemis/components/EntryEdit \
+# uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource \
+# uk/ac/sanger/artemis/components/DbfetchEntrySource \
+# uk/ac/sanger/artemis/components/EMBLCorbaEntrySource \
+# uk/ac/sanger/artemis/components/CorbaEntrySource \
+# uk/ac/sanger/artemis/components/BioJavaEntrySource \
+# uk/ac/sanger/artemis/components/FileDialogEntrySource \
+# uk/ac/sanger/artemis/components/EntryActionListener \
+# uk/ac/sanger/artemis/components/DisplayComponent \
+# uk/ac/sanger/artemis/components/EntryFileDialog \
+# uk/ac/sanger/artemis/components/StickyFileChooser \
+# uk/ac/sanger/artemis/components/LogReadListener \
+# uk/ac/sanger/artemis/components/Selector \
+# uk/ac/sanger/artemis/components/Navigator \
+# uk/ac/sanger/artemis/components/EntryGroupPanel \
+# uk/ac/sanger/artemis/components/FeaturePopup \
+# uk/ac/sanger/artemis/components/WriteMenu \
+# uk/ac/sanger/artemis/components/SelectMenu \
+# uk/ac/sanger/artemis/components/GraphMenu \
+# uk/ac/sanger/artemis/components/RunMenu \
+# uk/ac/sanger/artemis/components/AddMenu \
+# uk/ac/sanger/artemis/components/ViewMenu \
+# uk/ac/sanger/artemis/components/GotoMenu \
+# uk/ac/sanger/artemis/components/EditMenu \
+# uk/ac/sanger/artemis/components/SelectionMenu \
+# uk/ac/sanger/artemis/components/SelectionViewer \
+# uk/ac/sanger/artemis/components/FeaturePlotGroup \
+# uk/ac/sanger/artemis/components/FeaturePlot \
+# uk/ac/sanger/artemis/components/FeatureEdit \
+# uk/ac/sanger/artemis/components/FeatureViewer \
+# uk/ac/sanger/artemis/components/SearchResultViewer \
+# uk/ac/sanger/artemis/components/LogViewer \
+# uk/ac/sanger/artemis/components/FileViewer \
+# uk/ac/sanger/artemis/components/MessageFrame \
+# uk/ac/sanger/artemis/components/MessageDialog \
+# uk/ac/sanger/artemis/components/YesNoDialog \
+# uk/ac/sanger/artemis/components/KeyChooser \
+# uk/ac/sanger/artemis/components/KeyChoice \
+# uk/ac/sanger/artemis/components/QualifierEditor \
+# uk/ac/sanger/artemis/components/QualifierTextArea \
+# uk/ac/sanger/artemis/components/QualifierChoice \
+# uk/ac/sanger/artemis/components/ChoiceFrame \
+# uk/ac/sanger/artemis/components/SelectionInfoDisplay \
+# uk/ac/sanger/artemis/components/EntryGroupMenu \
+# uk/ac/sanger/artemis/components/EntryGroupInfoDisplay \
+# uk/ac/sanger/artemis/components/Utilities \
+# uk/ac/sanger/artemis/components/MarkerRangeRequester \
+# uk/ac/sanger/artemis/components/MarkerRangeRequesterListener \
+# uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent \
+# uk/ac/sanger/artemis/components/TextRequester \
+# uk/ac/sanger/artemis/components/TextRequesterListener \
+# uk/ac/sanger/artemis/components/TextRequesterEvent \
+# uk/ac/sanger/artemis/components/TextDialog \
+# uk/ac/sanger/artemis/components/BasePlot \
+# uk/ac/sanger/artemis/components/ProcessWatcher \
+# uk/ac/sanger/artemis/components/ProcessWatcherEvent \
+# uk/ac/sanger/artemis/components/ProcessWatcherListener \
+# uk/ac/sanger/artemis/components/ExternalProgramOptions \
+# uk/ac/sanger/artemis/components/Plot \
+# uk/ac/sanger/artemis/components/PlotMouseListener \
+# uk/ac/sanger/artemis/components/FeatureAminoAcidViewer \
+# uk/ac/sanger/artemis/components/FeatureBaseViewer \
+# uk/ac/sanger/artemis/components/SequenceViewer \
+# uk/ac/sanger/artemis/components/FeatureInfo \
+# uk/ac/sanger/artemis/components/DisplayAdjustmentListener \
+# uk/ac/sanger/artemis/components/DisplayAdjustmentEvent \
+# uk/ac/sanger/artemis/components/ScoreChanger \
+# uk/ac/sanger/artemis/components/ScoreScrollbar \
+# uk/ac/sanger/artemis/components/ScoreChangeListener \
+# uk/ac/sanger/artemis/components/ScoreChangeEvent \
+# uk/ac/sanger/artemis/components/InputStreamProgressDialog \
+# uk/ac/sanger/artemis/plot/KarlinSigAlgorithm \
+# uk/ac/sanger/artemis/plot/UserDataAlgorithm \
+# uk/ac/sanger/artemis/plot/Codon12CorrelationAlgorithm \
+# uk/ac/sanger/artemis/plot/ATDeviationAlgorithm \
+# uk/ac/sanger/artemis/plot/GCDeviationAlgorithm \
+# uk/ac/sanger/artemis/plot/GCFrameAlgorithm \
+# uk/ac/sanger/artemis/plot/CodonUsageAlgorithm \
+# uk/ac/sanger/artemis/plot/CodonUsageFormatException \
+# uk/ac/sanger/artemis/plot/CodonUsageWeight \
+# uk/ac/sanger/artemis/plot/CodonWeight \
+# uk/ac/sanger/artemis/plot/AGWindowAlgorithm \
+# uk/ac/sanger/artemis/plot/GCSDWindowAlgorithm \
+# uk/ac/sanger/artemis/plot/GCWindowAlgorithm \
+# uk/ac/sanger/artemis/plot/HydrophilicityAlgorithm \
+# uk/ac/sanger/artemis/plot/HydroAlgorithm \
+# uk/ac/sanger/artemis/plot/HydrophobicityAlgorithm \
+# uk/ac/sanger/artemis/plot/CoilFeatureAlgorithm \
+# uk/ac/sanger/artemis/plot/FeatureAlgorithm \
+# uk/ac/sanger/artemis/plot/BaseAlgorithm \
+# uk/ac/sanger/artemis/plot/Algorithm \
+# uk/ac/sanger/artemis/Logger \
+# uk/ac/sanger/artemis/ExternalProgramListener \
+# uk/ac/sanger/artemis/ExternalProgramException \
+# uk/ac/sanger/artemis/ExternalProgramVector \
+# uk/ac/sanger/artemis/SimpleExternalProgramMonitor \
+# uk/ac/sanger/artemis/ProcessMonitor \
+# uk/ac/sanger/artemis/ProcessMonitor \
+# uk/ac/sanger/artemis/ExternalProgramMonitor \
+# uk/ac/sanger/artemis/ExternalProgram \
+# uk/ac/sanger/artemis/EntryGroupChangeListener \
+# uk/ac/sanger/artemis/EntryGroupChangeEvent \
+# uk/ac/sanger/artemis/EntryChangeListener \
+# uk/ac/sanger/artemis/EntryChangeEvent \
+# uk/ac/sanger/artemis/FilteredEntryGroup \
+# uk/ac/sanger/artemis/SimpleEntryGroup \
+# uk/ac/sanger/artemis/EntryGroup \
+# uk/ac/sanger/artemis/EntryVector \
+# uk/ac/sanger/artemis/EntrySourceVector \
+# uk/ac/sanger/artemis/EntrySource \
+# uk/ac/sanger/artemis/Entry \
+# uk/ac/sanger/artemis/LastSegmentException \
+# uk/ac/sanger/artemis/FeatureFromVectorPredicate \
+# uk/ac/sanger/artemis/FeatureKeyQualifierPredicate \
+# uk/ac/sanger/artemis/FeatureKeyPredicate \
+# uk/ac/sanger/artemis/FeaturePatternPredicate \
+# uk/ac/sanger/artemis/FeaturePredicateConjunction \
+# uk/ac/sanger/artemis/FeaturePredicate \
+# uk/ac/sanger/artemis/FeaturePredicateVector \
+# uk/ac/sanger/artemis/FeatureEnumeration \
+# uk/ac/sanger/artemis/FeatureSegmentVector \
+# uk/ac/sanger/artemis/FeatureChangeListener \
+# uk/ac/sanger/artemis/FeatureChangeEvent \
+# uk/ac/sanger/artemis/FeatureVector \
+# uk/ac/sanger/artemis/Feature \
+# uk/ac/sanger/artemis/FeatureSegment \
+# uk/ac/sanger/artemis/ActionController \
+# uk/ac/sanger/artemis/ActionVector \
+# uk/ac/sanger/artemis/Action \
+# uk/ac/sanger/artemis/ChangeListener \
+# uk/ac/sanger/artemis/ChangeEventVector \
+# uk/ac/sanger/artemis/ChangeEvent \
+# uk/ac/sanger/artemis/GotoListener \
+# uk/ac/sanger/artemis/GotoEvent \
+# uk/ac/sanger/artemis/GotoEventSource \
+# uk/ac/sanger/artemis/SimpleGotoEventSource \
+# uk/ac/sanger/artemis/SelectionChangeListener \
+# uk/ac/sanger/artemis/SelectionChangeEvent \
+# uk/ac/sanger/artemis/Selectable \
+# uk/ac/sanger/artemis/sequence/NoSequenceException \
+# uk/ac/sanger/artemis/sequence/MarkerRangeVector \
+# uk/ac/sanger/artemis/sequence/MarkerRange \
+# uk/ac/sanger/artemis/sequence/MarkerChangeListener \
+# uk/ac/sanger/artemis/sequence/MarkerChangeEvent \
+# uk/ac/sanger/artemis/sequence/Marker \
+# uk/ac/sanger/artemis/sequence/Strand \
+# uk/ac/sanger/artemis/sequence/BasePatternFormatException \
+# uk/ac/sanger/artemis/sequence/BasePattern \
+# uk/ac/sanger/artemis/sequence/Bases \
+# uk/ac/sanger/artemis/sequence/AminoAcidSequence \
+# uk/ac/sanger/artemis/sequence/SequenceChangeListener \
+# uk/ac/sanger/artemis/sequence/SequenceChangeEvent \
+# uk/ac/sanger/artemis/io/BioJavaFeature \
+# uk/ac/sanger/artemis/io/BioJavaSequence \
+# uk/ac/sanger/artemis/io/BioJavaEntry \
+# uk/ac/sanger/artemis/io/GenbankStreamSequence \
+# uk/ac/sanger/artemis/io/RWCorbaEntry \
+# uk/ac/sanger/artemis/io/CorbaEntry \
+# uk/ac/sanger/artemis/io/DocumentEntryFactory \
+# uk/ac/sanger/artemis/io/BlastDocumentEntry \
+# uk/ac/sanger/artemis/io/MSPcrunchDocumentEntry \
+# uk/ac/sanger/artemis/io/GFFDocumentEntry \
+# uk/ac/sanger/artemis/io/GenbankDocumentEntry \
+# uk/ac/sanger/artemis/io/EmblDocumentEntry \
+# uk/ac/sanger/artemis/io/PublicDBDocumentEntry \
+# uk/ac/sanger/artemis/io/SimpleDocumentEntry \
+# uk/ac/sanger/artemis/io/DocumentEntry \
+# uk/ac/sanger/artemis/io/Entry \
+# uk/ac/sanger/artemis/io/ReadOnlyEntry \
+# uk/ac/sanger/artemis/io/QualifierInfoException \
+# uk/ac/sanger/artemis/io/QualifierInfo \
+# uk/ac/sanger/artemis/io/QualifierInfoVector \
+# uk/ac/sanger/artemis/io/QualifierInfoHash \
+# uk/ac/sanger/artemis/chado/DbSqlConfig \
+# uk/ac/sanger/artemis/circular/DNADraw \
+# uk/ac/sanger/artemis/circular/digest/Utils \
+# uk/ac/sanger/artemis/circular/digest/CircularGenomeController \
+# uk/ac/sanger/artemis/io/GffToEMBL
+
+ARTEMIS_DIRS = uk/ac/sanger/artemis \
+uk/ac/sanger/artemis/chado \
+uk/ac/sanger/artemis/circular \
+uk/ac/sanger/artemis/circular/digest \
+uk/ac/sanger/artemis/components \
+uk/ac/sanger/artemis/components/alignment \
+uk/ac/sanger/artemis/components/database \
+uk/ac/sanger/artemis/components/filetree \
+uk/ac/sanger/artemis/components/genebuilder \
+uk/ac/sanger/artemis/components/genebuilder/cv \
+uk/ac/sanger/artemis/components/genebuilder/gff \
+uk/ac/sanger/artemis/components/genebuilder/ortholog \
+uk/ac/sanger/artemis/components/variant \
+uk/ac/sanger/artemis/editor \
+uk/ac/sanger/artemis/io \
+uk/ac/sanger/artemis/j2ssh \
+uk/ac/sanger/artemis/plot \
+uk/ac/sanger/artemis/sequence \
+uk/ac/sanger/artemis/util
+
+#CLASSES := $(NAMES:%=%.class)
+SOURCES := $(foreach DIR,$(ARTEMIS_DIRS),$(wildcard $(DIR)/*.java))
+CLASSES := $(SOURCES:%.java=%.class)
+
+all: idl code
+
+# Utils needs to be built before controller
+uk/ac/sanger/artemis/circular/digest/CircularGenomeController.class:uk/ac/sanger/artemis/circular/digest/Utils.class
+ $(REAL_CLASSPATH) $(JAVAC) $(@:%.class=%.java)
+
+code: $(CLASSES)
+
+topdown: idl
+ $(REAL_CLASSPATH) $(JAVAC) uk/ac/sanger/artemis/components/ArtemisMain.java
+
+%.class : %.java
+ $(REAL_CLASSPATH) $(JAVAC) $<
+
+idl : type/*.java nsdb/*.java seqdb/*.java
+
+IDL = idlj
+IDLCMD = $(IDL) -Icorba
+
+type/*.java : corba/types.idl
+ $(IDLCMD) corba/types.idl
+
+nsdb/*.java : corba/nsdb.idl corba/nsdb_write.idl
+ $(IDLCMD) corba/nsdb.idl
+ $(IDLCMD) corba/nsdb_write.idl
+
+seqdb/*.java : corba/seqdb.idl
+ $(IDLCMD) corba/seqdb.idl
+
+doc :
+ $(REAL_CLASSPATH) javadoc -J-mx200m -version \
+ AppGlobal.java \
+ uk.ac.sanger.artemis uk.ac.sanger.artemis.components \
+ uk.ac.sanger.artemis.sequence uk.ac.sanger.artemis.plot \
+ uk.ac.sanger.artemis.util uk.ac.sanger.artemis.io
+
+manual :
+ (cd docs; make)
+
+CLASS_FILES := `find org uk nsdb type seqdb -name '*.class' -print`
+
+OTHER_FILES := `find images/PSUlogo.gif images/icon.gif COPYING README`
+
+dist :
+ rm -rf artemis_compiled.tar.gz tar_build
+ mkdir tar_build
+ mkdir tar_build/artemis
+ rm -f artemis_compiled_latest.tar.gz
+ tar cf - $(OTHER_FILES) act art Makefile corba etc | (cd tar_build/artemis; tar xf -)
+ tar cf - artemis_sqlmap dnaplotter uk org nsdb type seqdb lib | (cd tar_build/artemis; tar xf -)
+ (cd tar_build; find . -name 'CVS' -print | xargs rm -rf; find . -name '.svn' -print | xargs rm -rf; tar cvf ../artemis_compiled.tar artemis)
+
+jar : all artemis.jar
+
+artemis.jar : $(CLASSES)
+ mkdir jar_build
+ rm -f artemis.jar
+ cd jar_build; \
+ if [ ! -d org ]; then \
+ for fileJar in ../lib/*.jar; do \
+ jar xvf $$fileJar; \
+ rm -rf META-INF/MANIFEST.MF; \
+ done; \
+ for fileJar in ../lib/j2ssh/*.jar; do \
+ jar xvf $$fileJar; \
+ rm -rf META-INF/MANIFEST.MF; \
+ done; \
+ for fileJar in ../lib/ibatis/*.jar; do \
+ jar xvf $$fileJar; \
+ rm -rf META-INF/MANIFEST.MF; \
+ done; \
+ for fileJar in ../lib/batik/*.jar; do \
+ jar xvf $$fileJar; \
+ rm -rf META-INF/MANIFEST.MF; \
+ done; \
+ for fileJar in ../lib/picard/*.jar; do \
+ jar xvf $$fileJar; \
+ rm -rf META-INF/MANIFEST.MF; \
+ done; \
+ fi; \
+ cp -R ../lib/LICENSE.Apache ../uk ../org ../nsdb ../type ../seqdb ../etc ../images ../lib/j2ssh/j2ssh.properties \
+ ../images/PSUlogo.gif ../images/icon.gif ../README ../artemis_sqlmap .
+ find jar_build -name '*.java' -print | xargs rm -f
+ find jar_build -name '.svn' -print | xargs rm -rf
+ cd jar_build; \
+ rm -rf META-INF/MANIFEST.MF; \
+ echo "Main-Class: uk.ac.sanger.artemis.components.ArtemisMain\nPermissions: all-permissions" > manifest-art; \
+ jar cmf manifest-art artemis.jar META-INF/services images/PSUlogo.gif images/icon.gif README etc \
+ artemis_sqlmap org uk com net nsdb type seqdb LICENSE.Apache j2ssh.properties; \
+ echo "Main-Class: uk.ac.sanger.artemis.circular.DNADraw\nPermissions: all-permissions" > manifest-circular; \
+ jar cmf manifest-circular DNAPlotter.jar images/PSUlogo.gif README etc \
+ uk org/gmod org/w3c org/apache org/biojava/bio/ com/ibatis/common/jdbc/ net/sf/samtools/ LICENSE.Apache j2ssh.properties; \
+ echo "Main-Class: uk.ac.sanger.artemis.components.alignment.BamView\nPermissions: all-permissions" > manifest-bamview; \
+ jar cmf manifest-bamview BamView.jar META-INF/services etc uk org/apache org/biojava org/biojavax org/gmod org/w3c net/sf com/ibatis; \
+ echo "Main-Class: uk.ac.sanger.artemis.components.ActMain\nPermissions: all-permissions" > manifest-act; \
+ jar cmf manifest-act act.jar META-INF/services images/PSUlogo.gif images/icon.gif README etc \
+ artemis_sqlmap org uk com net nsdb type seqdb LICENSE.Apache j2ssh.properties; \
+ rm -f etc/log4j.properties; \
+ jar cmf manifest-art artemis_mac.jar images/PSUlogo.gif images/icon.gif README \
+ uk org/gmod nsdb type seqdb LICENSE.Apache artemis_sqlmap
+
+clean :
+ -rm -rf *.html artemis.jar seqdb nsdb type resources uk/ac/sanger/jcon/ jar_build tar_build artemis_compiled.tar
+ -rm -rf TAGS* *.o
+ -find . -name '*.class' -print | xargs rm -f
diff --git a/README b/README
new file mode 100644
index 0000000..5109688
--- /dev/null
+++ b/README
@@ -0,0 +1,55 @@
+INTRODUCTION
+
+Artemis is a free genome browser and annotation tool that allows visualisation
+of sequence features, next generation data and the results of analyses within
+the context of the sequence, and also its six-frame translation. Artemis is written
+in Java, and is available for UNIX, Macintosh and Windows systems. It can read
+EMBL and GENBANK database entries or sequence in FASTA, indexed FASTA or raw format.
+Other sequence features can be in EMBL, GENBANK or GFF format.
+
+ACT is a free tool for displaying pairwise comparisons between two or more DNA
+sequences. It can be used to identify and analyse regions of similarity and
+difference between genomes and to explore conservation of synteny, in the context
+of the entire sequences and their annotation.
+
+
+DOCUMENTATION
+
+The Artemis user manual is at:
+ http://www.sanger.ac.uk/resources/software/artemis/
+
+The ACT user manual is at:
+ http://www.sanger.ac.uk/resources/software/act/
+
+INSTALLATION
+
+The installation instructions are included in the user manual.
+
+
+DISTRIBUTION
+
+Artemis may be freely distributed under the terms of the GNU Public License,
+and should run on any system with a recent version of Java.
+
+For information on how to get Artemis see this web page:
+ http://www.sanger.ac.uk/resources/software/artemis/
+
+
+COPYRIGHT
+
+Copyright (C) 1998-2013 Genome Research Limited
+
+This program 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 2
+of the License, or (at your option) any later version.
+
+This program 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 this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
diff --git a/act b/act
new file mode 100755
index 0000000..4200521
--- /dev/null
+++ b/act
@@ -0,0 +1,148 @@
+#!/bin/sh -
+
+# This script will start ACT on a UNIX system. This script should
+# be left in the same directory as the rest of the ACT
+# distribution, so that the java class files can be found. If
+# necessary a symbolic link can be made to this script from
+# /usr/local/bin/ or elsewhere.
+
+
+# $Header: //tmp/pathsoft/artemis/act,v 1.15 2009-08-10 08:14:46 tjc Exp $
+
+# resolve links - $0 may be a link
+PRG=$0
+progname=`basename $0`
+
+#PSU_PROD_JAVA_VERSION=1.4.2
+#. $PSU_CONFIG_DIR/shell/java_environment.sh
+
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '.*/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname $PRG`/$link"
+ fi
+done
+
+ACT_HOME=`dirname "$PRG"`/.
+
+
+CLASSPATH=$ACT_HOME:$ACT_HOME/lib/JacORB.jar:$ACT_HOME/lib/jemAlign.jar:$ACT_HOME/lib/jakarta-regexp-1.2.jar:$ACT_HOME/lib/macos.jar:$ACT_HOME/lib/chado-14-interface.jar:$ACT_HOME/lib/postgresql-8.4-701.jdbc3.jar:$CLASSPATH
+
+# batik jars
+CLASSPATH=$CLASSPATH:$ACT_HOME/lib/batik/batik-awt-util.jar:$ACT_HOME/lib/batik/batik-dom.jar:$ACT_HOME/lib/batik/batik-ext.jar:$ACT_HOME/lib/batik/batik-svggen.jar:$ACT_HOME/lib/batik/batik-util.jar:$ACT_HOME/lib/batik/batik-xml.jar
+
+# j2ssh jars
+CLASSPATH=$CLASSPATH:$ACT_HOME/lib/j2ssh/commons-logging.jar:$ACT_HOME/lib/j2ssh/j2ssh-core.jar:$ACT_HOME/lib/j2ssh/
+
+# iBatis jars
+CLASSPATH=$CLASSPATH:$ACT_HOME/lib/ibatis/ibatis-2.3.4.726.jar:$ACT_HOME/lib/ibatis/:$ACT_HOME/lib/ibatis/log4j-1.2.14.jar:$ACT_HOME/lib/ibatis/cglib-nodep-2.2.jar:$ACT_HOME/lib/retrotranslator-runtime-1.1.0.jar
+
+# picard jars
+CLASSPATH=$ACT_HOME/lib/picard/sam.jar:$ACT_HOME/lib/picard/picard.jar:$CLASSPATH
+
+export CLASSPATH
+
+ACT_PROPERTIES="-Dartemis.environment=UNIX"
+
+MEM="-mx500m -ms20m"
+
+if [ "$JVM_FLAGS" = "" ]
+then
+ FLAGS="$MEM -noverify"
+else
+ FLAGS="$MEM -noverify $JVM_FLAGS"
+fi
+
+
+# work-around for OSF JVM core dump problem
+if [ `uname` = OSF1 ]
+then
+ FLAGS="$FLAGS -Dsimple_splash_screen=true"
+fi
+
+
+QUIET=no
+
+if [ $# = 0 ]
+then
+ :
+else
+ if [ "$1" = "-h" -o "$1" = "--help" -o "$1" = "-help" ]
+ then
+ cat <<EOF
+
+SYNOPSIS
+ Artemis Comparison Tool (ACT): Genome Comparison Tool
+USAGE
+ $0 [options] <SEQUENCE_1> <COMPARISON_1_2> <SEQUENCE_2> ...
+OPTIONS
+ SEQUENCE An EMBL, GenBank, FASTA, or GFF3 file
+ FEATURE An Artemis TAB file, or GFF file
+ COMPARISON A BLAST comparison file in tabular format
+
+ -options FILE Read a text file of options from FILE
+ -debug Run using the debugging JVM instead
+
+ -Dblack_belt_mode=? Keep warning messages to a minimum [true,false]
+ -DuserplotX=FILE[,FILE2] For sequence 'X" open one or more userplots
+ -DloguserplotX=FILE[,FILE2] For sequence 'X" open one or more userplots, take log(data)
+ -DbamX=FILE[,FILE2,...] For sequence 'X" open one or more BAM, VCF, or BCF files
+ -Dchado="h:p/d?u" Get ACT to open this CHADO database
+ -Dread_only Open CHADO database read-only
+EXAMPLES
+ % act
+ % act af063097.embl af063097_v_b132222.crunch b132222.embl
+ % act -Dblack_belt_mode=true -Dbam1=MAL_0h.bam -Dbam2=MAL_7h.bam,var.raw.new.bcf
+ % act -Duserplot2=/pathToFile/userPlot
+
+HOMEPAGE
+ http://www.sanger.ac.uk/resources/software/act/
+
+EOF
+ exit 0
+ fi
+
+
+ while test $# != 0
+ do
+ case $1 in
+ -options) FLAGS="$FLAGS -Dextra_options=$2"; shift ;;
+ -D*) FLAGS="$FLAGS $1" ;;
+ -fast) FLAGS="$FLAGS -fast" ;;
+ -quiet) QUIET=yes ; FLAGS="$FLAGS -Drun_quietly=true" ;;
+ -debug) DEBUG=yes ;;
+ *) break ;;
+ esac
+ shift
+ done
+fi
+
+if [ "$JAVA_VM" = "" ]
+then
+ if [ "$DEBUG" = yes ]
+ then
+ JAVA=java_g
+ else
+ JAVA=java
+ fi
+else
+ JAVA=$JAVA_VM
+fi
+
+PLATTMP=`uname`
+if [ "$PLATTMP" = "Darwin" ]
+then
+ FLAGS="$FLAGS -Dapple.laf.useScreenMenuBar=true -Dcom.apple.mrj.application.apple.menu.about.name=ACT"
+fi
+
+
+if [ $QUIET = no ]
+then
+ echo starting ACT with flags: $FLAGS 1>&2
+fi
+
+$JAVA $FLAGS -Djdbc.drivers=org.postgresql.Driver $ACT_PROPERTIES uk.ac.sanger.artemis.components.ActMain $*
+
diff --git a/act.command b/act.command
new file mode 100755
index 0000000..ea81f1c
--- /dev/null
+++ b/act.command
@@ -0,0 +1,103 @@
+#!/bin/sh -
+
+# This script will start ACT on a UNIX system. This script should
+# be left in the same directory as the rest of the ACT
+# distribution, so that the java class files can be found. If
+# necessary a symbolic link can be made to this script from
+# /usr/local/bin/ or elsewhere.
+
+
+# $Header: //tmp/pathsoft/artemis/act.command,v 1.1 2005-06-20 09:56:09 tjc Exp $
+
+# resolve links - $0 may be a link
+PRG=$0
+progname=`basename $0`
+
+#PSU_PROD_JAVA_VERSION=1.4.2
+#. $PSU_CONFIG_DIR/shell/java_environment.sh
+
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '.*/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname $PRG`/$link"
+ fi
+done
+
+ACT_HOME=`dirname "$PRG"`/.
+
+
+CLASSPATH=$ACT_HOME:$ACT_HOME/lib/JacORB.jar:$ACT_HOME/lib/jemAlign.jar:$ACT_HOME/lib/jakarta-regexp-1.2.jar:$ACT_HOME/lib/macos.jar:$CLASSPATH
+
+export CLASSPATH
+
+ACT_PROPERTIES="-Dartemis.environment=UNIX"
+
+MEM="-mx150m -ms20m"
+
+if [ "$JVM_FLAGS" = "" ]
+then
+ FLAGS="$MEM -noverify"
+else
+ FLAGS="$MEM -noverify $JVM_FLAGS"
+fi
+
+
+# work-around for OSF JVM core dump problem
+if [ `uname` = OSF1 ]
+then
+ FLAGS="$FLAGS -Dsimple_splash_screen=true"
+fi
+
+
+QUIET=no
+
+if [ $# = 0 ]
+then
+ :
+else
+ if [ x$1 = x-h -o x$1 = x--help ]
+ then
+ cat <<EOF
+usage: $0 [EMBL/GENBANK/SEQUENCE file] [EMBL/GENBANK/SEQUENCE file] [crunch file]
+EOF
+ exit 0
+ fi
+
+
+ while test $# != 0
+ do
+ case $1 in
+ -options) FLAGS="$FLAGS -Dextra_options=$2"; shift ;;
+ -D*) FLAGS="$FLAGS $1" ;;
+ -fast) FLAGS="$FLAGS -fast" ;;
+ -quiet) QUIET=yes ; FLAGS="$FLAGS -Drun_quietly=true" ;;
+ -debug) DEBUG=yes ;;
+ *) break ;;
+ esac
+ shift
+ done
+fi
+
+if [ "$JAVA_VM" = "" ]
+then
+ if [ "$DEBUG" = yes ]
+ then
+ JAVA=java_g
+ else
+ JAVA=java
+ fi
+else
+ JAVA=$JAVA_VM
+fi
+
+
+if [ $QUIET = no ]
+then
+ echo starting ACT with flags: $FLAGS 1>&2
+fi
+
+$JAVA -Dcom.apple.mrj.application.apple.menu.about.name="ACT" $FLAGS $ACT_PROPERTIES uk.ac.sanger.artemis.components.ActMain $*
+
diff --git a/art b/art
new file mode 100755
index 0000000..180d773
--- /dev/null
+++ b/art
@@ -0,0 +1,167 @@
+#!/bin/sh -
+
+# This script will start Artemis on a UNIX system. This script should
+# be left in the same directory as the rest of the Artemis
+# distribution, so that the java class files can be found. If
+# necessary a symbolic link can be made to this script from
+# /usr/local/bin/ or elsewhere.
+
+# resolve links - $0 may be a link
+PRG=$0
+progname=`basename $0`
+
+#PSU_PROD_JAVA_VERSION=1.4.2
+#. $PSU_CONFIG_DIR/shell/java_environment.sh
+
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '.*/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname $PRG`/$link"
+ fi
+done
+
+ARTEMIS_HOME=`dirname "$PRG"`/.
+LIBDIR=/nfs/pathsoft/prod/javalibs
+
+CLASSPATH=$ARTEMIS_HOME:$ARTEMIS_HOME/lib/biojava.jar:$ARTEMIS_HOME/lib/jemAlign.jar:$ARTEMIS_HOME/lib/jakarta-regexp-1.2.jar:$ARTEMIS_HOME/lib/macos.jar:$ARTEMIS_HOME/lib/postgresql-8.4-701.jdbc3.jar:$CLASSPATH
+
+# batik jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/batik/batik-awt-util.jar:$ARTEMIS_HOME/lib/batik/batik-dom.jar:$ARTEMIS_HOME/lib/batik/batik-ext.jar:$ARTEMIS_HOME/lib/batik/batik-svggen.jar:$ARTEMIS_HOME/lib/batik/batik-util.jar:$ARTEMIS_HOME/lib/batik/batik-xml.jar:$ARTEMIS_HOME/lib/batik/batik-codec.jar
+
+# j2ssh jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/j2ssh/commons-logging.jar:$ARTEMIS_HOME/lib/j2ssh/j2ssh-core.jar:$ARTEMIS_HOME/lib/j2ssh/
+
+# iBatis jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/ibatis/ibatis-2.3.4.726.jar:$ARTEMIS_HOME/lib/ibatis/:$ARTEMIS_HOME/lib/ibatis/log4j-1.2.14.jar:$ARTEMIS_HOME/lib/ibatis/cglib-nodep-2.2.jar
+export CLASSPATH
+
+# picard jars
+CLASSPATH=$ARTEMIS_HOME/lib/picard/sam.jar:$ARTEMIS_HOME/lib/picard/picard.jar:$ARTEMIS_HOME/lib/commons-net-2.2.jar:$CLASSPATH
+export CLASSPATH
+
+
+ARTEMIS_PROPERTIES="-Dartemis.environment=UNIX"
+
+# Allow URLs to work from behind firewalls
+if [ "$http_proxy" = "" ]
+then
+ http_proxy=$HTTP_PROXY
+fi
+
+if [ "$http_proxy" = "" ]
+then
+ http_proxy=$HTTP_proxy
+fi
+
+if [ "$http_proxy" != "" ]
+then
+ ARTEMIS_PROPERTIES="$ARTEMIS_PROPERTIES -DproxySet=true "`echo $http_proxy | sed 's/http:\/\/\(.*\):\(.*\)/ -Dhttp.proxyHost=\1 -Dhttp.proxyPort=\2/'`
+fi
+
+
+# "-mx500m" sets the maximum amount of memory that Artemis can use. This may
+# need to be increased when dealing with large files
+MEM="-mx500m -ms20m"
+
+if [ "$JVM_FLAGS" = "" ]
+then
+ FLAGS="$MEM -noverify"
+else
+ FLAGS="$MEM -noverify $JVM_FLAGS"
+fi
+
+
+QUIET=no
+DEBUG=no
+
+if [ $# = 0 ]
+then
+ :
+else
+ if [ "$1" = "-h" -o "$1" = "--help" -o "$1" = "-help" ]
+ then
+ cat <<EOF
+
+SYNOPSIS
+ Artemis: Genome Browser and Annotation Tool
+USAGE
+ $0 [options] <SEQUENCE_FILE> [+FEATURE_FILE ...]
+OPTIONS
+ SEQUENCE_FILE An EMBL, GenBank, FASTA, or GFF3 file
+ FEATURE_FILE An Artemis TAB file, or GFF file
+
+ -options FILE Read a text file of options from FILE
+ -debug Run using the debugging JVM instead
+ -fast | -fast64 Use the FastVM (hp Tru64 UNIX) with 32/64 bit pointers
+
+ -Dblack_belt_mode=? Keep warning messages to a minimum [true,false]
+ -Doffset=XXX Open viewer at base position XXX [integer >= 1]
+ -Duserplot=FILE[,FILE2] Open one or more userplots
+ -Dloguserplot=FILE[,FILE2] Open one or more userplots, take log(data)
+ -Dbam=FILE[,FILE2,...] Open one or more BAM, VCF or BCF files
+ -DbamClone=n Open all BAMs in multiple (n > 1) panels
+ -Dbam[1,2,..]=FILE[,FILE2,..] Open BAMs in separate panels
+ -Dshow_snps Show SNP marks in BamView
+ -Dshow_snp_plot Open SNP plot in BamView
+ -Dshow_cov_plot Open coverage plot in BamView
+ -Dshow_forward_lines=? Hide/show forward frame lines [true,false]
+ -Dshow_reverse_lines=? Hide/show reverse frame lines [true,false]
+ -Dchado="h:p/d?u" Get Artemis to open this CHADO database
+ -Dread_only Open CHADO database read-only
+EXAMPLES
+ % art AJ006275.embl
+ % art contigs.fa +annotation.gff +islands.tab
+ % art -Dblack_belt_mode=true -Dbam=ecoli_hiseq.bam E_coli_K12.gbk
+ % art -Duserplot=repeatmap.plot,geecee.plot Plasmid.gff3
+HOMEPAGE
+ http://www.sanger.ac.uk/resources/software/artemis/
+
+EOF
+ exit 0
+ fi
+
+
+ while test $# != 0
+ do
+ case $1 in
+ -options) FLAGS="$FLAGS -Dextra_options=$2"; shift ;;
+ -D*) FLAGS="$FLAGS $1" ;;
+ -fast) FAST_FLAG="-fast " ;; # -fast must be the first argument
+ -fast64) FAST_FLAG="-fast64 " ;; # -fast64 must be the first argument
+ -quiet) QUIET=yes ; FLAGS="$FLAGS -Drun_quietly=true" ;;
+ -debug) DEBUG=yes ;;
+ *) break ;;
+ esac
+ shift
+ done
+fi
+
+FLAGS=$FAST_FLAG$FLAGS
+
+if [ "$JAVA_VM" = "" ]
+then
+ if [ "$DEBUG" = yes ]
+ then
+ JAVA=java_g
+ else
+ JAVA=java
+ fi
+else
+ JAVA=$JAVA_VM
+fi
+
+PLATTMP=`uname`
+if [ "$PLATTMP" = "Darwin" ]
+then
+ FLAGS="$FLAGS -Dapple.laf.useScreenMenuBar=true -Dcom.apple.mrj.application.apple.menu.about.name=Artemis"
+fi
+
+if [ "$QUIET" = no ]
+then
+ echo starting Artemis with flags: $FLAGS $ARTEMIS_PROPERTIES 1>&2
+fi
+
+$JAVA -Djdbc.drivers=org.postgresql.Driver $FLAGS $ARTEMIS_PROPERTIES uk.ac.sanger.artemis.components.ArtemisMain $*
diff --git a/art.bat b/art.bat
new file mode 100644
index 0000000..9b3ee5d
--- /dev/null
+++ b/art.bat
@@ -0,0 +1,9 @@
+ at echo off
+
+REM
+REM Batch file for starting Artemis on windows
+REM
+
+REM execute Artemis
+start javaw -classpath .;lib\biojava.jar;lib\jobcontrol.jar;lib\jemAlign.jar uk.ac.sanger.artemis.components.ArtemisMain
+
diff --git a/artemis_sqlmap/Analysis.xml b/artemis_sqlmap/Analysis.xml
new file mode 100644
index 0000000..3b357ff
--- /dev/null
+++ b/artemis_sqlmap/Analysis.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="Analysis">
+
+ <typeAlias alias="Analysis"
+ type="org.gmod.schema.analysis.Analysis"/>
+
+ <select id="getAnalysisByAnalysisId" resultClass="Analysis">
+ SELECT name, description, program, programversion, algorithm,
+ sourcename, sourceversion,sourceuri, timeexecuted
+ FROM analysis
+ WHERE analysis_id=#analysis_id#
+ </select>
+
+ <!-- INSERT -->
+ <insert id="insertAnalysis" parameterClass="Analysis">
+ INSERT INTO analysis
+ (
+ program, programversion
+ <isNotNull property="name"> ,name </isNotNull>
+ <isNotNull property="description"> ,description </isNotNull>
+ <isNotNull property="algorithm"> ,algorithm </isNotNull>
+ <isNotNull property="sourceName"> ,sourcename </isNotNull>
+ <isNotNull property="sourceVersion"> ,sourceversion </isNotNull>
+ <isNotNull property="sourceUri"> ,sourceuri </isNotNull>
+ <isNotNull property="timeExecuted"> ,timeexecuted </isNotNull>
+ )
+ VALUES
+ ( #program#, #programVersion#
+ <isNotNull property="name"> ,#name# </isNotNull>
+ <isNotNull property="description"> ,#description# </isNotNull>
+ <isNotNull property="algorithm"> ,#algorithm# </isNotNull>
+ <isNotNull property="sourceName"> ,#sourceName# </isNotNull>
+ <isNotNull property="sourceVersion"> ,#sourceVersion# </isNotNull>
+ <isNotNull property="sourceUri"> ,#sourceUri# </isNotNull>
+ <isNotNull property="timeExecuted"> ,#timeExecuted# </isNotNull>
+ )
+ <selectKey keyProperty="analysisId" resultClass="int">
+ SELECT currval('analysis_analysis_id_seq')
+ </selectKey>
+ </insert>
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/AnalysisFeature.xml b/artemis_sqlmap/AnalysisFeature.xml
new file mode 100644
index 0000000..752da50
--- /dev/null
+++ b/artemis_sqlmap/AnalysisFeature.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="AnalysisFeature">
+
+ <typeAlias alias="AnalysisFeature"
+ type="org.gmod.schema.analysis.AnalysisFeature"/>
+
+ <resultMap id="map-analysisfeature"
+ class="AnalysisFeature">
+ <result property="rawScore" column="rawscore" />
+ <result property="identity" column="identity" />
+ <result property="normScore" column="normscore" />
+ <result property="significance" column="significance" />
+ <result property="analysis.description" column="description" />
+ <result property="analysis.program" column="program" />
+ <result property="analysis.programVersion" column="programversion" />
+ <result property="analysis.algorithm" column="algorithm" />
+ <result property="analysis.sourceName" column="sourcename" />
+ <result property="analysis.sourceVersion" column="sourceversion" />
+ <result property="analysis.sourceUri" column="sourceuri" />
+ <result property="analysis.timeExecuted" column="timeexecuted" />
+ </resultMap>
+
+ <resultMap id="map-analysisfeaturelazy"
+ class="AnalysisFeature">
+ <result property="rawScore" column="rawscore" />
+ <result property="normScore" column="normscore" />
+ <result property="significance" column="significance" />
+ <result property="analysis" column="analysis_id" select="getAnalysisByAnalysisId" />
+ </resultMap>
+
+ <select id="getAnalysisFeaturesByFeatureId" resultMap="map-analysisfeaturelazy">
+ SELECT rawscore, normscore, significance, analysis_id
+ FROM analysisfeature
+ WHERE feature_id=#feature_id#
+ </select>
+
+
+ <!-- INSERT -->
+ <insert id="insertAnalysisFeature"
+ parameterClass="AnalysisFeature">
+ INSERT INTO analysisfeature
+ ( feature_id, analysis_id
+ <isNotNull property="rawScore"> ,rawscore </isNotNull>
+ <isNotNull property="normScore"> ,normscore </isNotNull>
+ <isNotNull property="significance"> ,significance </isNotNull>
+ <isNotNull property="identity"> ,identity </isNotNull>)
+ VALUES
+ ( $feature.featureId$, $analysis.analysisId$
+ <isNotNull property="rawScore"> ,$rawScore$ </isNotNull>
+ <isNotNull property="normScore"> ,$normScore$ </isNotNull>
+ <isNotNull property="significance"> ,$significance$ </isNotNull>
+ <isNotNull property="identity"> ,$identity$ </isNotNull> )
+ </insert>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/Cv.xml b/artemis_sqlmap/Cv.xml
new file mode 100644
index 0000000..0a7ef71
--- /dev/null
+++ b/artemis_sqlmap/Cv.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="Cv">
+
+ <typeAlias alias="Cv"
+ type="org.gmod.schema.cv.Cv"/>
+
+ <resultMap id="select-cv"
+ class="Cv">
+ <result property="cvId" column="cv_id"/>
+ <result property="name" column="name"/>
+ <result property="definition" column="definition"/>
+ </resultMap>
+
+ <select id="selectCv" resultMap="select-cv">
+ SELECT * FROM cv WHERE cv_id=$cvId$
+ </select>
+
+ <select id="getCvByName" resultMap="select-cv">
+ SELECT * FROM cv WHERE name=#value#
+ </select>
+
+ <select id="getAllCvs" resultMap="select-cv">
+ SELECT * FROM cv
+ </select>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/CvTerm.xml b/artemis_sqlmap/CvTerm.xml
new file mode 100644
index 0000000..a20ed0c
--- /dev/null
+++ b/artemis_sqlmap/CvTerm.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="CvTerm">
+
+ <cacheModel id="cvterm-cache" type ="LRU" readOnly="true">
+ <flushInterval hours="24"/>
+ <property name="cache-size" value="100" />
+ </cacheModel>
+
+ <typeAlias alias="CvTerm"
+ type="org.gmod.schema.cv.CvTerm"/>
+
+ <resultMap id="select-cvterm-names-result" class="CvTerm">
+ <result property="cvTermId" column="cvterm_id"/>
+ <result property="cv.cvId" column="cv_id"/>
+ <result property="cv.name" column="cv_name"/>
+ <result property="name" column="name"/>
+ <result property="definition" column="definition"/>
+ <result property="dbXRef" column="dbxref_id" select="getDbXRefByDbXRefId"/>
+ <result property="isObsolete" column="is_obsolete"/>
+ <result property="isRelationshipType" column="is_relationshiptype"/>
+ </resultMap>
+
+ <resultMap id="lazy-cvterm" class="CvTerm">
+ <result property="cvTermId" column="cvterm_id"/>
+ <result property="cv" column="cv_id" select="selectCv"/>
+ <result property="name" column="name"/>
+ <result property="definition" column="definition"/>
+ <result property="dbXRef" column="dbxref_id" select="getDbXRefByDbXRefId"/>
+ <result property="isObsolete" column="is_obsolete"/>
+ <result property="isRelationshipType" column="is_relationshiptype"/>
+ </resultMap>
+
+
+ <!-- look up cvterms names and id -->
+ <select id="getCvterm" resultMap="select-cvterm-names-result">
+ SELECT
+ cvterm.*, cv.name as cv_name
+ FROM cvterm, cv
+ WHERE
+ <dynamic>
+ <isNotNull property="name">
+ cvterm.name LIKE #name# AND
+ </isNotNull>
+ <isNotNull property="cv.name">
+ cv.name=#cv.name# AND
+ </isNotNull>
+ <isNotNull property="cvTermId">
+ <isGreaterThan property="cvTermId" compareValue="0">
+ cvterm_id=$cvTermId$ AND
+ </isGreaterThan>
+ </isNotNull>
+ </dynamic>
+ cv.cv_id = cvterm.cv_id
+ </select>
+
+ <!-- look up cvterms names and id -->
+ <select id="getCvtermByCvTermId"
+ resultMap="lazy-cvterm" cacheModel="cvterm-cache">
+ SELECT *
+ FROM cvterm
+ WHERE cvterm_id=#value#
+ </select>
+
+ <!-- WRITE BACK -->
+ <insert id="insertCvTerm" parameterClass="CvTerm">
+ INSERT INTO cvterm ( cv_id, name <isNotNull property="definition">, definition</isNotNull>, dbxref_id )
+ VALUES ($cv.cvId$, #name# <isNotNull property="definition"> ,#definition# </isNotNull>, $dbXRef.dbXRefId$)
+ </insert>
+</sqlMap>
diff --git a/artemis_sqlmap/Db.xml b/artemis_sqlmap/Db.xml
new file mode 100644
index 0000000..ccfac17
--- /dev/null
+++ b/artemis_sqlmap/Db.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="Db">
+
+ <typeAlias alias="Db"
+ type="org.gmod.schema.general.Db"/>
+
+ <resultMap id="select-db"
+ class="Db">
+ <result property="dbId" column="db_id"/>
+ <result property="name" column="name"/>
+ <result property="description" column="description"/>
+ <result property="urlPrefix" column="urlPrefix"/>
+ <result property="url" column="url"/>
+ </resultMap>
+
+ <select id="getDbId" parameterClass="Db"
+ resultClass="java.lang.Integer">
+ SELECT db_id FROM db WHERE name=#name#
+ </select>
+
+ <select id="getDbIdIgnoreCase" parameterClass="Db"
+ resultClass="java.lang.Integer">
+ SELECT db_id FROM db WHERE lower(name)=lower(#name#)
+ </select>
+
+ <select id="getDbByDbId" resultClass="Db">
+ SELECT * FROM db WHERE db_id=$db_id$
+ </select>
+
+ <select id="getDbs" resultClass="Db">
+ SELECT * FROM db
+ </select>
+</sqlMap>
+
\ No newline at end of file
diff --git a/artemis_sqlmap/DbXRef.xml b/artemis_sqlmap/DbXRef.xml
new file mode 100644
index 0000000..88d8c7c
--- /dev/null
+++ b/artemis_sqlmap/DbXRef.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="DbXRef">
+
+ <typeAlias alias="DbXRef"
+ type="org.gmod.schema.general.DbXRef"/>
+
+ <resultMap id="select-dbxref-lazy"
+ class="DbXRef">
+ <result property="dbXRefId" column="dbxref_id"/>
+ <result property="db" column="db_id" select="getDbByDbId"/>
+ <result property="accession" column="accession"/>
+ <result property="version" column="version"/>
+ <result property="description" column="description"/>
+ </resultMap>
+
+ <resultMap id="select-dbxref"
+ class="DbXRef">
+ <result property="dbXRefId" column="dbxref_id"/>
+ <result property="accession" column="accession"/>
+ <result property="version" column="version"/>
+ <result property="description" column="description"/>
+ <result property="db" resultMap="Db.select-db"/>
+ </resultMap>
+
+ <select id="getDbXRefId" parameterClass="DbXRef"
+ resultClass="java.lang.Integer">
+ SELECT dbxref_id FROM dbxref WHERE accession=#accession#
+ AND db_id=$db.dbId$
+ </select>
+
+ <select id="getDbXRefByDbXRefIdLazy" resultMap="select-dbxref-lazy">
+ SELECT * FROM dbxref WHERE dbxref_id=$dbXRefId$
+ </select>
+
+ <select id="getDbXRefByDbXRefId" resultMap="select-dbxref">
+ SELECT * FROM dbxref LEFT JOIN db ON dbxref.db_id=db.db_id WHERE dbxref_id=$dbXRefId$
+ </select>
+
+ <select id="getDbXRef" parameterClass="DbXRef"
+ resultMap="select-dbxref">
+ SELECT * FROM dbxref
+ <dynamic>
+ <isNotNull property="db">
+ LEFT JOIN db ON db.db_id=dbxref.db_id
+ </isNotNull>
+ WHERE
+ <isGreaterThan property="dbXRefId" compareValue="0">
+ dbxref_id=$dbXRefId$
+ </isGreaterThan>
+ <isEqual property="dbXRefId" compareValue="0">
+ accession=#accession#
+ </isEqual>
+ <isNotNull property="db">
+ AND db.name=#db.name#
+ </isNotNull>
+ </dynamic>
+ </select>
+
+
+ <!-- WRITE BACK -->
+ <insert id="insertDbXRef" parameterClass="DbXRef">
+ INSERT INTO dbxref ( db_id, accession <isNotNull property="version"> ,version </isNotNull>)
+ VALUES ($db.dbId$, #accession# <isNotNull property="version"> ,#version# </isNotNull>)
+ </insert>
+
+</sqlMap>
diff --git a/artemis_sqlmap/Feature.xml b/artemis_sqlmap/Feature.xml
new file mode 100644
index 0000000..806eaac
--- /dev/null
+++ b/artemis_sqlmap/Feature.xml
@@ -0,0 +1,593 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="Feature">
+
+ <typeAlias alias="Feature"
+ type="org.gmod.schema.sequence.Feature"/>
+
+ <cacheModel id="feature-cache" type ="LRU" readOnly="true">
+ <flushInterval hours="24"/>
+ <flushOnExecute statement="insertFeature"/>
+ <flushOnExecute statement="updateFeature"/>
+ <flushOnExecute statement="deleteFeature"/>
+ <flushOnExecute statement="deleteFeatureById"/>
+ <property name="cache-size" value="10" />
+ </cacheModel>
+
+ <parameterMap id="schema-cvlist"
+ class="Feature">
+ <parameter property="schema" javaType="java.lang.String"/>
+ <parameter property="featureCvTerms" javaType="java.util.List"/>
+ </parameterMap>
+
+ <!-- resultMap templates below -->
+
+ <resultMap id="template-basicfeature" class="Feature">
+ <result property="timeLastModified" column="timelastmodified"/>
+ <result property="featureId" column="id"/>
+ <result property="uniqueName" column="uniquename"/>
+ <result property="name" column="feature_name"/>
+ <result property="seqLen" column="seqlen" nullValue="-1" />
+ <result property="obsolete" column="obsolete" />
+ </resultMap>
+
+ <resultMap id="template-basicfeature-all-lazy" class="Feature"
+ extends="template-basicfeature">
+ <result property="cvTerm" column="type_id" select="getCvtermByCvTermId" />
+ <result property="dbXRef" column="dbXRefId"
+ select="getDbXRefByDbXRefId" />
+ <result property="featureLocsForFeatureId" column="{id=id}"
+ select="getFeatureLoc" />
+ <result property="featureProps" column="{feature_id=id}"
+ select="getFeaturePropByFeatureId" />
+ <result property="featureDbXRefs" column="{featureId=id}"
+ select="getFeatureDbXRef" />
+ <result property="featureRelationshipsForObjectId" column="{object_id=id}"
+ select="getFeatureRelationship" />
+ <result property="featureRelationshipsForSubjectId" column="{subject_id=id}"
+ select="getFeatureRelationship" />
+ <result property="featureSynonyms" column="{feature.uniqueName=uniqueName}"
+ select="getLazyFeatureSynonymsByUniquename" />
+ <result property="organism" column="{organismId=organismId}"
+ select="getOrganism" />
+ </resultMap>
+
+ <resultMap id="template-feature" class="Feature"
+ extends="template-basicfeature">
+ <result property="organism.organismId" column="organism_id"/>
+ <result property="dbXRef" resultMap="DbXRef.select-dbxref" />
+ <result property="featureProps" resultMap="FeatureProp.map-featureprop" />
+ <result property="featureRelationshipsForSubjectId"
+ resultMap="FeatureRelationship.map-feature-relationship-for-objectId" />
+ <result property="featureLoc.strand" column="strand" nullValue="0"/>
+ <result property="featureLoc.fmin" column="fmin" nullValue="-1"/>
+ <result property="featureLoc.fmax" column="fmax" nullValue="0"/>
+ <result property="featureLoc.phase" column="phase"/>
+ <result property="featureLoc.fminPartial" column="is_fmin_partial"/>
+ <result property="featureLoc.fmaxPartial" column="is_fmax_partial"/>
+ <result property="cvTerm.cvTermId" column="type_id"/>
+
+ <result property="featureSynonyms" column="{feature.uniqueName=uniqueName}"
+ select="getLazyFeatureSynonymsByUniquename" />
+ <result property="featureDbXRefs" column="{featureId=id}"
+ select="getFeatureDbXRef" />
+ <result property="featurePubs" column="{featureId=id}" select="getFeaturePubsByFeature"/>
+ </resultMap>
+
+ <!-- resultMap proper below -->
+
+ <resultMap id="map-feature" class="Feature"
+ groupBy="featureId" extends="template-feature">
+ <result property="featureCvTerms" column="{{uniqueName=uniquename}}"
+ select="getFeatureCvTermsByFeature"/>
+ </resultMap>
+
+ <resultMap id="map-feature-no-rank" class="Feature"
+ groupBy="featureId" extends="template-feature">
+ <result property="featureCvTerms" column="{{uniqueName=uniquename}}"
+ select="getFeatureCvTermsNoRankByFeature"/>
+ </resultMap>
+
+
+ <!-- mapping for feature table and lazy loading -->
+ <resultMap id="map-feature-lazy-no-residues" class="Feature"
+ extends="template-basicfeature-all-lazy">
+ <result property="featureCvTerms" column="{{uniqueName=uniquename}}"
+ select="getFeatureCvTermsByFeature"/>
+ </resultMap>
+
+ <resultMap id="map-feature-lazy" class="Feature"
+ extends="template-basicfeature-all-lazy">
+ <result property="featureCvTerms" column="{{uniqueName=uniquename}}"
+ select="getFeatureCvTermsByFeature"/>
+ <result property="residues" column="residues" />
+ </resultMap>
+
+ <resultMap id="map-feature-lazy-no-rank" class="Feature"
+ extends="template-basicfeature-all-lazy">
+ <result property="featureCvTerms" column="{{uniqueName=uniquename}}"
+ select="getFeatureCvTermsNoRankByFeature"/>
+ <result property="residues" column="residues" />
+ </resultMap>
+
+ <resultMap id="map-feature-name-lazy" class="Feature">
+ <result property="featureId" column="id"/>
+ <result property="uniqueName" column="uniquename"/>
+ <result property="cvTerm.name" column="name" />
+ <result property="featureRelationshipsForSubjectId" column="{subject_id=id}"
+ select="getParentFeatureRelationship" />
+ </resultMap>
+
+ <resultMap id="map-feature-withprop" class="Feature"
+ groupBy="featureId" extends="template-basicfeature">
+ <result property="cvTerm" column="type_id" select="getCvtermByCvTermId" />
+ <result property="featureProps" resultMap="FeatureProp.map-featureprop" />
+ <result property="featureDbXRefs" column="{featureId=id}"
+ select="getFeatureDbXRef" />
+ <result property="dbXRef" resultMap="DbXRef.select-dbxref" />
+ </resultMap>
+
+ <resultMap id="map-feature-residues" class="Feature">
+ <result property="residues" column="residues"/>
+ <result property="seqLen" column="seqLen"/>
+ <result property="featureLoc.fmin" column="fmin" nullValue="-1"/>
+ <result property="featureLoc.fmax" column="fmax" nullValue="0"/>
+ <result property="featureLoc.strand" column="strand" nullValue="0" />
+ <result property="featureLoc.phase" column="phase" nullValue="0" />
+ </resultMap>
+
+
+ <resultMap id="map-similaritymatch-lazy" class="Feature"
+ groupBy="featureId" extends="template-basicfeature">
+ <result property="cvTerm.cvTermId" column="type_id" />
+ <result property="featureLocsForFeatureId" column="{id=id}"
+ select="getFeatureLoc" />
+ <result property="featureProps" column="{feature_id=id}"
+ select="getFeaturePropByFeatureId" />
+ <result property="featureDbXRefs" column="{featureId=id}"
+ select="getFeatureDbXRef" />
+ <result property="dbXRef" column="dbXRefId"
+ select="getDbXRefByDbXRefId" />
+ <result property="analysisFeatures" column="{feature_id=id}"
+ select="getAnalysisFeaturesByFeatureId" />
+ </resultMap>
+
+
+ <resultMap id="map-clusterfeature" class="Feature"
+ groupBy="featureId">
+ <result property="featureId" column="id"/>
+ <result property="uniqueName" column="cluster_name"/>
+ <result property="cvTerm.name" column="cluster_cvterm"/>
+ <result property="featureProps" column="{feature_id=id}"
+ select="getFeaturePropByFeatureId" />
+
+ <result property="featureRelationshipsForSubjectId"
+ resultMap="FeatureRelationship.map-feature-relationship-for-subjectId" />
+ <result property="analysisFeatures" resultMap="AnalysisFeature.map-analysisfeature" />
+ </resultMap>
+
+ <resultMap id="map-feature-with-residues-result" class="Feature">
+ <result property="uniqueName" column="uniquename"/>
+ <result property="featureId" column="feature_id"/>
+ <result property="cvTerm.cvTermId" column="type_id"/>
+ <result property="cvTerm.name" column="cvterm_name"/>
+ <!--<result property="cvTerm" column="type_id" select="getCvtermByCvTermId" />-->
+ <result property="organism" column="{organismId=organism_id}"
+ select="getOrganism" />
+ </resultMap>
+
+ <!-- SQL -->
+ <!-- get feature -->
+ <sql id="sql-feature">
+ SELECT
+ timelastmodified,
+ f.feature_id AS id,
+ f.organism_id,
+ f.is_obsolete AS obsolete,
+ dbx.*,
+ db.*,
+ fr.feature_relationship_id,
+ fr.object_id,
+ fr.type_id AS relation_type_id,
+ fr.rank,
+ fr.value AS relation_value,
+ fl.strand,
+ fmin,
+ fmax,
+ f.name AS feature_name,
+ uniquename,
+ f.type_id,
+ f.seqlen,
+ fp.featureprop_id,
+ fp.type_id AS prop_type_id,
+ fp.rank AS prop_rank,
+ fp.value,
+ fl.phase,
+ fl.is_fmin_partial,
+ fl.is_fmax_partial
+ FROM feature f
+ LEFT JOIN featureloc fl ON ( f.feature_id = fl.feature_id )
+ LEFT JOIN feature_relationship fr ON fr.subject_id = f.feature_id
+ LEFT JOIN featureprop fp ON fp.feature_id = f.feature_id
+ LEFT JOIN dbxref dbx ON f.dbxref_id = dbx.dbxref_id
+ LEFT JOIN db ON dbx.db_id = db.db_id
+ WHERE
+ <dynamic>
+ <isGreaterThan property="featureLoc.featureBySrcFeatureId.featureId" compareValue="0">
+ srcfeature_id=$featureLoc.featureBySrcFeatureId.featureId$
+ <isGreaterThan property="cvTerm.cvTermId" compareValue="0" prepend="AND">
+ ( is_analysis=#analysis# OR f.type_id!=$cvTerm.cvTermId$ )
+ </isGreaterThan>
+ </isGreaterThan>
+ <isGreaterEqual property="featureLoc.fmin" compareValue="0" prepend="AND">
+ <![CDATA[ fl.fmin >= $featureLoc.fmin$ AND fl.fmax < $featureLoc.fmax$ ]]>
+ </isGreaterEqual>
+ <isNotNull property="uniqueName">
+ uniquename LIKE #uniqueName#
+ </isNotNull>
+ </dynamic>
+ </sql>
+
+ <select id="getFeature" parameterClass="Feature"
+ resultMap="map-feature">
+ <include refid="sql-feature"/>
+ </select>
+
+ <select id="getFeatureNoFeatureCvTermRank" parameterClass="Feature"
+ resultMap="map-feature-no-rank">
+ <include refid="sql-feature"/>
+ </select>
+
+ <sql id="sql-lazy-feature">
+ SELECT
+ timelastmodified,
+ f.feature_id AS id,
+ uniquename,
+ organism_id AS organismId,
+ f.is_obsolete AS obsolete,
+ f.name AS feature_name,
+ f.type_id,
+ f.dbxref_id AS dbXRefId,
+ f.seqlen,
+ residues
+ FROM feature f
+ <dynamic>
+ <isNotNull property="cvTerm.name">
+ LEFT JOIN cvterm ON f.type_id=cvterm.cvterm_id
+ </isNotNull>
+ </dynamic>
+
+ <dynamic prepend="WHERE">
+ <isGreaterThan property="featureId" compareValue="0">
+ f.feature_id=$featureId$
+ </isGreaterThan>
+
+ <isNotNull property="uniqueName" prepend="AND">
+ <isNotNull property="featureSynonyms">
+ <iterate property="featureSynonyms" conjunction="OR" open="(" close=") OR">
+ f.feature_id=#featureSynonyms[].feature.featureId#
+ </iterate>
+ </isNotNull>
+ ( uniquename LIKE #uniqueName# )
+ </isNotNull>
+
+ <isGreaterThan property="cvTerm.cvTermId" compareValue="0" prepend="AND">
+ f.type_id=$cvTerm.cvTermId$
+ </isGreaterThan>
+
+ <isNotNull property="cvTerm.name" prepend="AND">
+ cvterm.name=#cvTerm.name#
+ </isNotNull>
+ </dynamic>
+ </sql>
+
+ <select id="getLazyFeature" resultMap ="map-feature-lazy"
+ parameterClass="Feature">
+ <include refid="sql-lazy-feature"/>
+ </select>
+
+ <select id="getLazyFeatureNoFeatureCvTermRank"
+ resultMap ="map-feature-lazy-no-rank"
+ parameterClass="Feature">
+ <include refid="sql-lazy-feature"/>
+ </select>
+
+
+
+
+
+
+ <sql id="sql-lazy-feature-exact-name">
+ SELECT
+ timelastmodified,
+ f.feature_id AS id,
+ uniquename,
+ organism_id AS organismId,
+ f.is_obsolete AS obsolete,
+ f.name AS feature_name,
+ f.type_id,
+ f.dbxref_id AS dbXRefId,
+ f.seqlen,
+ residues
+ FROM feature f
+ <dynamic>
+ <isNotNull property="cvTerm.name">
+ LEFT JOIN cvterm ON f.type_id=cvterm.cvterm_id
+ </isNotNull>
+ </dynamic>
+ WHERE ( uniquename = #uniqueName# )
+ <isGreaterThan property="cvTerm.cvTermId" compareValue="0" prepend="AND">
+ f.type_id=$cvTerm.cvTermId$
+ </isGreaterThan>
+ <isNotNull property="cvTerm.name" prepend="AND">
+ cvterm.name=#cvTerm.name#
+ </isNotNull>
+ </sql>
+
+
+
+ <select id="getLazyFeatureExact" resultMap ="map-feature-lazy"
+ parameterClass="Feature">
+ <include refid="sql-lazy-feature-exact-name"/>
+ </select>
+
+ <select id="getLazyFeatureExactNoFeatureCvTermRank"
+ resultMap ="map-feature-lazy-no-rank"
+ parameterClass="Feature">
+ <include refid="sql-lazy-feature-exact-name"/>
+ </select>
+
+
+
+
+ <select id="getLazyFeatureNoResiduesById" resultMap ="map-feature-lazy-no-residues"
+ cacheModel="feature-cache">
+ SELECT
+ timelastmodified,
+ f.feature_id AS id,
+ uniquename,
+ organism_id AS organismId,
+ f.is_obsolete AS obsolete,
+ f.name AS feature_name,
+ f.type_id,
+ f.dbxref_id AS dbXRefId,
+ f.seqlen
+ FROM feature f
+ WHERE f.feature_id=$featureId$
+ </select>
+
+ <select id="getFeaturesByListOfIds" resultMap ="map-feature-withprop">
+ SELECT
+ timelastmodified,
+ f.feature_id AS id,
+ uniquename,
+ organism_id AS organismId,
+ f.is_obsolete AS obsolete,
+ f.name AS feature_name,
+ f.type_id,
+ f.dbxref_id AS dbXRefId,
+ f.seqlen,
+ fp.type_id AS prop_type_id, fp.rank AS prop_rank, fp.value, fp.featureprop_id,
+ dbxref.*, db.*
+ FROM feature f
+ LEFT JOIN featureprop fp ON fp.feature_id=f.feature_id
+ LEFT JOIN dbxref ON f.dbxref_id=dbxref.dbxref_id
+ LEFT JOIN db ON dbxref.db_id=db.db_id
+ WHERE f.feature_id IN <iterate open="(" close=")" conjunction=","> $[]$ </iterate>
+ </select>
+
+
+ <sql id="selectSimilarityMatch">
+ SELECT
+ timelastmodified,
+ f.feature_id AS id,
+ f.uniquename,
+ f.is_obsolete AS obsolete,
+ f.type_id,
+ f.dbxref_id AS dbXRefId,
+ f.name AS feature_name,
+ f.seqlen,
+ f.residues
+ FROM feature f
+ <!--LEFT JOIN featureloc fl ON f.feature_id=fl.feature_id
+ LEFT JOIN analysisfeature af ON f.feature_id=af.feature_id
+ LEFT JOIN analysis a ON af.analysis_id=a.analysis_id-->
+ WHERE f.feature_id IN
+ </sql>
+
+ <!-- Returns matches for all features on a given srcfeature -->
+ <select id="getLazySimilarityMatches" parameterClass="java.lang.Integer"
+ resultMap="map-similaritymatch-lazy">
+ <include refid="selectSimilarityMatch"/>
+ ( SELECT feature_id FROM featureloc WHERE srcfeature_id IN
+ ( SELECT feature_id FROM featureloc WHERE srcfeature_id=$value$) )
+ ORDER BY f.feature_id
+ </select>
+
+ <!-- Returns matches for a list of feature_id's -->
+ <select id="getLazySimilarityMatchesByFeatureIds" parameterClass="java.util.List"
+ resultMap="map-similaritymatch-lazy">
+ <include refid="selectSimilarityMatch"/>
+ ( SELECT feature_id FROM featureloc WHERE srcfeature_id IN
+ <iterate open="(" close=")" conjunction=","> $[]$ </iterate>
+ )
+ ORDER BY f.feature_id
+ </select>
+
+ <!-- Returns cluster/ortholog/paralog for a list of feature_id's -->
+ <select id="getLazyClustersByFeatureIds" parameterClass="java.util.List"
+ resultMap="map-clusterfeature">
+ SELECT
+ f.feature_id AS id,
+ f.uniquename AS cluster_name,
+ cv_f.name AS cluster_cvterm,
+ f1.uniquename AS subject_name,
+ fr.subject_id,
+ cv_fr.name AS fr_name,
+ common_name AS subject_common_name,
+ cv_f1.name AS subject_cvterm,
+ rawscore, normscore, significance, identity, a.analysis_id,
+ a.name, description, program, programversion, algorithm, sourcename, sourceversion,sourceuri, timeexecuted
+ FROM feature f
+ LEFT JOIN feature_relationship fr ON f.feature_id=fr.object_id
+ LEFT JOIN analysisfeature af ON f.feature_id=af.feature_id
+ LEFT JOIN analysis a ON af.analysis_id=a.analysis_id
+ LEFT JOIN feature f1 ON f1.feature_id=subject_id
+ LEFT JOIN organism ON f1.organism_id=organism.organism_id
+ LEFT JOIN cvterm AS cv_f1 ON cv_f1.cvterm_id=f1.type_id
+ LEFT JOIN cvterm AS cv_fr ON cv_fr.cvterm_id=fr.type_id
+ LEFT JOIN cvterm AS cv_f ON cv_f.cvterm_id=f.type_id
+ WHERE f.feature_id IN <iterate open="(" close=")" conjunction=","> #[]# </iterate>
+ AND (cv_fr.name='orthologous_to' OR cv_fr.name='paralogous_to') <!-- cv_f.name LIKE '%match%' -->
+ </select>
+
+ <select id="getSchema" resultClass="java.lang.String">
+ SELECT schema_name FROM information_schema.schemata WHERE schema_name=schema_owner
+ ORDER BY schema_name
+ </select>
+
+ <select id="getResidueType" parameterClass="java.lang.String"
+ resultClass="java.lang.Long">
+ SELECT DISTINCT type_id FROM feature WHERE residues notnull
+ </select>
+
+
+ <sql id="source_feature_cvterm_names">
+ cvterm.name LIKE '%chromosome%' OR cvterm.name LIKE '%sequence%' OR
+ cvterm.name IN ('supercontig', 'ultra_scaffold', 'golden_path_region', 'contig', 'plasmid')
+ </sql>
+ <select id="getResidueFeatures" parameterClass="java.lang.Integer"
+ resultMap="map-feature-with-residues-result">
+ SELECT uniquename, feature_id, type_id, feature.organism_id,
+ cvterm.name AS cvterm_name
+ FROM cvterm
+ LEFT JOIN feature ON cvterm.cvterm_id=feature.type_id
+ WHERE <isNotNull> feature.organism_id=$value$ AND </isNotNull>
+ residues notnull AND residues != '' AND
+ ( <include refid="source_feature_cvterm_names"/> )
+ ORDER BY uniquename
+ </select>
+
+
+ <select id="getTopLevelFeatures" parameterClass="java.lang.Integer"
+ resultMap="map-feature-with-residues-result">
+ SELECT feature.name, uniquename, feature_id, feature.type_id, feature.organism_id,
+ cvterm.name AS cvterm_name
+ FROM feature
+ JOIN cvterm on feature.type_id = cvterm.cvterm_id
+ JOIN featureprop using (feature_id)
+ WHERE <isNotNull> feature.organism_id=$value$ AND </isNotNull>
+ featureprop.type_id in (
+ SELECT cvterm_id FROM cvterm JOIN cv using (cv_id) WHERE cv.name = 'genedb_misc' AND cvterm.name = 'top_level_seq' )
+ ORDER BY uniquename
+ </select>
+
+
+ <select id="getResidueFeaturesByOrganismCommonName" parameterClass="java.lang.String"
+ resultMap="map-feature-with-residues-result">
+ SELECT uniquename, feature_id, type_id,
+ feature.organism_id
+ FROM cvterm
+ LEFT JOIN feature ON cvterm.cvterm_id=feature.type_id
+ WHERE <isNotNull> organism.common_name=#value# AND </isNotNull>
+ residues notnull AND residues != '' AND
+ ( <include refid="source_feature_cvterm_names"/> )
+ ORDER BY uniquename
+ </select>
+
+
+ <select id="getResiduesByUniqueName" parameterClass="java.lang.String"
+ resultMap="map-feature-residues">
+ SELECT substr(residues, fl.fmin+1, fl.fmax-fl.fmin) AS residues,
+ length(residues) AS seqLen, fl.fmin, fl.fmax, fl.strand, fl.phase
+ FROM featureloc fl
+ LEFT JOIN feature f ON fl.srcfeature_id=f.feature_id
+ WHERE fl.feature_id=(SELECT feature_id FROM feature WHERE uniquename=#value#);
+ </select>
+
+
+ <select id="currval" resultClass="java.lang.Integer"
+ parameterClass="java.lang.String">
+ SELECT currval('$value$')
+ </select>
+
+
+ <!-- WRITE BACK METHODS -->
+
+ <!-- UPDATE -->
+ <update id="updateFeature"
+ parameterClass="Feature">
+ UPDATE feature
+ SET uniquename=#uniqueName#, is_obsolete=#obsolete#
+ <isNotEqual property="name" compareValue="0"> , name=#name# </isNotEqual>
+ <isNotNull property="cvTerm.cvTermId"> , type_id=$cvTerm.cvTermId$ </isNotNull>
+ <isNotNull property="timeLastModified"> , timelastmodified=#timeLastModified# </isNotNull>
+ WHERE feature_id=$featureId$
+ </update>
+
+ <update id="updateFeatureResidues"
+ parameterClass="uk.ac.sanger.artemis.chado.FeatureForUpdatingResidues">
+ UPDATE feature SET
+ residues=substring(residues from 1 for $startBase$) ||
+ <dynamic>
+ <isNotNull property="newSubSequence"> #newSubSequence# || </isNotNull>
+ </dynamic>
+ substring(residues from $endBase$ for $basesToEnd$),
+ seqlen=$seqLen$
+ WHERE
+ <isNotEqual property="featureId" compareValue="-1">feature_id=$featureId$</isNotEqual>
+ <isEqual property="featureId" compareValue="-1">
+ feature_id=(SELECT feature_id FROM feature WHERE uniquename=#uniqueName#)
+ </isEqual>
+ </update>
+
+ <!-- INSERT -->
+ <insert id="insertFeature"
+ parameterClass="Feature">
+ INSERT INTO feature
+ ( feature_id, organism_id, name, uniquename, type_id
+ <isGreaterThan property="seqLen" compareValue="0"> , seqlen </isGreaterThan>
+ <isNotNull property="residues"> , residues </isNotNull>
+ <isNotNull property="dbXRef.dbXRefId"> , dbxref_id </isNotNull> )
+ VALUES
+ ( nextval('feature_feature_id_seq'),
+ $organism.organismId$,
+ #name#,
+ #uniqueName#,
+ $cvTerm.cvTermId$
+ <isGreaterThan property="seqLen" compareValue="0"> , $seqLen$ </isGreaterThan>
+ <isNotNull property="residues"> , #residues# </isNotNull>
+ <isNotNull property="dbXRef.dbXRefId"> , $dbXRef.dbXRefId$ </isNotNull> )
+ </insert>
+
+ <!--
+ <update id="updateRegionSequence"
+ parameterClass="Feature">
+ UPDATE feature SET
+ residues=substring(residues from 1 for $featureLoc.fmin$) ||
+ #residues,jdbcType=text# ||
+ substring(residues from $featureLoc.fmin$ for
+ (seqLen + $seqLen$)),
+ seqlen=(seqLen+$seqLen$)
+ WHERE feature_id=$featureLoc.featureBySrcFeatureId.featureId$
+ </update>
+ -->
+
+ <!-- DELETE -->
+ <delete id="deleteFeature"
+ parameterClass="Feature">
+ DELETE FROM feature
+ WHERE uniquename=#uniqueName# AND type_id=$cvTerm.cvTermId$
+ </delete>
+
+ <delete id="deleteFeatureById"
+ parameterClass="Feature">
+ DELETE FROM feature
+ WHERE feature_id=$featureId$
+ </delete>
+</sqlMap>
diff --git a/artemis_sqlmap/FeatureCvTerm.xml b/artemis_sqlmap/FeatureCvTerm.xml
new file mode 100644
index 0000000..6ef8cd9
--- /dev/null
+++ b/artemis_sqlmap/FeatureCvTerm.xml
@@ -0,0 +1,167 @@
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeatureCvTerm">
+
+ <cacheModel id="featurecvterm-rank-cache" type ="LRU" readOnly="true">
+ <flushInterval hours="24"/>
+ <property name="cache-size" value="15" />
+ </cacheModel>
+
+ <typeAlias alias="FeatureCvTerm"
+ type="org.gmod.schema.sequence.FeatureCvTerm"/>
+
+ <resultMap id="select-featurecvterm-no-rank" class="FeatureCvTerm"
+ groupBy="featureCvTermId">
+ <result property="featureCvTermId" column="feature_cvterm_id" />
+ <result property="feature.featureId" column="feature_id" />
+ <result property="cvTerm.cvTermId" column="cvterm_id" />
+ <result property="cvTerm.name" column="cvterm_name" />
+ <result property="cvTerm.cv.name" column="cv_name" />
+ <result property="not" column="is_not" />
+
+ <result property="featureCvTermProps" resultMap="FeatureCvTermProp.feature_cvtermprop" />
+ <result property="pub.pubId" column="pub_id" />
+ <result property="pub.uniqueName" column="uniquename" />
+ <result property="cvTerm.dbXRef.accession" column="accession" />
+ <result property="cvTerm.dbXRef.db.name" column="name" />
+ <result property="featureCvTermDbXRefs" column="feature_cvterm_id"
+ select="getFeatureCvTermDbXRefByFeatureCvTerm" />
+ <!-- <result property="pub" column="pub_id" select="selectPub" /> -->
+ </resultMap>
+
+ <resultMap id="select-featurecvterm" class="FeatureCvTerm"
+ extends="select-featurecvterm-no-rank">
+ <result property="rank" column="rank" />
+ </resultMap>
+
+
+ <!-- select feature_cvterm -->
+ <sql id="sql_select_feature_cvterm">
+ SELECT
+ fc.*,
+ fcp.type_id, fcp.value, fcp.rank AS fcp_rank,
+ cvterm.name AS cvterm_name,
+ cv.name AS cv_name,
+ pub.pub_id,
+ pub.uniquename,
+ db.name,
+ dbxref.accession
+ FROM feature_cvterm fc
+ LEFT JOIN feature_cvtermprop fcp ON fc.feature_cvterm_id=fcp.feature_cvterm_id
+ LEFT JOIN cvterm ON cvterm.cvterm_id=fc.cvterm_id
+ LEFT JOIN cv ON cvterm.cv_id=cv.cv_id
+ LEFT JOIN pub ON fc.pub_id=pub.pub_id
+ LEFT JOIN dbxref ON cvterm.dbxref_id=dbxref.dbxref_id
+ LEFT JOIN db ON dbxref.db_id=db.db_id
+ </sql>
+
+ <select id="getFeatureCvTermsBySrcFeature" parameterClass="org.gmod.schema.sequence.Feature"
+ resultMap="select-featurecvterm">
+ <include refid="sql_select_feature_cvterm"/>
+ WHERE feature_id IN
+ (SELECT feature_id FROM featureloc fl WHERE srcfeature_id=$featureId$
+ <isGreaterThan property="featureLoc.fmin" compareValue="0" prepend="AND">
+ <![CDATA[ fl.fmin > $featureLoc.fmin$ AND fl.fmax < $featureLoc.fmax$ ]]>
+ </isGreaterThan>)
+ ORDER BY fc.feature_cvterm_id, fc.rank, type_id, fcp.rank;
+ </select>
+
+
+ <select id="getFeatureCvTermsNoRankBySrcFeature" parameterClass="org.gmod.schema.sequence.Feature"
+ resultMap="select-featurecvterm-no-rank">
+ <include refid="sql_select_feature_cvterm"/>
+ WHERE feature_id IN
+ (SELECT feature_id FROM featureloc fl WHERE srcfeature_id=$featureId$
+ <isGreaterThan property="featureLoc.fmin" compareValue="0" prepend="AND">
+ <![CDATA[ fl.fmin > $featureLoc.fmin$ AND fl.fmax < $featureLoc.fmax$ ]]>
+ </isGreaterThan>)
+ ORDER BY fc.feature_cvterm_id, type_id, fcp.rank;
+ </select>
+
+
+ <select id="getFeatureCvTermsByFeature" parameterClass="Feature"
+ resultMap="select-featurecvterm">
+ <include refid="sql_select_feature_cvterm"/>
+ <dynamic>
+ <isNotNull property="uniqueName" prepend="WHERE">
+ feature_id IN (SELECT feature_id FROM feature WHERE uniquename=#uniqueName#)
+ </isNotNull>
+ </dynamic>
+ ORDER BY fc.feature_cvterm_id, fc.rank, type_id, fcp.rank;
+ </select>
+
+
+ <!-- same as getFeatureCvTermsByFeature but without feature_cvterm.rank -->
+ <select id="getFeatureCvTermsNoRankByFeature" parameterClass="Feature"
+ resultMap="select-featurecvterm-no-rank">
+ <include refid="sql_select_feature_cvterm"/>
+ <dynamic>
+ <isNotNull property="uniqueName" prepend="WHERE">
+ feature_id=(SELECT feature_id FROM feature WHERE uniquename=#uniqueName#)
+ </isNotNull>
+ </dynamic>
+ ORDER BY fc.feature_cvterm_id, type_id, fcp.rank;
+ </select>
+
+
+ <select id="getFeatureCvTermColumnsForASchema" parameterClass="java.lang.String"
+ resultClass="java.lang.String" cacheModel="featurecvterm-rank-cache">
+ SELECT pg_attribute.attname
+ FROM pg_attribute, pg_class, pg_namespace
+ WHERE pg_namespace.oid=pg_class.relnamespace AND
+ attrelid=pg_class.oid AND
+ relname='feature_cvterm' AND
+ attnum > 0
+ <!--AND nspname=#value#-->
+ </select>
+
+
+ <delete id="deleteFeatureCvTerm" parameterClass="FeatureCvTerm">
+ DELETE FROM feature_cvterm WHERE
+ feature_cvterm_id=$featureCvTermId$
+ </delete>
+
+ <!-- UPDATE -->
+ <update id="updateFeatureCvTerm"
+ parameterClass="FeatureCvTerm">
+ UPDATE feature_cvterm
+ SET rank=$rank$
+ WHERE feature_cvterm_id=$featureCvTermId$
+ </update>
+
+ <!-- INSERT -->
+ <insert id="insertFeatureCvTerm"
+ parameterClass="FeatureCvTerm">
+ INSERT INTO feature_cvterm
+ ( feature_cvterm_id, feature_id, cvterm_id, pub_id, rank, is_not )
+ VALUES
+ ( nextval('feature_cvterm_feature_cvterm_id_seq'),
+ (SELECT feature_id FROM feature WHERE uniquename=#feature.uniqueName#),
+ <isGreaterThan property="cvTerm.cvTermId" compareValue="0">
+ $cvTerm.cvTermId$,
+ </isGreaterThan>
+ <isEqual property="cvTerm.cvTermId" compareValue="0">
+ (SELECT cvterm_id FROM cvterm WHERE name=#cvTerm.name# AND
+ cv_id = (SELECT cv_id FROM cv WHERE name=#cvTerm.cv.name#)),
+ </isEqual>
+ <dynamic>
+ <isNotNull property="pub">
+ <isEqual property="pub.pubId" compareValue="0">
+ (SELECT pub_id FROM pub WHERE uniquename=#pub.uniqueName#),
+ </isEqual>
+ <isGreaterThan property="pub.pubId" compareValue="0">
+ $pub.pubId$,
+ </isGreaterThan>
+ </isNotNull>
+ <isNull property="pub">
+ (SELECT pub_id FROM pub WHERE uniquename='null' OR uniquename='NULL'),
+ </isNull>
+ </dynamic>
+ $rank$,
+ $not$ )
+ </insert>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/FeatureCvTermDbXRef.xml b/artemis_sqlmap/FeatureCvTermDbXRef.xml
new file mode 100644
index 0000000..ccbfce7
--- /dev/null
+++ b/artemis_sqlmap/FeatureCvTermDbXRef.xml
@@ -0,0 +1,67 @@
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeatureCvTermDbXRef">
+
+ <typeAlias alias="FeatureCvTermDbXRef"
+ type="org.gmod.schema.sequence.FeatureCvTermDbXRef"/>
+
+ <resultMap id="map-featurecvtermdbxref" class="FeatureCvTermDbXRef"
+ groupBy="featureCvTermId">
+ <result property="featureCvTerm.featureCvTermId" column="feature_cvterm_id" />
+ <result property="dbXRef.accession" column="accession"/>
+ <result property="dbXRef.version" column="version"/>
+ <result property="dbXRef.description" column="description"/>
+ <result property="dbXRef.db.name" column="name"/>
+ </resultMap>
+
+ <select id="getFeatureCvTermDbXRefByFeature" parameterClass="Feature"
+ resultMap="map-featurecvtermdbxref">
+ SELECT fcd.feature_cvterm_id, dbx.*, db.name
+ FROM feature_cvterm_dbxref fcd
+ LEFT JOIN dbxref dbx ON dbx.dbxref_id=fcd.dbxref_id
+ LEFT JOIN db ON db.db_id=dbx.db_id
+ <dynamic>
+ <isNotNull property="uniqueName">
+ LEFT JOIN feature_cvterm fc ON fcd.feature_cvterm_id=fc.feature_cvterm_id
+ WHERE
+ feature_id=(SELECT feature_id FROM feature WHERE uniquename=#uniqueName#)
+ </isNotNull>
+ </dynamic>
+ </select>
+
+ <select id="getFeatureCvTermDbXRefBySrcFeature" parameterClass="org.gmod.schema.sequence.Feature"
+ resultMap="map-featurecvtermdbxref">
+ SELECT fcd.feature_cvterm_id, dbx.*, db.name
+ FROM feature_cvterm_dbxref fcd
+ LEFT JOIN dbxref dbx ON dbx.dbxref_id=fcd.dbxref_id
+ LEFT JOIN db ON db.db_id=dbx.db_id
+ LEFT JOIN feature_cvterm fc ON fcd.feature_cvterm_id=fc.feature_cvterm_id
+ WHERE feature_id IN
+ (SELECT feature_id FROM featureloc fl WHERE srcfeature_id=$featureId$
+ <isGreaterThan property="featureLoc.fmin" compareValue="0" prepend="AND">
+ <![CDATA[ fl.fmin > $featureLoc.fmin$ AND fl.fmax < $featureLoc.fmax$ ]]>
+ </isGreaterThan>)
+ </select>
+
+ <select id="getFeatureCvTermDbXRefByFeatureCvTerm" parameterClass="java.lang.Integer"
+ resultMap="map-featurecvtermdbxref">
+ SELECT fcd.feature_cvterm_id, dbx.*, db.name
+ FROM feature_cvterm_dbxref fcd
+ LEFT JOIN dbxref dbx ON dbx.dbxref_id=fcd.dbxref_id
+ LEFT JOIN db ON db.db_id=dbx.db_id
+ WHERE feature_cvterm_id=$feature_cvterm_id$
+ </select>
+
+ <!-- INSERT -->
+ <insert id="insertFeatureCvTermDbXRef"
+ parameterClass="FeatureCvTermDbXRef">
+ INSERT INTO feature_cvterm_dbxref
+ ( feature_cvterm_id, dbxref_id )
+ VALUES
+ ( $featureCvTerm.featureCvTermId$, $dbXRef.dbXRefId$ )
+ </insert>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/FeatureCvTermProp.xml b/artemis_sqlmap/FeatureCvTermProp.xml
new file mode 100644
index 0000000..17c0261
--- /dev/null
+++ b/artemis_sqlmap/FeatureCvTermProp.xml
@@ -0,0 +1,29 @@
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeatureCvTermProp">
+
+ <typeAlias alias="FeatureCvTermProp"
+ type="org.gmod.schema.sequence.FeatureCvTermProp"/>
+
+ <resultMap id="feature_cvtermprop" class="FeatureCvTermProp">
+ <result property="cvTerm.cvTermId" column="type_id" />
+ <result property="value" column="value" />
+ <result property="rank" column="fcp_rank" />
+ </resultMap>
+
+ <!-- INSERT -->
+ <insert id="insertFeatureCvTermProp"
+ parameterClass="FeatureCvTermProp">
+ INSERT INTO feature_cvtermprop
+ ( feature_cvtermprop_id, feature_cvterm_id, type_id, value, rank )
+ VALUES
+ ( nextval('feature_cvtermprop_feature_cvtermprop_id_seq'),
+ $featureCvTerm.featureCvTermId$,
+ $cvTerm.cvTermId$,
+ #value#,
+ $rank$ )
+ </insert>
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/FeatureCvTermPub.xml b/artemis_sqlmap/FeatureCvTermPub.xml
new file mode 100644
index 0000000..9fa5039
--- /dev/null
+++ b/artemis_sqlmap/FeatureCvTermPub.xml
@@ -0,0 +1,55 @@
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeatureCvTermPub">
+
+ <typeAlias alias="FeatureCvTermPub"
+ type="org.gmod.schema.sequence.FeatureCvTermPub"/>
+
+ <resultMap id="feature_cvterm_pub" class="FeatureCvTermPub">
+ <result property="featureCvTerm.featureCvTermId" column="feature_cvterm_id" />
+ <result property="pub.pubId" column="pub_id"/>
+ <result property="pub.cvTerm.cvTermId" column="type_id" />
+ <result property="pub.uniqueName" column="uniquename"/>
+ </resultMap>
+
+ <select id="getFeatureCvTermPubByFeature" parameterClass="Feature"
+ resultMap="feature_cvterm_pub">
+ SELECT fcp.feature_cvterm_id, pub.*
+ FROM feature_cvterm_pub fcp
+ LEFT JOIN pub ON fcp.pub_id=pub.pub_id
+ <dynamic>
+ <isNotNull property="uniqueName">
+ LEFT JOIN feature_cvterm fc ON fcp.feature_cvterm_id=fc.feature_cvterm_id
+ WHERE
+ feature_id=(SELECT feature_id FROM feature WHERE uniquename=#uniqueName#)
+ </isNotNull>
+ </dynamic>
+ </select>
+
+
+ <select id="getFeatureCvTermPubBySrcFeature" parameterClass="org.gmod.schema.sequence.Feature"
+ resultMap="feature_cvterm_pub">
+ SELECT fcp.feature_cvterm_id, pub.*
+ FROM feature_cvterm_pub fcp
+ LEFT JOIN pub ON fcp.pub_id=pub.pub_id
+ LEFT JOIN feature_cvterm fc ON fcp.feature_cvterm_id=fc.feature_cvterm_id
+ WHERE feature_id IN
+ (SELECT feature_id FROM featureloc fl WHERE srcfeature_id=$featureId$
+ <isGreaterThan property="featureLoc.fmin" compareValue="0" prepend="AND">
+ <![CDATA[ fl.fmin > $featureLoc.fmin$ AND fl.fmax < $featureLoc.fmax$ ]]>
+ </isGreaterThan>)
+ </select>
+
+
+ <!-- INSERT -->
+ <insert id="insertFeatureCvTermPub"
+ parameterClass="FeatureCvTermPub">
+ INSERT INTO feature_cvterm_pub
+ ( feature_cvterm_id, pub_id )
+ VALUES
+ ( $featureCvTerm.featureCvTermId$, $pub.pubId$ )
+ </insert>
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/FeatureDbXRef.xml b/artemis_sqlmap/FeatureDbXRef.xml
new file mode 100644
index 0000000..01028ce
--- /dev/null
+++ b/artemis_sqlmap/FeatureDbXRef.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeatureDbXRef">
+
+ <typeAlias alias="FeatureDbXRef"
+ type="org.gmod.schema.sequence.FeatureDbXRef"/>
+
+ <resultMap id="map-feature-dbxref"
+ class="FeatureDbXRef">
+ <result property="feature.featureId" column="feature_id"/>
+ <result property="dbXRef.db.name" column="name"/>
+ <result property="dbXRef.accession" column="accession"/>
+ <result property="dbXRef.version" column="version"/>
+ <result property="dbXRef.description" column="description"/>
+ <result property="current" column="is_current" nullValue="false"/>
+ </resultMap>
+
+ <resultMap id="map-feature-of-feature-dbxref"
+ class="Feature" groupBy="featureId">
+ <result property="featureId" column="feature_id"/>
+ <result property="featureDbXRefs" resultMap="FeatureDbXRef.map-feature-dbxref" />
+ </resultMap>
+
+ <select id="getFeatureDbXRef"
+ parameterClass="org.gmod.schema.sequence.Feature"
+ resultMap="map-feature-dbxref">
+ SELECT db.name, dbx.accession, dbx.version, dbx.description,
+ dbx_f.feature_id, dbx_f.is_current
+ FROM feature_dbxref dbx_f
+ LEFT JOIN dbxref dbx ON dbx.dbxref_id=dbx_f.dbxref_id
+ LEFT JOIN db ON db.db_id=dbx.db_id
+ <!--LEFT JOIN feature f ON dbx_f.feature_id=f.feature_id-->
+ <isNotNull property="uniqueName">
+ WHERE dbx_f.feature_id=(SELECT feature_id FROM feature where uniquename=#uniqueName#)
+ </isNotNull>
+ <isGreaterThan property="featureId" compareValue="0">
+ WHERE dbx_f.feature_id=$featureId$
+ </isGreaterThan>
+ ORDER BY feature_id
+ </select>
+
+ <select id="getFeatureDbXRefsByFeatureId" resultMap="map-feature-of-feature-dbxref">
+ SELECT db.name, dbx.accession, dbx.version, dbx.description,
+ dbx_f.feature_id, dbx_f.is_current
+ FROM feature_dbxref dbx_f
+ LEFT JOIN dbxref dbx ON dbx.dbxref_id=dbx_f.dbxref_id
+ LEFT JOIN db ON db.db_id=dbx.db_id
+ <!-- RIGHT JOIN feature f ON dbx_f.feature_id=f.feature_id -->
+ WHERE dbx_f.feature_id IN <iterate open="(" close=")" conjunction=","> $[]$ </iterate>
+ ORDER BY dbx_f.feature_id
+ </select>
+
+ <select id="getFeatureDbXRefsBySrcFeature"
+ parameterClass="org.gmod.schema.sequence.Feature"
+ resultMap="map-feature-dbxref">
+ SELECT db.name, dbx.accession, dbx.version, dbx.description,
+ dbx_f.feature_id, dbx_f.is_current
+ FROM featureloc fl
+ RIGHT JOIN feature_dbxref dbx_f ON fl.feature_id = dbx_f.feature_id
+ LEFT JOIN dbxref dbx ON dbx.dbxref_id=dbx_f.dbxref_id
+ LEFT JOIN db ON db.db_id=dbx.db_id
+ WHERE srcfeature_id = $featureId$
+ <isGreaterThan property="featureLoc.fmin" compareValue="0" prepend="AND">
+ <![CDATA[ fl.fmin > $featureLoc.fmin$ AND fl.fmax < $featureLoc.fmax$ ]]>
+ </isGreaterThan>
+ ORDER BY fl.feature_id, dbx.accession , db.name;
+ </select>
+
+ <!-- WRITE BACK -->
+ <delete id="deleteFeatureDbXRef" parameterClass="FeatureDbXRef">
+ DELETE FROM feature_dbxref
+ WHERE dbxref_id=
+ (SELECT dbxref_id FROM dbxref WHERE accession=#dbXRef.accession#
+ AND db_id=(SELECT db_id FROM db WHERE name=#dbXRef.db.name#))
+ AND feature_id=
+ (SELECT feature_id FROM feature WHERE uniquename=#feature.uniqueName#)
+ </delete>
+
+ <insert id="insertFeatureDbXRef" parameterClass="FeatureDbXRef">
+ INSERT INTO feature_dbxref
+ (feature_id, dbxref_id, is_current)
+ VALUES
+ ($feature.featureId$, $dbXRef.dbXRefId$, $current$)
+ </insert>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/FeatureLoc.xml b/artemis_sqlmap/FeatureLoc.xml
new file mode 100644
index 0000000..9989f7d
--- /dev/null
+++ b/artemis_sqlmap/FeatureLoc.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeatureLoc">
+
+ <typeAlias alias="FeatureLoc"
+ type="org.gmod.schema.sequence.FeatureLoc"/>
+
+ <resultMap id="map-location-lazy"
+ class="FeatureLoc">
+ <result property="featureByFeatureId.featureId" column="feature_id" />
+ <result property="srcFeatureId" column="srcfeature_id" nullValue="-999" />
+ <result property="featureBySrcFeatureId" column="{featureId=srcfeature_id}"
+ select="getLazyFeatureNoResiduesById" />
+ <result property="fmin" column="fmin" nullValue="-999" />
+ <result property="fmax" column="fmax" nullValue="-999" />
+ <result property="strand" column="strand" nullValue="0" />
+ <result property="phase" column="phase" />
+ <result property="residueInfo" column="residue_info"/>
+ <result property="locGroup" column="locgroup"/>
+ <result property="rank" column="rank"/>
+ <result property="fminPartial" column="is_fmin_partial"/>
+ <result property="fmaxPartial" column="is_fmax_partial"/>
+ </resultMap>
+
+
+ <!-- select featureloc -->
+ <select id="getFeatureLoc" resultMap="map-location-lazy">
+ SELECT feature_id, srcfeature_id, fmin, fmax, strand, phase, residue_info, locgroup, rank, is_fmin_partial, is_fmax_partial
+ FROM featureloc
+ WHERE featureloc.feature_id=#id#
+ </select>
+
+ <select id="getFeatureLocBySrcFeatureId" resultMap="map-location-lazy">
+ SELECT feature_id, srcfeature_id, fmin, fmax, strand, phase, residue_info, locgroup, rank, is_fmin_partial, is_fmax_partial
+ FROM featureloc
+ WHERE srcfeature_id=#id#
+ </select>
+
+ <select id="getFeatureLocsByListOfIds" resultMap ="map-location-lazy">
+ SELECT feature_id, srcfeature_id, fmin, fmax, strand, phase, residue_info, locgroup, rank, is_fmin_partial, is_fmax_partial
+ FROM featureloc
+ WHERE featureloc.feature_id IN <iterate open="(" close=")" conjunction=","> $[]$ </iterate>
+ </select>
+
+ <!-- used to find match feature from the subject and query featureId -->
+ <select id="getFeatureIdBySrcFeatureId" parameterClass="Feature" resultClass="java.lang.Integer">
+ <iterate property="featureLocsForFeatureId" conjunction="INTERSECT" >
+ SELECT feature_id FROM featureloc WHERE srcfeature_id=#featureLocsForFeatureId[].featureBySrcFeatureId.featureId#
+ </iterate>
+ </select>
+
+ <!-- WRITE BACK METHODS -->
+
+ <!-- UPDATE -->
+ <update id="updateFeatureLoc"
+ parameterClass="FeatureLoc">
+ UPDATE featureloc
+ SET fmin=$fmin$, fmax=$fmax$, strand=$strand$,
+ is_fmin_partial=#fminPartial#,
+ is_fmax_partial=#fmaxPartial#
+ <isNotNull property="phase">
+ , phase=$phase$
+ </isNotNull>
+ <isNull property="phase">
+ , phase=NULL
+ </isNull>
+ WHERE
+ <isGreaterThan property="featureByFeatureId.featureId" compareValue="0">
+ feature_id=$featureByFeatureId.featureId$
+ </isGreaterThan>
+ <isLessEqual property="featureByFeatureId.featureId" compareValue="0">
+ <isNotNull property="featureByFeatureId.uniqueName">
+ feature_id=(SELECT feature_id FROM feature WHERE uniquename=#featureByFeatureId.uniqueName#)
+ </isNotNull>
+ </isLessEqual>
+ <isGreaterThan property="featureBySrcFeatureId.featureId" compareValue="0" prepend="AND">
+ srcfeature_id=$featureBySrcFeatureId.featureId$
+ </isGreaterThan>
+ </update>
+
+ <update id="updateFeatureLocByChangingSequence"
+ parameterClass="uk.ac.sanger.artemis.chado.FeatureForUpdatingResidues">
+ UPDATE featureloc
+ SET
+ <isNotNull property="newSubSequence">
+ fmin=fmin+$length$, fmax=fmax+$length$
+ </isNotNull>
+ <isNull property="newSubSequence">
+ fmin=fmin-$length$, fmax=fmax-$length$
+ </isNull>
+ WHERE fmin >= $startBase$ AND srcfeature_id=$featureId$
+ </update>
+
+ <insert id="insertFeatureLoc"
+ parameterClass="FeatureLoc">
+ INSERT INTO featureloc
+ ( featureloc_id, feature_id, srcfeature_id, fmin, fmax, strand, rank
+ <isNotNull property="phase">
+ , phase
+ </isNotNull>
+ )
+ VALUES
+ ( nextval('featureloc_featureloc_id_seq'),
+ $featureByFeatureId.featureId$,
+ $featureBySrcFeatureId.featureId$,
+ $fmin$,
+ $fmax$,
+ $strand$,
+ $rank$
+ <isNotNull property="phase">
+ , $phase$
+ </isNotNull>
+ )
+ </insert>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/FeatureProp.xml b/artemis_sqlmap/FeatureProp.xml
new file mode 100644
index 0000000..461b96e
--- /dev/null
+++ b/artemis_sqlmap/FeatureProp.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeatureProp">
+
+ <typeAlias alias="FeatureProp"
+ type="org.gmod.schema.sequence.FeatureProp"/>
+
+ <resultMap id="map-featureprop-with-cvterm-lazy"
+ class="FeatureProp">
+ <result property="value" column="value" />
+ <result property="rank" column="rank" />
+ <result property="cvTerm" column="type_id" select="getCvtermByCvTermId" />
+ </resultMap>
+
+ <resultMap id="map-featureprop-with-cvterm"
+ class="FeatureProp">
+ <result property="value" column="value" />
+ <result property="rank" column="rank" />
+ <result property="cvTerm" resultMap="CvTerm.lazy-cvterm" />
+ </resultMap>
+
+ <resultMap id="map-featureprop"
+ class="FeatureProp" groupBy="featurePropId">
+ <result property="featurePropId" column="featureprop_id" />
+ <result property="value" column="value" />
+ <result property="rank" column="prop_rank" />
+ <result property="cvTerm.cvTermId" column="prop_type_id" />
+ </resultMap>
+
+ <resultMap id="map-feature-with-featureprops"
+ class="org.gmod.schema.sequence.Feature" groupBy="featureId">
+ <result property="featureId" column="feature_id" />
+ <result property="featureProps" resultMap="FeatureProp.map-featureprop-with-cvterm" />
+ </resultMap>
+
+ <!-- select featureprops e.g. for lazy loading -->
+ <select id="getFeaturePropByFeatureIdLazy" resultMap="map-featureprop-with-cvterm-lazy">
+ SELECT type_id, value, rank
+ FROM featureprop
+ WHERE feature_id=#feature_id#
+ </select>
+
+ <select id="getFeaturePropByFeatureId" resultMap="map-featureprop-with-cvterm">
+ SELECT value, rank, cvterm.*
+ FROM featureprop
+ LEFY JOIN cvterm ON type_id=cvterm_id
+ WHERE feature_id=#feature_id#
+ </select>
+
+ <select id="getFeaturePropByFeatureIds" resultMap="map-feature-with-featureprops">
+ SELECT f.feature_id, value, rank, cvterm.*
+ FROM feature f
+ LEFT JOIN featureprop fp ON f.feature_id=fp.feature_id
+ LEFT JOIN cvterm ON fp.type_id=cvterm_id
+ WHERE f.feature_id IN <iterate open="(" close=")" conjunction=","> $[]$ </iterate>
+ </select>
+
+ <update id="updateFeatureProp"
+ parameterClass="FeatureProp">
+ UPDATE featureprop
+ SET value=#value#
+ WHERE rank=$rank$ AND type_id=$cvTerm.cvTermId$ AND
+ feature_id=(SELECT feature_id FROM feature WHERE uniquename=#feature.uniqueName#)
+ </update>
+
+ <!-- INSERT -->
+ <insert id="insertFeatureProp"
+ parameterClass="FeatureProp">
+ INSERT INTO featureprop
+ ( feature_id, type_id, value, rank )
+ VALUES
+ (
+ <isGreaterThan property="feature.featureId" compareValue="0">
+ $feature.featureId$,
+ </isGreaterThan>
+ <isLessEqual property="feature.featureId" compareValue="0">
+ (SELECT feature_id FROM feature WHERE uniquename=#feature.uniqueName#),
+ </isLessEqual>
+ $cvTerm.cvTermId$, #value#, $rank$ )
+ </insert>
+
+ <!-- DELETE -->
+ <delete id="deleteFeatureProp"
+ parameterClass="FeatureProp">
+ DELETE FROM featureprop
+ WHERE
+ <isNotNull property="feature.uniqueName">
+ feature_id=
+ (SELECT feature_id FROM feature WHERE uniquename=#feature.uniqueName#)
+ AND
+ </isNotNull>
+ <isGreaterThan property="rank" compareValue="-1">
+ rank=$rank$ AND
+ </isGreaterThan>
+ <isNotNull property="value">
+ value=#value# AND
+ </isNotNull>
+ type_id=$cvTerm.cvTermId$
+ </delete>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/FeaturePub.xml b/artemis_sqlmap/FeaturePub.xml
new file mode 100644
index 0000000..88c93c2
--- /dev/null
+++ b/artemis_sqlmap/FeaturePub.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeaturePub">
+
+ <typeAlias alias="FeaturePub"
+ type="org.gmod.schema.sequence.FeaturePub"/>
+
+
+ <resultMap id="map-featurepubs"
+ class="FeaturePub" >
+ <result property="feature.featureId" column="id"/>
+ <result property="pub.pubId" column="pub_id"/>
+ <result property="pub.title" column="title" />
+ <result property="pub.volumeTitle" column="volumetitle" />
+ <result property="pub.volume" column="volume" />
+ <result property="pub.seriesName" column="series_name" />
+ <result property="pub.issue" column="issue" />
+ <result property="pub.pyear" column="pyear"/>
+ <result property="pub.pages" column="pages"/>
+ <result property="pub.miniRef" column="miniref"/>
+ <result property="pub.uniqueName" column="uniquename" />
+ <result property="pub.obsolete" column="is_obsolete" />
+ <result property="pub.publisher" column="publisher" />
+ <result property="pub.pubPlace" column="pubplace" />
+ <result property="pub.cvTerm.cvTermId" column="type_id" />
+ </resultMap>
+
+ <select id="getFeaturePubsBySrcFeature" parameterClass="org.gmod.schema.sequence.Feature"
+ resultMap="map-featurepubs">
+ SELECT
+ f.feature_id AS id, pub.*
+ FROM feature f
+ INNER JOIN feature_pub fpub ON fpub.feature_id = f.feature_id
+ LEFT JOIN pub ON fpub.pub_id=pub.pub_id
+ WHERE f.feature_id IN
+ (SELECT feature_id FROM featureloc fl WHERE srcfeature_id=$featureId$
+ <isGreaterThan property="featureLoc.fmin" compareValue="0" prepend="AND">
+ <![CDATA[ fl.fmin > $featureLoc.fmin$ AND fl.fmax < $featureLoc.fmax$ ]]>
+ </isGreaterThan>)
+ ORDER BY f.uniquename
+ </select>
+
+
+ <select id="getFeaturePubsByFeature" parameterClass="org.gmod.schema.sequence.Feature"
+ resultMap="map-featurepubs">
+ SELECT
+ f.feature_id AS id, pub.*
+ FROM feature f
+ INNER JOIN feature_pub fpub ON fpub.feature_id = f.feature_id
+ LEFT JOIN pub ON fpub.pub_id=pub.pub_id
+ <isGreaterThan property="featureId" compareValue="0">
+ WHERE f.feature_id = $featureId$
+ </isGreaterThan>
+ <isEqual property="featureId" compareValue="0">
+ <isNotNull property="uniqueName">
+ WHERE f.uniquename=#uniqueName#
+ </isNotNull>
+ </isEqual>
+ </select>
+
+ <insert id="insertFeaturePub" parameterClass="FeaturePub">
+ INSERT INTO feature_pub
+ (feature_id, pub_id)
+ VALUES
+ ( (SELECT feature_id FROM feature WHERE uniquename=#feature.uniqueName#),
+ $pub.pubId$)
+ </insert>
+
+ <delete id="deleteFeaturePub" parameterClass="FeaturePub">
+ DELETE FROM feature_pub WHERE
+ feature_pub_id=(
+ SELECT feature_pub_id FROM feature_pub WHERE
+ feature_id=(SELECT feature_id FROM feature WHERE uniquename=#feature.uniqueName#) AND
+ pub_id =(SELECT pub_id FROM pub WHERE uniquename=#pub.uniqueName#) )
+ </delete>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/FeatureRelationship.xml b/artemis_sqlmap/FeatureRelationship.xml
new file mode 100644
index 0000000..a8743c8
--- /dev/null
+++ b/artemis_sqlmap/FeatureRelationship.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeatureRelationship">
+
+
+ <typeAlias alias="FeatureRelationship"
+ type="org.gmod.schema.sequence.FeatureRelationship"/>
+
+ <resultMap id="map-basic-feature-relationship" class="FeatureRelationship">
+ <result property="featureRelationshipId" column="feature_relationship_id"/>
+ <result property="cvTerm.cvTermId" column="relation_type_id"/>
+ <result property="rank" column="rank"/>
+ <result property="value" column="relation_value" />
+ </resultMap>
+
+ <resultMap id="map-feature-relationship-for-objectId" class="FeatureRelationship"
+ extends="map-basic-feature-relationship">
+ <result property="featureByObjectId.featureId" column="object_id"/>
+ </resultMap>
+
+ <resultMap id="map-feature-relationship-for-subjectId" class="FeatureRelationship">
+ <result property="cvTerm.name" column="fr_name"/>
+ <result property="featureBySubjectId.featureId" column="subject_id"/>
+ <result property="featureBySubjectId.uniqueName" column="subject_name"/>
+ <result property="featureBySubjectId.organism.commonName" column="subject_common_name"/>
+ <result property="featureBySubjectId.cvTerm.name" column="subject_cvterm" />
+ <result property="featureBySubjectId.featureCvTerms"
+ column="{{uniqueName=subject_name}}"
+ select="getFeatureCvTermsByFeature"/>
+ <result property="featureBySubjectId.featureRelationshipsForSubjectId"
+ column="{{subject_id=subject_id}}"
+ select="getParentFeatureRelationship" />
+ </resultMap>
+
+ <resultMap id="select-relationship" class="FeatureRelationship">
+ <result property="featureRelationshipId" column="feature_relationship_id"/>
+ <result property="featureBySubjectId" column="subject_id"
+ select="getLazyFeatureNoResiduesById" />
+ <result property="featureByObjectId" column="object_id"
+ select="getLazyFeatureNoResiduesById" />
+ <result property="value" column="value" />
+ <result property="rank" column="rank" />
+ <result property="cvTerm" column="type_id" select="getCvtermByCvTermId" />
+ </resultMap>
+
+ <resultMap id="map-parent-relationship" class="FeatureRelationship">
+ <result property="featureRelationshipId" column="feature_relationship_id"/>
+ <result property="featureBySubjectId.featureId" column="subject_id" />
+ <!--<result property="featureByObjectId" column="object_id"
+ select="getLazyFeatureNameAndTypeById" />-->
+ <result property="featureByObjectId" resultMap="Feature.map-feature-name-lazy" />
+ <result property="value" column="value" />
+ <result property="rank" column="rank" />
+ <result property="cvTerm.name" column="fr_name" />
+ </resultMap>
+
+ <!-- select feature_relationship -->
+ <select id="getFeatureRelationship" resultMap="select-relationship">
+ SELECT feature_relationship_id, subject_id, object_id, type_id, value, rank
+ FROM feature_relationship
+ WHERE
+ <dynamic>
+ <isNotNull property="object_id">
+ object_id=#object_id#
+ </isNotNull>
+ <isNotNull property="subject_id">
+ subject_id=#subject_id#
+ </isNotNull>
+ </dynamic>
+ </select>
+
+ <select id="getParentFeatureRelationship" resultMap="map-parent-relationship">
+ SELECT feature_relationship_id, subject_id, object_id, value, rank,
+ fr_cvterm.name AS fr_name, parent.uniquename, parent_cvterm.name AS name, parent.feature_id AS id
+ FROM feature_relationship AS fr
+ LEFT JOIN cvterm AS fr_cvterm ON fr_cvterm.cvterm_id=fr.type_id
+ LEFT JOIN feature AS parent ON object_id=feature_id
+ LEFT JOIN cvterm AS parent_cvterm ON parent_cvterm.cvterm_id=parent.type_id
+ WHERE subject_id=#subject_id# AND
+ ( fr_cvterm.name='derives_from' OR fr_cvterm.name='part_of' OR fr_cvterm.name='proper_part_of' )
+
+ <!--
+ SELECT feature_relationship_id, subject_id, object_id, value, rank,
+ fr_cvterm.name AS fr_name, parent.uniquename, parent_cvterm.name, parent.feature_id AS id
+ FROM feature_relationship AS fr
+ LEFT JOIN cvterm AS fr_cvterm ON fr_cvterm.cvterm_id=fr.type_id
+ LEFT JOIN feature AS parent ON object_id=feature_id
+ LEFT JOIN cvterm AS parent_cvterm ON parent_cvterm.cvterm_id=parent.type_id
+ WHERE
+ ( subject_id=#subject_id#
+ AND (fr_cvterm.name='derives_from' OR fr_cvterm.name='part_of' OR fr_cvterm.name='proper_part_of')
+ AND (parent_cvterm.name='gene' OR parent_cvterm.name='pseudogene'))
+ OR ( subject_id IN (SELECT object_id FROM feature_relationship AS fr2
+ LEFT JOIN cvterm AS fr2_cvterm ON fr2_cvterm.cvterm_id=fr2.type_id
+ WHERE subject_id=#subject_id#
+ AND (fr2_cvterm.name='derives_from' OR fr2_cvterm.name='part_of' OR fr2_cvterm.name='proper_part_of'))
+ AND (fr_cvterm.name='derives_from' OR fr_cvterm.name='part_of' OR fr_cvterm.name='proper_part_of')
+ AND (parent_cvterm.name='gene' OR parent_cvterm.name='pseudogene')) ;
+ -->
+
+ </select>
+
+ <select id="getParentFeaturesByChildFeatureIds" resultMap="map-parent-relationship">
+ SELECT feature_relationship_id, subject_id, object_id, value, rank,
+ fr_cvterm.name AS fr_name, parent.uniquename, parent_cvterm.name AS name, parent.feature_id AS id
+ FROM feature_relationship AS fr
+ LEFT JOIN cvterm AS fr_cvterm ON fr_cvterm.cvterm_id=fr.type_id
+ LEFT JOIN feature AS parent ON object_id=feature_id
+ LEFT JOIN cvterm AS parent_cvterm ON parent_cvterm.cvterm_id=parent.type_id
+ WHERE
+ ( subject_id IN <iterate open="(" close=")" conjunction=","> $[]$ </iterate>
+ AND (fr_cvterm.name='derives_from' OR fr_cvterm.name='part_of' OR fr_cvterm.name='proper_part_of') )
+ </select>
+
+ <!--
+ <select id="getGeneFeaturesByChildFeatureIds" resultMap="map-parent-relationship">
+ SELECT feature_relationship_id, subject_id, object_id, value, rank,
+ fr_cvterm.name AS fr_name, parent.uniquename, parent_cvterm.name AS name, parent.feature_id AS id
+ FROM feature_relationship AS fr
+ LEFT JOIN cvterm AS fr_cvterm ON fr_cvterm.cvterm_id=fr.type_id
+ LEFT JOIN feature AS parent ON object_id=feature_id
+ LEFT JOIN cvterm AS parent_cvterm ON parent_cvterm.cvterm_id=parent.type_id
+ WHERE
+ ( subject_id IN <iterate open="(" close=")" conjunction=","> $[]$ </iterate>
+ AND (fr_cvterm.name='derives_from' OR fr_cvterm.name='part_of' OR fr_cvterm.name='proper_part_of')
+ AND (parent_cvterm.name='gene' OR parent_cvterm.name='pseudogene') )
+ OR ( subject_id IN (SELECT object_id FROM feature_relationship AS fr2
+ LEFT JOIN cvterm AS fr2_cvterm ON fr2_cvterm.cvterm_id=fr2.type_id
+ WHERE subject_id IN <iterate open="(" close=")" conjunction=","> $[]$ </iterate>
+ AND (fr2_cvterm.name='derives_from' OR fr2_cvterm.name='part_of' OR fr2_cvterm.name='proper_part_of'))
+ AND (fr_cvterm.name='derives_from' OR fr_cvterm.name='part_of' OR fr_cvterm.name='proper_part_of')
+ AND (parent_cvterm.name='gene' OR parent_cvterm.name='pseudogene') ) ;
+ </select>
+ -->
+
+ <update id="updateFeatureRelationshipsForSubjectId"
+ parameterClass="FeatureRelationship">
+ UPDATE feature_relationship
+ SET
+ rank=$rank$, type_id=$cvTerm.cvTermId$
+ WHERE subject_id=
+ ( SELECT feature_id FROM feature WHERE uniquename=#featureBySubjectId.uniqueName# )
+ AND object_id=
+ ( SELECT feature_id FROM feature WHERE uniquename=#featureByObjectId.uniqueName# )
+ </update>
+
+ <insert id="insertFeatureRelationship"
+ parameterClass="FeatureRelationship">
+ INSERT INTO feature_relationship
+ ( subject_id, object_id, type_id, rank )
+ VALUES
+ ( (SELECT feature_id FROM feature WHERE uniquename=#featureBySubjectId.uniqueName#),
+ (SELECT feature_id FROM feature WHERE uniquename=#featureByObjectId.uniqueName#),
+ $cvTerm.cvTermId$, $rank$ )
+ </insert>
+
+ <delete id="deleteFeatureRelationship"
+ parameterClass="FeatureRelationship">
+ DELETE FROM feature_relationship WHERE
+ object_id=(SELECT feature_id FROM feature WHERE uniquename=#featureByObjectId.uniqueName#)
+ AND
+ subject_id=(SELECT feature_id FROM feature WHERE uniquename=#featureBySubjectId.uniqueName#)
+ AND type_id=$cvTerm.cvTermId$;
+ </delete>
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/FeatureSynonym.xml b/artemis_sqlmap/FeatureSynonym.xml
new file mode 100644
index 0000000..c7eade9
--- /dev/null
+++ b/artemis_sqlmap/FeatureSynonym.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="FeatureSynonym">
+
+ <typeAlias alias="FeatureSynonym"
+ type="org.gmod.schema.sequence.FeatureSynonym"/>
+
+ <typeAlias alias="Feature"
+ type="org.gmod.schema.sequence.Feature"/>
+
+ <typeAlias alias="Synonym"
+ type="org.gmod.schema.sequence.Synonym"/>
+
+ <resultMap id="map-featuresynonyms" class="Feature" groupBy="featureId" >
+ <result property="featureId" column="feature_id"/>
+ <result property="featureSynonyms" resultMap="map-alias" />
+ </resultMap>
+
+ <resultMap id="map-alias"
+ class="FeatureSynonym">
+ <result property="feature.featureId" column="feature_id"/>
+ <result property="pub.pubId" column="pub_id"/>
+ <result property="current" column="is_current"/>
+ <result property="internal" column="is_internal"/>
+ <result property="synonym.synonymId" column="synonym_id"/>
+ <result property="synonym.name" column="name"/>
+ <result property="synonym.synonymSgml" column="synonym_sgml"/>
+ <result property="synonym.cvTerm.cvTermId" column="type_id" />
+ </resultMap>
+
+ <resultMap id="map-alias-part-lazy"
+ class="FeatureSynonym">
+ <result property="feature.featureId" column="feature_id"/>
+ <result property="pub.pubId" column="pub_id"/>
+ <result property="current" column="is_current"/>
+ <result property="internal" column="is_internal"/>
+ <result property="synonym" column="synonym_id" select="getSynonymBySynonymId"/>
+ </resultMap>
+
+ <resultMap id="map-alias-part-lazy2"
+ class="FeatureSynonym">
+ <result property="feature.featureId" column="feature_id"/>
+ <result property="pub.pubId" column="pub_id"/>
+ <result property="current" column="is_current"/>
+ <result property="internal" column="is_internal"/>
+ <result property="synonym" resultMap="Synonym.map-synonym-lazy"/>
+ </resultMap>
+
+ <resultMap id="map-alias-lazy"
+ class="FeatureSynonym">
+ <result property="feature.featureId" column="feature_id"/>
+ <result property="pub.pubId" column="pub_id"/>
+ <result property="current" column="is_current"/>
+ <result property="internal" column="is_internal"/>
+ <result property="synonym" column="synonym_id" select="getSynonymBySynonymId"/>
+ </resultMap>
+
+
+ <!-- SQL -->
+ <select id="getAllFeatureSynonymsAsFeature" resultMap="map-featuresynonyms">
+ SELECT fs.*, s.name, s.synonym_sgml, s.type_id, s.synonym_id
+ FROM feature_synonym fs
+ LEFT JOIN synonym s ON fs.synonym_id=s.synonym_id
+ </select>
+
+ <select id="getFeatureSynonymsByUniquename" resultMap="map-alias"
+ parameterClass="Feature">
+ SELECT fs.*, s.name, s.synonym_sgml, s.type_id
+ FROM feature_synonym fs
+ LEFT JOIN feature f ON f.feature_id=fs.feature_id
+ LEFT JOIN synonym s ON fs.synonym_id=s.synonym_id
+ <isNotNull property="uniqueName">
+ WHERE uniquename=#uniqueName#
+ </isNotNull>
+ </select>
+
+ <select id="getFeatureSynonymsBySrcFeature" resultMap="map-alias"
+ parameterClass="Feature">
+ SELECT fs.*, s.name, s.synonym_sgml, s.type_id
+ FROM feature_synonym fs
+ LEFT JOIN synonym s ON fs.synonym_id=s.synonym_id
+ LEFT JOIN featureloc fl ON fl.feature_id=fs.feature_id AND srcfeature_id=$featureId$
+ WHERE srcfeature_id=$featureId$
+ <isGreaterThan property="featureLoc.fmin" compareValue="0" prepend="AND">
+ <![CDATA[ fl.fmin > $featureLoc.fmin$ AND fl.fmax < $featureLoc.fmax$ ]]>
+ </isGreaterThan>
+ <!--
+ SELECT fs.*, s.name, s.synonym_sgml, s.type_id
+ FROM feature_synonym fs
+ LEFT JOIN feature f ON f.feature_id=fs.feature_id
+ LEFT JOIN synonym s ON fs.synonym_id=s.synonym_id
+ WHERE f.feature_id IN
+ (SELECT feature_id FROM featureloc fl WHERE srcfeature_id=$featureId$
+ <isGreaterThan property="featureLoc.fmin" compareValue="0" prepend="AND">
+ <![CDATA[ fl.fmin > $featureLoc.fmin$ AND fl.fmax < $featureLoc.fmax$ ]]>
+ </isGreaterThan>)
+ -->
+ </select>
+
+ <select id="getFeatureSynonymsByFeatureIds" resultMap="map-alias-part-lazy2">
+ SELECT fs.*, s.name, s.synonym_sgml, s.type_id
+ FROM feature_synonym fs
+ LEFT JOIN synonym s ON fs.synonym_id=s.synonym_id
+ WHERE fs.feature_id IN
+ <iterate open="(" close=")" conjunction=","> $[]$ </iterate>
+ </select>
+
+
+ <select id="getLazyFeatureSynonymsByUniquename" resultMap="map-alias-part-lazy"
+ parameterClass="FeatureSynonym">
+ SELECT fs.*
+ FROM feature_synonym fs
+ LEFT JOIN feature f ON f.feature_id=fs.feature_id
+ <isNotNull property="feature.uniqueName">
+ WHERE uniquename=#feature.uniqueName#
+ </isNotNull>
+ </select>
+
+ <select id="getFeatureSynonymsByName" resultMap="map-alias-lazy"
+ parameterClass="Synonym">
+ SELECT * FROM feature_synonym
+ WHERE
+ synonym_id=
+ ( SELECT synonym_id FROM synonym WHERE name=#name#
+ <isNotNull property="cvTerm"> AND type_id=#cvTerm.cvTermId# </isNotNull> )
+ </select>
+
+
+ <!-- WRITE BACK -->
+ <delete id="deleteFeatureAlias" parameterClass="FeatureSynonym">
+ DELETE FROM feature_synonym WHERE
+ synonym_id=$synonym.synonymId$ AND
+ feature_id=(SELECT feature_id FROM feature WHERE
+ uniquename=#feature.uniqueName#)
+ </delete>
+
+ <insert id="insertFeatureAlias" parameterClass="FeatureSynonym">
+ INSERT INTO feature_synonym
+ ( synonym_id, feature_id, pub_id, is_current )
+ VALUES ( $synonym.synonymId$ ,
+ (SELECT feature_id FROM feature WHERE uniquename=#feature.uniqueName#),
+ 1, #current# )
+ </insert>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/Graph.xml b/artemis_sqlmap/Graph.xml
new file mode 100644
index 0000000..a4f1b08
--- /dev/null
+++ b/artemis_sqlmap/Graph.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="Graph">
+
+ <typeAlias alias="Graph"
+ type="uk.ac.sanger.artemis.chado.Graph"/>
+
+ <resultMap id="select-graph"
+ class="Graph">
+ <result property="graphId" column="graph_id"/>
+ <result property="featureId" column="feature_id"/>
+ <result property="name" column="name"/>
+ <result property="description" column="description"/>
+ </resultMap>
+
+ <resultMap id="select-graphdata"
+ class="Graph">
+ <result property="graphId" column="graph_id"/>
+ <result property="featureId" column="feature_id"/>
+ <result property="name" column="name"/>
+ <result property="description" column="description"/>
+ <result property="data" column="data"/>
+ </resultMap>
+
+ <select id="getGraphs" resultMap="select-graph">
+ SELECT graph_id, feature_id, name, description FROM graph.graph WHERE feature_id=$featureId$
+ </select>
+
+ <select id="getGraph" resultMap="select-graphdata">
+ SELECT * FROM graph.graph WHERE graph_id=$graphId$
+ </select>
+
+ <select id="getTableColumns" parameterClass="java.lang.String"
+ resultClass="java.lang.String">
+ SELECT pg_attribute.attname
+ FROM pg_attribute, pg_class, pg_namespace
+ WHERE pg_namespace.oid=pg_class.relnamespace AND
+ attrelid=pg_class.oid AND
+ relname=#value# AND
+ attnum > 0
+ <!--AND nspname=#value#-->
+ </select>
+
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/Organism.xml b/artemis_sqlmap/Organism.xml
new file mode 100644
index 0000000..2d86cd5
--- /dev/null
+++ b/artemis_sqlmap/Organism.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+<sqlMap namespace="Organism">
+
+ <cacheModel id="organism-cache" type ="LRU" readOnly="true">
+ <flushInterval hours="24"/>
+ <property name="cache-size" value="85" />
+ </cacheModel>
+
+ <resultMap id="map-organism" class="org.gmod.schema.organism.Organism">
+ <result property="organismId" column="organismId"/>
+ <result property="abbreviation" column="abbreviation"/>
+ <result property="commonName" column="commonName"/>
+ <result property="genus" column="genus"/>
+ <result property="species" column="species"/>
+ <result property="comment" column="comment"/>
+ <result property="organismProps" column="organismId" select="getOrganismPropByOrganismIdLazy"/>
+ </resultMap>
+
+ <select id="getOrganism" resultMap="map-organism" cacheModel="organism-cache">
+ SELECT organism_id AS organismId, abbreviation, genus, species, common_name AS commonName, comment
+ FROM organism
+ <dynamic>
+ <isNotNull property="organismId">
+ <isGreaterThan property="organismId" compareValue="0">
+ WHERE organism_id=#organismId#
+ <isNotNull prepend="AND" property="commonName">
+ common_name=#commonName#
+ </isNotNull>
+ </isGreaterThan>
+
+ <isLessEqual property="organismId" compareValue="0">
+ <isNotNull prepend="WHERE" property="commonName">
+ common_name=#commonName#
+ </isNotNull>
+ </isLessEqual>
+ </isNotNull>
+
+ <isNull property="organismId">
+ <isNotNull prepend="WHERE" property="commonName">
+ common_name=#commonName#
+ </isNotNull>
+ </isNull>
+
+ <isNull prepend="ORDER BY" property="organismId">
+ commonName
+ </isNull>
+ </dynamic>
+ </select>
+
+
+ <select id="getOrganismIdBySrcFeatureIdOrFeatureId" resultClass="java.lang.Integer"
+ parameterClass="org.gmod.schema.sequence.Feature" cacheModel="organism-cache">
+ SELECT organism_id AS organismId FROM feature f WHERE
+ <isGreaterThan property="featureLoc.featureBySrcFeatureId.featureId" compareValue="0">
+ f.feature_id=$featureLoc.featureBySrcFeatureId.featureId$
+ </isGreaterThan>
+ <isEqual property="featureLoc.featureBySrcFeatureId.featureId" compareValue="0">
+ <isGreaterThan property="featureId" compareValue="0">
+ f.feature_id=$featureId$
+ </isGreaterThan>
+ </isEqual>
+ <isNull property="featureLoc.featureBySrcFeatureId">
+ f.feature_id=$featureId$
+ </isNull>
+ </select>
+
+ <select id="getOrganismsContainingSrcFeatures" resultMap="map-organism">
+ SELECT DISTINCT ON( feature.organism_id )
+ organism.organism_id AS organismId, abbreviation, genus, species, common_name AS commonName, comment
+ FROM feature
+ join organism using (organism_id)
+ join cvterm on feature.type_id = cvterm.cvterm_id
+ WHERE residues notnull AND residues != ''
+ AND ( <include refid="source_feature_cvterm_names"/> )
+ </select>
+
+ <select id="getTopLevelOrganisms" resultMap="map-organism">
+ SELECT organism.organism_id AS organismId, abbreviation, genus, species, common_name AS commonName, comment
+ FROM organism
+ WHERE exists (
+ SELECT *
+ FROM organismprop
+ JOIN cvterm on organismprop.type_id = cvterm.cvterm_id
+ JOIN cv using (cv_id)
+ WHERE organism_id = organism.organism_id
+ AND cv.name = 'genedb_misc' and cvterm.name = 'populated' )
+ </select>
+
+</sqlMap>
diff --git a/artemis_sqlmap/OrganismProp.xml b/artemis_sqlmap/OrganismProp.xml
new file mode 100644
index 0000000..66394bb
--- /dev/null
+++ b/artemis_sqlmap/OrganismProp.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+<sqlMap namespace="OrganismProp">
+ <resultMap id="map-organismprop-with-cvterm-lazy"
+ class="org.gmod.schema.organism.OrganismProp">
+ <result property="value" column="value" />
+ <result property="rank" column="rank" />
+ <result property="cvTerm" column="type_id" select="getCvtermByCvTermId" />
+ </resultMap>
+
+ <select id="getOrganismPropByOrganismIdLazy" resultMap="map-organismprop-with-cvterm-lazy">
+ SELECT type_id, value, rank
+ FROM organismprop
+ WHERE organism_id=#organism_id#
+ </select>
+</sqlMap>
diff --git a/artemis_sqlmap/Pub.xml b/artemis_sqlmap/Pub.xml
new file mode 100644
index 0000000..08f3896
--- /dev/null
+++ b/artemis_sqlmap/Pub.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="Pub">
+
+ <typeAlias alias="Pub"
+ type="org.gmod.schema.pub.Pub"/>
+
+ <resultMap id="select-pub"
+ class="Pub">
+ <result property="pubId" column="pub_id" />
+ <result property="title" column="title" />
+ <result property="volumeTitle" column="volumetitle" />
+ <result property="volume" column="volume" />
+ <result property="seriesName" column="series_name" />
+ <result property="issue" column="issue" />
+ <result property="pyear" column="pyear"/>
+ <result property="pages" column="pages"/>
+ <result property="miniRef" column="miniref"/>
+ <result property="uniqueName" column="uniquename" />
+ <result property="obsolete" column="is_obsolete" />
+ <result property="publisher" column="publisher" />
+ <result property="pubPlace" column="pubplace" />
+ <result property="cvTerm" column="type_id" select="getCvtermByCvTermId" />
+ </resultMap>
+
+ <!-- SQL -->
+
+ <select id="selectPub" resultMap="select-pub">
+ SELECT * FROM pub WHERE pub_id=$pub_id$
+ </select>
+
+ <select id="getPubByUniqueName" resultMap="select-pub">
+ SELECT * FROM pub WHERE uniquename=#uniqueName#
+ </select>
+
+ <!-- WRITE BACK -->
+ <insert id="insertPub" parameterClass="Pub">
+ INSERT INTO pub (
+ <isNotNull property="title"> title, </isNotNull>
+ <isNotNull property="volumeTitle"> volumetitle, </isNotNull>
+ <isNotNull property="volume"> volume, </isNotNull>
+ <isNotNull property="seriesName"> series_name, </isNotNull>
+ <isNotNull property="issue"> issue, </isNotNull>
+ <isNotNull property="pyear"> pyear, </isNotNull>
+ <isNotNull property="pages"> pages, </isNotNull>
+ <isNotNull property="miniRef"> miniref, </isNotNull>
+ uniquename, type_id
+ <isNotNull property="obsolete"> , is_obsolete </isNotNull>
+ <isNotNull property="publisher"> , publisher </isNotNull>
+ <isNotNull property="pubPlace"> , pubplace </isNotNull> )
+ VALUES (
+ <isNotNull property="title"> #title#, </isNotNull>
+ <isNotNull property="volumeTitle"> #volumeTitle#, </isNotNull>
+ <isNotNull property="volume"> #volume#, </isNotNull>
+ <isNotNull property="seriesName"> #seriesName#, </isNotNull>
+ <isNotNull property="issue"> #issue#, </isNotNull>
+ <isNotNull property="pyear"> #pyear#, </isNotNull>
+ <isNotNull property="pages"> #pages#, </isNotNull>
+ <isNotNull property="miniRef"> #miniRef#, </isNotNull>
+ #uniqueName#, $cvTerm.cvTermId$
+ <isNotNull property="obsolete"> , $obsolete$ </isNotNull>
+ <isNotNull property="publisher"> , #publisher# </isNotNull>
+ <isNotNull property="pubPlace"> , #pubPlace# </isNotNull> )
+ </insert>
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/PubDbXRef.xml b/artemis_sqlmap/PubDbXRef.xml
new file mode 100644
index 0000000..484d88e
--- /dev/null
+++ b/artemis_sqlmap/PubDbXRef.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="PubDbXRef">
+
+ <typeAlias alias="PubDbXRef"
+ type="org.gmod.schema.pub.PubDbXRef"/>
+
+ <resultMap id="select-pubdbxref" class="PubDbXRef">
+ <result property="pub.pubId" column="pub_id" />
+ <result property="dbXRef.accession" column="accession" />
+ <result property="dbXRef.version" column="version" />
+ <result property="dbXRef.description" column="dbx_description" />
+ <result property="dbXRef.db.name" column="name" />
+ <result property="dbXRef.db.description" column="description" />
+ <result property="dbXRef.db.urlPrefix" column="urlprefix" />
+ <result property="dbXRef.db.url" column="url" />
+ </resultMap>
+
+ <!-- SQL -->
+
+ <select id="getPubDbXRef" resultMap="select-pubdbxref">
+ select pub_id, pub_dbxref.dbxref_id,
+ accession, version, dbx.description AS dbx_description,
+ db.name, db.description, db.urlprefix, db.url FROM pub_dbxref
+ LEFT JOIN dbxref dbx ON pub_dbxref.dbxref_id=dbx.dbxref_id
+ LEFT JOIN db ON db.db_id=dbx.db_id
+ </select>
+
+ <!-- WRITE BACK -->
+ <insert id="insertPubDbXRef" parameterClass="PubDbXRef">
+ INSERT INTO pub_dbxref ( pub_id, dbxref_id )
+ VALUES ( $pub.pubId$, $dbXRef.dbXRefId$ )
+ </insert>
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/Synonym.xml b/artemis_sqlmap/Synonym.xml
new file mode 100644
index 0000000..564838b
--- /dev/null
+++ b/artemis_sqlmap/Synonym.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE sqlMap
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+
+<sqlMap namespace="Synonym">
+
+ <typeAlias alias="Synonym"
+ type="org.gmod.schema.sequence.Synonym"/>
+
+ <resultMap id="map-synonym-lazy"
+ class="Synonym">
+ <result property="synonymId" column="synonym_id"/>
+ <result property="name" column="name"/>
+ <result property="synonymSgml" column="synonym_sgml"/>
+ <result property="cvTerm" column="type_id" select="getCvtermByCvTermId"/>
+ </resultMap>
+
+
+ <!-- SQL -->
+ <select id="getSynonymByNameAndType" resultMap="map-synonym-lazy"
+ parameterClass="Synonym">
+ SELECT * FROM synonym WHERE
+ <isNotNull property="name">
+ name=#name# AND
+ </isNotNull>
+ <isNotNull property="cvTerm">
+ type_id=$cvTerm.cvTermId$ AND
+ </isNotNull>
+ synonym_id > 0
+ </select>
+
+ <select id="getSynonymBySynonymId" resultMap="map-synonym-lazy">
+ SELECT * FROM synonym WHERE synonym_id=#value#
+ </select>
+
+
+
+ <!-- WRITE BACK -->
+ <delete id="deleteAlias" parameterClass="FeatureSynonym">
+ DELETE FROM synonym WHERE synonym_id=$synonym.synonymId$
+ </delete>
+
+ <insert id="insertAlias" parameterClass="FeatureSynonym">
+ INSERT INTO synonym ( name, type_id, synonym_sgml )
+ VALUES ( #synonym.name#, $synonym.cvTerm.cvTermId$, #synonym.name# )
+ </insert>
+
+</sqlMap>
\ No newline at end of file
diff --git a/artemis_sqlmap/chado_iBatis_config.properties b/artemis_sqlmap/chado_iBatis_config.properties
new file mode 100644
index 0000000..60d72ac
--- /dev/null
+++ b/artemis_sqlmap/chado_iBatis_config.properties
@@ -0,0 +1,9 @@
+# This is just a simple properties file that simplifies automated configuration
+# of the SQL Maps configuration file (e.g. by Ant builds or continuous
+# integration tools for different environments... etc.)
+# These values can be used in any property value in the file above (e.g. ${driver})
+# Using a properties file such as this is completely optional.
+
+jdbc.drivers=org.postgresql.Driver
+chado=localhost:2996/chado
+username=lbraziliensis
diff --git a/artemis_sqlmap/chado_iBatis_config.xml b/artemis_sqlmap/chado_iBatis_config.xml
new file mode 100644
index 0000000..30a3d41
--- /dev/null
+++ b/artemis_sqlmap/chado_iBatis_config.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+ <!DOCTYPE sqlMapConfig
+ PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
+ "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
+
+<!-- Always ensure to use the correct XML header as above! -->
+
+<sqlMapConfig>
+
+ <!-- The properties (name=value) in the file specified here can be used placeholders in this config
+ file (e.g. ${driver}. The file is usually relative to the classpath and is optional. -->
+ <!--
+ <properties resource="artemis_sqlmap/chado_iBatis_config.properties" />
+ -->
+
+ <!-- These settings control SqlMap configuration details, primarily to do with transaction
+ management. They are all optional (see the Developer Guide for more). -->
+ <settings
+ cacheModelsEnabled="true"
+ enhancementEnabled="true"
+ lazyLoadingEnabled="true"
+ maxRequests="128"
+ maxSessions="3"
+ maxTransactions="3"
+ useStatementNamespaces="false"
+ />
+
+
+ <!-- Type aliases allow you to use a shorter name for long fully qualified class names. -->
+
+ <!--
+ <typeAlias alias="order" type="testdomain.Order"/>
+ -->
+
+ <!-- Configure a datasource to use with this SQL Map using SimpleDataSource.
+ Notice the use of the properties from the above resource -->
+ <transactionManager type="JDBC" >
+ <dataSource type="SIMPLE">
+ <property name="JDBC.Driver" value="org.postgresql.Driver"/>
+ <property name="JDBC.ConnectionURL" value="jdbc:postgresql://${chado}"/>
+ <property name="JDBC.Username" value="${username}"/>
+ <property name="JDBC.Password" value="${password}"/>
+ <property name="Pool.MaximumActiveConnections" value="2" />
+ <property name="Pool.MaximumIdleConnections" value="1"/>
+ <property name="Pool.MaximumCheckoutTime" value="1800000" />
+ <property name="Pool.TimeToWait" value="10000"/>
+ <property name="Pool.PingQuery" value="select * from cv"/>
+ <property name="Pool.PingEnabled" value="true"/>
+ <property name="Pool.PingConnectionsOlderThan" value="0"/>
+ <property name="Pool.PingConnectionsNotUsedFor" value="1800000"/>
+ </dataSource>
+ </transactionManager>
+
+ <!-- the following alternative transactionmanager requires the
+ libraries commons-dbcp and common-pool in the CLASSPATH
+ <transactionManager type="JDBC">
+ <dataSource type="DBCP">
+ <property name="driverClassName" value="org.postgresql.Driver"/>
+ <property name="url" value="jdbc:postgresql://${chado}"/>
+ <property name="username" value="${username}"/>
+ <property name="password" value="${password}"/>
+
+ <property name="maxActive" value="3"/>
+ <property name="maxIdle" value="1"/>
+ <property name="maxWait" value="10000"/>
+
+ <property name="validationQuery" value="select * from cv"/>
+ <property name="logAbandoned" value="false"/>
+ <property name="removeAbandoned" value="false"/>
+ <property name="removeAbandonedTimeout" value="50000"/>
+ </dataSource>
+ </transactionManager>
+ -->
+
+ <!-- Identify all SQL Map XML files to be loaded by this SQL map. Notice the paths
+ are relative to the classpath. -->
+
+ <sqlMap resource="artemis_sqlmap/Cv.xml" />
+ <sqlMap resource="artemis_sqlmap/CvTerm.xml" />
+ <sqlMap resource="artemis_sqlmap/Pub.xml" />
+ <sqlMap resource="artemis_sqlmap/PubDbXRef.xml" />
+ <sqlMap resource="artemis_sqlmap/Feature.xml" />
+ <sqlMap resource="artemis_sqlmap/Organism.xml" />
+ <sqlMap resource="artemis_sqlmap/OrganismProp.xml" />
+ <sqlMap resource="artemis_sqlmap/FeatureCvTerm.xml" />
+ <sqlMap resource="artemis_sqlmap/FeatureCvTermDbXRef.xml" />
+ <sqlMap resource="artemis_sqlmap/FeatureCvTermProp.xml" />
+ <sqlMap resource="artemis_sqlmap/FeatureCvTermPub.xml" />
+ <sqlMap resource="artemis_sqlmap/FeatureDbXRef.xml" />
+ <sqlMap resource="artemis_sqlmap/FeatureLoc.xml" />
+ <sqlMap resource="artemis_sqlmap/FeatureProp.xml" />
+ <sqlMap resource="artemis_sqlmap/FeatureRelationship.xml" />
+ <sqlMap resource="artemis_sqlmap/FeaturePub.xml" />
+ <sqlMap resource="artemis_sqlmap/FeatureSynonym.xml" />
+ <sqlMap resource="artemis_sqlmap/Db.xml" />
+ <sqlMap resource="artemis_sqlmap/DbXRef.xml" />
+ <sqlMap resource="artemis_sqlmap/Synonym.xml" />
+ <sqlMap resource="artemis_sqlmap/AnalysisFeature.xml" />
+ <sqlMap resource="artemis_sqlmap/Analysis.xml" />
+ <sqlMap resource="artemis_sqlmap/Graph.xml" />
+
+</sqlMapConfig>
+
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..1598ece
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0"?>
+
+<!--
+
+ Ant build file for Artemis
+
+-->
+
+<project default="package" basedir=".">
+
+ <target name="init">
+ <tstamp />
+ <property name="name" value="artemis" />
+ <property name="version" value="live" />
+ <property name="build.compiler" value="modern" />
+ <property name="classpath" value="" />
+ <property name="src.dir" value="" />
+ <property name="docs.dir" value="docs" />
+
+ <property name="packages" value="uk.*" />
+
+ <property name="build.dir" value="./ant-build" />
+ <!-- Subdirectories for main source and classes -->
+ <property name="build.src.main" value="${build.dir}/src/main" />
+ <property name="build.dest.main" value="${build.dir}/classes/main" />
+
+ <!-- Subdirectories for tests source and classes -->
+ <property name="build.src.tests" value="${build.dir}/src/tests" />
+ <property name="build.dest.tests" value="${build.dir}/classes/tests" />
+
+ <!-- Subdirectories for docs -->
+ <property name="build.src.docs" value="${build.dir}/src/docs" />
+ <property name="build.dest.docs" value="${build.dir}/docs" />
+ <property name="build.dest.javadocs" value="${build.dest.docs}/api" />
+
+ <!-- Subdirectory for libraries used during build -->
+ <property name="build.src.main.lib" value="${build.src.main}/lib" />
+
+ <property name="dist.root" value="./dist" />
+ <property name="dist.dir" value="${dist.root}/${name}-${version}" />
+
+ <!-- Compile classpath -->
+ <path id="compile.classpath">
+ <!-- Main classes from build -->
+ <pathelement path="${build.src.main}" />
+ <!-- Dependency classes -->
+ <fileset dir="${build.src.main}/lib">
+ <include name="**.jar" />
+ </fileset>
+ <fileset dir="${build.src.main}/lib">
+ <include name="**/*.jar" />
+ <exclude name="j2ssh/j2ssh-artemis-plugin.jar"/>
+ </fileset>
+ </path>
+ </target>
+
+ <!-- Prepares the build directory -->
+ <target name="prepare" depends="init">
+ <mkdir dir="${build.dir}" />
+ </target>
+
+ <!-- Prepares the source code -->
+ <target name="prepare-core" depends="init,prepare">
+
+ <!-- Creates directories -->
+ <mkdir dir="${build.src.main}" />
+ <mkdir dir="${build.dest.main}" />
+ <mkdir dir="${build.src.docs}" />
+ <mkdir dir="${build.dest.docs}" />
+ <mkdir dir="${build.dest.javadocs}" />
+
+ <!-- Copies src files -->
+ <copy todir="${build.src.main}">
+ <fileset dir="${src.dir}">
+ <exclude name="**/CVS/**" />
+ <exclude name="test/**" />
+ <exclude name="ant-build/**" />
+ </fileset>
+ </copy>
+
+ <!-- Copies docs files -->
+ <copy todir="${build.src.docs}">
+ <fileset dir="${docs.dir}">
+ <exclude name="**/CVS/**" />
+ </fileset>
+ </copy>
+
+ <!-- Copies jars -->
+ <copy todir="${build.dir}">
+ <fileset dir=".">
+ <include name="*.jar" />
+ <exclude name="${docbook.tools.file}" />
+ </fileset>
+ </copy>
+ </target>
+
+ <target name="prepare-src" depends="init,prepare-core" />
+
+ <!-- Compile IDL stub classes into the build directory (jar preparation) -->
+ <target name="compile-idl" depends="init,prepare-core">
+ <exec executable="idlj" dir="${build.src.main}">
+ <arg line="-Icorba corba/types.idl" />
+ </exec>
+ <exec executable="idlj" dir="${build.src.main}">
+ <arg line="-Icorba corba/nsdb.idl" />
+ </exec>
+ <exec executable="idlj" dir="${build.src.main}">
+ <arg line="-Icorba corba/nsdb_write.idl" />
+ </exec>
+ <exec executable="idlj" dir="${build.src.main}">
+ <arg line="-Icorba corba/seqdb.idl" />
+ </exec>
+ </target>
+
+ <!-- Compiles the source directory -->
+ <target name="compile" depends="init,compile-idl,prepare-src">
+ <javac
+ fork="true"
+ memoryinitialsize="256m"
+ memorymaximumsize="256m"
+ srcdir="${build.src.main}"
+ destdir="${build.dest.main}"
+ excludes="nsdb/**,seqdb/**,type/**,lib/**,src/**,uk/ac/sanger/artemis/components/MacHandler.java,uk/ac/sanger/artemis/components/filetree/FileTree.java,uk/ac/sanger/artemis/components/filetree/SshFileTree.java,uk/ac/sanger/artemis/ExternalProgramUtils.java"
+ deprecation="false"
+ depend="no"
+ debug="true">
+ <classpath refid="compile.classpath"/>
+ </javac>
+ </target>
+
+ <!-- Creates the chado class package -->
+ <target name="chado-access" depends="init,compile">
+ <jar
+ jarfile="${build.dir}/chado-access.jar"
+ basedir="${build.dest.main}"
+ manifest="META-INF/MANIFEST.MF"
+ includes="uk/ac/sanger/artemis/chado/**,uk/ac/sanger/artemis/util/ByteBuffer*"
+ />
+ <jar
+ jarfile="${build.dir}/chado-access.jar"
+ update="yes"
+ basedir="${build.src.main}"
+ includes="artemis_sqlmap/**,uk/ac/sanger/artemis/chado/**,uk/ac/sanger/artemis/util/ByteBuffer*"
+ />
+ </target>
+
+ <!-- Creates the class package -->
+ <target name="package" depends="init,compile">
+ <jar
+ jarfile="${build.dir}/${name}.jar"
+ basedir="${build.dest.main}"
+ manifest="META-INF/MANIFEST.MF"
+ includes="**"
+ />
+ </target>
+
+
+ <!-- Cleans everything -->
+ <target name="clean" depends="init">
+ <delete dir="${build.dir}" />
+ <delete dir="${dist.root}" />
+ <delete file="${name}-${version}.tar.gz" />
+ <delete file="${name}-${version}.tar" />
+ <delete file="${name}-${version}.zip" />
+ </target>
+</project>
diff --git a/corba/apollo.idl b/corba/apollo.idl
new file mode 100644
index 0000000..49b0972
--- /dev/null
+++ b/corba/apollo.idl
@@ -0,0 +1,206 @@
+// Apollo transport layer
+// all syntax subject to change
+// typedef sequence <Xxx> XxxList assumed
+// maybe some of the paramsets should be
+// more strongly typed
+//
+// Ewan Birney and Chris Mungall. Apollo list apollo at ebi.ac.uk
+//
+
+module Apollo {
+
+ exception NotSupported { string reason; }; // more exceptions to come
+ exception ProcessError { string reason; };
+ exception OutOfRange { string reason; };
+ exception NeedsUpdate { string reason;};
+
+ struct Param {
+ string name;
+ string value;
+ };
+ typedef sequence <Param> ParamList;
+
+ // use a enum for unit (%, ratio, etc)?
+ struct Score {
+ string type;
+ string value;
+ // EB - lets also have this as a number. Clients have to figure out by the
+ // type.
+ double double_value;
+ };
+ typedef sequence <Score> ScoreList;
+
+ // we should abstract out identifier aspects into
+ // a seperate struct - that way we can attach this to
+ // any kind of object
+ struct Identifier {
+ string name; //main display label
+ string description; //detailed desc
+ sequence <string> synonyms;
+ // DbXrefList dbxrefs;
+ };
+ typedef sequence <Identifier> IdentifierList;
+
+
+ enum StrandType { plus, minus};
+ // a range can be attached to any seq-featurey object
+ struct Range {
+ long range_min;
+ long range_max;
+ StrandType strand;
+ };
+
+
+ struct ResultSpan {
+ string result_id;
+ ScoreList scores;
+ Range range1;
+ Range range2;
+ };
+ typedef sequence<ResultSpan> ResultSpanList;
+
+
+ // any kind of analysis result or alignment
+ // (genscan-gene, genscan-exon, sim4exonset, sim4exon, blasthit,
+ // blast-hsp, etc)
+ struct ResultSet {
+ string result_id;
+ ScoreList scores;
+ string type;
+ ResultSpanList ResultSpans; // eg hsps for a blast hit
+ Range range1; // eg query start/end
+ Range range2; // eg subject start/end
+ };
+ typedef sequence <ResultSet> ResultSetList;
+
+
+
+ // Evidence is one of the Result lists
+ typedef string Evidence;
+
+ typedef sequence <Evidence> EvidenceList;
+
+ // collection of analysis results
+ struct Analysis {
+ // eg Blast, Pfam
+ string program;
+ ParamList parameters;
+ ResultList results;
+ };
+ typedef sequence <Analysis> AnalysisList;
+
+ // Annotation Comments etc
+
+
+ struct Person {
+ string readable_name;
+ string person_id;
+ }
+
+ typedef long TimeStamp;
+
+
+ struct Comment {
+ string comment_id;
+ string text;
+ Person person;
+ TimeStamp time;
+ };
+ typedef sequence<Comment> CommentList;
+
+
+
+ // Design decision: most of these inherit from a notional seqfeature
+ // superclass - do we (1) merge them into a single struct, with
+ // the seqfeature struct having 'type' and contained-seqfeatures
+ // or (2) have distinct structs and delegate out the commonalities.
+ // i chose the latter, with all the strcuts having a Range attribute
+
+
+ // to fetch the sequence for a gene, it has to be spliced
+ // from the exons
+ struct Exon {
+ Identifier ident;
+ Range range;
+ EvidenceList evidence_list;
+ };
+
+ typedef sequence <Exon> ExonList;
+
+ struct Transcript {
+ Identifier ident;
+ ExonList exons;
+ Range cds_Range; // start/end of translation
+ // note we don't need range including UTR, its implicit from exons
+ EvidenceList evidence_list;
+ CommentList comments;
+ };
+ typedef sequence <Transcript> TranscriptList;
+
+ enum GeneType { PROTEIN_CODING_GENE, TRNA_GENE, TRANSPOSON_GENE };
+
+ // Where does silly text annotation go?
+ struct AnnotatedGene {
+ GeneType type;
+ Identifier ident;
+ TranscriptList transcripts;
+ CommentList comments;
+ };
+ typedef sequence <AnnotatedGene> AnnotatedGeneList;
+
+ struct GenericAnnotation {
+ Identifier ident;
+ string type;
+ ParamList qualifiers;
+ CommentList comments;
+ Range range;
+ EvidenceList evidence_list;
+ };
+
+ typedef sequence <GenericAnnotation> GenericAnnotationList;
+
+
+
+ // collection of annotations and analyses on
+ // a particular piece of sequence
+ // (could be a clone, a contig , a scaffold (order&oriented contigs), an
+ // arbitrary slice of a scaffold, a chromosome, etc)
+ interface AnnotatedRegion {
+ // bind sequence here
+ string sequence_as_string();
+ string sequence_region_as_string(in long start,in long end)
+ raises ( OutOfRange );
+
+ // gets
+ AnalysisList get_analysis_list() raises ( ProcessError ) ;
+ AnnotatedGeneList get_gene_list() raises (ProcessError);
+ GenericAnnotation get_generic_annotation() raises (ProcessError);
+
+
+ // sets
+ void save_AnnotatedGenes(in AnnotatedGeneList new,
+ in AnnotatedGeneList updated,
+ in AnnotatedGeneList dead)
+ raises (NeedsUpdate, ProcessError,OutOfRange);
+
+ void save_GenericAnnotation(in GenericAnnotationList new,
+ in GenericAnnotationList updated,
+ in GenericAnnotationList dead)
+ raises (NeedsUpdate, ProcessError, OutOfRange );
+ };
+
+ // session [or persistence handle]
+ interface Session {
+ void connect(in ParamList param_set);
+ AnnotatedRegion get_AnnotatedRegion(in string id);
+ };
+
+ // singleton;
+ interface SessionManager {
+ Session initiate_Session(in ParamList param_set);
+ Session retrieve_Session(in ParamList param_set)
+ raises (NotSupported);
+ };
+
+
+};
diff --git a/corba/ensembl.idl b/corba/ensembl.idl
new file mode 100644
index 0000000..1c5eae4
--- /dev/null
+++ b/corba/ensembl.idl
@@ -0,0 +1,53 @@
+
+module Ensembl {
+ module artemis {
+
+ exception RequestedSequenceTooLong { };
+ exception NoEntry { string reason; };
+
+ interface BioSequence {
+ string getSubSequence(in long start,in long end) raises (RequestedSequenceTooLong);
+ long length();
+ long max_sequence_request();
+ };
+
+ struct Qualifier {
+ string name;
+ sequence<string> values;
+ };
+
+ typedef sequence <Qualifier> QualifierList;
+
+ interface Feature {
+ string getKey();
+ string getLocation();
+ QualifierList getQualifiers();
+ };
+
+ typedef sequence <Feature> FeatureList;
+
+ interface Entry {
+ string getName(); // accession number usually.
+ long getFeatureCount();
+ FeatureList getAllFeatures();
+ BioSequence getSequence();
+ };
+
+ typedef sequence<string> QualifierDefinitionList;
+ struct FeatureDefinition {
+ string key;
+ QualifierDefinitionList qualifiers;
+ };
+ typedef sequence<FeatureDefinition> FeatureDefinitionList;
+
+ typedef sequence<string> EntryNameList;
+
+ interface DB {
+ Entry getEntry(in string entryname) raises (NoEntry);
+ EntryNameList getallEntryNames();
+
+ // coordination of the ORB.
+ FeatureDefinitionList getFeatureDefinitionList();
+ };
+ };
+};
diff --git a/corba/nsdb.idl b/corba/nsdb.idl
new file mode 100644
index 0000000..4d01089
--- /dev/null
+++ b/corba/nsdb.idl
@@ -0,0 +1,560 @@
+/* **************************************************************************
+ * $Source: //tmp/pathsoft/artemis/corba/nsdb.idl,v $
+ * $Revision: 1.1 $
+ * $Date: 2004-06-09 12:06:34 $
+ * $Author: tjc $
+ * **************************************************************************/
+// Version 2.0
+#ifndef embl_ebi_nsdb_idl
+#define embl_ebi_nsdb_idl
+
+#include "types.idl"
+#include "seqdb.idl"
+
+
+ /**
+ * IDL interfaces for the
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/embl_db.html">
+ * EMBL Nucleotide Sequence Database</A>.
+ */
+ module nsdb {
+ // ====================
+ // Forward declarations
+ // ====================
+ interface NucSeq;
+ typedef sequence<NucSeq> NucSeqList;
+ interface NucFeature;
+ typedef sequence<NucFeature> NucFeatureList;
+ interface Location;
+ typedef sequence<Location> LocationList;
+ interface FeatureLocation;
+ typedef sequence<FeatureLocation> FeatureLocationList;
+ interface EntryInfo;
+ typedef sequence<EntryInfo> EntryInfoList;
+
+
+ /**
+ * If a sub-sequence is retrieved for which the location information is
+ * inexact, an InexactLocation is raised
+ */
+ exception InexactLocation { string reason;};
+
+ /**
+ * The EMBL database contains information, which is not really part of the
+ * sequence information. This information is stored in the EntryInfo.
+ */
+
+ interface EntryInfo {
+
+ /**
+ * Retrieve entry indentifier. More information on the
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/usrman/id_line.html">format</A> of
+ * an entry name is available in the
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/usrman/usrman.html">
+ * EMBL User Manual</A>.
+ */
+ string getEntryName();
+
+ /**
+ * The entry version defines the current version of an EMBL Sequence Database
+ * Entry. I.e. the complete set information related to a particular sequence.
+ * The Entry version is incremented whenever anything changes in the sequence or it's
+ * associated information.
+ */
+ unsigned long getEntryVersion();
+
+
+ /**
+ * Get entry status code.
+ * @see meta::nsdb
+ */
+ string getEntryStatus();
+
+
+ /**
+ * Sequence of revisions when Entry was created/changed.
+ */
+ type::RevisionList getRevisions();
+
+ /**
+ * List of secondary accession numbers. I.e. accession numbers
+ * of deprecated entries, now merged into the current entry or
+ * split over multiple entries, as decribed in the
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/usrman/ac_line.html">
+ * AC line</A> documentation in the User manual.
+ */
+ type::stringList getSecondaryIds();
+
+ /**
+ * number of adenine
+ */
+ unsigned long getCountA();
+
+ /**
+ * number of cytosine
+ */
+ unsigned long getCountC();
+
+ /**
+ * number of guanine
+ */
+ unsigned long getCountG();
+
+ /**
+ * number of thymine (in DNA sequence)
+ * or
+ * number of uracil (in RNA sequence)
+ */
+ unsigned long getCountT();
+
+
+ };
+
+ // =========================
+ // Location
+ // =========================
+
+
+ /**
+ * A location is built from one or more nodes forming a tree
+ * local node IDs are only unique within a location tree
+ * the root node has id == 0
+ * the Node id defines the position of the node in the sequence<LocationNode>
+ * The value is relative to the current (parent) node
+ * e.g if the current node is at position x in the LocationNodes sequence
+ * and there is a childId with value j
+ * then the position of this child in the LocationNodes sequence will be x+j
+ */
+ interface Location {
+
+ typedef sequence<unsigned long> IdList;
+
+ /**
+ * valid types of Location Nodes in a Location
+ * are defined by LocNodeTypeCode. Currently values 1-4 are in use.
+ * 6-10 are reserved for future use.
+ */
+ typedef long LocNodeTypeCode;
+
+ /**
+ * The sequence segment is derived from another sequence
+ */
+ const long VirtualSegment_ltc = 1;
+
+ /**
+ * The sequence segment is explicitly given as a DNA string.
+ */
+ const long PhysicalSegment_ltc = 2;
+
+ /**
+ * Sequence is unknown. Only it's (estimated) size in known.
+ */
+ const long Gap_ltc = 3;
+
+ /**
+ * defines how to link a set of LocationNodes
+ */
+ const long Operator_ltc = 4;
+
+
+
+ /**
+ * Virtual segment of a sequence.The sequence segment is derived from another sequence.
+ * <p><dl>
+ * <dt>bio_seq_id
+ * <dd>sequence from which this segment is derived
+ * <dd>contains an accession number
+ * <dt>start
+ * <dd>start position of segment (inclusive)
+ * <dt>end
+ * <dd>end position of segment (inclusive)
+ * <dd>In the case that start defines a 'between' position, end is unused
+ * <dt>complement
+ * <dd> true if segment should be complemented before any further manipulations
+ * </dl>
+ */
+ struct LocVirtualSegment {
+ string bio_seq_id;
+ type::Fuzzy start;
+ type::Fuzzy end;
+ boolean complement;
+ };
+
+ /**
+ * spacer between sequence fragments
+ */
+ typedef type::Fuzzy LocGap;
+
+ /**
+ * The sequence segment is explicitly given as a DNA string
+ * that should be inserted literally.
+ */
+ typedef string LocPhysicalSegment;
+
+ /**
+ * Location operator. This is a node in the location tree that combines
+ * nodes lower down in the tree
+ * <dl>
+ * <dt> op
+ * <dd> operator defining what to do with the nodes
+ * <dd> <dl>
+ * <dt> join
+ * <dd> The indicated elements should be joined (placed end-to-end)
+ * to form one contiguous sequence
+ * <dt> order
+ * <dd> The elements can be found in the specified order
+ * (5' to 3' direction), but nothing is implied about the
+ * reasonableness about joining them
+ * </dl>
+ * <dt> childIds
+ * <dd> identifiers of the child nodes
+ * <dd> Ids is an array of IDs relative to the current node
+ * e.g if the current node is at position x in the LocationNodes sequence
+ * and there is a childId with value j
+ * then the position of this child in the LocationNodes sequence will be x+j
+ *</dl>
+ */
+ struct LocOperator {
+ string op;
+ IdList childIds;
+ };
+
+ union LocationNode_u switch (LocNodeTypeCode) {
+ case VirtualSegment_ltc: LocVirtualSegment virtual;
+ case PhysicalSegment_ltc: LocPhysicalSegment physical;
+ case Gap_ltc: LocGap gap;
+ case Operator_ltc: LocOperator operator;
+ };
+
+ typedef sequence<LocationNode_u> LocationNodeList;
+
+ /**
+ * retrieve
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/ft/components.html#location">
+ * string representation of location</A>
+ */
+ string getLocationString();
+
+
+ /**
+ * retrieve tree representation of location
+ */
+ LocationNodeList getNodes();
+
+
+
+ /**
+ * Create nucleotide sequence defined by location. This can imply getting
+ * fragments from multiple sequences and concatenating.
+ * If it is not possible to resolve the location into a single sequence
+ * (e.g. when it contains a 'group' operator, or gap nodes)
+ * each fragment will be returned as a seperate string. No assumption should be
+ * made on the order, if multiple fragments are returned
+ * @raises InexactLocation if an exact sequence cannot be
+ * determined due to the location being inexact
+ */
+ string getSeq()
+ raises ( InexactLocation );
+
+ };
+ /**
+ * Location of a NucFeature
+ * This interface does not allow to change the nuc_feature
+ * If a location is assigned to a nucfeature, the inverse relation
+ * should be properly updated
+ * @see NucFeature.getLocation
+ */
+ interface FeatureLocation : Location {
+
+ /**
+ * nucfeature to which the location is associated
+ */
+ NucFeature getNucFeature();
+ };
+
+
+ // ============================
+ // Features
+ // ============================
+ /**
+ * Nucleotide Sequence Feature interface. Features are <I>owned</I> by a
+ * sequence and contain information about that (and maybe other) sequence.
+ * The relation between the feature and the sequence is defined by it's
+ * location.
+ * @see Location
+ */
+
+ interface NucFeature : seqdb::Feature {
+
+ /**
+ * Qualifier TypeCode definitions.
+ * Each Qualifier type has an assigned typecode. values 1-100
+ * are reserved to allow for future extension.
+ * <p>
+ * this is a stripped down version of the <B>featuremeta</B> IDL. Types which
+ * are typedefs of the same base-type in <B>featuremeta</B> are not
+ * distinguished here.
+ * @see featuremeta
+ */
+ typedef long QualifierTypeCode;
+
+ // values 1-100 are reserved for EBI QualifierTypeCodes
+ const long string_qtc = 1;
+ const long boolean_qtc = 2;
+ const long integer_qtc = 3;
+ const long real_qtc = 4;
+ const long TranslationException_qtc = 17;
+ const long CodonTranslation_qtc = 18;
+ const long Anticodon_qtc = 19;
+ const long SpliceConsensus_qtc = 20;
+ const long RepeatUnit_qtc = 21;
+ const long DbXref_qtc = 22;
+
+
+ union QualifierValue_u switch (QualifierTypeCode) {
+
+ // EBI typecodes
+ case boolean_qtc : boolean applicable;
+ case string_qtc : string text;
+ case integer_qtc : long integer;
+ case real_qtc : float real;
+ case TranslationException_qtc : type::TranslationException translation_exception;
+ case CodonTranslation_qtc : type::CodonTranslation codon_translation;
+ case Anticodon_qtc : type::AntiCodon anti_codon;
+ case SpliceConsensus_qtc : type::SpliceConsensus splice_consensus;
+ case RepeatUnit_qtc : type::RepeatUnit repeat_unit;
+ case DbXref_qtc : type::DbXref db_xref;
+
+ // add your own extension types below
+#ifdef ANALYSIS
+ // analysis qualifier extension
+ // see analysis.idl
+ case ANALYSIS::Analysis_qtc : ANALYSIS::Scores score;
+#endif
+ };
+
+
+ typedef sequence<QualifierValue_u> QualifierValueList;
+
+ /**
+ * Qualifier.<p>
+ * <dl>
+ * <dt> name
+ * <dd> name of the qualifier
+ * <dt> values
+ * <dd> sequence of QualifierValues. All QualifierValues associated with
+ * a single Qualifier are of the same type
+ * </dl>
+ */
+ struct Qualifier {
+ string name;
+ QualifierValueList values;
+ };
+
+ typedef sequence<Qualifier> QualifierList;
+
+ /**
+ * Retrieve sequence of qualifiers.
+ * @raises type::NoResult if no qualifiers are associated with
+ * the feature.
+ */
+ QualifierList getQualifiers()
+ raises (type::NoResult);
+
+
+
+ /**
+ * retrieve qualifier of a certain type.
+ * To find out which qualifier/feature combinations are valid, a client should
+ * query the NucFeatureMeta server.
+ * @raises type::NoResult if no qualifier of this type is associated with
+ * the feature
+ * @raises type::InvalidRelation if the requested qualifier cannot be
+ * associated to the current feature type
+ * @see metafeature::NucFeatureMeta
+ */
+ Qualifier getQualifier(in string qualifier_name)
+ raises (type::NoResult, type::InvalidRelation);
+
+
+ /**
+ * If the location of the feature references multiple sequences, get a
+ * sequence of all sequences referenced
+ * This method is equivalent to retrieving the feature location, and looping
+ * through all location nodes to find the referenced sequences, converting
+ * the accession numbers into DbXref's.
+ */
+ type::DbXrefList getNucSeqs();
+
+ /**
+ * retrieve location of feature.
+ * @raises type::NoResult if no location is associated with the feature.
+ */
+
+ FeatureLocation getLocation()
+ raises (type::NoResult);
+
+
+ };
+
+ /**
+ * Generic Nucleotide sequence interface.
+ * The accession number is retrieved using the getBioSeqId method inherited from BioSeq.
+ * @see EmblSeq
+ * @see seqdb::BioSeq
+ */
+
+ interface NucSeq : seqdb::BioSeq {
+
+
+ /**
+ * retrieve string representation of nucleotide sequence. Each character
+ * in the string is a
+ * <href="http://www.ebi.ac.uk/ebi_docs/embl_db/ft/iupac_codes.html">
+ * IUPAC nucleotide base code</A>
+ * <P>
+ * This method can be used instead of the getAnySeq()
+ * method in seqdb::BioSeq
+ * @see seqdb::BioSeq
+ */
+ string getSeq();
+
+
+ /**
+ * Checksum on sequence to allow validation.
+ *
+ */
+ unsigned long getCheckSum();
+
+ /**
+ * topology of the nucleotide sequence
+ * @see meta::nsdb
+ */
+ string getTopology();
+
+
+ /**
+ * molecule type of the nucleotide sequence
+ * @see meta::nsdb
+ */
+ string getMoleculeType();
+
+
+ /**
+ * retrieve sequence of NucFeatureList associated with
+ * the nucleotide sequence.
+ * A sequence has <I>ownership</I> of all these
+ * features. It is possible on the other hand that
+ * features, owned by another sequence, reference
+ * the current sequence. Currently there is no way to find out.
+ * @raises type::NoResult if no features are owned by the sequence
+ */
+ NucFeatureList getNucFeatures()
+ raises (type::NoResult);
+
+
+ /**
+ * A location of a NucFeature can span multiple sequences.
+ * If only the location of a feature relevant to the current
+ * sequence is required, this method will calculate that.
+ * @parm nuc_feature Feature from which location needs to be
+ * intersected with the current sequence.
+ * @raises type::InvalidRelation if the nuc_feature
+ * is not associated to the current sequence
+ */
+ Location getLocalLocation(in NucFeature nuc_feature)
+ raises (type::InvalidRelation);
+
+ /**
+ * organism(s) from which the NucSeq was obtained.
+ * If the sequence is chimeric, multiple organisms will be returned.
+ * For each organism, there should be a source feature associated
+ * with the current sequence. This source feature has a location
+ * defining which part of the sequence was derived from the specified
+ * organism.
+ * This method provides a shortcut to:
+ * invoke getNucFeaturesByKey("source"), invoke getQualifiers() on each
+ * source feature and loop through qualifiers to find DbXref.
+ * @raises type::NoResult if no source features are associated with
+ * the sequence (should never happen).
+ */
+ type::DbXrefList getOrganisms()
+ raises (type::NoResult);
+
+ /**
+ * retreive all features of a specific kind (FeatureKey)
+ * @parm key Type of features to be retrieved
+ * @raises type::NoResult if the sequence has no associated features
+ * of the requested type.
+ * @raises type::InvalidArgumentValue if the key is not a
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/ft/feature_table.html">
+ * valid feature key</A>
+ * @see NucFeature
+ */
+ NucFeatureList getNucFeaturesByKey(in string key)
+ raises (type::NoResult, type::InvalidArgumentValue);
+
+ /**
+ * Create nucleotide sequence of the segment specified
+ * The first base in the sequence is numbered 1
+ *
+ * @parm start first base of sub-sequence (inclusive)
+ * @parm end last base of sub-sequence (inclusive)
+ * @raises type::IndexOutOfRange if start < 1
+ * or end > length
+ */
+ string getSubSeq(in unsigned long start, in unsigned long end)
+ raises (type::IndexOutOfRange);
+
+ /**
+ * Create nucleotide sequence of the location derived from the specified feature
+ * and contained in the current NucSeq.
+ * @raises type::InvalidRelation if the nuc_feature
+ * is not associated to the current sequence
+ * @raises InexactLocation if an exact sequence cannot be
+ * determined due to the feature's location being inexact
+ */
+ string getSubSeqByFeature(in NucFeature feature)
+ raises (type::InvalidRelation, InexactLocation);
+ /**
+ * @raises type::InvalidRelation if reference does not exist
+ * @raises type::InvalidRelation if reference is not
+ * associated to the current sequence
+ * @raises type::NoResult if there is no location associated with this reference
+ */
+ Location getReferenceLocation(in string reference_id)
+ raises (type::InvalidRelation, type::NoResult);
+ };
+
+ /**
+ * EMBL Nucleotide sequence interface. An EMBL sequence contains all information
+ * to generate a flat-file entry out of it.
+ * It defines no new behaviour but inherits from
+ * several interfaces and acts as a container of the combined functionality.
+ */
+
+ interface EmblSeq : NucSeq, seqdb::SeqInfo , EntryInfo {
+ };
+
+
+/**
+ * Entry point for Nucleotide Sequence Database.
+ * Query methods on the database are defined here.
+ * Currently this is minimal, but should be extended later on.
+ */
+interface Embl {
+
+exception Superceded { type::stringList bio_seq_ids; };
+
+ /**
+ * retrieve Nucleotide sequence, given it's accession number. If a client only supports
+ * NucSeq, and not EmblSeq, than it can widen the EmblSeq to a NucSeq (implicit).
+ * @raises type::NoResult if the accession number does not exist.
+ * @raises Superceded if the sequence referenced by the accession
+ * number does not exist any more, because it was merged, or split.
+ */
+ EmblSeq getEmblSeq(in string bio_seq_id)
+ raises (type::NoResult, Superceded);
+};
+};
+#endif // embl_ebi_nsdb_idl
diff --git a/corba/nsdb_write.idl b/corba/nsdb_write.idl
new file mode 100644
index 0000000..27a1720
--- /dev/null
+++ b/corba/nsdb_write.idl
@@ -0,0 +1,260 @@
+#ifndef EMBL_EBI_NSDB_WRITE_IDL
+#define EMBL_EBI_NSDB_WRITE_IDL
+
+#include "nsdb.idl"
+
+module nsdb {
+ exception OutOfDate { };
+
+ exception ReadOnlyException { };
+
+ exception LocationParse {
+ string reason;
+ };
+
+ exception QualifierParse {
+ string reason;
+ };
+
+ exception InvalidQualifier { };
+
+ interface EmblSeqWriter;
+ interface NucFeatureWriter;
+
+ typedef sequence<NucFeatureWriter> NucFeatureWriterList;
+
+
+ // datestamp_t is seconds since the standard "the epoch", namely
+ // January 1, 1970, 00:00:00 GMT
+ typedef long datestamp_t;
+
+ struct Datestamp {
+ datestamp_t value;
+ };
+
+ struct EntryStats {
+ string name;
+ datestamp_t last_change_time;
+ };
+
+ struct ServerInfo {
+ // Information about the loaded entries
+ sequence <EntryStats> entry_stats_list;
+ // Information about the files in the server directory
+ sequence <EntryStats> file_stats_list;
+ };
+
+ interface EmblWriter : Embl {
+
+ /**
+ * Retrieve a writable Nucleotide sequence object, given a
+ * sequence_id.
+ * @raises type::NoResult if the given sequence_id does not exist.
+ */
+ EmblSeqWriter getEmblSeqWriter (in string sequence_id)
+ raises (type::NoResult);
+
+ /**
+ * Return a ServerInfo structure for the server.
+ **/
+ ServerInfo getServerInfo ();
+ };
+
+
+ exception InvalidKey { };
+
+
+ exception CommitFailed {
+ string reason;
+ };
+
+ interface EmblSeqWriter : EmblSeq {
+
+ /**
+ * Create a new feature in this EmblSeq object.
+ * @parm key Type of the feature to be created
+ * @parm location_string The location of the new NucFeature
+ * @raises LocationParse If the location string is not a valid location.
+ * @raises type::IndexOutOfRange If any part of the location is beyond the
+ * end of the sequence
+ * @raises InvalidKey if the given key is not a possible EMBL key.
+ */
+ NucFeatureWriter createNucFeature (in string key,
+ in string location)
+ raises (LocationParse, type::IndexOutOfRange, InvalidKey,
+ ReadOnlyException);
+
+ /**
+ * Remove the given feature.
+ */
+ void remove (in NucFeature nuc_feature)
+ raises (ReadOnlyException);
+
+ /**
+ * retrieve sequence of NucFeatureList associated with
+ * the nucleotide sequence that are within the given range of bases.
+ * @raises type::NoResult if no features are owned by the sequence
+ * @raises type::IndexOutOfRange if either of start_base or end_base is
+ * less than 1 or greater than the length of the sequence.
+ */
+ NucFeatureList getNucFeaturesInRange (in long start_base, in long end_base)
+ raises (type::NoResult, type::IndexOutOfRange);
+
+
+ /**
+ * Return the number of features
+ */
+ long getNucFeatureCount ();
+
+ /**
+ * Return the ith NucFeature from this Entry. The feature are returned in
+ * a consistent order, sorted by the first base of each Feature.
+ * @raises type::IndexOutOfRange if the index is less than 0 or greater
+ * than the number of features.
+ **/
+ NucFeature getFeatureAtIndex (in long i)
+ raises (type::IndexOutOfRange);
+
+ /**
+ * Return the index of the given Feature. This does the reverse of
+ * getFeatureAtIndex (). Returns -1 if the given NucFeature is not in
+ * this EmblSeq object.
+ **/
+ long indexOf (in NucFeature feature);
+
+ /**
+ * commit any pending changes to the database immediately.
+ */
+ void commit ()
+ raises (CommitFailed);
+
+ /**
+ * Return a Datestamp that will be passed to the set methods on the
+ * EmblSeqWriter methods and NucFeatureWriter methods. The object that is
+ * returned represents the time when the entry was last changed (the last
+ * time a feature was added, removed or changed location).
+ */
+ Datestamp getDatestamp ();
+ };
+
+
+ interface NucFeatureWriter : NucFeature {
+ /**
+ * Set the key, location and qualifiers of this NucFeature
+ * @parm key The new feature key
+ * @parm location The new feature location
+ * @raises InvalidKey if the given key is not a possible EMBL key.
+ * @raises LocationParse If the location string is not a valid location.
+ * @raises type::IndexOutOfRange If any part of the location is out
+ * of range for the sequence.
+ * @raises type::InvalidRelation if one of the qualifiers in this
+ * feature cannot be associated with the given feature key.
+ * @raises QualifierParse if the format of any the qualifiers is not
+ * appropriate for a Qualifier with the given name. For
+ * example the value part of /codon_start qualifier must be a number: 1,
+ * 2 or 3. Also thrown if a qualifier has value when it should not or
+ * vice versa.
+ * @raises InvalidQualifier if the name of the Qualifier is not a
+ * valid embl qualifier name.
+ * @raises ReadOnlyException If this Feature cannot be changed.
+ * @raises OutOfDate If the key has changed since the time given by
+ * datestamp.
+ */
+ void set (in Datestamp datestamp,
+ in string key,
+ in string location,
+ in QualifierList qualifier_list)
+ raises (InvalidKey, LocationParse, type::IndexOutOfRange,
+ type::InvalidRelation, QualifierParse,
+ InvalidQualifier, OutOfDate, ReadOnlyException);
+
+ /**
+ * Set the key of this NucFeature
+ * @parm key The new feature key
+ * @raises InvalidKey if the given key is not a possible EMBL key.
+ * @raises type::InvalidRelation if one of the qualifiers in this
+ * feature cannot be associated with the given feature key.
+ * @raises OutOfDate If the key has changed since the time given by
+ * datestamp.
+ */
+ void setKey (in Datestamp datestamp,
+ in string key)
+ raises (InvalidKey, type::InvalidRelation, OutOfDate, ReadOnlyException);
+
+ /**
+ * Set the location of this NucFeature
+ * @parm location The new feature location
+ * @raises LocationParse If the location string is not a valid location.
+ * @raises type::IndexOutOfRange If any part of the location is out
+ * of range for the sequence.
+ * @raises OutOfDate If the location has changed since the time given by
+ * datestamp.
+ */
+ void setLocation (in Datestamp datestamp,
+ in string location)
+ raises (LocationParse, type::IndexOutOfRange, OutOfDate, ReadOnlyException);
+
+ /**
+ * Set the qualifiers of this feature, replacing the current qualifiers.
+ * @raises InvalidRelationException if this Feature cannot
+ * contain one of the given qualifiers.
+ * @raises QualifierParse if the format of any the qualifiers is not
+ * appropriate for a Qualifier with the given name. For
+ * example the value part of /codon_start qualifier must be a number: 1,
+ * 2 or 3. Also thrown if a qualifier has value when it should not or
+ * vice versa.
+ * @raises InvalidQualifier if the name of the Qualifier is not a
+ * valid embl qualifier name.
+ * @raises OutOfDate if any of the qualifiers has changed since the time
+ * given by datestamp.
+ */
+ void setQualifiers (in Datestamp datestamp,
+ in QualifierList qualifier_list)
+ raises (type::InvalidRelation, QualifierParse, InvalidQualifier,
+ OutOfDate, ReadOnlyException);
+
+ /**
+ * Add the given Qualifier to this Feature. If this Feature contains a
+ * Qualifier with the same name as the new Qualifier it will be replaced.
+ * @parm qualifier The new qualifier to add.
+ * @raises InvalidRelationException if this Feature cannot
+ * contain the given Qualifier.
+ * @raises QualifierParse if the format of the qualifier is not
+ * appropriate for a Qualifier with the given name. For
+ * example the value part of /codon_start qualifier must be a number: 1,
+ * 2 or 3. Also thrown if a qualifier has value when it should not or
+ * vice versa.
+ * @raises InvalidQualifier if the name of the Qualifier is not a
+ * valid embl qualifier name.
+ * @raises OutOfDate if there is a an existing qualifier with the same
+ * name as the argument qualifier and it has changed since the time
+ * given by datestamp.
+ */
+ void setQualifier (in Datestamp datestamp,
+ in Qualifier qualifier)
+ raises (type::InvalidRelation, QualifierParse, InvalidQualifier,
+ OutOfDate, ReadOnlyException);
+
+ /**
+ * Remove the Qualifier with the given name. If there is no Qualifier
+ * with that name, then return immediately.
+ * @parm name The Qualifier name to look for.
+ * @raises OutOfDate if there is a an existing qualifier with the same
+ * name as the argument name and it has changed since the time given by
+ * the datestamp.
+ */
+ void removeQualiferByName (in Datestamp datestamp,
+ in string name)
+ raises (type::InvalidRelation, OutOfDate, ReadOnlyException);
+
+ /**
+ * Return a Datestamp that will be passed to the set methods on the
+ * EmblSeqWriter methods and NucFeatureWriter methods. The object that is
+ * returned represents the time when the feature was last changed (the
+ * last time the key, location or qualifiers changed).
+ */
+ Datestamp getDatestamp ();
+ };
+};
+
+#endif
diff --git a/corba/seqdb.idl b/corba/seqdb.idl
new file mode 100644
index 0000000..7be3f25
--- /dev/null
+++ b/corba/seqdb.idl
@@ -0,0 +1,131 @@
+/* **************************************************************************
+ * $Source: //tmp/pathsoft/artemis/corba/seqdb.idl,v $
+ * $Revision: 1.1 $
+ * $Date: 2004-06-09 12:06:36 $
+ * $Author: tjc $
+ * **************************************************************************/
+
+#ifndef embl_ebi_seqdb_idl
+#define embl_ebi_seqdb_idl
+
+#include "types.idl"
+/**
+ * seqdb contains the (abstract) definition
+ * of common attributes of biosequences
+ */
+ module seqdb {
+
+
+ /**
+ * generic biosequence. Provides al functionality we would like to see
+ * on any sequence.
+ */
+ interface BioSeq {
+
+ /**
+ * retrieve unique identifier
+ */
+ string getBioSeqId();
+
+
+
+ /**
+ * length (nr of elements) of the biosequence
+ */
+ unsigned long getLength();
+
+
+ /**
+ * sequence of objects describing the elements in the biosequence
+ * This is a generic description. Most subclasses
+ * will define more convenient methods for accessing the biosequence.
+ * @returns any containing an set of objects. The any should have
+ * a typecode tk_array.
+ */
+ any getAnySeq();
+
+
+
+ /**
+ * Return current version of the BioSeq. returns 0 if versioning is not
+ * implemented on the bioseq.
+ */
+ unsigned long getBioSeqVersion();
+
+ };
+
+ /**
+ * generic Feature. Only has a key to identify it's type, an ID to
+ * identify the instance and a version.
+ */
+ interface Feature {
+
+ /**
+ * feature identifier.
+ */
+ string getFeatureId();
+
+
+
+ /**
+ * Feature types are defined by a key.
+ */
+ string getKey();
+
+
+
+ /**
+ * return current version of the feature. returns 0 if
+ * no versioning is implemented.
+ */
+ unsigned long getFeatureVersion();
+
+ };
+
+ /**
+ * Information associated with a sequence
+ */
+ interface SeqInfo {
+ /**
+ * short (one line) description
+ */
+ string getDescription()
+ raises (type::NoResult);
+
+
+ /**
+ * sequence of keywords, describing the characteristics of the sequence
+ */
+ type::stringList getKeywords()
+ raises (type::NoResult);
+
+
+ /**
+ * sequence of comments, describing the characteristics of the sequence
+ */
+ type::stringList getComments()
+ raises (type::NoResult);
+
+
+ /**
+ * cross references to other databases containing related or additional
+ * information
+ */
+ type::DbXrefList getDbXrefs()
+ raises (type::NoResult);
+
+ /**
+ * cross references to the EMBL publication database
+ * information
+ */
+ type::DbXrefList getReferences()
+ raises (type::NoResult);
+
+ };
+
+ };
+
+
+
+
+#endif // embl_ebi_seqdb_idl
diff --git a/corba/types.idl b/corba/types.idl
new file mode 100644
index 0000000..717f3cb
--- /dev/null
+++ b/corba/types.idl
@@ -0,0 +1,334 @@
+/* **************************************************************************
+ * $Source: //tmp/pathsoft/artemis/corba/types.idl,v $
+ * $Revision: 1.1 $
+ * $Date: 2004-06-09 12:06:37 $
+ * $Author: tjc $
+ * **************************************************************************/
+#ifndef embl_ebi_types_idl
+#define embl_ebi_types_idl
+/**
+ * The type module contains types (typedefs and structs) that are shared
+ * between several modules of the EMBL::EBI suite.
+ * It should be included within the module EMBL::EBI.
+ */
+module type {
+ /**
+ * sequence of strings.
+ */
+ typedef sequence<string> stringList;
+
+ typedef sequence<long> longList;
+
+ typedef sequence<unsigned long> ulongList;
+
+ /**
+ * If a query in a database returns no results, the NoResult is raised
+ */
+ exception NoResult {};
+
+ /**
+ * If no write permission is granted (i.e. a set-method cannot be performed)
+ * the NoWritePermission is raised
+ */
+ exception NoWritePermission { string reason;};
+
+ /**
+ * If an object reference given as an input parameter is invalid, in the
+ * context of the current interface, an InvalidRelation is raised
+ */
+ exception InvalidRelation { string reason;};
+
+ /**
+ * If a number indicating a position in a sequence is
+ * outside the limits of the sequence, or more elements are associated to
+ * an object than it can handle,
+ * an IndexOutOfRange exception is raised
+ */
+ exception IndexOutOfRange { string reason; };
+
+/**
+ * controlled values (i.e. an attribute can only contain a value taken
+ * from a well defined range of values, or an in parameter to a method has a
+ * restricted set of acceptable values) are presented as typescodes, used in
+ * as union. If a method tries to set a controlled value
+ * attribute/parameter to an invalid value, an invalidValue is raised
+ */
+ exception InvalidArgumentValue { string reason; };
+
+
+ /**
+ * a sequence (e.g. string) with an expected defined format cannot be parsed
+ */
+ exception ParseError { string reason; };
+
+ /**
+ * Date is a struct to describe a date, independent of any report format
+ * <dl>
+ * <dt>day
+ * <dd>day of month as a number between 1-31 (inclusive)
+ * <dt>month
+ * <dd> month of the year as a number between 1-12 (inclusive)
+ * <dt>year
+ * <dd>year as a 4 digit number
+ * </dl>
+ */
+ struct Date {
+ unsigned short day;
+ unsigned short month;
+ unsigned short year;
+ };
+
+ typedef sequence<Date> DateList;
+
+
+ /**
+ * A person information : this is used to specify the authors
+ * <p><dl>
+ * <dt>surname
+ * <dd>the person surname
+ * <dt>firstname
+ * <dd>the person firstname
+ * <dt>midinitial
+ * <dd>the person mid initial
+ * <dl><p>
+ */
+ struct Person {
+ string surname;
+ string firstname;
+ string midinitial;
+ };
+ typedef sequence<Person> PersonList;
+
+ /**
+ * Revision
+ * <dl>
+ * <dt>date
+ * <dd>datestamp of revision
+ * <dt>type
+ * <dd>type of revision. Valid types are defined in meta
+ * </dl>
+ */
+ struct Revision {
+ Date date;
+ string type;
+ };
+
+ typedef sequence<Revision> RevisionList;
+
+ /**
+ * Database cross-reference. The list of valid database identifiers
+ * used by the collaboration is defined at the
+ * <A href="http://www.ncbi.nlm.nih.gov/collab/db_xref.html">NCBI
+ collaborative web server</A>
+ * <dl>
+ * <dt>db
+ * <dd>database identifier
+ * <dt>primary_id
+ * <dd>object identifier in the database
+ * <dt>version
+ * <dd> if the referenced database supports versioning, version
+ * refers to the version of the object, when the cross-reference
+ * was generated. If can be used to verify that a cross-reference
+ * is up to date.
+ * <dt>label
+ * <dd>secondary identifier, possibly used to indicate a sub/super-part of
+ * the primary object or an alternative name to the primary id.
+ * The label can contain information for the convenience
+ * of the user, but no assumptions should be made on the long-term
+ * stability of it.
+ * </dl>
+ */
+ struct DbXref {
+ string db;
+ string primary_id;
+ unsigned long version;
+ string label;
+ };
+
+ typedef sequence<DbXref> DbXrefList;
+
+ /**
+ * Amino Acid. This can be any amino acid, including modified or unusual ones.
+ * <P>
+ * <dl>
+ * <dt> code
+ * <dd> <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/ft/aa_abbrevs.html">
+ * IUPAC-IUB</A> one-letter code if the amino acid has one assigned
+ * <dd> othwerwise the code will be a blank character
+ * <dt> name
+ * <dd> descriptive name of the
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/ft/aa_abbrevs.html">
+ * Amino Acid</A> or
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/ft/modified_aa.html">
+ * modified or unusual Amino Acid</A>
+ * <dt> abbreviation
+ * <dd> abbreviated name of the
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/ft/aa_abbrevs.html">
+ * Amino Acid</A> or
+ * <A href="http://www.ebi.ac.uk/ebi_docs/embl_db/ft/modified_aa.html">
+ * modified or unusual Amino Acid</A>
+ *</dl>
+ */
+ struct AminoAcid {
+ char code;
+ string name;
+ string abbreviation;
+ };
+ typedef sequence<AminoAcid> AminoAcidList;
+
+ /**
+ * Translation rule specifying the amino acid encoded by a codon.
+ * Standard rules are defined by the genetic code of an organism.
+ * If a CDS uses non-standard rules, this can be annotated with
+ * qualifier codon (value type CodonTranslation_s).
+ * <p>
+ * <dt> codon
+ * <dd> literal sequence of the codon.
+ * <dt> amino_acid
+ * <dd> amino acid used.
+ * <dd> No modified AA are allowed.
+ */
+ struct CodonTranslation {
+ string codon;
+ AminoAcid amino_acid;
+ };
+ typedef sequence<CodonTranslation> CodonTranslationList;
+
+ /**
+ * Location of the anticodon of tRNA and the amino acid for which
+ * it codes.
+ * <p>
+ * <dl>
+ * <dt> start
+ * <dd> start position of the anticodon
+ * <dt> end
+ * <dd> end position of the anticodon
+ * <dt> amino_acid
+ * <dd> amino acid used.
+ * <dd> No modified AA are allowed.
+ * </dl>
+ */
+ struct AntiCodon {
+ long start;
+ long end;
+ AminoAcid amino_acid;
+ };
+ typedef sequence<AntiCodon> AntiCodonList;
+
+
+ /**
+ * Translation exception of a single triplet within a sequence.
+ * <p>
+ * <dl>
+ * <dt> primary_acc
+ * <dd> This attribute is likely to change in the future. It is very rarely
+ * used, but it is needed for translation exceptions on CDS's
+ * spanning entries
+ * <dt> start
+ * <dd> startposition of exception in the sequence.
+ * <dt> end
+ * <dd> endposition of exception in the sequence
+ * <dt> amino_acid
+ * <dd> amino acid used in this exception.
+ * <dd> No modified AA are allowed.
+ *</dl>
+ */
+ struct TranslationException {
+ string primary_acc;
+ long start;
+ long end;
+ AminoAcid amino_acid;
+ };
+ typedef sequence<TranslationException> TranslationExceptionList;
+
+ /**
+ * flag to indicate that the splice site consensus sequence is not
+ * present at one of the feature's splice junctions
+ * If no qualifier is present, the default is that both splice sites
+ * contain the consensus
+ */
+ struct SpliceConsensus {
+ boolean five_cons;
+ boolean three_cons;
+ };
+ typedef sequence<SpliceConsensus> SpliceConsensusList;
+
+
+ /**
+ * A RepeatUnit identifies the exact unit that is being repeated. It can be
+ * either a base range, or can refer to the label of a labeled repeat_unit
+ * feature (but not both; this datatype is likely to be superceded by a
+ * union).
+ * <dt> start
+ * <dd> position of first base in the first occurrence of the repeated segment
+ * <dt> end
+ * <dd> position of the last base in first occurence of the repeated segment
+ * <dt> label
+ * <dd> Currently, usually a textual description of the repeating segment,
+ * but can refer to a labeled repeat_unit feature.
+ * This attribute may in future become a proper DbXref to a feature;
+ */
+ struct RepeatUnit {
+ long start;
+ long end;
+ string label;
+ };
+ typedef sequence<RepeatUnit> RepeatUnitList;
+
+ /**
+ * a position can be fuzzy. FuzzyType
+ * defines how to interprete the combination of value and size, to define
+ * the range of fuzziness.
+ * Currently values 1-5 are in use.
+ * 5-10 are reserved for future use.
+ */
+ typedef long FuzzyTypeCode;
+
+
+ /**
+ * value is an exact position
+ */
+ const long Exact_ftc = 1;
+
+ /**
+ *A single base chosen from a range or span of bases is indicated
+ * by the first base number (value) and the last base number of the range
+ * (value+size) inclusive.
+ */
+ const long In_ftc = 2;
+
+ /**
+ * A site between two points (nucleotides), such as an endonucleolytic
+ * cleavage site. The site is a single position between 2 consecutive bases
+ * in the range. The range is defined by value, value+size (inclusive)
+ */
+ const long Between_ftc = 3;
+
+
+ /**
+ * an end point is undefined but behind (and does not include) the
+ * base number specified in value.
+ * size is unused.
+ */
+ const long Less_ftc = 4;
+
+
+ /**
+ * an end point is undefined but before (and does not include) the
+ * base number specified in value.
+ * size is unused.
+ */
+ const long Greater_ftc = 5;
+
+ struct Fuzzy {
+ long value;
+ long size;
+ FuzzyTypeCode type;
+ };
+ typedef sequence<Fuzzy> FuzzyList;
+
+};
+
+#endif
+
+
diff --git a/dnaplotter b/dnaplotter
new file mode 100755
index 0000000..7c6e2ea
--- /dev/null
+++ b/dnaplotter
@@ -0,0 +1,79 @@
+#!/bin/sh -
+
+# This script will start the Artemis circular plot on a UNIX system.
+
+# resolve links - $0 may be a link
+PRG=$0
+progname=`basename $0`
+
+ARTEMIS_HOME=`dirname "$PRG"`/.
+
+CLASSPATH=$ARTEMIS_HOME:$ARTEMIS_HOME/lib/biojava.jar:$ARTEMIS_HOME/lib/jemAlign.jar:$ARTEMIS_HOME/lib/jakarta-regexp-1.2.jar:$ARTEMIS_HOME/lib/macos.jar:$ARTEMIS_HOME/lib/postgresql-8.4-701.jdbc3.jar:$ARTEMIS_HOME/lib/chado-14-interface.jar:$CLASSPATH
+
+# j2ssh jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/j2ssh/commons-logging.jar:$ARTEMIS_HOME/lib/j2ssh/j2ssh-core.jar:$ARTEMIS_HOME/lib/j2ssh/
+
+# iBatis jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/ibatis/ibatis-2.3.4.726.jar:$ARTEMIS_HOME/lib/ibatis/:$ARTEMIS_HOME/lib/ibatis/log4j-1.2.14.jar:$ARTEMIS_HOME/lib/ibatis/cglib-nodep-2.2.jar:$ARTEMIS_HOME/lib/retrotranslator-runtime-1.1.0.jar
+export CLASSPATH
+
+# batik jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/batik/batik-awt-util.jar:$ARTEMIS_HOME/lib/batik/batik-dom.jar:$ARTEMIS_HOME/lib/batik/batik-ext.jar:$ARTEMIS_HOME/lib/batik/batik-svggen.jar:$ARTEMIS_HOME/lib/batik/batik-util.jar:$ARTEMIS_HOME/lib/batik/batik-xml.jar
+
+# picard jars
+CLASSPATH=$ARTEMIS_HOME/lib/picard/sam.jar:$ARTEMIS_HOME/lib/picard/picard.jar:$ARTEMIS_HOME/lib/commons-net-2.2.jar:$CLASSPATH
+export CLASSPATH
+
+
+ARTEMIS_PROPERTIES="-Dartemis.environment=UNIX"
+
+# Allow URLs to work from behind firewalls
+if [ "$http_proxy" = "" ]
+then
+ http_proxy=$HTTP_PROXY
+fi
+
+if [ "$http_proxy" = "" ]
+then
+ http_proxy=$HTTP_proxy
+fi
+
+if [ "$http_proxy" != "" ]
+then
+ ARTEMIS_PROPERTIES="$ARTEMIS_PROPERTIES -DproxySet=true "`echo $http_proxy | sed 's/http:\/\/\(.*\):\(.*\)/ -Dhttp.proxyHost=\1 -Dhttp.proxyPort=\2/'`
+fi
+
+
+# "-mx500m" sets the maximum amount of memory that Artemis can use. This may
+# need to be increased when dealing with large files
+MEM="-mx500m -ms20m"
+
+if [ "$JVM_FLAGS" = "" ]
+then
+ FLAGS="$MEM -noverify"
+else
+ FLAGS="$MEM -noverify $JVM_FLAGS"
+fi
+
+
+FLAGS=$FAST_FLAG$FLAGS
+
+if [ "$JAVA_VM" = "" ]
+then
+ if [ "$DEBUG" = yes ]
+ then
+ JAVA=java_g
+ else
+ JAVA=java
+ fi
+else
+ JAVA=$JAVA_VM
+fi
+
+PLATTMP=`uname`
+if [ "$PLATTMP" = "Darwin" ]
+then
+ FLAGS="$FLAGS -Dapple.laf.useScreenMenuBar=true -Dcom.apple.mrj.application.apple.menu.about.name=Circular Plot"
+fi
+
+$JAVA $MEM uk.ac.sanger.artemis.circular.DNADraw $*
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..69c19c8
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,76 @@
+# $Header: //tmp/pathsoft/artemis/docs/Makefile,v 1.2 2009-01-07 09:25:57 tjc Exp $
+
+SHELL=/bin/sh
+
+SANGER_CAT = /software/pathogen/projects/artemis/docbook-sgml/dtd_4.1/CATALOG
+
+JADE = jade
+JADE_PARAM = -t sgml -c $(SANGER_CAT) -d ../artemis.dsl\#html
+JADETEX_PARAM = -t tex -c $(SANGER_CAT) -d ../artemis.dsl\#tex
+
+html : art_html act_html
+
+art_html : art_html_build
+act_html : act_html_build
+
+tex : art_tex act_tex
+
+art_tex : art_tex_build
+act_tex : act_tex_build
+
+art_html_build : *.sgml *.dsl *.css
+ (if [ -d art_html_build ] ; \
+ then \
+ touch art_html_build; \
+ else \
+ mkdir art_html_build; \
+ fi; \
+ cd art_html_build; ln -sf ../*.sgml ../*.gif ../*.png ../*.eps ../*.css .; \
+ $(JADE) $(JADE_PARAM) manual.sgml;\
+ $(JADE) -V nochunks $(JADE_PARAM) manual.sgml > artemis_manual_complete.html)
+
+act_html_build : *.sgml *.dsl *.css
+ (if [ -d act_html_build ] ; \
+ then \
+ touch act_html_build; \
+ else \
+ mkdir act_html_build; \
+ fi; \
+ cd act_html_build; ln -sf ../*.sgml ../*.gif ../*.png ../*.eps ../*.css .; \
+ $(JADE) $(JADE_PARAM) act_manual.sgml;\
+ $(JADE) -V nochunks $(JADE_PARAM) act_manual.sgml > act_manual_complete.html)
+
+art_tex_build : *.sgml *.dsl *.css
+ (if [ -d art_tex_build ] ; \
+ then \
+ touch art_tex_build; \
+ else \
+ mkdir art_tex_build; \
+ fi; \
+ cd art_tex_build; ln -sf ../*.sgml ../*.gif ../*.ps ../*.eps .; \
+ $(JADE) $(JADETEX_PARAM) manual.sgml)
+
+act_tex_build : *.sgml *.dsl *.css
+ (if [ -d act_tex_build ] ; \
+ then \
+ touch act_tex_build; \
+ else \
+ mkdir act_tex_build; \
+ fi; \
+ cd act_tex_build; ln -sf ../*.sgml ../*.gif ../*.ps ../*.eps .; \
+ $(JADE) $(JADETEX_PARAM) act_manual.sgml)
+
+tex_format : art_tex_format act_tex_format
+
+art_tex_format :
+ (cd art_tex_build; latex -fmt jadetex manual.tex; \
+ latex -fmt jadetex manual.tex)
+
+act_tex_format :
+ (cd act_tex_build; latex -fmt jadetex act_manual.tex; \
+ latex -fmt jadetex act_manual.tex)
+
+all : clean html tex tex_format
+
+clean :
+ rm -rf art_html_build act_html_build art_tex_build act_tex_build
diff --git a/docs/acknowledgments.sgml b/docs/acknowledgments.sgml
new file mode 100644
index 0000000..888f6ed
--- /dev/null
+++ b/docs/acknowledgments.sgml
@@ -0,0 +1,17 @@
+ <SECT1 ID="ACKNOWLEDGMENTS">
+ <TITLE>Distribution Conditions and Acknowledgments</TITLE>
+ <PARA>
+&prog; may be freely distributed under the terms of the <ULINK
+URL="http://www.gnu.org/copyleft/gpl.html">GNU General Public License</ULINK>.
+See <XREF LINKEND="COPYRIGHT"> for the full text of the license.
+ </PARA>
+
+ <PARA>
+The development of &prog; is funded by the <ULINK
+URL="http://www.wellcome.ac.uk/" TYPE="external">Wellcome Trust's</ULINK>
+through it's support of the <ULINK
+URL="http://www.sanger.ac.uk/research/projects/" TYPE="external">Pathogen
+Genomics Group</ULINK> at <ULINK URL="http://www.sanger.ac.uk"
+TYPE="external">The Sanger Institute</ULINK>.
+ </PARA>
+ </SECT1>
diff --git a/docs/act_display_menu.sgml b/docs/act_display_menu.sgml
new file mode 100644
index 0000000..76131d6
--- /dev/null
+++ b/docs/act_display_menu.sgml
@@ -0,0 +1,23 @@
+<SECT1 ID="DISPLAYMENU">
+ <TITLE>The Display Menu</TITLE>
+ <PARA>
+The items in this menu control the global appearance of the display.
+ </PARA>
+
+ <SECT2 ID="DISPLAY-MENU2-HIDE-FRAME-LINES">
+ <TITLE>Hide Frame Lines</TITLE>
+ <PARA>
+Selecting this menu item will turn off the frame lines of the query and
+subject views. The frame lines can be turned on selectively using the pop up
+menu in the view (see <XREF LINKEND="VIEWS-POPUPMENU">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAY-MENU2-SHOW-FRAME-LINES">
+ <TITLE>Show Frame Lines</TITLE>
+ <PARA>
+Selecting this menu item will turn on the frame lines of the query and
+subject views.
+ </PARA>
+ </SECT2>
+</SECT1>
diff --git a/docs/act_file_menu.sgml b/docs/act_file_menu.sgml
new file mode 100644
index 0000000..db6eedd
--- /dev/null
+++ b/docs/act_file_menu.sgml
@@ -0,0 +1,166 @@
+<SECT1 ID="FILEMENU">
+ <TITLE>The File Menu</TITLE>
+ <PARA>
+ </PARA>
+
+ <SECT2 ID="FILEMENU-READ-AN-ENTRY">
+ <TITLE>Read An Entry ...</TITLE>
+ <PARA>
+Read an entry for this sequence, but keep it separate from the others. See
+<XREF LINKEND="CONCEPTS-ENTRY"> to see what we mean when we say "entry". The
+new entry will be marked as active (see <XREF LINKEND="CONCEPTS-ACTIVEENTRY">)
+and will be the new default entry (see <XREF
+LINKEND="CONCEPTS-DEFAULTENTRY">).
+ </PARA>
+ <PARA>
+This function only reads the feature section of the input file - the sequence
+(if any) is ignored.
+ </PARA>
+ <PARA>
+&prog; can read these feature files formats:
+ </PARA>
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <PARA>
+ <ULINK URL="http://www.ebi.ac.uk/embl/Documentation/FT_definitions/feature_table.html">EMBL or GenBank feature tables</ULINK>
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+ <ULINK URL="http://www.sequenceontology.org/resources/gff3.html">GFF files</ULINK>
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+The output of <ULINK
+URL="http://www.cgr.ki.se/cgr/groups/sonnhammer/MSPcrunch.html"><COMMAND>MSPcrunch</COMMAND></ULINK>.
+<COMMAND>MSPcrunch</COMMAND> must be run with the <COMMAND>-x</COMMAND> or
+<COMMAND>-d</COMMAND> flags.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+The output of <ULINK URL="http://www.ncbi.nlm.nih.gov/BLAST/">blastall</ULINK>
+version 2.2.2 or better. <COMMAND>blastall</COMMAND> must be run with the
+<COMMAND>-m 8</COMMAND> flag which generates one line of information per HSP.
+Note that currently &prog; displays each Blast HSP as a separate feature
+rather than displaying each BLAST hit as a feature.
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+ </SECT2>
+
+ <SECT2 ID="FILEMENU-SAVE-ENTRY">
+ <TITLE>Save Entry</TITLE>
+ <PARA>
+Save the entry to the file it came from, unless the entry has been
+given a new name, in which case the entry is saved to a file with that name.
+If the entry has no name, &prog; will prompt the user for a new name.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="FILEMENU-SAVE-ALL">
+ <TITLE>Save All</TITLE>
+ <PARA>
+Save all the entries that have been loaded on top of this sequence.
+ </PARA>
+ </SECT2>
+
+
+ <SECT2 ID="WRITEMENU">
+ <TITLE>Write</TITLE>
+ <PARA>
+
+ </PARA>
+
+ <SECT3 ID="WRITEMENU-AMINO-ACIDS-OF-SELECTED-FEATURES">
+ <TITLE>Amino Acids Of Selected Features</TITLE>
+ <PARA>
+Prompt for a file name and then write the translation of the bases of the
+selected features to that file. The file is written in FASTA format.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-PIR-DATABASE-OF-SELECTED-FEATURES">
+ <TITLE>PIR Database Of Selected Features</TITLE>
+ <PARA>
+Prompt for a file name and then write the translation of the bases of the
+selected features to that file. The file is written in PIR format (similar to
+FASTA, but with a * as the last line of each record).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-BASES-OF-SELECTION">
+ <TITLE>Bases Of Selection</TITLE>
+ <PARA>
+Prompt for a file name and then write the bases of the selection to that
+file in the selected format. If the selection consists of features (rather
+than a base range) then the bases of each feature
+will be written to the file as a separate record. If the selection is a range
+of bases, then those bases will be written.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-UPSTREAM-BASES-OF-SELECTION">
+ <TITLE>Upstream Bases Of Selection</TITLE>
+ <PARA>
+Prompt for a number and a file name, then write that many bases upstream of
+each selected feature to the file in the selected format. For example if the
+selected feature has a
+location of "<LITERAL>100..200</LITERAL>", then asking for 50 upstream will
+write the bases in the range 50 to 99. Writing upstream bases of a feature on
+the complementary strand will work in the expected way.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-DOWNSTREAM-BASES-OF-SELECTION">
+ <TITLE>Downstream Bases Of Selection</TITLE>
+ <PARA>
+Prompt for a number and a file name, then write that many bases downstream of
+each selected feature to the file in the selected format.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-ALL-BASES">
+ <TITLE>All Bases</TITLE>
+ <PARA>
+Prompt for a file name, then write the complete sequence to that file in the
+selected format.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-CODON-USAGE">
+ <TITLE>Codon Usage of Selected Features</TITLE>
+ <PARA>
+Prompt for a file name, then write a codon usage table for the selected
+features. The file in written in the same format as the data at <ULINK
+URL="http://www.kazusa.or.jp/codon/" TYPE="external">Kazusa codon usage
+database site</ULINK>. In the output file each codon is followed by it's
+occurrence count (per thousand) and it's percentage occurrence. (See <XREF
+LINKEND="GRAPHMENU-USAGE-PLOTS"> to find out how to plot a usage graph).
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+ &nextgen;
+
+ <SECT2 ID="FILEMENU-EDIT-IN-ARTEMIS">
+ <TITLE>Edit In Artemis ...</TITLE>
+ <PARA>
+Open an &art; edit window for this sequence and features. All changes
+made in the &art; window will be immediately visible in the &prog; window and
+vice versa.
+ </PARA>
+ <PARA>
+See <ULINK URL="ftp://ftp.sanger.ac.uk/pub4/resources/software/artemis/artemis.pdf">the
+&art; manual</ULINK> for more about &art;.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="FILEMENU-CLOSE">
+ <TITLE>Close</TITLE>
+ <PARA>
+Close this window.
+ </PARA>
+ </SECT2>
+</SECT1>
diff --git a/docs/act_intro_chapter.sgml b/docs/act_intro_chapter.sgml
new file mode 100644
index 0000000..3dfad65
--- /dev/null
+++ b/docs/act_intro_chapter.sgml
@@ -0,0 +1,128 @@
+<CHAPTER ID="INTRODUCTION">
+ <TITLE>Introduction to &prog;</TITLE>
+
+ <SECT1 ID="WHATIS">
+ <TITLE>What is &prog;?</TITLE>
+ <PARA>
+&prog; is a DNA sequence comparison viewer based on <ULINK
+URL="http://www.sanger.ac.uk/resources/software/artemis/"
+type="external">Artemis</ULINK>. The name &prog; stands for Artemis
+Comparison Tool. &prog; is written in Java, reads EMBL or GENBANK format
+sequences and feature tables, and can work on sequences of any size.
+ </PARA>
+
+ <PARA>
+&prog; understands several different comparison file formats. See <XREF
+LINKEND="COMPARISON-RUN"> for details. Note that &prog; is a comparison
+viewer only. The comparison information itself must be generated externally.
+See <XREF LINKEND="COMPARISON-RUN"> for more details.
+ </PARA>
+
+ <PARA>
+For other information see the <ULINK
+URL="http://www.sanger.ac.uk/resources/software/act/" TYPE="external">the &prog; web
+pages</ULINK> or the <ULINK
+URL="http://www.sanger.ac.uk/resources/software/artemis/" TYPE="external">the &art; web
+pages</ULINK>.
+ </PARA>
+ </SECT1>
+
+&requirements;
+
+ <SECT1 ID="INSTALLATION">
+ <TITLE>Getting and Installing &prog;</TITLE>
+ <PARA>
+The most up to date version of &prog; is always available from the <ULINK
+URL="http://www.sanger.ac.uk/resources/software/act/">&prog; web pages</ULINK>.
+ </PARA>
+
+&gettingjava;
+
+ <SECT2 ID="UNIXINSTALLATION">
+ <TITLE>Installation Instructions for UNIX and GNU/Linux</TITLE>
+ <PARA>
+Change directory to the directory you wish to install &prog; in. We will use
+<LITERAL>~/</LITERAL> in this example and in the next chapter. Uncompress and
+untar the <LITERAL>act_compiled.tar.gz</LITERAL> file. The command
+is:
+ </PARA>
+ <PARA>
+ <COMMAND>
+tar zxf act_compiled.tar.gz
+ </COMMAND>
+ </PARA>
+ <PARA>
+This will create a directory called <LITERAL>~/act</LITERAL> which will
+contain all the files necessity for running &prog;.
+ </PARA>
+
+ <PARA>
+For instructions on how to run &prog; on UNIX and GNU/Linux once the archive
+is unpacked see <XREF LINKEND="RUNNINGUNIX">.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="MACINSTALLATION">
+ <TITLE>Installation Instructions for MacOSX</TITLE>
+ <PARA>
+For MacOSX users an archive artemis_act.dmg.gz disk image is provided.
+This contains both Artemis and ACT applications.
+This can be uncompressed using gunzip:
+ </PARA>
+ <PARA>
+<ComputerOutput>gunzip artemis_act.dmg.gz</ComputerOutput>
+ </PARA>
+ <PARA>
+Alternatively on OS X an easier solution is provided by StuffIt Expander.
+Double-click on any file ending in ".gz" and StuffIt Expander will be
+launched to uncompress that file.
+
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="mac_osx_dmg.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+The uncompressed disk image file "artemis_act.dmg" can be mounted by double
+clicking on it. The mounted image, "artemis_act", can then be opened
+and the software contents displayed by double clicking on it. Open an read the
+"Readme.txt" file.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DOSINSTALLATION">
+ <TITLE>Installation Instructions for Windows</TITLE>
+ <PARA>
+On Windows systems with Java (version 1.6 or higher), installing &prog;
+is as simple as downloading the the act.jar file to an appropriate
+place (such as the desktop or the Programs folder). When downloading
+some versions of windows mistake this file for a zip file ensure it
+does not name this act.zip, if it does rename it to act.jar
+(DO NOT UNZIP THIS FILE).
+ </PARA>
+
+ <PARA>
+For instructions on how to run &prog; on Windows once it is unpacked see <XREF
+LINKEND="RUNNINGPC"> .
+ </PARA>
+ </SECT2>
+ </SECT1>
+
+&concepts;
+
+ <SECT1 ID="CONTRIBUTIONS">
+ <TITLE>Contributions and Suggestions</TITLE>
+ <PARA>
+We welcome contributions to &prog; and to &art;, bug reports from users and
+suggestions for new features. An email discussion list has been set up for
+this purpose. To join, send a message to 'artemis-users-join at sanger.ac.uk' with
+'subscribe artemis-users' in the body (not the subject). Announcements will
+also be sent to this list.
+ </PARA>
+ </SECT1>
+
+&acknowledgments;
+
+©right;
+
+</CHAPTER>
diff --git a/docs/act_main_window.sgml b/docs/act_main_window.sgml
new file mode 100644
index 0000000..555d0b8
--- /dev/null
+++ b/docs/act_main_window.sgml
@@ -0,0 +1,338 @@
+<CHAPTER ID="MAINWINDOW-CHAPTER">
+ <TITLE>The &prog; Main Window</TITLE>
+ <PARA>
+This chapter describes the main viewing window of &prog;. First is an
+overview of the window and a description of the menus. The menus are
+also accessible from the pop-up menu in the view windows (see <XREF
+LINKEND="VIEWS-OTHERMOUSE"> and <XREF LINKEND="VIEWS-POPUPMENU"> for more).
+ </PARA>
+
+ <SECT1 ID="MAINWINDOW-OVERVIEW">
+ <TITLE>Overview of the Comparison Window</TITLE>
+ <PARA>
+The following images show a breakdown of the main &prog; window taken from a
+<ULINK URL="act_user_manual_screen_shot.gif" TYPE="external">screen
+shot</ULINK> of a two sequence comparison.
+ </PARA>
+
+ <ORDEREDLIST ID="MAINWINDOW-BREAKDOWN-LIST">
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-MENU">
+ <SCREENSHOT>
+ <SCREENINFO>
+The menu bar of the main &prog; window
+ </SCREENINFO>
+
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="act_main_window_menu.gif">
+ </IMAGEOBJECT>
+
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="act_main_window_menu.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </LISTITEM>
+
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-SUBJECT">
+ <SCREENSHOT>
+ <SCREENINFO>
+An overview of the subject sequence
+ </SCREENINFO>
+
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="act_main_window_subject.gif">
+ </IMAGEOBJECT>
+
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="act_main_window_subject.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </LISTITEM>
+
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-COMPARISON">
+ <SCREENSHOT>
+ <SCREENINFO>
+The comparison view
+ </SCREENINFO>
+
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="act_main_window_comp.gif">
+ </IMAGEOBJECT>
+
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="act_main_window_comp.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </LISTITEM>
+
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-QUERY">
+ <SCREENSHOT>
+ <SCREENINFO>
+An overview of the query sequence
+ </SCREENINFO>
+
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="act_main_window_query.gif">
+ </IMAGEOBJECT>
+
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="act_main_window_query.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </LISTITEM>
+ </ORDEREDLIST>
+ <PARA>
+Key:
+ </PARA>
+ <ORDEREDLIST ID="MAINWINDOW-INFO">
+ <LISTITEM ID="MAINWINDOW-INFO-MENU">
+ <PARA>
+The menus for the main window (described later in this chapter). Each menu
+contains a sub-menu to control each of the sequences in the comparison. The
+Display menu (see <XREF LINKEND="DISPLAYMENU">) is the exception since it
+controls the overall display. Note that all the menus apart from the File
+menu and the Display menus can also be accessed via a pop-up menu on the third
+mouse button. See <XREF LINKEND="VIEWS-OTHERMOUSE"> and <XREF
+LINKEND="VIEWS-POPUPMENU"> for more.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="MAINWINDOW-INFO-SUBJECT">
+ <PARA>
+This a view of the subject sequence of the comparison. See <XREF
+LINKEND="VIEWS"> for more information on how to operate this part of the
+window.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="MAINWINDOW-INFO-COMPARISON">
+ <PARA>
+This part of the window shows the comparison between the two sequences. See
+<XREF LINKEND="COMPARISON-VIEW"> for more information.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="MAINWINDOW-INFO-QUERY">
+ <PARA>
+The query sequence of the comparison. See <XREF
+LINKEND="VIEWS"> for more.
+ </PARA>
+ </LISTITEM>
+ </ORDEREDLIST>
+ </SECT1>
+
+ &act-filemenu;
+ &menus;
+ &act-displaymenu;
+
+ <SECT1 ID="VIEWS">
+ <TITLE>The DNA Views</TITLE>
+ <PARA>
+The two sequence views in &prog; show the forward and reverse strands of the
+sequence and a representation of the three translation frames in each
+direction. The top view shows the subject sequence of the comparison (see
+<XREF LINKEND="MAINWINDOW-BREAKDOWN-SUBJECT">), the bottom view shows the query
+sequence (see <XREF LINKEND="MAINWINDOW-BREAKDOWN-QUERY">). The horizontal
+scrollbar controls which part of the sequence is currently visible. The
+scroll bar at the left controls the zoom level. By default the view shows
+only the stop codons in each translation frame, but if the view is zoomed in
+it will show the complete six frame translation.
+ </PARA>
+ <PARA>
+See also <XREF LINKEND="MAINWINDOW-OVERVIEW">.
+ </PARA>
+
+&views-selection;
+
+ <SECT2 ID="VIEWS-OTHERMOUSE">
+ <TITLE>Other Mouse Controlled Functions</TITLE>
+ <PARA>
+Double clicking on a feature with the first mouse button causes the view to
+centre itself on that feature. Similarly, double clicking the first mouse
+button on a base or amino acid will centre the view on that base/amino acid.
+ </PARA>
+ <PARA>
+A double click of the middle mouse button on a feature will open an edit
+window for that feature. This is the same as clicking once and then choosing
+the Edit Selected Features menu item (see <XREF
+LINKEND="EDITMENU-EDIT-SELECTED-FEATURES">).
+ </PARA>
+ <PARA>
+The third button activates a pop-up menu with functions specific to this
+view. See <XREF LINKEND="VIEWS-POPUPMENU"> for more.
+ </PARA>
+ </SECT2>
+
+&views-popup;
+&views-scrolling;
+&views-scale;
+&views-directedit;
+ </SECT1>
+
+ <SECT1 ID="COMPARISON-VIEW">
+ <TITLE>Comparison View</TITLE>
+ <PARA>
+This part of the window shows the comparison between two sequences. Each
+red/pink box corresponds to single match, with red representing a good match
+and white/pale pink a lower scoring match. This view cannot be scrolled
+directly, instead it tracks the movement of the subject and query sequence
+views (see <XREF LINKEND="VIEWS">).
+ </PARA>
+ <PARA>
+See <XREF LINKEND="MAINWINDOW-BREAKDOWN-COMPARISON"> to see where this view
+fits in the main window.
+ </PARA>
+ <SECT2 ID="COMPARISON-VIEW-CONTROLS">
+ <TITLE>Controls</TITLE>
+ <PARA>
+The vertical scrollbar at the right of the view sets the minimum size (in
+bases on the subject sequence) of the matches that are displayed. The default
+is 1, so when started &prog; will show all matches.
+ </PARA>
+ <PARA>
+Clicking the left button on a match will highlight the match. Double clicking
+the mouse on one of the matches will cause the sequence views to align around
+that match.
+ </PARA>
+ <PARA>
+Clicking the middle mouse button toggles the locking of the two sequences that
+are being compared by this comparison view. When locked the sequences will
+scroll in parallel. When unlocked the sequences can be scrolled
+independently. (See <XREF LINKEND="MOUSEBUTTONS"> if you do not have a middle
+mouse button)
+ </PARA>
+ <SECT3 ID="COMPARISON-VIEW-POPUPMENU">
+ <TITLE>The Comparison View Pop-up Menu</TITLE>
+ <PARA>
+The pop-up menu is activated by pressing the third mouse button within
+the comparison view. The menu items are as follows:
+ </PARA>
+ <ITEMIZEDLIST SPACING="compact">
+ <LISTITEM ID="COMPARISON-VIEW-POPUPMENU-VIEW-MATCHES">
+ <FORMALPARA>
+ <TITLE>View Selected Matches</TITLE>
+ <PARA>
+Creates a new window which contains a list of currently selected (ie. yellow)
+matches. Clicking on a match in the list will centre the comparison view on
+that match.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="COMPARISON-VIEW-POPUPMENU-FLIP-QUERY">
+ <FORMALPARA>
+ <TITLE>Flip Query Sequence</TITLE>
+ <PARA>
+Flips the query sequence display so that it appears to be reverse complemented.
+(See <XREF LINKEND="VIEWS-POPUPMENU-TOGGLES-FLIPDISPLAY">.)
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="COMPARISON-VIEW-POPUPMENU-FLIP-SUBJECT">
+ <FORMALPARA>
+ <TITLE>Flip Subject Sequence</TITLE>
+ <PARA>
+Flips the subject sequence display so that it appears to be reverse complemented.
+(See <XREF LINKEND="VIEWS-POPUPMENU-TOGGLES-FLIPDISPLAY">.)
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="COMPARISON-VIEW-POPUPMENU-LOCK">
+ <FORMALPARA>
+ <TITLE>Lock Sequences</TITLE>
+ <PARA>
+Lock the two sequences so that they scroll together.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="COMPARISON-VIEW-POPUPMENU-UNLOCK">
+ <FORMALPARA>
+ <TITLE>Unlock Sequences</TITLE>
+ <PARA>
+Allow the two sequences to scroll independently.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="COMPARISON-VIEW-POPUPMENU-SET-SCORE-CUTOFFS">
+ <FORMALPARA>
+ <TITLE>Set Score Cutoffs ...</TITLE>
+ <PARA>
+Pop-up a gadget that allows the matches to be filtered on "score", generally
+the BLAST score .
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="COMPARISON-VIEW-POPUPMENU-SET-PID-CUTOFFS">
+ <FORMALPARA>
+ <TITLE>Set Percent ID Cutoffs ...</TITLE>
+ <PARA>
+Pop-up a gadget that allows the matches to be filtered on the percentage
+identity.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="COMPARISON-VIEW-POPUPMENU-OFFER-TO-REVCOMP">
+ <FORMALPARA>
+ <TITLE>Offer To RevComp</TITLE>
+ <PARA>
+When this option is "on", double clicking on a reverse complemented match will
+cause &prog; to ask the user whether to flip the query sequence
+before centering on the match. If "off" the match will just be centred.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="COMPARISON-VIEW-POPUPMENU-IGNORESELFMATCHES">
+ <FORMALPARA>
+ <TITLE>Ignore Self Matches</TITLE>
+ <PARA>
+When this option is "on", matches that have the same start and
+end positions on both the subject and query sequences will not be shown.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+ </SECT3>
+ </SECT2>
+ </SECT1>
+
+&featurelist;
+
+ <SECT1 ID="GRAPHS">
+ <TITLE>Graphs and Plots</TITLE>
+ <PARA>
+The DNA plots are accessed from the Graphs(top) and Graphs(bottom) menus (see
+<XREF LINKEND="DISPLAYMENU">). When a graph is turned on it is locked to the
+overview window, so it will follow the overview window when the overview is
+scrolled. It will also automatically change the viewing scale to match the
+scale of the overview window. The vertical scrollbar to the right of the
+graph controls the window size of algorithm.
+ </PARA>
+
+ <PARA>
+The feature plots window (see <XREF LINKEND="VIEWMENU-SHOW-FEATURE-PLOTS">)
+has a horizontal scroll bar that sets the position of the plot in the feature.
+As with the DNA plots, the vertical scrollbar to the right of the graph
+controls the window size of algorithm.
+ </PARA>
+
+ <PARA>
+Note that clicking the left mouse button on the plot will show the base or
+residue at that position. Clicking the right mouse button will pop-up a menu.
+The menu contains a toggle which controls whether the graph is scaled and some
+menu items which allow the maximum window size to be set.
+ </PARA>
+ </SECT1>
+
+&mousebuttons;
+</CHAPTER>
diff --git a/docs/act_main_window_comp.gif b/docs/act_main_window_comp.gif
new file mode 100644
index 0000000..fd088b0
Binary files /dev/null and b/docs/act_main_window_comp.gif differ
diff --git a/docs/act_main_window_menu.gif b/docs/act_main_window_menu.gif
new file mode 100644
index 0000000..cd1a9ef
Binary files /dev/null and b/docs/act_main_window_menu.gif differ
diff --git a/docs/act_main_window_query.gif b/docs/act_main_window_query.gif
new file mode 100644
index 0000000..fcb5dce
Binary files /dev/null and b/docs/act_main_window_query.gif differ
diff --git a/docs/act_main_window_subject.gif b/docs/act_main_window_subject.gif
new file mode 100644
index 0000000..382c6ed
Binary files /dev/null and b/docs/act_main_window_subject.gif differ
diff --git a/docs/act_manual.sgml b/docs/act_manual.sgml
new file mode 100644
index 0000000..9dbe2b9
--- /dev/null
+++ b/docs/act_manual.sgml
@@ -0,0 +1,85 @@
+<!DOCTYPE Book PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
+<!ENTITY intro SYSTEM "act_intro_chapter.sgml">
+<!ENTITY start SYSTEM "act_start_chapter.sgml">
+<!ENTITY mainwindow SYSTEM "act_main_window.sgml">
+<!ENTITY ssh SYSTEM "ssh_chapter.sgml">
+<!ENTITY options SYSTEM "options.sgml">
+<!ENTITY act-filemenu SYSTEM "act_file_menu.sgml">
+<!ENTITY menus SYSTEM "menus.sgml">
+<!ENTITY act-displaymenu SYSTEM "act_display_menu.sgml">
+<!ENTITY requirements SYSTEM "requirements.sgml">
+<!ENTITY acknowledgments SYSTEM "acknowledgments.sgml">
+<!ENTITY copyright SYSTEM "copyright.sgml">
+<!ENTITY concepts SYSTEM "concepts.sgml">
+<!ENTITY views-popup SYSTEM "views_popup.sgml">
+<!ENTITY views-scrolling SYSTEM "views_scrolling.sgml">
+<!ENTITY views-scale SYSTEM "views_scale.sgml">
+<!ENTITY views-selection SYSTEM "views_selection.sgml">
+<!ENTITY views-directedit SYSTEM "views_directedit.sgml">
+<!ENTITY featurelist SYSTEM "feature_list.sgml">
+<!ENTITY mousebuttons SYSTEM "mousebuttons.sgml">
+<!ENTITY gettingjava SYSTEM "getting_java.sgml">
+<!ENTITY unixargs SYSTEM "unix_args.sgml">
+<!ENTITY jvmopts SYSTEM "jvm_options.sgml">
+<!ENTITY options-menu SYSTEM "options_menu.sgml">
+<!ENTITY nextgen SYSTEM "next_gen.sgml">
+
+<!ENTITY % act-only "INCLUDE">
+<!ENTITY % artemis-only "IGNORE">
+
+<!ENTITY prog "ACT">
+<!ENTITY art "Artemis">
+
+<!ENTITY command-name "act">
+
+<!ENTITY lt CDATA "<">
+<!ENTITY gt CDATA ">">
+]>
+<!-- $Header: //tmp/pathsoft/artemis/docs/act_manual.sgml,v 1.8 2008-01-15 16:36:19 tjc Exp $ -->
+<BOOK ID="index">
+ <TITLE> The &prog; Manual </TITLE>
+ <BOOKINFO>
+ <PRODUCTNAME>
+ <EMPHASIS>&prog;</EMPHASIS> - The Artemis Comparison Tool
+ </PRODUCTNAME>
+ <PUBDATE>16 May 2000</PUBDATE>
+ <COPYRIGHT>
+ <YEAR>2000-2014</YEAR>
+ <HOLDER>Genome Research Limited</HOLDER>
+ </COPYRIGHT>
+ <LEGALNOTICE>
+ <PARA>&prog; 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 2 of the License, or (at your option) any later
+ version.</PARA>
+
+ <PARA>This program 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 <ULINK URL="http://www.gnu.org/copyleft/gpl.html"
+ TYPE="external">GNU General Public License</ULINK> for more
+ details.</PARA>
+
+ <PARA>You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ MA 02111-1307 USA</PARA>
+
+ <PARA>For the full text of the license see <XREF LINKEND="COPYRIGHT">.
+ </PARA>
+ </LEGALNOTICE>
+ <ABSTRACT>
+ <PARA>
+This document describes release 13 of &prog; - a DNA sequence comparison
+viewer.
+ </PARA>
+ </ABSTRACT>
+ </BOOKINFO>
+ <TOC></TOC>
+ &intro;
+ &start;
+ &mainwindow;
+ &ssh;
+ &options;
+</BOOK>
diff --git a/docs/act_start_chapter.sgml b/docs/act_start_chapter.sgml
new file mode 100644
index 0000000..45b24c1
--- /dev/null
+++ b/docs/act_start_chapter.sgml
@@ -0,0 +1,230 @@
+<CHAPTER ID="START">
+ <TITLE>Starting &prog;</TITLE>
+
+ <SECT1 ID="COMPARISON-RUN">
+ <TITLE>Running a Comparison</TITLE>
+ <PARA>
+Before running &prog; you will need to obtain a comparison file.
+&prog; supports three different comparison file formats:
+ </PARA>
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <FORMALPARA>
+ <TITLE>The output of <ULINK
+ URL="http://www.ncbi.nlm.nih.gov/BLAST/">BLAST
+ </ULINK> version 2.2.2 or better</TITLE>
+ <PARA>
+The <COMMAND>blastall</COMMAND> command must be run with the <COMMAND>-m
+8</COMMAND> flag which generates one line of information per HSP. If
+<ULINK URL="http://blast.ncbi.nlm.nih.gov/">BLAST+</ULINK>
+is used then the legacy_blast.pl PERL script (bundled along with the BLAST+ applications)
+can be used to generate the tabular format or run it with the <COMMAND>-outfmt 6</COMMAND> flag.
+For more details see the <ULINK URL=
+"ftp://ftp.ncbi.nlm.nih.gov/blast/executables/blast+/LATEST/user_manual.pdf">BLAST+ manual</ULINK>.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ <LISTITEM>
+ <FORMALPARA>
+ <TITLE>MEGABLAST output</TITLE>
+ <PARA>
+ACT can also read the output of <ULINK
+URL="http://www.ncbi.nlm.nih.gov/blast/megablast.shtml">MEGABLAST</ULINK>,
+which is part of the blast distribution.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ <LISTITEM>
+ <FORMALPARA>
+ <TITLE>MSPcrunch output</TITLE>
+ <PARA>
+<ULINK URL="http://sonnhammer.sbc.su.se/download/software/MSPcrunch+Blixem/"
+TYPE="external">MSPcrunch</ULINK> is program for UNIX and GNU/Linux systems
+which can post-process <ULINK
+URL="http://www.ncbi.nlm.nih.gov/BLAST/">BLAST</ULINK> version 1 output into
+an easier to read format. MSPcrunch much be run with the
+<LITERAL>-d</LITERAL> flag.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+
+ <PARA>
+ Anthony Underwood and Jonathan Green at the <ULINK URL="http://www.hpa.org.uk/">
+ Health Protection Agency</ULINK> have developed an online tool, <ULINK
+ URL="http://www.hpa-bioinfotools.org.uk/pise/double_act.html">Double ACT</ULINK>, to generate
+ ACT comparison files.
+ </PARA>
+
+ <PARA>
+ Also there is <ULINK URL="http://www.webact.org/">WebACT</ULINK> which was
+ designed and built by James Abbott and David Aanensen at Imperial College, London
+ and is hosted in the laboratory of Professor Brian Spratt at Imperial College London
+ with funding from The Wellcome Trust.
+ </PARA>
+
+ <SECT2 ID="COMPARISON-BIG-BLAST">
+ <TITLE><COMMAND>big_blast.pl</COMMAND></TITLE>
+ <PARA>
+At the Sanger Centre we generally use a perl script called <ULINK
+URL="ftp://ftp.sanger.ac.uk/pub/pathogens/software/artemis/extra/big_blast.pl"
+TYPE="external"><LITERAL>big_blast.pl</LITERAL></ULINK> to run blast and
+generate a file of results in MSPcrunch format. To run this script you will
+need to install a copy of the Pathogen Sequencing Unit <ULINK
+URL="ftp://ftp.sanger.ac.uk/pub/pathogens/software/artemis/extra/zoo_modules.tar.gz"
+TYPE="external">internal perl modules</ULINK>. <ULINK
+URL="ftp://ftp.sanger.ac.uk/pub/pathogens/software/artemis/extra/big_blast.pl"
+TYPE="external"><LITERAL>big_blast.pl</LITERAL></ULINK> can also be used to
+process blast results after the fact with a command line like this:
+ </PARA>
+
+ <PARA>
+ <COMMAND>
+big_blast.pl blast_results.out
+ </COMMAND>
+ </PARA>
+ <PARA>
+Note that big_blast.pl has only been tested on UNIX and GNU/Linux machines.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="COMPARISON-FILE-FORMAT">
+ <TITLE>The MSPcrunch Comparison File Format</TITLE>
+ <PARA>
+Here is an example input file file generated by <LITERAL>MSPcrunch
+-d</LITERAL>.
+ <SYNOPSIS>
+1399 97.00 940 2539 sequence1.dna 1 1596 AF140550.seq
+1033 93.00 9041 10501 sequence1.dna 9420 10880 AF140550.seq
+828 95.00 6823 7890 sequence1.dna 7211 8276 AF140550.seq
+773 94.00 2837 3841 sequence1.dna 2338 3342 AF140550.seq
+ </SYNOPSIS>
+ </PARA>
+ <PARA>
+The columns have the following meanings (in order): score, percent identity,
+match start in the query sequence, match end in the query sequence, query
+sequence name, subject sequence start, subject sequence end, subject sequence
+name.
+ </PARA>
+ <PARA>
+The columns are separated by spaces.
+ </PARA>
+ </SECT2>
+ </SECT1>
+
+ <SECT1 ID="RUNNINGUNIX">
+ <TITLE>Running &prog; on UNIX and GNU/Linux Systems</TITLE>
+ <PARA>
+On Unix and GNU/Linux the easiest way to run the program is to run the script
+called <COMMAND>act</COMMAND> in the &prog; installation directory (see <XREF
+LINKEND="INSTALLATION">) like this:
+ </PARA>
+
+ <PARA>
+ <COMMAND>~/act/act</COMMAND>
+ </PARA>
+
+ <PARA>
+If all goes well you will be presented with a small window with one menu (the
+File menu). See <XREF LINKEND="LAUNCH-WINDOW"> for a description of this
+window.
+ </PARA>
+
+ <PARA>
+Alternatively you start &prog; with the names of two sequence/EMBL/GENBANK
+files and a file containing the comparison data eg:
+ </PARA>
+
+ <PARA>
+ <COMMAND>
+~/act/act sequence1 blast_output.crunch sequence2
+ </COMMAND>
+ </PARA>
+
+ <PARA>
+If you have more than two sequences (there is no upper limit) they should be
+listed on the command line separated by the corresponding comparison file:
+ </PARA>
+
+ <PARA>
+ <COMMAND>
+~/act/act sequence1 seq2_v_seq1.crunch sequence2 seq3_v_seq2.crunch sequence3
+ </COMMAND>
+ </PARA>
+
+ <PARA>
+See <XREF LINKEND="UNIXARGS"> for a list of the other possible command line
+arguments.
+ </PARA>
+
+ <PARA>
+Here is an alternative way to run &prog; if the <COMMAND>act</COMMAND> script
+doesn't work for you:
+ </PARA>
+
+ <PARA>
+ <COMMAND>
+cd ~/act
+ </COMMAND>
+ </PARA>
+
+ <PARA>
+ <COMMAND>
+java -classpath lib/biojava.jar:lib/jemAlign.jar:lib/j2ssh/j2ssh-core.jar:lib/ibatis/ibatis-2.3.4.726.jar:lib/ibatis/log4j-1.2.14.jar:lib/chado-14-interface.jar:lib/postgresql-8.4-701.jdbc3.jar:lib/picard.jar:lib/picard/sam.jar:lib/batik/batik-awt-util.jar:lib/batik/batik-codec.jar:lib/batik/batik-dom.jar:lib/batik/batik-ext.jar:lib/batik/batik-svggen.jar:lib/batik/batik-util.jar:lib/batik/batik-xml.jar:. -Dartemis.environment=UNIX uk.ac.sanger.artemis.components.ActMain
+ </COMMAND>
+ </PARA>
+ </SECT1>
+
+&unixargs;
+
+ <SECT1 ID="RUNNINGMAC">
+ <TITLE>Running &prog; on Macintosh Systems</TITLE>
+ <PARA>
+On Macintosh machines &prog; can be started by double clicking on the ACT
+icon.
+ </PARA>
+ </SECT1>
+
+ <SECT1 ID="RUNNINGPC">
+ <TITLE>Running &prog; on Windows Systems</TITLE>
+ <PARA>
+On systems with Java 1.6 (or higher) installed &prog; can be
+started by double clicking on the act.jar icon.
+ </PARA>
+ </SECT1>
+
+ <SECT1 ID="LAUNCH-WINDOW">
+ <TITLE>The &prog; Launch Window</TITLE>
+ <PARA>
+This is the first window that opens when you start &prog;.
+ </PARA>
+
+ <SECT2 ID="LAUNCH-WINDOW-FILE-MENU">
+ <TITLE>The File Menu</TITLE>
+ <SECT3 ID="LAUNCH-WINDOW-FILE-MENU-OPEN">
+ <TITLE>Open ...</TITLE>
+ <PARA>
+If you select this menu item a file requester will be displayed which allows
+you to open the sequence files and the comparison files. If the files you
+select are read successfully, a new window will open, which shows the
+sequences and the comparison. See <XREF LINKEND="MAINWINDOW-CHAPTER"> to find
+out how to use the main window. The comparison file is described in <XREF
+LINKEND="COMPARISON-FILE-FORMAT">.
+ </PARA>
+ <PARA>
+The "more files ..." button expands the window so that more sequence and
+comparison files can be read.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-FILE-MENU-QUIT">
+ <TITLE>Quit</TITLE>
+ <PARA>
+This menu item will close all windows and then exit the program
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+&options-menu;
+ </SECT1>
+</CHAPTER>
diff --git a/docs/act_user_manual_screen_shot.gif b/docs/act_user_manual_screen_shot.gif
new file mode 100644
index 0000000..29867ff
Binary files /dev/null and b/docs/act_user_manual_screen_shot.gif differ
diff --git a/docs/artemis.dsl b/docs/artemis.dsl
new file mode 100644
index 0000000..b4ebe83
--- /dev/null
+++ b/docs/artemis.dsl
@@ -0,0 +1,145 @@
+<!DOCTYPE style-sheet PUBLIC "-//James Clark//DTD DSSSL Style Sheet//EN" [
+<!ENTITY html-docbook-ss SYSTEM "/software/pathogen/projects/artemis/docbook-sgml/stylesheets_1.64/html/docbook.dsl" CDATA DSSSL>
+<!ENTITY tex-docbook-ss SYSTEM "/software/pathogen/projects/artemis/docbook-sgml/stylesheets_1.64/print/docbook.dsl" CDATA DSSSL>
+]>
+
+<!--
+$Header: //tmp/pathsoft/artemis/docs/artemis.dsl,v 1.2 2009-01-07 09:26:49 tjc Exp $
+-->
+
+<style-specification id="html" use="html-spec">
+ <style-specification-body>
+(define %body-attr%
+ ;; REFENTRY body-attr
+ ;; PURP What attributes should be hung off of BODY?
+ ;; DESC
+ ;; A list of the the BODY attributes that should be generated.
+ ;; The format is a list of lists, each interior list contains the
+ ;; name and value of a BODY attribute.
+ ;; /DESC
+ ;; AUTHOR N/A
+ ;; /REFENTRY
+ (list
+ (list "BGCOLOR" "#FFFFFF")
+ (list "TEXT" "#000000")))
+
+<!-- (define %html-header-tags%
+ ;; REFENTRY html-header-tags
+ ;; PURP What additional HEAD tags should be generated?
+ ;; DESC
+ ;; A list of the the HTML HEAD tags that should be generated.
+ ;; The format is a list of lists, each interior list consists
+ ;; of a tag name and a set of attribute/value pairs:
+ ;; '(("META" ("NAME" "name") ("CONTENT" "content")))
+ ;; /DESC
+ ;; AUTHOR N/A
+ ;; /REFENTRY
+ '())
+-->
+
+(define html-index
+ ;; REFENTRY html-index
+ ;; PURP HTML indexing?
+ ;; DESC
+ ;; Turns on HTML indexing. If true, then index data will be written
+ ;; to the file defined by 'html-index-filename'. This data can be
+ ;; collated and turned into a DocBook index with bin/collateindex.pl.
+ ;; /DESC
+ ;; AUTHOR N/A
+ ;; /REFENTRY
+ #t)
+
+(define html-manifest
+ ;; REFENTRY html-manifest
+ ;; PURP Write a manifest?
+ ;; DESC
+ ;; If not '#f' then the list of HTML files created by the stylesheet
+ ;; will be written to the file named by 'html-manifest-filename'.
+ ;; /DESC
+ ;; AUTHOR N/A
+ ;; /REFENTRY
+ #t)
+
+(define nochunks
+ ;; REFENTRY nochunks
+ ;; PURP Suppress chunking of output pages
+ ;; DESC
+ ;; If true, the entire source document is formatted as a single HTML
+ ;; document and output on stdout.
+ ;; (This option can conveniently be set with '-V nochunks' on the
+ ;; Jade command line).
+ ;; /DESC
+ ;; AUTHOR N/A
+ ;; /REFENTRY
+ #f)
+
+(define rootchunk
+ ;; REFENTRY rootchunk
+ ;; PURP Make a chunk for the root element when nochunks is used
+ ;; DESC
+ ;; If true, a chunk will be created for the root element, even though
+ ;; nochunks is specified. This option has no effect if nochunks is not
+ ;; true.
+ ;; (This option can conveniently be set with '-V rootchunk' on the
+ ;; Jade command line).
+ ;; /DESC
+ ;; AUTHOR N/A
+ ;; /REFENTRY
+ #f)
+
+(define %link-mailto-url%
+ ;; REFENTRY link-mailto-url
+ ;; PURP Mailto URL for LINK REL=made
+ ;; DESC
+ ;; If not '#f', the '%link-mailto-url%' address will be used in a
+ ;; LINK REL=made element in the HTML HEAD.
+ ;; /DESC
+ ;; AUTHOR N/A
+ ;; /REFENTRY
+ "artemis at sanger.ac.uk")
+
+(define %show-comments%
+ ;; REFENTRY show-comments
+ ;; PURP Display Comment elements?
+ ;; DESC
+ ;; If true, comments will be displayed, otherwise they are suppressed.
+ ;; Comments here refers to the 'Comment' element, which will be renamed
+ ;; 'Remark' in DocBook V4.0, not SGML/XML comments which are unavailable.
+ ;; /DESC
+ ;; AUTHOR N/A
+ ;; /REFENTRY
+ #f)
+
+(define %stylesheet%
+ ;; REFENTRY stylesheet
+ ;; PURP Name of the stylesheet to use
+ ;; DESC
+ ;; The name of the stylesheet to place in the HTML LINK TAG, or '#f' to
+ ;; suppress the stylesheet LINK.
+ ;; /DESC
+ ;; AUTHOR N/A
+ ;; /REFENTRY
+ "psu.css")
+
+(define %html-ext% ".html")
+
+; Includes a summary at the beginning of an item.
+(define %generate-article-toc% #t)
+
+(define %use-id-as-filename% #t)
+
+ </style-specification-body>
+</style-specification>
+
+
+<style-specification id="tex" use="tex-spec">
+
+ <style-specification-body>
+; Includes a summary at the beginning of an item.
+(define %generate-article-toc% #t)
+
+ </style-specification-body>
+</style-specification>
+
+<external-specification id="html-spec" document="html-docbook-ss">
+<external-specification id="tex-spec" document="tex-docbook-ss">
diff --git a/docs/chado/ACT.html b/docs/chado/ACT.html
new file mode 100644
index 0000000..242cce9
--- /dev/null
+++ b/docs/chado/ACT.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1"/>
+<title>Artemis - Chado tutorial</title>
+
+</head>
+<body>
+
+<h2>Generating ACT Comparison File</h2>
+
+<p>BLAST is used to generate the ACT comparison file (for more details see the <a href="http://www.sanger.ac.uk/Software/ACT/">ACT manual</a>) one of the
+sequences needs to be made the database sequence and indexed with formatdb:
+</p><pre>formatdb -i NC_004314.fasta -p 'F'
+</pre>
+<p>The other sequence is the query and you can then search for matches between these:
+</p>
+<pre>blastall -p tblastx -d NC_004314.fasta -i NC_011907.fasta -o NC_004314_v_NC_011907_tblastx -m 8
+</pre><p>or
+</p>
+<pre>blastall -p blastn -d NC_004314.fasta -i NC_011907.fasta -o NC_004314_v_NC_011907_blastn -m 8 -e 0.1
+</pre>
+<p>Use the '-m 8' option to provide the tabular output that ACT uses.
+</p><p>An alternative to this is to use <a href="http://www.webact.org">WebACT</a> or <a href="http://www.hpa-bioinfotools.org.uk/pise/double_act.html">Double ACT</a> to generate the comparison file.
+</p>
+</body>
+</html>
diff --git a/docs/chado/ActSelection.gif b/docs/chado/ActSelection.gif
new file mode 100644
index 0000000..b9e3133
Binary files /dev/null and b/docs/chado/ActSelection.gif differ
diff --git a/docs/chado/ActSelection2seqs.gif b/docs/chado/ActSelection2seqs.gif
new file mode 100644
index 0000000..5503015
Binary files /dev/null and b/docs/chado/ActSelection2seqs.gif differ
diff --git a/docs/chado/AddCV.gif b/docs/chado/AddCV.gif
new file mode 100644
index 0000000..27e3bbc
Binary files /dev/null and b/docs/chado/AddCV.gif differ
diff --git a/docs/chado/Artemis.gif b/docs/chado/Artemis.gif
new file mode 100644
index 0000000..7da2adf
Binary files /dev/null and b/docs/chado/Artemis.gif differ
diff --git a/docs/chado/ArtemisLogin.gif b/docs/chado/ArtemisLogin.gif
new file mode 100644
index 0000000..fd7cec9
Binary files /dev/null and b/docs/chado/ArtemisLogin.gif differ
diff --git a/docs/chado/CV.gif b/docs/chado/CV.gif
new file mode 100644
index 0000000..7e2c392
Binary files /dev/null and b/docs/chado/CV.gif differ
diff --git a/docs/chado/GMOD2009SummerSchool.shtml b/docs/chado/GMOD2009SummerSchool.shtml
new file mode 100644
index 0000000..cf9b679
--- /dev/null
+++ b/docs/chado/GMOD2009SummerSchool.shtml
@@ -0,0 +1,342 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="keywords" content="Artemis, Chado, ACT" />
+
+ <title>GMOD : Artemis Chado Set Up Tutorial</title>
+ </head>
+<body>
+
+<a name="Contents"></a><h2>Contents</h2>
+<ul>
+<li> <a href="#Overview" title="">Overview</a></li>
+<li> <a href="#Download_and_Install_Postgres" title="">Download and Install Postgres</a>
+</li><li> <a href="#Download_and_Install_Chado" title="">Download and Install Chado</a>
+</li><li> <a href="#Examples_of_Loading_Sequences_into_the_Database" title="">Examples of Loading Sequences into the Database</a>
+
+</li><li> <a href="#Download_Artemis_and_ACT" title="">Download Artemis and ACT</a>
+</li><li> <a href="#Running_Artemis" title="">Running Artemis</a>
+</li><li> <a href="#Configuration_Options" title="">Configuration Options</a>
+</li><li> <a href="#Adding_Controlled_Vocabulary_Qualifiers_in_the_Artemis_Gene_Builder" title="">Adding Controlled Vocabulary Qualifiers in the Artemis Gene Builder</a>
+</li><li> <a href="#Transfer_Annotation_Tool_.28TAT.29" title="">Transfer Annotation Tool (TAT)</a>
+</li><li> <a href="#Logging_Information" title="">Logging Information</a>
+
+</li><li> <a href="#Running_ACT" title="">Running ACT</a>
+</li><li> <a href="#Writing_Out_Sequence_Files" title="">Writing Out Sequence Files</a>
+</li><li> <a href="#Mailing_List" title="">Mailing List</a>
+</li><li> <a href="#References" title="">References</a>
+</li></ul>
+
+<a name="Overview"></a><h2>Overview</h2>
+<p>In this tutorial we present how to install and configure
+<a href="http://www.sanger.ac.uk/Software/Artemis/">Artemis</a> and
+<a href="http://www.sanger.ac.uk/Software/ACT/">ACT</a> to use with a
+Chado database. The first two sections relate to installing Postgres
+and Chado, this is included for completeness only and you should refer
+to the Chado session for more details on this.
+
+</p>Artemis is a DNA sequence browser which works with flat files (e.g. EMBL, GenBank, GFF) and more recently with Chado databases. ACT (Artemis Comparison Tool) is based on Artemis. ACT uses BLAST comparison files to highlight regions of interest between pairs of sequences. Artemis and ACT in database mode are increasingly being used in the Pathogen Genomics Group at the Sanger Institute.
+</p>
+
+
+<a name="Download_and_Install_Postgres"></a><h2>Download and Install Postgres</h2>
+
+<pre>./configure --prefix=/home/gmod/gmod_test/pgsl --with-pgport=5432 --with-includes=/Developer
+make
+make install
+
+cd /home/gmod/gmod_test/pgsl
+bin/initdb -D data/
+</pre>
+<p>Added the line to data/postgresql.conf:
+</p>
+<pre>listen_addresses = 'localhost'
+</pre>
+<p>Create the database:
+</p>
+<pre>postmaster -D data &
+createuser --createdb username
+createlang plpgsql template1
+createdb --port=5432 chado_pathogen
+</pre>
+<a name="Download_and_Install_Chado"></a><h2>Download and Install Chado</h2>
+
+<ul><li> Download stable release (gmod-1.0.tar.gz)
+</li><li> Install BioPerl (<a href="http://www.bioperl.org/wiki/Installing_Bioperl_for_Unix" class="external free" title="http://www.bioperl.org/wiki/Installing_Bioperl_for_Unix" rel="nofollow">http://www.bioperl.org/wiki/Installing_Bioperl_for_Unix</a>)
+</li><li> Install go-perl <a href="http://search.cpan.org/~cmungall/go-perl/" class="external free" title="http://search.cpan.org/~cmungall/go-perl/" rel="nofollow">http://search.cpan.org/~cmungall/go-perl/</a>
+</li><li> Install BioPerl (<a href="http://www.bioperl.org/wiki/Installing_Bioperl_for_Unix" class="external free" title="http://www.bioperl.org/wiki/Installing_Bioperl_for_Unix" rel="nofollow">http://www.bioperl.org/wiki/Installing_Bioperl_for_Unix</a>)
+</li><li> Install go-perl <a href="http://search.cpan.org/~cmungall/go-perl/" class="external free" title="http://search.cpan.org/~cmungall/go-perl/" rel="nofollow">http://search.cpan.org/~cmungall/go-perl/</a>
+
+</li><li> Install Bundle::GMOD from cpan
+</li></ul>
+<pre>export GMOD_ROOT=/usr/local/gmod CHADO_DB_NAME=chado_pathogen CHADO_DB_USERNAME=username CHADO_DB_PORT=5432
+</pre>
+<p>Now compile Chado and install the standard components (schema and ontologies):
+</p>
+<pre>perl Makefile.PL
+make
+sudo make install
+make load_schema
+make prepdb
+make ontologies
+</pre>
+<a name="Examples_of_Loading_Sequences_into_the_Database">
+</a><h2>Examples of Loading Sequences into the Database</h2>
+<p>In this section we detail how to load 3 Plasmodium sequences into Chado for viewing in Artemis and ACT. Alternatively you can use your own sequences of interest.
+</p><p>The GenBank files are available from Entrez with the links below. Make sure you download it with the sequence by clicking on the option 'Show sequence' and 'Update View'. Then go to the Download menu and select GenBank(Full):
+
+</p>
+<ul><li> <a href="http://www.ncbi.nlm.nih.gov/sites/entrez?db=nucleotide&cmd=search&term=NC_004314" class="external text" title="http://www.ncbi.nlm.nih.gov/sites/entrez?db=nucleotide&cmd=search&term=NC_004314" rel="nofollow">NC_004314</a> (Plasmodium falciparum 3D7 chromosome 10)
+</li><li> <a href="http://www.ncbi.nlm.nih.gov/sites/entrez?db=nucleotide&cmd=search&term=NC_011907" class="external text" title="http://www.ncbi.nlm.nih.gov/sites/entrez?db=nucleotide&cmd=search&term=NC_011907" rel="nofollow">NC_011907</a> (Plasmodium knowlesi chromosome 6) and
+</li><li> <a href="http://www.ncbi.nlm.nih.gov/sites/entrez?db=nucleotide&cmd=search&term=NC_011909" class="external text" title="http://www.ncbi.nlm.nih.gov/sites/entrez?db=nucleotide&cmd=search&term=NC_011909" rel="nofollow">NC_011909</a> (Plasmodium knowlesi chromosome 8).
+</li></ul>
+<p>These are usually downloaded to the Desktop directory (depending on the browser). They are saved as something like sequences.gbwithparts. Re-name them as NC_004314.gbk, NC_011907.gbk and NC_011909.gbk. Pfalciparum and Pknowlesi will need to be added to your organism table in Chado.
+</p>
+<pre> psql chado_pathogen
+</pre>
+
+<pre class="code"> <span style="color:red">INSERT INTO</span> organism
+ ( abbreviation, genus, species, common_name )
+ <span style="color:red">VALUES</span>
+ ( 'Pfalciparum', 'Plasmodium', 'falciparum', 'Pfalciparum'),
+ ( 'Pknowlesi', 'Plasmodium', 'knowlesi', 'Pknowlesi');</pre>
+
+<p>Using the perl script 'bp_genbank2gff3.pl' to convert the GenBank files to GFF3 format:
+</p>
+<pre>bp_genbank2gff3.pl -noCDS *.gbk
+</pre>
+<p>You need to modify the gff files so that the correct SO term is used:
+</p>
+<pre>perl -pi~ -e s'|processed_transcript|mature_transcript|' *.gff
+</pre>
+<p>Then load the GFF3 files that have been created:
+</p>
+<pre>gmod_bulk_load_gff3.pl -organism Pfalciparum -dbname chado_pathogen \
+ -dbuser gmod -dbport 5432 -dbpass dd -recreate_cache < NC_004314.gbk.gff
+
+gmod_bulk_load_gff3.pl -organism Pknowlesi -dbname chado_pathogen \
+ -dbuser gmod -dbport 5432 -dbpass dd -recreate_cache < NC_011907.gbk.gff
+
+gmod_bulk_load_gff3.pl -organism Pknowlesi -dbname chado_pathogen \
+ -dbuser gmod -dbport 5432 -dbpass dd -recreate_cache < NC_011909.gbk.gff
+
+</pre>
+<a name="Download_Artemis_and_ACT"></a><h2>Download Artemis and ACT</h2>
+<p>You can download <a href="http://www.sanger.ac.uk/Software/Artemis/" class="external text" title="http://www.sanger.ac.uk/Software/Artemis/" rel="nofollow">Artemis</a> and <a href="http://www.sanger.ac.uk/Software/ACT/" class="external text" title="http://www.sanger.ac.uk/Software/ACT/" rel="nofollow">ACT</a> from their home pages at the Sanger Institute. For the most up-to-date developments download the software from the CVS server:
+</p>
+<pre>cvs -d :pserver:cvsuser at cvs.sanger.ac.uk:/cvsroot/pathsoft co artemis
+</pre>
+<p>Now compile the software:
+</p>
+<pre>cd artemis
+make
+</pre>
+<p>Or download the development version from the <a href="http://www.sanger.ac.uk/Software/Artemis/#development" class="external text" title="http://www.sanger.ac.uk/Software/Artemis/#development" rel="nofollow">Development section</a> on the Artemis home page. Note that on the Artemis web site there is also a <a href="http://www.sanger.ac.uk/Software/Artemis/stable/" class="external text" title="http://www.sanger.ac.uk/Software/Artemis/stable/" rel="nofollow">stable</a> release available.
+</p>
+<a name="Running_Artemis"></a><h2>Running Artemis</h2>
+
+<p>Try running the 'art' script in the download:
+</p>
+<pre>./art -Dchado="localhost:5432/chado_pathogen?gmod" -Dibatis
+</pre>
+<p>This opens the login window:
+</p><p>
+<a href="ArtemisLogin.gif" class="image" title="Artemis Login"><img alt="Image:ArtemisLogin.gif" src="ArtemisLogin.gif" width="550" height="253" border="0" /></a>
+</p><p>
+The Artemis Database Manager and File Manager will open once your login has been authenticated. The top part of this relates to the Chado database and the bottom comprises the file management:
+</p><p>
+<a href="databasemanager.gif" class="image" title="Database Manager"><img alt="Image:DatabaseManager.gif" src="databasemanager.gif" width="434" height="867" border="0" /></a>
+</p><p>
+Select the sequence NC_004314 and double click on it to open it up in Artemis.
+</p><p>
+<a href="Artemis.gif" class="image" title="Artemis"><img alt="Artemis.gif" src="Artemis.gif" width="900" height="700" border="0" /></a>
+</p><p>
+There are 3 main components to the Artemis window. The two top Feature Displays show the sequence at different levels of granularity and below these is a feature list:
+
+</p>
+<ol><li> the <b>top Feature Display</b> is a zoomed out view of the sequence. The 3 forward and 3 reverse frames of translation are show with stop codons marked as black vertical lines.
+</li><li> the <b>second Feature Display</b> shows the sequence at the nucleotide level. The amino acid translations are seen in this view.
+</li><li> the <b>Feature List</b> shows the feature types and location. Options for displaying user defined qualifiers (e.g. Dbxref) can be accessed by right clicking on this list and selecting "Show Selected Qualifiers".
+</li></ol>
+
+<p>These three components are connected, so that if you select a feature in one then that feature becomes selected in the others. Double clicking on the feature centers the feature in both feature displays. The scroll bars on the right hand side of the feature displays allow you to zoom in and out.
+</p><p>The alternative way to open your sequence is to provide the entry (e.g. Pfalciparum:NC_004314) you want to open as a command line argument:
+</p>
+<pre> ./art -Dchado="localhost:5432/chado_pathogen?gmod" -Dibatis \
+ Pfalciparum:NC_004314
+</pre>
+<p>For any of the gene features in Artemis you can select them and press the short cut key 'E' (Edit->Selected Features in Editor). This opens up the Gene Builder. Within this the Gene Model can be edited and annotation added.
+</p><p>
+<a href="genebuilder.gif" class="image" title="GeneBuilder"><img alt="GeneBuilder" src="genebuilder.gif" width="760" height="921" border="0" /></a>
+</p><p>
+It is also possible to launch the Artemis Gene Builder in a standalone mode for a particular gene:
+</p>
+<pre>etc/gene_builder -Dchado="localhost:5432/chado_pathogen?gmod" -Dibatis -Dshow_log PF10_0003
+</pre>
+<p>or in read-only mode you can open a gene in GeneDB (at the Sanger Institute):
+</p>
+
+<pre>etc/gene_builder -Dchado="db.genedb.org:5432/snapshot?genedb_ro" -Dibatis -Dshow_log -Dread_only PFA0010c
+</pre>
+<p>Note using the JVM option 'show_log' will open the log window.
+</p>
+
+<a name="Configuration_Options"></a><h2>Configuration Options</h2>
+<p>Edit 'etc/options' (to change settings globally) or create a file ~/.artemis_options in your home directory (for your own settings). There are various flags that can be used to configure Artemis and ACT with Chado.
+</p><p><b>chado_servers</b> This allows you to provide a list of available servers for the user to select:
+</p>
+<pre>chado_servers = \
+ Plasmodium localhost:5432/chado_pathogen?username \
+ GeneDB db.genedb.org:5432/snapshot?genedb_ro
+</pre>
+
+<p><b>product_cv</b> In the Pathogen Genomics Group the product qualifiers are stored as an ontology (as a cv in feature_cvterm). This can be changed so that they are stored as featureprop's by setting the product_cv option:
+</p>
+<pre>product_cv=no
+</pre>
+<p>This will mean that the product will be shown in the "Core" section of the Artemis Gene Builder rather than the "Controlled Vocabulary" section.
+</p><p><b>synonym_cvname</b> If synonym types are loaded into a CV, Artemis checks this ontology.
+</p><p><b>set_obsolete_on_delete</b> This will set the default behaviour of Artemis when features are deleted. If set to:
+</p>
+<pre>set_obsolete_on_delete=yes
+</pre>
+<p>the features will be made obsolete. The user is still prompted with the option to permanently delete the feature. If this line is not in the option file the default is to permanently delete features.
+
+</p><p><b>Selecting an alternative gene model</b> Artemis supports 2 types of gene model representations:
+</p><p>A) Pathogen Genomics Gene Model - implicit CDS + explicit UTRs
+</p>
+<pre> gene
+ |
+ |- part_of mRNA
+ |
+ |---- part_of exon
+ |
+ |---- derives_from polypeptide
+ |
+ |---- part_of five_prime_UTR
+ |
+ |---- part_of three_prime_UTR
+
+</pre>
+<p>B) implicit CDS + UTRs
+</p>
+<pre> gene
+ |
+ |- part_of mRNA
+ |
+ |---- part_of exon
+ |
+ |---- derives_from polypeptide
+</pre>
+<p>The Artemis default is model A. To use model B then set:
+</p>
+<pre>chado_infer_CDS_UTR=yes
+</pre>
+
+<p><b>sequence_update_features</b> This lists the features that Artemis will maintain the feature.residue column for. This is generally useful for polypeptide and transcript features.
+</p>
+<a name="Artemis_Database_Manager"></a><h2>Artemis Database Manager</h2>
+<p>The database manager provides the list of organisms that have features with residues (currently Artemis searches for these on features of type: '*chromosome*', '*sequence*', 'supercontig', 'ultra_scaffold', 'golden_path_region', 'contig'). The database manager is cached between sessions (this is on by default and can be switched off with -Ddatabase_manager_cache_off). There is an option under the File menu to clear this cache.
+</p>
+<a name="Adding_Controlled_Vocabulary_Qualifiers_in_the_Artemis_Gene_Builder"></a><h2>
+Adding Controlled Vocabulary Qualifiers in the Artemis Gene Builder</h2>
+
+<p>These use evidence codes which are stored as a feature_cvtermprop's with a type_id which corresponds to a cvterm.name = 'evidence'. There is a useful SQL script (etc/chado_extra.sql) in the Artemis distribution for creating this term in Chado. Run this on the chado_pathogen instance of the database:
+</p>
+<pre>psql -d chado_pathogen -f etc/chado_extra.sql
+</pre>
+<p>(This will also create other terms that are used to store literature (PMID's) qualifiers.)
+</p><p>GO terms can now be selected in the Controlled Vocabulary (CV) section of the Gene Builder and added to features. Additional custom CV's can also be used. For Artemis to recognise it and display it the name of the CV needs to be prefixed by 'CC_'. These then appear in a drop down list when adding CV terms to a feature. Try adding a new CV:
+</p>
+<pre> psql chado_pathogen
+</pre>
+<pre> <span style="color:red">INSERT INTO</span> cv ( name, definition ) <span style="color:red">VALUES</span> ( 'CC_test', 'test' );
+</pre>
+<p>and create a CvTerm in this CV:
+</p>
+<pre> <span style="color:red">INSERT INTO</span> dbxref
+ ( db_id, accession )
+ <span style="color:red">VALUES</span>
+ ( (SELECT db_id FROM db WHERE name = 'CCGEN'), 'test1' );
+</pre>
+<pre> <span style="color:red">INSERT INTO</span> cvterm
+ ( cv_id, name, dbxref_id )
+ <span style="color:red">VALUES</span>
+
+ ( (SELECT cv_id FROM cv WHERE name ='CC_test'), 'test1',
+ (SELECT dbxref_id FROM dbxref WHERE accession='test1') );
+</pre>
+<p>Now re-launch Artemis and open the Gene Builder at any feature and go to the 'Controlled Vocabulary' section and click the 'ADD' button. This CV (CC_test) will appear in the drop down menu:
+</p><p>
+<a href="AddCV.gif" class="image" title="Add CV"><img alt="Image:AddCV.gif" src="AddCV.gif" width="284" height="172" border="0" /></a>
+</p><p>
+Click on CC_test and hit the 'Next' button. This opens a keyword selection box. If you leave this blank all the terms are retrieved and displayed. If you keep clicking 'Next' this term is then added to the 'Controlled Vocabulary' section.
+</p><p>
+</p>
+<a name="Transfer_Annotation_Tool_.28TAT.29"></a><h2>Transfer Annotation Tool (TAT)</h2>
+
+<p>This tool can be accessed from the Gene Builder - look for the TAT button. It allows you to transfer annotation between sequences. In database mode Artemis provides an editable list of genes constructed from ortholog/parlog links. These links can be added in the Gene Builder in the Match section (for example you can try creating the ortholog link between PF10_0165 in Pfalciparum and PKH_060110 in Pknowlesi).
+
+</p>
+<a name="Logging_Information"></a><h2>Logging Information</h2>
+<p>Note that you can easily access the logging information Artemis produces. In the Artemis launch window under the 'Options' menu select the 'Show Log Window', this provides the logs. This is controlled by etc/log4j.properties. The logs can be useful for debugging and for monitoring activity if appended to a central file. See the <a href="http://logging.apache.org/log4j/" class="external text" title="http://logging.apache.org/log4j/" rel="nofollow">log4j</a> documentation for more information.
+</p>
+<a name="Running_ACT"></a><h2>Running ACT</h2>
+
+<p>ACT can read sequences in from the database as well. However, it currently does not read the BLAST comparisons from Chado but reads this data from files. These comparisons are displayed as the matches between the sequences. To distinguish forward and reverse matches the forward matches are red and reverse matches are blue.
+</p><p>For convenience the comparison files have been pre-generated for this exercise and can be downloaded from:
+</p>
+<pre><span style="color:red">wget</span> <a href="ftp://ftp.sanger.ac.uk/pub/pathogens/workshops/GMOD2009/NC_004314_v_NC_011907_tblastx.gz" class="external free" title="ftp://ftp.sanger.ac.uk/pub/pathogens/workshops/GMOD2009/NC_004314_v_NC_011907_tblastx.gz" rel="nofollow">ftp://ftp.sanger.ac.uk/pub/pathogens/workshops/GMOD2009/NC_004314_v_NC_011907_tblastx.gz</a>
+<span style="color:red">wget</span> <a href="ftp://ftp.sanger.ac.uk/pub/pathogens/workshops/GMOD2009/NC_004314_v_NC_011909_tblastx.gz" class="external free" title="ftp://ftp.sanger.ac.uk/pub/pathogens/workshops/GMOD2009/NC_004314_v_NC_011909_tblastx.gz" rel="nofollow">ftp://ftp.sanger.ac.uk/pub/pathogens/workshops/GMOD2009/NC_004314_v_NC_011909_tblastx.gz</a>
+</pre>
+<p>Note that both Artemis and ACT automatically open gzipped files. For details on generating these files go to <a href="ACT.html" title="ACT Comparison Files">ACT Comparison Files</a>.
+</p><p>To run ACT use the 'act' script:
+</p>
+
+<pre>./act -Dchado="localhost:5432/chado_pathogen?gmod" -Dibatis
+</pre>
+<p>From the 'File' menu select the option 'Open Database and SSH File Manager' and login. Drag and drop the Plasmodium entries from the Database Manager into the ACT selection window. Also, drag and drop the comparison files into this window, so it looks something like this (note the featureId numbers may well be different as these are the Chado feature_id):
+</p><p><br />
+<a href="ActSelection2seqs.gif" class="image" title="Image:ActSelection2seqs.gif"><img alt="Image:ActSelection2seqs.gif" src="ActSelection2seqs.gif" width="528" height="187" border="0" /></a>
+</p><p>
+Click on Apply to read these entries and open up ACT. You can use the right hand scroll bar to zoom in and out. If you zoom out you can indentify the regions that match between these sequences.
+</p><p>
+<a href="Pf10_Pk6.gif" class="image" title="Image:Pf10_Pk6.gif"><img alt="Image:Pf10_Pk6.gif" src="Pf10_Pk6.gif" width="900" height="700" border="0" /></a>
+</p><p>
+ACT can display multiple pairwise comparison. So the two P.knowlesi sequences can be compared to the P.falciparum sequence. From the ACT launch window go to the File menu and select 'Open Database and SSH File Manager'. Drag in the sequences and comparison files (clicking on 'more files' to add the additional sequence and comparison).
+</p><p>
+<a href="ActSelection.gif" class="image" title="Image:ActSelection.gif"><img alt="Image:ActSelection.gif" src="ActSelection.gif" width="528" height="245" border="0" /></a>
+</p><p>
+Zooming out you will see that Pfalciparum chromosome 10 matches to regions in Pknowlesi chromosome 7 and 9.
+</p><p>
+<a href="Pk6_Pf10_Pk8.gif" class="image" title="Image:Pk6_Pf10_Pk8.gif"><img alt="Image:Pk6_Pf10_Pk8.gif" src="Pk6_Pf10_Pk8.gif" width="900" height="700" border="0" /></a>
+
+</p><p>
+</p>
+<a name="Writing_Out_Sequence_Files"></a><h2>Writing Out Sequence Files</h2>
+<p>Artemis can write out EMBL and GFF files for an entry opened from the database. You can optionally flatten the gene model (i.e. gene, transcript, exon) to just a CDS feature. Also an option is given to ignore any obsolete features. For EMBL it uses mappings for conversion of the keys and qualifiers. These mappings are stored in the 'etc/key_mapping' and 'etc/qualifier_mapping' files.
+</p><p>A utility script (etc/write_db_entry) is also provided as a means of writing out multiple sequences from the database. The script takes the following options:
+</p>
+<pre>-h show help
+-f [y|n] flatten the gene model, default is y
+-i [y|n] ignore obsolete features, default is y
+-s space separated list of sequences to read and write out
+-o [EMBL|GFF] output format, default is EMBL
+-a [y|n] for EMBL submission format change to n, default is y
+</pre>
+<p>Try running:
+</p>
+<pre>etc/writedb_entry -Dchado="localhost:5432/chado_pathogen?gmod" NC_004314
+</pre>
+
+<a name="Mailing_List"></a><h2>Mailing List</h2>
+<p>There is an Artemis mailing list: <a href="http://lists.sanger.ac.uk/mailman/listinfo/artemis-users" class="external text" title="http://lists.sanger.ac.uk/mailman/listinfo/artemis-users" rel="nofollow">artemis-user</a>.
+</p>
+<a name="References"></a><h2>References</h2>
+<p><a href="http://www.sanger.ac.uk/Software/Artemis/" class="external text" title="http://www.sanger.ac.uk/Software/Artemis/" rel="nofollow">Artemis home page</a><br /><a href="http://www.sanger.ac.uk/Software/ACT/" class="external text" title="http://www.sanger.ac.uk/Software/ACT/" rel="nofollow">ACT home page</a><br /><a href="http://www.sanger.ac.uk/Software/Artemis/v11/chado/" class="external text" title="http://www.sanger.ac.uk/Software/Artemis/v11/chado/" rel="nofollow">Artemis Co [...]
+
+</p><p>
+
+</body></html>
+
diff --git a/docs/chado/Pf10_Pk6.gif b/docs/chado/Pf10_Pk6.gif
new file mode 100644
index 0000000..0660233
Binary files /dev/null and b/docs/chado/Pf10_Pk6.gif differ
diff --git a/docs/chado/Pk6_Pf10_Pk8.gif b/docs/chado/Pk6_Pf10_Pk8.gif
new file mode 100644
index 0000000..6594d9b
Binary files /dev/null and b/docs/chado/Pk6_Pf10_Pk8.gif differ
diff --git a/docs/chado/Properties.gif b/docs/chado/Properties.gif
new file mode 100644
index 0000000..9ef475a
Binary files /dev/null and b/docs/chado/Properties.gif differ
diff --git a/docs/chado/TAT.gif b/docs/chado/TAT.gif
new file mode 100644
index 0000000..76dd585
Binary files /dev/null and b/docs/chado/TAT.gif differ
diff --git a/docs/chado/addterm.gif b/docs/chado/addterm.gif
new file mode 100644
index 0000000..6deac49
Binary files /dev/null and b/docs/chado/addterm.gif differ
diff --git a/docs/chado/admin.shtml b/docs/chado/admin.shtml
new file mode 100644
index 0000000..0ec233a
--- /dev/null
+++ b/docs/chado/admin.shtml
@@ -0,0 +1,191 @@
+<!--#set var="banner" value="Artemis - Chado Admin/Developer Documentation"-->
+<!--#include virtual="/perl/header"-->
+
+
+<p>
+This page describes some of the development background and admin of
+using and setting up an Artemis and ACT connection with a Chado database.
+
+<ul>
+<li><a href="#DATABASEMANAGER">Opening the Database Manager</a>
+<li><a href="#OPENART">Opening the main Artemis/ACT window</a>
+<li><a href="#CONFIG">Option File Configuration</a>
+<li><a href="#GENEBUILDER">Opening the Standalone Gene Builder</a>
+</ul>
+
+
+
+<h3><a name="DATABASEMANAGER">Opening the Database Manager</a></h3>
+<p>
+The Artemis Database Manager is cached between sessions in the directory
+'.artemis/cache' in the users home directory. There is an option under the File menu
+to clear this cache.
+<p>
+To open the Artemis Database Manager panel (from which the browser is launched),
+Artemis looks initially for the existence of the cvterm.name = 'top_level_seq' which
+belongs to cv.name = 'genedb_misc'. If these exist it follows method A:
+
+<ol type="A">
+<li>
+-call 'getTopLevelOrganisms' (in Organism.xml mapping). This relies on the the source
+features (e.g. chromosome) having a featureprop with a type_id corresponding to 'top_level_seq'.
+<p>
+If the 'top_level_seq' is not implemented in the database it then follows method B:
+<p>
+</li>
+
+<li>
+-call 'getOrganismsContainingSrcFeatures' (in Organism.xml mapping). This searches for those
+organisms that contain sequences with residues and have a type_id that
+corresponds to a cvterm name that matches:
+<p>
+*chromosome*, *sequence*, supercontig, ultra_scaffold, golden_path_region, or contig
+<p>
+</li>
+</ol>
+When the organisms with the source feature have been identified these are displayed. When
+a user clicks on an organism it opens that node and finds the types (e.g. chromosome, contig) of source
+features and the underlying features that have residues (getResidueFeatures in Feature.xml).
+<p>
+<h3><a name="OPENART">Opening the main Artemis/ACT window</a></h3>
+<p>
+The organismprop's are loaded lazily when a sequence is opened. If an organismprop
+is of type 'translationTable' the value of the organismprop is then used as the
+translation table when Artemis opens a sequence from that organism.
+<p>
+When a sequence is double clicked to open it in Artemis, most things for that sequence
+are read from the database. The iBatis statement calls made when reading an entry are
+summarised below.
+<p>
+<table border=1 cellspacing=1 cellpadding=2>
+<tr><td>Statement ID</td><td>SQL Mapping File</td><td>Description</td></tr>
+<tr><td>getFeature </td><td>(Feature.xml)</td>
+ <td>Retrieves all the features and their featureloc's, featureprop's, feature_relationship's and primary dbxref</td></tr>
+<tr><td>getFeatureDbXRefsBySrcFeature </td><td>(FeatureDbXRef.xml)</td>
+ <td>Retrieves all secondary dbxref's</td></tr>
+<tr><td>getFeatureSynonymsBySrcFeature </td><td>(FeatureSynonym.xml)</td>
+ <td>Retrieves feature synonyms</td></tr>
+<tr><td>getFeatureCvTermsBySrcFeature </td><td>(FeatureCvTerm.xml)</td>
+ <td>Retrieves feature_cvterm's, feature_cvtermprop (evidence code, extra qualifiers, date).</td></tr>
+<tr><td>getFeatureCvTermDbXRefBySrcFeature </td><td>(FeatureCvTermDbXRef.xml)</td>
+ <td>Retrieves feature_cvterm_dbxref (WITH/FROM column).</td></tr>
+<tr><td>getFeatureCvTermPubBySrcFeature </td><td>(FeatureCvTermPub.xml)</td>
+ <td>Retrieves feature_cvterm_pub's.</td></tr>
+</table>
+<p>
+Artemis constructs an internal GFF3 stream from these calls for the selected sequence.
+This is then read in the same way as a GFF3 file as an Artemis DatabaseDocumentEntry
+(which extends GFFDocumentEntry) and creating GFFStreamFeatures.
+<p>
+If the lazy load option is selected from the Database Manager's File menu, then only
+getFeature is called. The resulting GFFStreamFeature object is marked as lazy loading
+and FeatureDbXRefs, FeatureSynonyms, FeatureCvTerms, FeatureCvTermDbXRefs
+and FeatureCvTermPubs are read from the database for a feature when the Gene Builder is opened.
+<p>
+The feature_relationship (from getFeature) is used to create the gene hierarchy; 'part_of'a
+and 'derives_from' relationships become Parent and Derives_from in GFF3 terms. If the
+feature_relationship type_id does not correspond to one of these terms (derives_from,
+part_of, proper_part_of, partof, producedby) then the object_id is recorded as a qualifer
+value. This is used to read orthologous_to and paralogous_to relations. The qualifier
+values for these are lazily stored (as ClusterLazyQualifierValue.java). When Artemis
+displays these qualifiers in the Gene Builder it then queries the database further to
+list the related genes.
+<p>
+Other properties that have a featureloc association with a feature
+are found by calling getLazySimilarityMatches (Feature.xml). Artemis then
+constructs lazy loading qualifiers (QualifierLazyLoading.java) from this that query
+the database further only when that qualifier is needed. This is used for
+blast/fasta similarity and polypeptide_domains.
+
+<p>
+The gene hierarchy is stored internally by the ChadoCanonicalGene.java object and is based
+on the Parent/Derives_from relationships. It stores the related children of the gene.
+The spliced features (exon, pseudogenic_exon) are combined into a single Artemis
+Feature. The joined exons become an Artemis CDS feature (GFFStreamFeature), which stores
+the uniquenames of the original exons in the database.
+
+<p>
+<h3><a name="CONFIG">Artemis Chado Configuration</a></h3>
+
+This is an example extract from the Artemis options file for the chado related options:
+
+<p>
+<pre style="color: #0000FF;">
+#
+# CHADO DATABASE OPTIONS
+#
+# chado gene model features default types
+chado_exon_model=CDS
+#chado_transcript=transcript
+
+# infer CDS and UTR features from gene model
+chado_infer_CDS_UTR=no
+
+# provide a list of available servers
+chado_servers = \
+ workshop localhost:10101/workshop?user \
+ GeneDB db.genedb.org:5432/snapshot?genedb_ro
+
+# define how product qualifiers are stored (as a cv or as a featureprop)
+product_cv=yes
+product_cvname = genedb_products
+# cv containing synonym names
+synonym_cvname = genedb_synonym_type
+
+# set default delete behaviour to make things obsolete, if
+# this is not provided the default is to permanently delete
+set_obsolete_on_delete=yes
+
+# list of features to record residues for in the database
+# - these are included when inserting or updating their featurelocs
+sequence_update_features = polypeptide mRNA rRNA tRNA snRNA snoRNA
+</pre>
+
+<p>
+Artemis combines the exons stored in chado and describes it as a 'CDS'
+feature by default. The <b>chado_exon_model</b> flag in the options file
+allows this to be changed.
+<p>
+When a gene model is created in Artemis it creates the transcript as a 'mRNA'
+feature by default. The <b>chado_transcript</b> flag in the options file allows this
+to be changed.
+<p>
+For Artemis the default gene model representation is described in the <a href="overview.shtml#GENE">
+overview</a>. In this representation the UTRs are explicitly created in the database.
+However the gmod loader (gmod_bulk_load_gff3.pl) does not create the UTRs and they can
+be inferred from the exon and protein features. If the gmod loader is used then Artemis
+can infer the CDS and UTR features by setting <b>chado_infer_CDS_UTR=yes</b> in the
+options file. Adjusting the polypeptide boundaries in the Gene Builder will result in the generation
+or deletion of UTRs.
+
+<p>
+A list of available databases can be configured in the options file with the <b>chado_servers</b> flag.
+For each database an alias is given followed by its location (host:port/database?user), each alias
+is displayed in a drop down menu in the login box.
+<p>
+If product qualifiers are stored as an ontology (in cvterm) then set <b>product_cv=yes</b>
+and set <b>product_cvname</b> is set to the name of the controlled vocabulary (cv) used in chado.
+<p>
+When features are deleted in Artemis the default behaviour can be set to make these
+features obsolete rather than permanently delete them from the database.
+
+<a name="GENEBUILDER"></a><h3>Opening the Standalone Gene Builder</h3>
+<p>
+The Gene Builder can be launched on its own without opening up Artemis. The following
+opens up a window which lets you type in a gene name to be opened:
+
+ <pre>
+ java -mx500m -Dibatis -Dchado="localhost:5432/database?" \
+ -Djdbc.drivers=org.postgresql.Driver -classpath artemis.jar \
+ uk.ac.sanger.artemis.components.genebuilder.GeneEdit
+ </pre>
+
+Alternatively the gene name can be given as an argument:
+ <pre>
+ java -mx500m -Dibatis -Dchado="db.genedb.org:5432/snapshot?genedb_ro" \
+ -Djdbc.drivers=org.postgresql.Driver -Dshow_log -Dread_only \
+ -classpath jar_build/artemis.jar:etc
+ uk.ac.sanger.artemis.components.genebuilder.GeneEdit PFA0010c
+ </pre>
+
+
diff --git a/docs/chado/chado_gene_model.gif b/docs/chado/chado_gene_model.gif
new file mode 100644
index 0000000..9ccfc4a
Binary files /dev/null and b/docs/chado/chado_gene_model.gif differ
diff --git a/docs/chado/commit.gif b/docs/chado/commit.gif
new file mode 100644
index 0000000..3f9b18a
Binary files /dev/null and b/docs/chado/commit.gif differ
diff --git a/docs/chado/databasemanager.gif b/docs/chado/databasemanager.gif
new file mode 100644
index 0000000..a43aa27
Binary files /dev/null and b/docs/chado/databasemanager.gif differ
diff --git a/docs/chado/databasemanager_example.gif b/docs/chado/databasemanager_example.gif
new file mode 100644
index 0000000..02264cf
Binary files /dev/null and b/docs/chado/databasemanager_example.gif differ
diff --git a/docs/chado/dbloading.shtml b/docs/chado/dbloading.shtml
new file mode 100644
index 0000000..cddec7d
--- /dev/null
+++ b/docs/chado/dbloading.shtml
@@ -0,0 +1,93 @@
+<!--#set var="banner" value="Artemis - Chado : Database Loading"-->
+<!--#include virtual="/perl/header"-->
+
+These notes are intended only as a guide and describes an installation carried out on
+a MacOSX (10.5.6). For details on Chado installation see <a href="http://gmod.org">
+gmod.org</a>.
+<ol>
+<li><b>Download and Install Postgres.</b>
+<pre>
+Download postgresql-8.3.5.
+
+./configure --prefix=/Users/tjc/gmod/pgsl --with-pgport=2432 --with-includes=/Developer
+make
+make install
+
+cd /Users/tjc/gmod/pgsl
+bin/initdb -D data/
+
+Added the line to data/postgresql.conf:
+listen_addresses = 'localhost'
+
+bin/postmaster -D data &
+bin/createuser --createdb tim
+bin/createlang plpgsql template1
+bin/createdb --port=2432 test
+</pre>
+</li>
+<li><b>Download and Install Chado.</b> See also the <a href="http://gmod.org/wiki/Chado">
+Chado documentation</a>.
+
+<pre>
+Download stable release (gmod-1.0.tar.gz)
+Install BioPerl (http://www.bioperl.org/wiki/Installing_Bioperl_for_Unix)
+Install go-perl http://search.cpan.org/~cmungall/go-perl/
+
+Install Bundle::GMOD from cpan
+
+setenv GMOD_ROOT /usr/local/gmod
+setenv CHADO_DB_NAME test
+setenv CHADO_DB_USERNAME tim
+setenv CHADO_DB_PORT 2432
+
+Chado installation:
+perl Makefile.PL
+make
+sudo make install
+make load_schema
+make prepdb
+make ontologies
+
+</pre>
+</li>
+<li><b>Examples of Loading Sequences into the Database.</b>
+<p><b>NC_001142:</b><p>
+You may find the following loading fails and you need to insert 'processed_transcript'
+as a sequence ontology cvterm. Or change them to 'mature_transcript' in the gff file
+(e.g. sed s'|processed_transcript|mature_transcript|' NC_001142.gff).
+
+<pre>
+cat NC_001142.gbk | perl /usr/local/bin/bp_genbank2gff3.pl -noCDS -in stdin -out stdout > NC_001142.gff
+cat NC_001142.gff | perl /usr/local/bin/gmod_bulk_load_gff3.pl -dbname test -organism "Saccharomyces cerevisiae" -dbuser tim -dbport 2432 -dbpass dd -recreate_cache
+</pre>
+<p><b>NC_008783:</b>
+<p>
+Add this to the organism table:
+<pre>
+INSERT INTO organism
+ (abbreviation, genus, species, common_name, organism_id) VALUES
+ ('B.bacilliformis', 'Bartonella', 'bacilliformis', 'BB', 8783);
+
+cat NC_008783.gbk | perl /usr/local/bin/bp_genbank2gff3.pl -noCDS -in stdin -out stdout > NC_008783.gff
+cat NC_008783.gff | perl /usr/local/bin/gmod_bulk_load_gff3.pl -dbname test -organism "BB" -dbuser tim -dbport 2432 -dbpass dd -recreate_cache
+
+</pre>
+
+Artemis does not (by default) look for sequences that are loaded as a 'region'.
+So change the sequence type of NC_001142 from region to a chromosome:
+<pre>
+UPDATE feature SET
+ type_id=(select cvterm_id from cvterm where cv_id=(
+ select cv_id from cv where name='sequence') and name='chromosome')
+WHERE uniquename='NC_008783';
+</pre>
+</li>
+<li><b>Run Artemis.</b>
+<pre>
+./art -Dchado="localhost:2432/test?tim" -Dibatis \
+ -Djdbc.drivers=org.postgresql.Driver
+</pre>
+<p>If this is successful then the login window will appear and the
+database manager will then open:</p>
+<img src="databasemanager_example.gif" align="middle" alt="databasemanager_example"/>
+
diff --git a/docs/chado/editor.gif b/docs/chado/editor.gif
new file mode 100644
index 0000000..20bcfdb
Binary files /dev/null and b/docs/chado/editor.gif differ
diff --git a/docs/chado/genebuilder.gif b/docs/chado/genebuilder.gif
new file mode 100644
index 0000000..7224809
Binary files /dev/null and b/docs/chado/genebuilder.gif differ
diff --git a/docs/chado/genebuilder2.gif b/docs/chado/genebuilder2.gif
new file mode 100644
index 0000000..0ee1ea6
Binary files /dev/null and b/docs/chado/genebuilder2.gif differ
diff --git a/docs/chado/index.shtml b/docs/chado/index.shtml
new file mode 100644
index 0000000..9d30abd
--- /dev/null
+++ b/docs/chado/index.shtml
@@ -0,0 +1,30 @@
+<!--#set var="banner" value="Artemis Connecting to Chado Databases"-->
+<!--#include virtual="/perl/header"-->
+
+ <a href="http://www.sanger.ac.uk/Software/Artemis/"><b>Artemis</b></a> and
+ <a href="http://www.sanger.ac.uk/Software/ACT/"><b>ACT</b></a> can be used
+ to connect to <a href="http://www.gmod.org/"><b>Chado</b></a> databases.
+ They are being developed to read from and write to the database and perform the
+ same functions as the standard Artemis and ACT.
+
+ <p>
+ An example read-only database can be found
+ <a href="http://www.sanger.ac.uk/Software/Artemis/databases/">here</a>.
+ <p>
+ <h3>Documentation</h3>
+ <p>
+ <ul>
+ <li><a href="overview.shtml">Overview Document</a></li>
+ <li><a href="admin.shtml">Admin Document</a></li>
+ <li><a href="storage.html">Data Storage</a> (for the Pathogen Group)</li>
+ <li><a href="dbloading.shtml">Data Loading Examples</a></li>
+ <li><a href="writedb_entry.html">Exporting from the command-line using writedb_entry</a></li>
+ </ul>
+
+<p>
+
+<!--#set var="pubmed_tabulate" value="yes"-->
+<!--#set var="pubmed_ids" value="18845581"-->
+<!--#include virtual="/perl/utils/pubmedalizer"-->
+
+
diff --git a/docs/chado/login.gif b/docs/chado/login.gif
new file mode 100644
index 0000000..8cd352e
Binary files /dev/null and b/docs/chado/login.gif differ
diff --git a/docs/chado/logviewer.gif b/docs/chado/logviewer.gif
new file mode 100644
index 0000000..e2619a2
Binary files /dev/null and b/docs/chado/logviewer.gif differ
diff --git a/docs/chado/overview.shtml b/docs/chado/overview.shtml
new file mode 100644
index 0000000..1cc42c8
--- /dev/null
+++ b/docs/chado/overview.shtml
@@ -0,0 +1,324 @@
+<!--#set var="banner" value="Artemis - Chado Overview"-->
+<!--#include virtual="/perl/header"-->
+
+ <p>This overview covers:
+
+<ul>
+<li><a href="#CONNECT">Connecting to a Chado Database</a>
+<li><a href="#READ">Reading From the Database</a>
+<li><a href="#IBATIS">iBatis Database Mapping</a>
+<li><a href="#GENE">Gene Representation</a>
+<li><a href="#GENEBUILDING">Gene Building</a>
+<li><a href="#MERGE+SPLIT">Gene merging and splitting</a>
+<li><a href="#TAT">Transfer Annotation Tool</a>
+<li><a href="#WRITE">Writing To The Database</a>
+<li><a href="#GENEBUILDER">Opening the Standalone Gene Builder</a>
+<li><a href="#COMMUNITY+ANNOTATION">Community Annotation</a>
+<li><a href="#WRITING-SEQUENCE-FILES">Writing Out Sequence Files</a>
+</ul>
+ <a NAME="CONNECT"></a><h2>Connecting to a Chado Database</h2>
+ The following java flags are used when running Artemis when connecting to a
+ database. These options currently are all needed.
+ <ol>
+ <li><b><pre>-Dchado</pre></b>
+ this is used to get Artemis to look for the database.
+ The address of the database (hostname, port and name) can be conveniently
+ included as follows:
+ <br><pre>-Dchado="hostname:port/test?username"</pre>
+ these details are then the default database address in the popup login window. A
+ list of available databases can be provided in the Artemis <a href="admin.shtml#CONFIG">options file</a>
+ these are presented in a drop down list in the login window.
+ <br><br>
+ <img src="login.gif" align="middle" alt="login"/>
+ <br>
+ </li>
+ <li><b><pre>-Djdbc.drivers=org.postgresql</pre></b>
+ this is used to define the <a href="http://jdbc.postgresql.org/">
+ JDBC postgres driver
+ </a>.
+ </li>
+ <li><b><pre>-Dibatis</pre></b>
+ use the <a href="http://ibatis.apache.org/" title="iBatis">iBATIS</a>
+ Data Mapper
+ </li>
+ </ol>
+ <br>
+ So the command line will look something like this example:
+ <pre> ./art -Dchado="localhost:2996/test?tjc" -Dibatis \
+ -Djdbc.drivers=org.postgresql.Driver</pre>
+
+For a read only connection -Dread_only is specified on the command line:
+ <pre> ./art -Dchado="db.genedb.org:5432/snapshot?genedb_ro" -Dibatis \
+ -Djdbc.drivers=org.postgresql.Driver -Dread_only</pre>
+
+ <a NAME="READ"></a><h2>Reading From the Database</h2>
+ On a successful login a database and file manager window will open up.
+ The database manager will display "Database Loading...". The organisms
+ in the database with residues are shown in a expandable tree. Double
+ clicking on the sequence names opens them up in Artemis. Alternatively
+ a gene name or chromosome name came be typed into the text field at the
+ top and the Open button used to launch Artemis.
+ <br><br>
+ <img src="databasemanager.gif"/>
+ <p>
+ A sequence can be opened in Artemis from the command line (without going
+ through the database manager). This is done by supplying a command line argument
+ with the organism and chromosome (or source feature):
+ <br><pre>Pfalciparum:Pf3D7_09</pre>
+ and optionally a range can be included to just display features within it:
+ <br><pre>Pfalciparum:Pf3D7_09:92000..112000</pre>
+ this could be used in combination with the <i>-Doffset=base</i> flag (<i>e.g.
+ -Doffset=10000</i>) to open Artemis at a particular section of a sequence
+
+ <p>
+ To reduce the number of transactions to the database, all of the sequence is
+ read into Artemis. This includes most of the feature qualifiers. There are some
+ qualifiers (ortho/paralog and similarity qualifiers) that lazily load their data
+ as and when it is needed, <i>i.e.</i> when opened for viewing in the gene builder.
+ This lazy loading improves the performance of reading data from the database
+ for sequences with a large number of features.
+
+
+ <a NAME="IBATIS"></a><h2>iBatis Database Mapping</h2>
+ <a href="http://ibatis.apache.org/" title="iBatis">iBatis</a> data mapper
+ framework has been used to facilitate the communication with the database
+ from Artemis. It uses XML descriptors to couple the SQL statements with the
+ Java objects that Artemis understands. The XML maps are in the '<i>artemis_sqlmap</i>'
+ in the Artemis distribution. These are divided up into files based on the
+ Chado table names.
+ <p>
+ The SQL statements can be seen in the Artemis <a href=
+ "http://www.sanger.ac.uk/Software/Artemis/manual/launch-window.html#LAUNCH-WINDOW-OPTIONS-SHOW-LOG">
+ Log Viewer</a> window:<br>
+ <img src="logviewer.gif" width="100%"/>
+ <br><br>This is mainly useful for debugging and tracking problems with reading
+ from and writing to the database. Artemis uses
+ <a href="http://logging.apache.org/log4j/">log4j</a> to produce logging
+ and the configuration file for this is in the file '<i>etc/log4j.properties</i>'.
+
+ <a NAME="GENE"></a><h2>Gene Representation</h2>
+ Below is an illustration of how the feature are stored in Chado
+ in the Sanger PSU.
+ <p>
+ <center><b><i>Gene Model</i></b>
+ <br>
+ <img src="chado_gene_model.gif"/></center>
+ <p>The names (in red) are the internal database uniquenames. These names are
+ automatically generated by the gene builder from an ID provided by the
+ user. <i>N.B.</i> in our data model UTRs are represented as distinct from
+ exons.
+ <p>
+ For the scenario where both the CDS and UTRs are not stored and their positions can be
+ inferred from the exon and polypeptide features set <i>chado_infer_CDS_UTR=yes</i> in the
+ Artemis <a href="admin.shtml#CONFIG">options file</a>. Adjusting the polypeptide boundaries
+ in the Gene Builder will then result in the generation or deletion of UTRs.
+ <a NAME="GENEBUILDING"></a><h3>Gene Building</h3>
+ A gene can be created in Artemis (or ACT) by highlighting a base range and selecting
+ from the '<i>Create</i>' menu the '<i>Gene Model From Base Range</i>' option.
+ This prompts for a unique ID and this corresponds to the names in the above
+ gene model representation. The basic constituent features are created; <i>i.e.</i>
+ gene, transcript, CDS and polypeptide. <i>N.B.</i> Artemis joins the exon
+ features and represents them as a CDS feature. These are shown on the frame
+ lines in the feature display window.
+ <p>
+ A gene builder for a selected gene feature can be opened from the '<i>Edit</i>' menu
+ by selecting the '<i>Selected Feature in Editor</i>' option or simply using the '<i>E</i>'
+ shortcut key.
+ <center><p><b><i>The Artemis Gene Builder</i></b><br>
+ <img src="editor.gif"></center>
+ <p>There are two distinct parts to the gene builder window. The top part shows
+ the <b><i>gene hierarchy and structure</i></b>. The bottom part shows the
+ <b><i>annotation</i></b> associated with one of the constituent features.
+ These two parts of the gene builder are described below.
+ <ol>
+ <li><b>Gene Hierarchy and Structure</b><br>
+ The top left hand side is a tree structure of the gene model. To the right
+ of this is a graphical representation of the features. A feature can be selected
+ from either the tree or the graphical view. The annotation for the selected
+ feature is displayed in the bottom part of the gene builder.
+ <p>Structural changes can be carried out in the graphical view. The feature ends
+ can be dragged to adjust their coordinates. On right clicking on this area there
+ is a popup menu for adding and deleting features in the gene model.
+ <center><p><b><i>Editing the Gene Model In the Gene Builder</i></b><br>
+ <img src="genebuilder2.gif" border="1"></center>
+ <p>
+ Additional transcripts can be added from here. The checkbox to the right of
+ the above CDS is used to hide and show the associated CDS in the Artemis
+ feature display. This can make structural edits clearer for multiple transcripts.
+ <p>
+ <li><b>Annotation</b><br>
+ There are 4 (Properties, Core, Controlled Vocabulary and Match) sections in
+ the annotation part of the gene builder. These are described below. These can
+ be viewed in a scrollable view or in a tabbed view. There is a check box at
+ the bottom of the gene builder to change between these views.
+ <p>
+ <ul>
+ <li><b>Properties</b><br>
+ This contains properties such as the synonyms and time last modified. Also
+ the ID and Name of the feature are given here, for a gene feature this is
+ used as a systematic identifier and a primary name.
+ Synonyms are added as a controlled vocabulary (these are in
+ a cv named '<i>genedb_synonym_type</i>'). The 'is obsolete' check box is
+ used to indicate if this is an obsolete feature. The feature is then marked
+ as obsolete in the database. Artemis (by default) does not show obsolete
+ features in the feature display and they are shown greyed out in the
+ feature list.
+
+ <center><p><b><i>Properties section</i></b><br>
+ <img src="Properties.gif" border="1"></center>
+
+ </li><p>
+ <li><b>Core</b><br>
+ The core annotation contains any other annotation that does not fit into the
+ other sections. <i>E.g.</i> comments, literature, Dbxref. Hyperlinks are
+ provided for SWALL, EMBL, UniProt, PMID, PubMed, InterPro and Pfam, and opening
+ up a local browser.
+ </li><p>
+ <li><b>Controlled Vocabulary (CV)</b><br>
+ The CV module in Chado is concerned with controlled vocabularies or ontologies.
+ Therefore, Chado can use the biological ontologies and this makes it very
+ expressive.
+ <p>
+ This section in the gene builder provides a form for adding and deleting GO,
+ controlled curation, product, Riley class annotation. CV terms are added by
+ clicking the 'ADD' button. When adding a term to a feature the user is
+ prompted for the CV name and then keyword. The term to be added is then
+ selected from a drop down list of terms containing the word or phrase.
+ To further assist in finding the CV term from the list, typing in the
+ text will start to autocomplete and scroll to the first matching term.
+
+ <center><p><b><i>CV section</i></b><br>
+ <img src="CV.gif" border="1"></center>
+
+ <p>GO terms are selected from molecular_function, biological_process
+ or cellular_component CV's.
+
+ <p>Products are stored in Chado as a CV (<i>i.e.</i> in cvterm in
+ a cv named '<i>genedb_products</i>'). They can be given evidence codes
+ and have associated WITH/FROM and Dbxref columns.
+
+ <p>Other generic controlled curations can be found by Artemis and shown
+ if their CV name in Chado is prefixed with '<i>CC_</i>' (<i>e.g.</i>
+ CC_controlledcuration, CC_workshop). These then appear in a drop down
+ list when adding CV terms to a feature.
+
+ <p>Adding new terms to the database can also be done from this section.
+ In the drop down selection of CV's there is an 'Add term...' option.
+ This opens an input panel for new terms.
+ <center><p><b><i>Adding a new CV term</i></b><br>
+ <img src="addterm.gif"></center>
+ </li><p>
+ <li><b>Match</b><br>
+ This section allows the user to add ortholog/paralog links to other genes
+ in the database.
+ <p>
+ The ortholog/paralog tables provide links for opening the gene editor or
+ an Artemis window for each entry. The '<i>VIEW</i>' button opens a
+ separate Artemis displaying the gene ortholog or paralog and the
+ surrounding features.
+ <p>
+ In addition similarity qualifiers can be added here from matches to
+ blast and fasta searches carried out in Artemis. These are added
+ from the Artemis Object Editor.
+ </li>
+ </ul>
+ </ol>
+
+ <a NAME="MERGE+SPLIT"></a><h3>Gene merging and splitting</h3>
+
+ To merge gene models, select the CDS segments that are to be merged. Then use
+ the menu option:
+ <p>
+ <pre>Edit->Selected Feature(s)->Merge</pre>
+ <p>
+ The annotation and names from the segment first selected are maintained and
+ the CDS features from the second gene model are added to the first selected gene model.
+ The second gene model is deleted automatically.
+ <p>
+ To unmerge (split) the gene model into two gene models consecutive segments
+ in the CDS are selected. This is done by clicking on the first segment and
+ then pressing SHIFT and clicking on the second segment. Then use the menu option:
+ <p>
+ <pre>Edit->Selected Feature(s)->Unmerge</pre>
+ <p>
+ On unmerging the annotation and synonyms are maintained in both gene models.
+ The second gene model component features are given a new internal ID (uniquename)
+ based on the original and prefixed with DUP1-.
+
+
+ <a NAME="TAT"></a><h3>Transfer Annotation Tool</h3>
+ The Transfer Annotation Tool (TAT) within Artemis can be used to transfer annotation
+ between features within an EMBL file or features within the same Chado database. It
+ is opened by clicking on the "TAT" button in the Feature Editor or, in database mode,
+ the Gene Builder.
+
+ <center><p><b><i>Transferring Annotation</i></b><br>
+ <img src="TAT.gif"></center>
+
+ <p>
+ The left hand column shows the list of orthologous genes. Features can be added to
+ this by clicking the "Add" button and pasting their systematic ID in. Multiple genes
+ can be added by separating their names with spaces or line returns. The genes to
+ transfer annotation to can be selected or de-selected. The toggle button above the gene
+ list will toggle the selection.
+
+ <p>
+ On the right hand side are the qualifiers associated with the feature and any other
+ features in the gene model. The qualifiers to be transferred are selected from this
+ list. The '+' button expands to show the list of values associated with that qualifier
+ from which those to be transferred can be selected.
+
+ <p>
+ Qualifiers on different features of the gene model that are selected will be transferred
+ to the equivalent feature type in the genes they will be added to.
+
+ <p>
+ The default behavior is for qualifiers to be appended to any existing qualifiers.
+ However if the 'Overwrite' check box is selected at the bottom of the tool then existing
+ qualifiers of the same type will get deleted and it will add in the new qualifiers.
+
+ <a NAME="WRITE"></a><h3>Writing To The Database</h3>
+ When a feature or qualifier is changed, added or deleted the '<i>Commit</i>' button (on
+ the top tool bar) changes colour to red. Changes in Artemis only get written back to the
+ database when this button is clicked.
+ <center><p><b><i>Commit Button</i></b><br>
+ <img src="commit.gif" border="1"></center>
+ <p>
+ There is also an option under the '<i>File</i>' menu to '<i>Commit To
+ Database</i>'. Note in ACT there is no commit button and the '<i>Commit To
+ Database</i>' menu option is used to write back to the database.
+ <p>
+ If there is an error during the commit then Artemis will provide the option to
+ force commit. This means it will commit what it can. Naturally this can be potentially
+ problematic. Therefore, <b>committing back to the database frequently is encouraged</b>.
+ Any errors are reported in the log viewer.
+
+ <a NAME="COMMUNITY+ANNOTATION"></a><h3>Community Annotation</h3>
+ Multiple users can launch Artemis and query the database. This has been stress
+ tested and used in the malaria re-annotation exercise with 30+ Artemis clients
+ connecting to the database.
+ <p>
+ Artemis records the time a features was last modified (<i>timelastmodified</i>). Before
+ changing a feature it will check this time stamp against the database record of the
+ <i>timelastmodified</i>.
+ If the corresponding feature in the database has changed by another user it will
+ ask whether to continue with the commit process.
+
+ <a NAME="WRITING-SEQUENCE-FILES"></a><h3>Writing Out Sequence Files</h3>
+ Artemis can write out EMBL and GFF files from the database. An option is given to
+ optionally flatten the gene model to just a CDS feature. Also an option is given to ignore
+ any obsolete features. For EMBL it uses mappings for conversion of the keys and qualifiers.
+ These mappings are stored in the 'etc/key_mapping' and 'etc/qualifier_mapping'
+ files.
+ <p>
+ A script (etc/writedb_entry) is also provided as a means of writing out multiple
+ sequences from the database. The script takes the following options:
+ <pre>
+-h show help
+-f [y|n] flatten the gene model, default is y
+-i [y|n] ignore obsolete features, default is y
+-s space separated list of sequences to read and write out
+-o [EMBL|GFF] output format, default is EMBL
+-a [y|n] for EMBL submission format change to n, default is y
+ </pre>
+
diff --git a/docs/chado/prompt1.png b/docs/chado/prompt1.png
new file mode 100644
index 0000000..cb3b9d4
Binary files /dev/null and b/docs/chado/prompt1.png differ
diff --git a/docs/chado/prompt2.png b/docs/chado/prompt2.png
new file mode 100644
index 0000000..343e612
Binary files /dev/null and b/docs/chado/prompt2.png differ
diff --git a/docs/chado/storage.html b/docs/chado/storage.html
new file mode 100644
index 0000000..8e8fdfd
--- /dev/null
+++ b/docs/chado/storage.html
@@ -0,0 +1,296 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" dir="ltr" lang="en"><head>
+
+
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="KEYWORDS" content="Chado Data Storage">
+<meta name="robots" content="index,follow">
+<title>Chado Data Storage - SangerWiki</title>
+
+</head><body class="ns-0">
+ <div id="globalWrapper">
+ <div id="column-content">
+ <div id="content">
+ <a name="top" id="top"></a>
+ <h1 class="firstHeading">Chado Data Storage</h1>
+ <div id="bodyContent">
+ <div id="contentSub"></div>
+ <!-- start content -->
+ <table id="toc" class="toc"><tbody><tr><td><div id="toctitle"><h2>Contents</h2> </div>
+<ul>
+<li class="toclevel-1"><a href="#Chado_Canonical_Gene"><span class="tocnumber">1</span> <span class="toctext">Chado Canonical Gene</span></a></li>
+<li class="toclevel-1"><a href="#Chado_Pseudogene"><span class="tocnumber">2</span> <span class="toctext">Chado Pseudogene</span></a></li>
+<li class="toclevel-1"><a href="#Gene_Model"><span class="tocnumber">3</span> <span class="toctext">Gene Model</span></a></li>
+<li class="toclevel-1"><a href="#Qualifier_Storage"><span class="tocnumber">4</span> <span class="toctext">Qualifier Storage</span></a>
+<ul>
+<li class="toclevel-2"><a href="#Note"><span class="tocnumber">4.1</span> <span class="toctext">Note</span></a></li>
+<li class="toclevel-2"><a href="#codon_start"><span class="tocnumber">4.2</span> <span class="toctext">codon_start</span></a></li>
+<li class="toclevel-2"><a href="#Similarity"><span class="tocnumber">4.3</span> <span class="toctext">Similarity</span></a></li>
+<li class="toclevel-2"><a href="#Controlled_Vocabulary_Qualifiers"><span class="tocnumber">4.4</span> <span class="toctext">Controlled Vocabulary Qualifiers</span></a>
+<ul>
+<li class="toclevel-3"><a href="#GO"><span class="tocnumber">4.4.1</span> <span class="toctext">GO</span></a></li>
+<li class="toclevel-3"><a href="#controlled_curation"><span class="tocnumber">4.4.2</span> <span class="toctext">controlled_curation</span></a></li>
+<li class="toclevel-3"><a href="#product"><span class="tocnumber">4.4.3</span> <span class="toctext">product</span></a></li>
+<li class="toclevel-3"><a href="#class_.28Riley_classification.29"><span class="tocnumber">4.4.4</span> <span class="toctext">class (Riley classification)</span></a></li>
+</ul>
+</li>
+<li class="toclevel-2"><a href="#Dbxref"><span class="tocnumber">4.5</span> <span class="toctext">Dbxref</span></a></li>
+<li class="toclevel-2"><a href="#EC_number"><span class="tocnumber">4.6</span> <span class="toctext">EC_number</span></a></li>
+<li class="toclevel-2"><a href="#literature"><span class="tocnumber">4.7</span> <span class="toctext">literature</span></a></li>
+<li class="toclevel-2"><a href="#Search_and_Results_files"><span class="tocnumber">4.8</span> <span class="toctext">Search and Results files</span></a>
+<ul>
+<li class="toclevel-3"><a href="#.2Fblast_file"><span class="tocnumber">4.8.1</span> <span class="toctext">/blast_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Fblastn_file"><span class="tocnumber">4.8.2</span> <span class="toctext">/blastn_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Fblastp.2Bgo_file"><span class="tocnumber">4.8.3</span> <span class="toctext">/blastp+go_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Fblastp_file"><span class="tocnumber">4.8.4</span> <span class="toctext">/blastp_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Fblastx_file"><span class="tocnumber">4.8.5</span> <span class="toctext">/blastx_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Ffasta_file"><span class="tocnumber">4.8.6</span> <span class="toctext">/fasta_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Ffastx_file"><span class="tocnumber">4.8.7</span> <span class="toctext">/fastx_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Ftblastn_file"><span class="tocnumber">4.8.8</span> <span class="toctext">/tblastn_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Ftblastx_file"><span class="tocnumber">4.8.9</span> <span class="toctext">/tblastx_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Fclustalx_file"><span class="tocnumber">4.8.10</span> <span class="toctext">/clustalx_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Fsigcleave_file"><span class="tocnumber">4.8.11</span> <span class="toctext">/sigcleave_file</span></a></li>
+<li class="toclevel-3"><a href="#.2Fpepstats_file"><span class="tocnumber">4.8.12</span> <span class="toctext">/pepstats_file</span></a></li>
+</ul>
+</li>
+<li class="toclevel-2"><a href="#Synonyms"><span class="tocnumber">4.9</span> <span class="toctext">Synonyms</span></a>
+<ul>
+<li class="toclevel-3"><a href="#.2Freserved_name"><span class="tocnumber">4.9.1</span> <span class="toctext">/reserved_name</span></a></li>
+<li class="toclevel-3"><a href="#.2Fsynonym"><span class="tocnumber">4.9.2</span> <span class="toctext">/synonym</span></a></li>
+<li class="toclevel-3"><a href="#.2Fprimary_name"><span class="tocnumber">4.9.3</span> <span class="toctext">/primary_name</span></a></li>
+<li class="toclevel-3"><a href="#.2Fprotein_name"><span class="tocnumber">4.9.4</span> <span class="toctext">/protein_name</span></a></li>
+<li class="toclevel-3"><a href="#.2Fsystematic_id"><span class="tocnumber">4.9.5</span> <span class="toctext">/systematic_id</span></a></li>
+<li class="toclevel-3"><a href="#.2Ftemporary_systematic_id"><span class="tocnumber">4.9.6</span> <span class="toctext">/temporary_systematic_id</span></a></li>
+</ul>
+</li>
+<li class="toclevel-2"><a href="#colour"><span class="tocnumber">4.10</span> <span class="toctext">colour</span></a></li>
+<li class="toclevel-2"><a href="#ortholog.2Fparalog.2Fcluster"><span class="tocnumber">4.11</span> <span class="toctext">ortholog/paralog/cluster</span></a></li>
+</ul>
+</li>
+</ul>
+</td></tr></tbody></table>
+<p><script type="text/javascript"> if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } </script>
+</p>
+<a name="Chado_Canonical_Gene"></a><h2> Chado Canonical Gene </h2>
+<pre>gene
+|
+|- part_of mRNA
+ |
+ |---- part_of exon
+ |
+ |---- derives_from polypeptide
+</pre>
+<a name="Chado_Pseudogene"></a><h2> Chado Pseudogene </h2>
+<pre>pseudogene
+|
+|- part_of pseudogenic_transcript
+ |
+ |---- part_of pseudogenic_exon
+ |
+ |---- derives_from polypeptide
+</pre>
+<a name="Gene_Model"></a><h2> Gene Model </h2>
+<p><a href="Chado_gene_model.gif" class="image" title="Image:Chado_gene_model.gif"><img src="Chado_gene_model.gif" alt="Image:Chado_gene_model.gif" height="540" width="720"></a>
+</p>
+<a name="Qualifier_Storage"></a><h2> Qualifier Storage </h2>
+<a name="Note"></a><h3> Note </h3>
+<p>-stored as a FeatureProp with CvTerm = comment
+</p>
+<a name="codon_start"></a><h3> codon_start </h3>
+<p>This is loaded as phase in the FeatureLoc table.
+</p><p>phase = 0 => codon_start = 1;<br>
+phase = 1 => codon_start = 2;<br>
+phase = 2 => codon_start = 3
+</p>
+<a name="Similarity"></a><h3> Similarity </h3>
+<p><i>e.g.</i>: /similarity="fasta; SWALL:O85168
+(EMBL:AF047828);Pseudomonas syringae; syringomycin synthetase;
+syrE;length 9376 aa; id=31.93%; ungapped id=35.04%;E()=1.5e-105; ;
+6198 aa overlap; query 36-6020 aa; subject 2593-8452 aa"
+</p>
+<pre> analysis ------ fasta
+ |
+ |
+ |
+ analysisfeature ---- raw score (null), evalue, id
+ |
+ |
+ | |---featureprop---ungapped id (35.04)
+ | |
+ Matchfeature ---|
+ / | |
+ / | |---featureprop---overlap (6198)
+rank=0 / |
+(srcfeature_id=product / |
+FeatureId) / |
+(subject 2593-8452)featureloc featureloc (query 36-6020) srcfeature_id=queryFeatureId rank=1
+ | |
+ | |
+ featuredbxref | |
+ (AF04782) | |
+ \ | |
+ \ | |
+ \ | |
+ \| |
+(dbxref=O85168) | |
+(seqlen=9376)feature feature (polypeptide if protein match | transcript if nucleotide match)
+ /|\
+ / | \
+ / | \
+ / | \
+ / | \
+ / | \
+ / featureprop \
+ featureprop | featureprop
+ | | |
+ | product |
+ | (syringomycin s) |
+ | |
+ | |
+ organism gene(syrE)
+(Pseudomonas syringae)
+
+</pre>
+<p>N.B. For now the match feature is entered as CvTerm = '<b>region'</b>.
+The Cv '<b>genedb_misc'</b> is used for Cvterms like 'ungapped id' found in /similarity.
+</p>
+<a name="Controlled_Vocabulary_Qualifiers"></a><h3> Controlled Vocabulary Qualifiers </h3>
+<p>These qualifiers are all FeatureCvTerm's.
+</p>
+<a name="GO"></a><h4> GO </h4>
+<p><br>
+/GO="aspect=;GOid=;term=;qualifer=;evidence='db_xref=;with=;date="
+</p>
+<p>GO annotation can be attached at different levels of the heirarchy.
+The GeneDB loader attaches it by default to the polypeptide as that
+seems to be the most typical case.
+</p>
+<p>Each GO entry has a CvTerm and a DbXRef associated with it. The GO
+term should be looked up by its DbXRef i.e. GO:123456, to get the
+correspontding CvTerm. A FeatureCvTerm links this CvTerm to the
+Feature. The FeatureCvTerm may well exist so needs to be looked up. The
+qualifier NOT is treated specially, as a field in FeatureCvTerm,
+becauses it reverses the meaning of the assignment, rather than adding
+more details as most qualifiers do. The FeatureCvTerm may have a number
+of associated FeatureCvTermProp's. This is general storage for the GO
+evidence code, extra qualifiers and the date of the assignment. (A hack
+for the evidence code would be possible, using a CvTerm to represent
+key and evidence code, but it wouldn't work for the date). One or more
+FeatureCvTermDbXRef's can be associated with the FeatureCvTerm which
+corresponds to the WITH/FROM column in GO. The dbxref value in this
+case correspond to publications, so the primary Pub is linked to the
+FeatureCvTerm.pub_id. One or more FeatureCvTermPub's can be associated
+with the FeatureCvTerm which corresponds to any ID's after the pipe
+symbol in the publication column.
+</p>
+<a name="controlled_curation"></a><h4> controlled_curation </h4>
+<p>/controlled_curation="term=;cv=; qualifier=;evidence=;db_xref=;residue=; attribution=;date="
+</p><p>Storage is similar to GO. The db_xref is stored either as a FeatureCvTermDbXref or a FeatureCvTermPub:
+</p>
+<ol><li> if the value is a PMID:12345 then it is stored in the pub
+table. a dummy dbxref is created with the 'accession' = 12345. a
+pubdbxref is created to link the pub with the dbxref.
+</li><li> if the value is other database like UNIPROT:23456 then it is
+stored in the dbxref table with accession=23456 and an entry is also
+created in the feature_cvterm_dbxref table to link the featurecvterm
+and the dbxref
+</li></ol>
+<a name="product"></a><h4> product </h4>
+<p>This is stored as a FeatureCvTerm with a CvTerm from the '<b>genedb_products'</b> Cv.
+</p>
+<a name="class_.28Riley_classification.29"></a><h4> class (Riley classification) </h4>
+<p><i>e.g.</i> /class=6.2.2
+</p>
+<p>
+These are linked to the Feature as below:
+
+</p><pre> Feature
+ |
+ FeatureCvTerm--name='anti sigma factor'
+ |
+ CvTerm
+ |
+ -----------
+ | |
+ RILEY--Cv DbXRef--accession=6.2.2
+ |
+ Db-name=RILEY
+</pre>
+
+<a name="Dbxref"></a><h3> Dbxref </h3>
+<p>- stored as a FeatureDbXRef
+</p>
+<a name="EC_number"></a><h3> EC_number </h3>
+<p>- stored as a FeatureProp
+</p>
+<a name="literature"></a><h3> literature </h3>
+<p>- stored as FeaturePub
+</p>
+<a name="Search_and_Results_files"></a><h3> Search and Results files </h3>
+<p>The following are stored as FeatureProp's:<br>
+</p>
+<a name=".2Fblast_file"></a><h4> /blast_file </h4>
+<a name=".2Fblastn_file"></a><h4> /blastn_file </h4>
+<a name=".2Fblastp.2Bgo_file"></a><h4> /blastp+go_file </h4>
+<a name=".2Fblastp_file"></a><h4> /blastp_file </h4>
+<a name=".2Fblastx_file"></a><h4> /blastx_file </h4>
+<a name=".2Ffasta_file"></a><h4> /fasta_file </h4>
+<a name=".2Ffastx_file"></a><h4> /fastx_file </h4>
+<a name=".2Ftblastn_file"></a><h4> /tblastn_file </h4>
+<a name=".2Ftblastx_file"></a><h4> /tblastx_file </h4>
+<a name=".2Fclustalx_file"></a><h4> /clustalx_file </h4>
+<a name=".2Fsigcleave_file"></a><h4> /sigcleave_file </h4>
+<a name=".2Fpepstats_file"></a><h4> /pepstats_file </h4>
+<a name="Synonyms"></a><h3> Synonyms </h3>
+<p>The following qualifiers are loaded in the Synonym table:<br>
+</p>
+<a name=".2Freserved_name"></a><h4> /reserved_name </h4>
+<a name=".2Fsynonym"></a><h4> /synonym </h4>
+<a name=".2Fprimary_name"></a><h4> /primary_name </h4>
+<a name=".2Fprotein_name"></a><h4> /protein_name </h4>
+<a name=".2Fsystematic_id"></a><h4> /systematic_id </h4>
+<a name=".2Ftemporary_systematic_id"></a><h4> /temporary_systematic_id </h4>
+<p>and these are linked to the Feature via FeatureSynonym's. These
+Synonym's are in the '<b>genedb_synonym_type'</b> Cv table.
+FeatureSynonym.is_current is used to store previous/obsolete
+synonyms.
+</p>
+<a name="colour"></a><h3> colour </h3>
+<p>Presumably a FeatureProp (at least for now). Additional qualifiers
+are being preposed status (containing information about functional
+annotation and whether the annotation is manual or automatic) and
+evidence. These are likely to be FeatureProp's.
+</p>
+<a name="ortholog.2Fparalog.2Fcluster"></a><h3> ortholog/paralog/cluster </h3>
+<p>Orthologue/paralogues cluster are stored in a similar way to <a href="#Similarity" title="">/similarity</a>.
+As input we have:
+<br>a) manually curated orthologues which simply list other genes'
+systematic ids and the relationship type
+<br>b) auto-generated clusters of genes which also have associated data like clustering method, cut-off/score etc.
+<br>However, they features are linked to each other by
+feature_relationship's (rather than featureloc which are used with
+/similarity). The feature_relationship's are given the type_id =
+'orthologous_to' or 'paralogous_to'. For manually curated
+ortholog/paralog data the analysisfeature and analysis are not required
+and are not added.
+</p>
+<pre>analysis
+ |
+ + analysisfeature
+ |
+ feature (type_id == protein_match)
+ |
+ +----+-------+------------+
+ | | |
+ | | |
+feature1 feature2 feature3
+gene gene gene
+
+</pre>
+<p>The bottom links are feature_relationships of SO type orthologous_to.
+</p>
+
+ <div id="footer">
+ </div>
+ </div>
+ </body></html>
diff --git a/docs/chado/writedb_entry.html b/docs/chado/writedb_entry.html
new file mode 100644
index 0000000..dd6a9cb
--- /dev/null
+++ b/docs/chado/writedb_entry.html
@@ -0,0 +1,137 @@
+<style>
+
+ body {
+ background:#ffffff;
+ color:#003366;
+ font-family:sans-serif;
+ }
+
+ img.displayed {
+ display: block;
+ margin-left: auto;
+ margin-right: auto
+ }
+
+ div.imageLegend {
+ margin:auto;
+ width:400px;
+ text-align:center;
+ color:#990000;
+ font-style:italic;
+ }
+
+
+ code {
+ background:#9999aa;
+ color:white;
+ margin:10px;
+ padding:10px;
+ font-weight:bold;
+ display:block;
+ white-space: pre;
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+ }
+
+</style>
+
+<h3>writedb_entry - exports EMBL or GFF3 files from Chado.</h3>
+
+<h3>Synopsys</h3>
+<P>
+writedb_entry is a wrapper script around Artemis's ReadAndWrite entry functions, which allows export to EMBL and GFF3 formats. It can be run in 3 ways.
+<P></P>
+The default method is the 'swing' method (because that's the original behaviour), which assumes that you are running it either on your own machine or on a server with X11 forwarding setup. Any errors or login prompts will popup in a graphical user interface as windows. In this mode, if it is not on your machine or no X11 is available, then any attempts to prompt will result in exceptions.
+<P></P>
+The 'console' mode is for running interactively on the shell without the need for a graphical user interface. All user-prompting (like password entry) is done inside the shell. Useful for one-offs where there is no X11 forwarding.
+<P></P>
+The 'script' mode is for batch runs where you don't want any user prompting. Passwords must be supplied as a command line parameter if needed in this case.
+</P>
+<h3>Command line parameters</h3>
+
+<dl>
+ <dt>-f</dt> <dd>[y|n] flatten the gene model, default is y</dd>
+
+ <dt>-i</dt> <dd>[y|n] ignore obsolete features, default is y</dd>
+
+ <dt>-s</dt> <dd>space separated list of sequences to read and write out</dd>
+
+ <dt>-o</dt> <dd>[EMBL|GFF] output format, default is EMBL</dd>
+
+ <dt>-a</dt> <dd>[y|n] for EMBL submission format change to n, default is y</dd>
+
+ <dt>-pp</dt> <dd>[y|n] read polypeptide domain features, default is n</dd>
+
+ <dt>-c</dt> <dd>the URL for your Chado database e.g. db.genedb.org:5432/snapshot?genedb_ro (you can also do this by using the -Dchado=... method, but this -c parameter will override that) </dd>
+
+ <dt>-u</dt> <dd>[swing|console|script] the UI mode : run in swing (with popup dialog boxes) mode, run in console mode (choices entered in the console window), or in script mode (all choices default to continue, all parameters passed on command line) </dd>
+
+ <dt>-p</dt> <dd>the password for connecting to the Chado database (this is required in script mode, where passwords can not be passed interactively)</dd>
+
+ <dt>-fp</dt> <dd>the file path (the folder you want to save the files in)</dd>
+
+</dl>
+<h3>Examples</h3>
+
+
+<h4>1. Using in swing mode. </h4>
+
+All prompting would then happen via graphical dialog boxes. In this example I am running this locally on my desktop machine.
+
+<CODE>$ /Volumes/us/data/gv1/workspace/artemis/etc/writedb_entry -c "db.genedb.org:5432/snapshot?genedb_ro" -o GFF -s Tcruzi_999
+
+read :: Tcruzi_999 write :: Tcruzi_999.gff
+reading options from "/Users/gv1/.artemis_options"
+done
+</CODE>
+
+<img class="displayed" src="prompt1.png">
+<div class="imageLegend">A prompt for login details.</div>
+<img class="displayed" src="prompt2.png">
+<div class="imageLegend">A prompt detailing a problem.</div>
+
+
+
+
+
+
+<h4>2. Using interactively in console mode : </h4>
+
+All prompting would then happen on the console. In this example I am running this on a server.
+
+<CODE>$ writedb_entry -c "db.genedb.org:5432/snapshot?genedb_ro" -o GFF -u console -s Tcruzi_999
+
+read :: Tcruzi_999 write :: Tcruzi_999.gff
+Enter Password:
+Destination format can't handle all keys/qualifiers - continue?
+region can't have isObsolete as a qualifier(y/n): y
+done
+
+</CODE>
+
+
+
+
+
+
+
+<h4>3. Using in script mode : </h4>
+
+In these examples I am running them on a server. Note that there is no password needed as it's going to the public snapshot.
+
+<CODE>$ writedb_entry -c "db.genedb.org:5432/snapshot?genedb_ro" -o GFF -u script -s Tcruzi_999
+
+read :: Tcruzi_999 write :: Tcruzi_999.gff
+Desti
+</CODE>
+
+If there was a password needed, you would have to enter it as follows.
+
+<CODE>$ writedb_entry -c "db.genedb.org:5432/snapshot?genedb_ro" -o GFF -u script -p mypass -s Tcruzi_999
+
+read :: Tcruzi_999 write :: Tcruzi_999.gff
+Destination format can't handle all keys/qualifiers - continue? : region can't have isObsolete as a qualifier : y
+done
+
+</CODE>
+
diff --git a/docs/concepts.sgml b/docs/concepts.sgml
new file mode 100644
index 0000000..0dd5a02
--- /dev/null
+++ b/docs/concepts.sgml
@@ -0,0 +1,150 @@
+ <SECT1 ID="CONCEPTS">
+ <TITLE>Concepts</TITLE>
+ <PARA>
+Here are some general concepts about &prog; that may make the rest of this
+manual clearer.
+ </PARA>
+
+ <SECT2 ID="CONCEPTS-ENTRY">
+ <TITLE>The "Entry"</TITLE>
+ <PARA>
+An "entry" in &prog;-speak is not necessarily a complete EMBL or GENBANK entry.
+In most places in this manual when we refer to an entry we mean a file that
+contains just the <ULINK
+URL="http://www.ebi.ac.uk/embl/Documentation/FT_definitions/feature_table.html">feature
+table</ULINK> lines (the <LITERAL>FT</LITERAL> lines) of an
+EMBL/GENBANK entry (see <XREF LINKEND="CONCEPTS-TABFILE">). After loading a
+sequence and opening an entry edit window (see <XREF
+LINKEND="LAUNCH-WINDOW-FILE-MENU-OPEN">) it is then possible to overlay many
+feature tables
+(see <XREF LINKEND="FILEMENU-READ-AN-ENTRY">). Each of these
+feature table files is called an entry by &prog; and it's features are kept
+separate from those of other entries.
+ </PARA>
+ <PARA>
+This meaning of the word "entry" is
+used by most of the items in the File menu (see <XREF LINKEND="FILEMENU">) and
+by the items in the Entry menu (see <XREF LINKEND="ENTRIESMENU">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CONCEPTS-FEATURE">
+ <TITLE>EMBL/Genbank Features</TITLE>
+ <PARA>
+A "feature" in an EMBL or Genbank file is a region of DNA that has been
+annotated with a key/type (see <XREF LINKEND="CONCEPTS-KEY">) and zero or more
+qualifiers (see <XREF LINKEND="CONCEPTS-QUALIFIERS">). In an EMBL or Genbank
+formated file the features of a piece of DNA are listed in the feature table
+section (see <XREF LINKEND="CONCEPTS-TABFILE">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CONCEPTS-KEY">
+ <TITLE>EMBL/Genbank Feature Keys</TITLE>
+ <PARA>
+All EMBL and Genbank features have exactly one "key" assigned to them. The
+key is the type of the feature. Examples include <LITERAL>CDS</LITERAL> (a
+<LITERAL>C</LITERAL>o<LITERAL>D</LITERAL>ing <LITERAL>S</LITERAL>equence),
+<LITERAL>intron</LITERAL> and <LITERAL>misc_feature</LITERAL>
+(<LITERAL>MISC</LITERAL>ellaneous <LITERAL>feature</LITERAL>).
+ </PARA>
+ <PARA>
+The <ULINK URL="http://www.ebi.ac.uk">EBI</ULINK> has a list of <ULINK
+URL="http://www.ebi.ac.uk/embl/Documentation/FT_definitions/feature_table.html">all possible feature keys</ULINK>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CONCEPTS-QUALIFIERS">
+ <TITLE>EMBL/Genbank Feature Qualifiers</TITLE>
+ <PARA>
+The qualifiers of a feature in an EMBL or Genbank file are the notes and extra
+information about the feature. For example an <LITERAL>exon</LITERAL> feature
+might have a <LITERAL>/gene="ratC"</LITERAL> qualifier, meaning that the exon
+feature is part of a gene named <LITERAL>ratC</LITERAL>.
+ </PARA>
+ <PARA>
+The <ULINK URL="http://www.ebi.ac.uk">EBI</ULINK> has a list of <ULINK
+URL="http://www.ebi.ac.uk/embl/Documentation/FT_definitions/feature_table.html">all possible feature qualifiers</ULINK>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CONCEPTS-TABFILE">
+ <TITLE>"Tab" Files or "Table" Files</TITLE>
+ <PARA>
+An EMBL or Genbank file that only contains a feature table (just
+<LITERAL>FT</LITERAL> lines, no sequence or
+header lines) is called a "table" file, or sometimes just a "tab" file because
+the often has a name like "somefile.tab".
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CONCEPTS-ACTIVEENTRY">
+ <TITLE>The Active Entries</TITLE>
+ <PARA>
+All entries in &prog; are considered to be "active" or "inactive". The
+overview, DNA view and feature list parts of the main window will only display
+features from active entries. To find out how to set the active and inactive
+entries see <XREF LINKEND="ENTRIESMENU">.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CONCEPTS-DEFAULTENTRY">
+ <TITLE>The Default Entry</TITLE>
+ <PARA>
+Many actions (such as creating features) require an
+entry to be identified as the source or destination for the action. Some
+actions, such as "Move Selected Features To ..." in the edit menu, will
+explicitly ask for an entry, but some assume that the action refers to a
+"default entry" that was previously set by the user.
+ </PARA>
+ <PARA>
+The default entry can be set by using the "Set Default Entry ..." menu item in
+the Entries menu (see <XREF LINKEND="ENTRIESMENU-SET-DEFAULT-ENTRY">)<![
+%artemis-only; [ or by using the entry buttons (see <XREF
+LINKEND="ENTRYBUTTONS">)]]>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CONCEPTS-SEGMENT">
+ <TITLE>Feature Segments</TITLE>
+ <PARA>
+The term "segment" in the context of a CDS feature means
+"exon". We use the term "segment", because non-CDS features (such as
+misc_feature) can have exon-like parts too, but the term "exon" would be
+inappropriate in that case.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CONCEPTS-SELECTION">
+ <TITLE>The Selection</TITLE>
+ <PARA>
+In common with applications like word processors and graphics programs, &prog;
+allows the user to "select" the objects that the program will act on. The
+objects to act on in &prog; are features, feature segments or bases. If a
+feature segment is added to the selection, the feature that contains the
+segment is implicitly added as well. The current selection can be changed
+with the Select Menu (see <XREF LINKEND="SELECTMENU">) or using the mouse (see
+<XREF LINKEND="VIEWS-SELECTION"><![ %artemis-only; [ and <XREF
+LINKEND="FEATURELIST-SELECTION">]]>).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CONCEPTS-COLOUR">
+ <TITLE>Feature Colours</TITLE>
+ <PARA>
+Each feature displayed in &prog; can be given a colour. The available colours
+are set in the options file (see <XREF LINKEND="OPTIONS-CHAPT">) and are
+assigned to a feature by adding a <LITERAL>/colour</LITERAL> qualifier (see
+<XREF LINKEND="EDITMENU-EDIT-SELECTED-FEATURES">). Currently there are two
+ways of specifying feature colours. The first way uses a single number. For
+example red is colour 2, so adding <LITERAL>/colour=2</LITERAL> as a feature
+qualifier will make that feature red. The second way is to specify the red,
+green and blue components of the colour. Each of the components can take
+values from 0 to 255, with 255 being the most intense. For example
+<LITERAL>/colour=255 0 0</LITERAL> is another way to give a feature the colour
+red. If no <LITERAL>/colour</LITERAL> qualifier is set for a feature a
+default colour is used (the default colours are also specified in the options
+file).
+ </PARA>
+ </SECT2>
+ </SECT1>
diff --git a/docs/copyright.sgml b/docs/copyright.sgml
new file mode 100644
index 0000000..acc87f5
--- /dev/null
+++ b/docs/copyright.sgml
@@ -0,0 +1,284 @@
+ <SECT1 ID="COPYRIGHT">
+ <TITLE>Copyright Notice</TITLE>
+ <SYNOPSIS>
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+ </SYNOPSIS>
+ </SECT1>
diff --git a/docs/display_menu.sgml b/docs/display_menu.sgml
new file mode 100644
index 0000000..a2c5f0a
--- /dev/null
+++ b/docs/display_menu.sgml
@@ -0,0 +1,32 @@
+<SECT1 ID="DISPLAYMENU">
+ <TITLE>The Display Menu</TITLE>
+ <PARA>
+The buttons in this menu are toggles controlling which parts of the
+&prog; display are visible. All the buttons default to on/active.
+ </PARA>
+
+ <SECT2 ID="DISPLAYMENU-SHOW-ENTRY-BUTTONS">
+ <TITLE>Show Entry Buttons</TITLE>
+ <PARA>
+This toggle controls whether the entry button line is visible. (see <XREF
+LINKEND="ENTRYBUTTONS">). [Default: on]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-SHOW-BASE-VIEW">
+ <TITLE>Show Base View</TITLE>
+ <PARA>
+This toggle controls whether the base view is visible. (see <XREF
+LINKEND="VIEWS">). [Default: on]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-SHOW-FEATURE-LIST">
+ <TITLE>Show Feature List</TITLE>
+ <PARA>
+This toggle controls whether the feature view is visible. (see <XREF
+LINKEND="FEATURELIST">).
+[Default: on]
+ </PARA>
+ </SECT2>
+</SECT1>
diff --git a/docs/display_menu_common.sgml b/docs/display_menu_common.sgml
new file mode 100644
index 0000000..18b98e7
--- /dev/null
+++ b/docs/display_menu_common.sgml
@@ -0,0 +1,160 @@
+ <SECT2 ID="DISPLAYMENU-GC-CONTENT">
+ <TITLE>GC Content (%)</TITLE>
+ <PARA>
+Controls whether the GC content plot is visible. This is a graph of the
+average GC content of a moving window (default size 120 base), across the
+bases visible in the overview window.
+[Default: off] [short name: <LITERAL>gc_content</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-GCSD-CONTENT">
+ <TITLE>GC Content (%) With 2.5 SD Cutoff</TITLE>
+ <PARA>
+Controls whether the cutoff GC content plot is visible. This is similar to
+the GC content graph, but the plot is clipped so that the GC content of each
+algorithm window is shown only when it is more than 2.5 times the standard
+deviation of the GC content in all the windows.
+[Default: off] [short name: <LITERAL>sd_gc_content</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-AG-CONTENT">
+ <TITLE>AG Content (%)</TITLE>
+ <PARA>
+Controls whether the AG content plot is visible. This is a graph of the
+average AG content of a moving window (default size 120 base), across the
+bases visible in the overview window.
+[Default: off] [short name: <LITERAL>ag_content</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-GC-FRAME-PLOT">
+ <TITLE>GC Frame Plot</TITLE>
+ <PARA>
+Controls whether the GC frame plot is visible. This graph is similar to the
+GC content graph but shows the GC content of the first, second and third
+position independently. For more information on the algorithm and on how to
+interpret the result see <ULINK
+URL="http://www.nih.go.jp/~jun/cgi-bin/frameplot.pl" TYPE="external">this web
+page</ULINK>.
+[Default: off] [short name: <LITERAL>gc_frame</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-CORRELATION-SCORES">
+ <TITLE>Correlation Scores</TITLE>
+ <PARA>
+Controls whether the (forward) correlation scores plot is visible. The graph
+shows the correlation between the amino acid composition of the globular
+proteins in TREMBL and the composition of the base translation in each of the
+three reading frames. The green line represents forward frame 1, blue
+represents frame 2 and red represents frame 3.
+[Default: off] [short name: <LITERAL>correlation_score</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-REVERSE-CORRELATION-SCORES">
+ <TITLE>Reverse Correlation Scores</TITLE>
+ <PARA>
+This does the same as "Correlation Scores", but does the calculation on the
+reverse strand. The green line represents reverse frame 1 (the bottom frame
+line), blue represents frame 2 and red represents frame 3.
+[Default: off] [short name: <LITERAL>correlation_score</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-GC-DEVIATION">
+ <TITLE>GC Deviation (G-C)/(G+C)</TITLE>
+ <PARA>
+Controls whether the GC deviation plot is visible. This graph shows the
+difference between the "G" content of the forward strand and the reverse
+strand.
+[Default: off] [short name: <LITERAL>gc_deviation</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-AT-DEVIATION">
+ <TITLE>AT Deviation (A-T)/(A+T)</TITLE>
+ <PARA>
+Controls whether the AT deviation plot is visible. This graph shows the
+difference between the "A" content of the forward strand and the reverse
+strand.
+[Default: off] [short name: <LITERAL>at_deviation</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-KARLINSIG">
+ <TITLE>Karlin Signature Difference</TITLE>
+ <PARA>
+This menu item toggles the display of the graph of the dinucleotide absolute
+relative abundance difference between the whole sequence and a sliding window.
+ </PARA>
+ <PARA>
+For details of the algorithm see "Global dinucleotide signatures and analysis
+of genomic heterogeneity" Samuel Karlin - <ULINK
+URL="http://www.ncbi.nlm.nih.gov:80/entrez/query.fcgi?cmd=Retrieve&db=PubMed&list_uids=10066522&dopt=Abstract">Current
+Opinion in Microbiology 1998, 1:598-610</ULINK>.
+[Default: off] [short name: <LITERAL>karlin_sig</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-CUMULATIVEAT">
+ <TITLE>Cumulative AT Skew, (A-T)/(A+T) and Cumulative GC Skew, (G-C)/(G+C)</TITLE>
+ <PARA>
+ Grigoriev A (1999) Strand-specific compositional asymmetries in
+ double-stranded DNA viruses. Virus Research 60, 1-19.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-POSITIONALASYM">
+ <TITLE>Positional Asymmetry</TITLE>
+ <PARA>
+ Shulman MJ, Steinberg CM, Westmoreland N (1981) The coding function of
+ nucleotide sequences can be discerned by statistical analysis. J Theor Biol
+ 88:409-20
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-ENTROPY">
+ <TITLE>Informational Entropy</TITLE>
+ <PARA>
+ Konopka A (1984) Is the information content of DNA evolutionarily
+ significant? J Theor Biol 107:697-704
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-SCALEDCHISQ">
+ <TITLE>Scaled Chi Square</TITLE>
+ <PARA>
+ Shields DC, Sharp PM (1987) Synonymous codon usage in Bacillus subtilis
+ reflects both translational selection and mutational biases. Nucleic Acids
+ Res 15:8023-40
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-MUTRES">
+ <TITLE>Mutational Response Index</TITLE>
+ <PARA>
+ Gatherer D, McEwan NR (1997) Small regions of preferential codon usage and
+ their effect on overall codon bias--the case of the plp gene. Biochem Mol
+ Biol Int 43:107-14
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-NC">
+ <TITLE>Effective Codon Number</TITLE>
+ <PARA>
+ Wright F (1990) The 'effective number of codons' used in a gene. Gene 87:23-9
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-INTRINSIC">
+ <TITLE>Intrinsic Codon Deviation Index</TITLE>
+ <PARA>
+ Freire-Picos MA, Gonzalez-Siso MI, Rodriguez-Belmonte E, Rodriguez-Torres
+ AM, Ramil E, Cerdan ME (1994) Codon usage in Kluyveromyces lactis and in
+ yeast cytochrome c-encoding genes. Gene 139:43-9
+ </PARA>
+ </SECT2>
+
diff --git a/docs/feature_edit.eps b/docs/feature_edit.eps
new file mode 100644
index 0000000..c3108a9
--- /dev/null
+++ b/docs/feature_edit.eps
@@ -0,0 +1,27141 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: /nfs/team81/kmr/powmap/docs/feature_edit.eps
+%%Creator: XV Version 3.10a Rev: 12/29/94 - by John Bradley
+%%BoundingBox: 86 318 526 473
+%%Pages: 1
+%%DocumentFonts:
+%%EndComments
+%%EndProlog
+
+%%Page: 1 1
+
+% remember original state
+/origstate save def
+
+% build a temporary dictionary
+20 dict begin
+
+% define string to hold a scanline's worth of data
+/pix 2868 string def
+
+% define space for color conversions
+/grays 956 string def % space for gray scale line
+/npixls 0 def
+/rgbindx 0 def
+
+% lower left corner
+86 318 translate
+
+% size of image (on paper, in 1/72inch coords)
+439.77600 155.44800 scale
+
+% define 'colorimage' if it isn't defined
+% ('colortogray' and 'mergeprocs' come from xwd2ps
+% via xgrab)
+/colorimage where % do we know about 'colorimage'?
+ { pop } % yes: pop off the 'dict' returned
+ { % no: define one
+ /colortogray { % define an RGB->I function
+ /rgbdata exch store % call input 'rgbdata'
+ rgbdata length 3 idiv
+ /npixls exch store
+ /rgbindx 0 store
+ 0 1 npixls 1 sub {
+ grays exch
+ rgbdata rgbindx get 20 mul % Red
+ rgbdata rgbindx 1 add get 32 mul % Green
+ rgbdata rgbindx 2 add get 12 mul % Blue
+ add add 64 idiv % I = .5G + .31R + .18B
+ put
+ /rgbindx rgbindx 3 add store
+ } for
+ grays 0 npixls getinterval
+ } bind def
+
+ % Utility procedure for colorimage operator.
+ % This procedure takes two procedures off the
+ % stack and merges them into a single procedure.
+
+ /mergeprocs { % def
+ dup length
+ 3 -1 roll
+ dup
+ length
+ dup
+ 5 1 roll
+ 3 -1 roll
+ add
+ array cvx
+ dup
+ 3 -1 roll
+ 0 exch
+ putinterval
+ dup
+ 4 2 roll
+ putinterval
+ } bind def
+
+ /colorimage { % def
+ pop pop % remove 'false 3' operands
+ {colortogray} mergeprocs
+ image
+ } bind def
+ } ifelse % end of 'false' case
+
+
+
+956 338 8 % dimensions of data
+[956 0 0 -338 0 338] % mapping matrix
+{currentfile pix readhexstring pop}
+false 3 colorimage
+
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c020c0c020c0c020c0c020c0c020c0c020c0c020c0
+c020c0c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c020002000c0
+c020c0c02000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000c020002000c0
+c020c0c02000200000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000200000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000200000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000200000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000200000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000200000c020002000c0
+c020c0c02000200000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000200000200000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000200000200000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000200000200000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000200000200000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000200000200000c020002000c0
+c020c0c02000200000c02000c02000802000802000c02000802000802000802000c02000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000ffffffffffff802000802000802000
+802000802000802000802000802000ffffff802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000ffffff802000802000802000
+802000802000802000802000802000802000802000802000802000802000ffffffffffff
+ffffffffffff802000802000802000802000802000802000802000802000802000802000
+802000802000ffffff802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000ffffffffffffffffffffffff802000802000802000802000ffffff802000
+802000ffffff802000802000802000802000ffffff802000802000802000802000ffffff
+ffffff802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000ffffff802000802000802000802000802000802000802000
+ffffffffffff802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000200000200000c02000c02000802000802000802000c02000
+c02000802000802000802000200000200000c02000c02000802000802000802000802000
+802000802000802000802000200000200000c020002000c0
+c020c0c02000200000c02000c02000802000200000802000c02000802000c02000802000
+200000200000200000c02000c02000802000802000802000802000802000802000802000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000ffffff802000802000ffffff802000802000
+802000802000802000802000802000ffffff802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000ffffff802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000ffffff802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000ffffff802000802000802000802000802000802000802000ffffff802000
+802000802000802000802000802000802000ffffff802000802000802000802000ffffff
+ffffff802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000ffffff802000802000802000802000802000802000ffffff
+802000802000ffffff802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000200000200000c02000c02000802000802000802000c02000
+200000802000802000802000200000200000c02000c02000802000802000802000802000
+802000802000802000802000200000200000c020002000c0
+c020c0c02000200000c02000c02000802000802000200000802000c02000802000200000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000ffffff802000802000ffffff802000ffffff
+802000ffffff802000802000ffffffffffffffffff802000802000802000ffffffffffff
+802000802000ffffff802000ffffff802000802000ffffffffffff802000802000802000
+ffffffffffffffffff802000802000802000802000802000802000802000ffffffffffff
+ffffff802000802000802000ffffffffffff802000802000802000ffffff802000ffffff
+802000ffffffffffffffffff802000802000ffffff802000802000ffffff802000ffffff
+802000ffffff802000802000802000ffffffffffff802000802000802000802000802000
+802000802000ffffffffffffffffff802000802000802000ffffff802000ffffff802000
+ffffffffffff802000802000802000ffffffffffffffffff802000802000802000802000
+802000802000802000802000802000802000802000802000ffffffffffffffffff802000
+802000802000ffffff802000ffffff802000802000ffffff802000ffffff802000802000
+802000802000ffffff802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000200000200000c02000c02000802000802000802000c02000
+200000802000802000802000200000200000c02000c02000802000802000c02000c02000
+c02000c02000802000802000200000200000c020002000c0
+c020c0c02000200000c02000c02000802000802000802000200000802000c02000802000
+802000200000200000c02000c02000802000c02000c02000c02000c02000c02000c02000
+200000200000200000c02000c02000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000ffffffffffffffffffffffff802000ffffff
+ffffff802000ffffff802000802000ffffff802000802000802000ffffffffffffffffff
+ffffffffffff802000ffffff802000ffffff802000802000ffffff802000802000802000
+ffffffffffff802000802000802000802000802000802000802000802000ffffff802000
+802000802000802000ffffffffffffffffffffffff802000ffffff802000ffffffffffff
+802000802000ffffff802000802000802000ffffff802000802000ffffff802000ffffff
+ffffff802000ffffff802000ffffffffffffffffffffffff802000802000802000802000
+802000802000ffffff802000802000802000802000ffffff802000ffffffffffff802000
+802000ffffff802000802000802000802000ffffff802000802000802000802000ffffff
+ffffff802000802000802000802000802000802000802000ffffff802000802000ffffff
+802000ffffff802000ffffffffffff802000ffffff802000ffffffffffff802000802000
+ffffffffffff802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000200000200000c02000c02000802000802000802000c02000
+200000802000802000802000200000200000c02000c02000802000802000802000200000
+c02000802000802000802000200000200000c020002000c0
+c020c0c02000200000c02000c02000802000802000c02000802000802000802000c02000
+802000200000200000c02000c02000802000c02000200000200000200000200000200000
+200000200000200000c02000c02000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000ffffff802000802000ffffff802000ffffff
+802000802000802000802000802000ffffff802000ffffff802000ffffff802000802000
+802000ffffff802000ffffff802000ffffff802000802000ffffff802000802000802000
+802000802000ffffff802000802000802000802000802000802000802000ffffff802000
+802000802000802000ffffff802000802000802000802000ffffff802000ffffffffffff
+802000802000ffffff802000ffffff802000ffffff802000802000ffffff802000ffffff
+802000802000802000802000ffffff802000802000802000802000802000802000802000
+802000802000ffffff802000802000802000802000ffffff802000ffffffffffff802000
+802000ffffff802000802000802000802000ffffff802000ffffff802000802000ffffff
+ffffff802000802000802000802000802000802000802000ffffff802000802000ffffff
+802000ffffff802000ffffffffffff802000ffffff802000ffffffffffff802000ffffff
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000200000200000c02000c02000802000802000802000c02000
+200000802000802000802000200000200000c02000c02000802000802000802000c02000
+200000802000802000802000200000200000c020002000c0
+c020c0c02000200000c02000c02000802000c02000802000802000200000802000802000
+200000200000200000c02000c02000802000802000802000802000802000802000802000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000ffffff802000802000ffffff802000ffffff
+802000802000802000802000802000802000ffffff802000802000802000ffffffffffff
+802000ffffff802000802000802000ffffff802000ffffffffffffffffff802000802000
+ffffffffffffffffff802000802000802000802000802000802000802000ffffff802000
+802000802000802000802000ffffffffffff802000802000802000ffffff802000ffffff
+802000802000802000ffffff802000802000802000ffffffffffffffffff802000ffffff
+802000802000802000802000802000ffffffffffff802000802000802000802000802000
+802000802000ffffffffffffffffffffffff802000802000ffffff802000ffffff802000
+ffffffffffffffffff802000802000802000802000ffffff802000802000802000802000
+802000802000802000802000802000802000802000802000ffffff802000802000ffffff
+802000802000ffffff802000ffffff802000802000ffffff802000ffffff802000ffffff
+ffffffffffffffffff802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000200000200000c02000c02000802000802000802000c02000
+200000802000802000802000200000200000c02000c02000802000802000c02000200000
+200000200000802000802000200000200000c020002000c0
+c020c0c02000200000c02000c02000802000802000200000200000802000200000200000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000200000200000c02000c02000802000802000802000c02000
+200000802000802000802000200000200000c02000c02000802000802000802000802000
+802000802000802000802000200000200000c020002000c0
+c020c0c02000200000c02000c02000802000802000200000802000802000802000200000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000200000200000c02000c02000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000802000802000802000802000802000802000802000802000
+802000802000802000802000200000200000c02000c02000802000802000802000200000
+200000802000802000802000200000200000c02000c02000802000802000802000802000
+802000802000802000802000200000200000c020002000c0
+c020c0c02000200000c02000200000200000200000200000200000200000200000200000
+200000200000200000c02000200000200000200000200000200000200000200000200000
+200000200000200000c02000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000c02000200000200000200000200000200000
+200000200000200000200000200000200000c02000200000200000200000200000200000
+200000200000200000200000200000200000c020002000c0
+c020c0c02000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000a0a080000000000000000000
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+000000000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080000000000000000000a0a080000000a0a080a0a080a0a080a0a080000000000000
+000000a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080000000
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080000000000000
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080
+000000000000000000000000000000000000a0a080a0a080a0a080a0a080000000000000
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080
+a0a080a0a080a0a080000000000000a0a080a0a080000000000000a0a080a0a080a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080000000a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080000000a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080000000000000000000a0a080000000a0a080000000000000000000000000000000
+000000a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040404040404040404040404040404040404040404040404040404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000
+000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080000000000000000000000000000000000000
+000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040404040404040404040404040404040404040404040404040ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080ffffff404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000a0a080a0a080a0a080000000000000000000a0a080a0a080000000000000000000
+a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040000000000000000000000000000000000000000000ffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080000000
+000000000000a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040000000000000000000000000000000000000000000ffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080000000000000a0a080000000000000a0a080000000000000a0a080000000000000
+a0a080000000000000a0a080000000000000a0a080a0a080a0a080000000000000000000
+000000a0a080a0a080a0a080000000000000a0a080000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000a0a080a0a080a0a080000000000000000000a0a080a0a080000000000000000000
+a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080000000000000000000000000a0a080000000000000000000000000a0a080a0a080
+a0a080000000000000000000a0a080000000000000a0a080a0a080a0a080000000000000
+000000a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080a0a080
+a0a080000000000000000000a0a080000000000000a0a080a0a080a0a080000000000000
+000000000000a0a080000000a0a080a0a080a0a080000000000000000000000000000000
+a0a080a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080
+000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080000000000000
+000000000000000000a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040000000000000000000000000000000000000000000ffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000000000a0a080000000000000a0a080000000a0a080a0a080000000
+000000a0a080000000000000a0a080000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+000000000000000000a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040000000000000000000000000000000000000000000ffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080000000a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080ffffff404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040000000000000000000000000000000000000000000ffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080000000a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+000000000000000000a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080000000000000000000000000a0a080a0a080000000000000000000000000000000
+000000a0a080a0a080a0a080a0a080000000a0a080000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040000000000000000000000000000000000000000000ffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080000000a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080000000000000
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040000000000000000000000000000000000000000000ffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+404040ffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000000000a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080000000000000000000a0a080000000000000a0a080000000000000000000000000
+000000a0a080000000000000a0a080000000000000a0a080a0a080000000000000000000
+000000a0a080a0a080a0a080000000000000000000a0a080a0a080000000000000000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080000000000000
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080404040
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffff000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000000000000000ffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffff000000000000000000ffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080000000
+a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080000000000000
+000000000000000000000000a0a080a0a080a0a080a0a080a0a080000000000000000000
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080000000000000a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080
+a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffff000000ffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000000000ffffff
+000000ffffffffffffffffff000000000000000000000000ffffffffffffffffff000000
+000000ffffff000000000000ffffff000000000000ffffff000000000000ffffff000000
+000000000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffff000000
+000000ffffff000000000000ffffff000000000000ffffffffffffffffffffffff000000
+000000000000ffffffffffffffffff000000000000ffffff000000000000000000ffffff
+ffffffffffff000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff000000
+000000ffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffff000000000000ffffff000000ffffffffffff000000000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffff000000000000ffffff000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000000000ffffffffffffffffff000000ffffff000000
+000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffff000000ffffff000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000000000000000ffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000000000000000000000000000000000000000ffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffffffffff000000000000000000
+000000000000000000000000ffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000000000000000000000000000a0a080a0a080a0a080a0a080000000000000000000
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080
+a0a080a0a080a0a080000000000000000000000000a0a080000000a0a080a0a080a0a080
+a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080000000000000000000
+000000000000a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080000000000000000000a0a080a0a080000000000000000000a0a080a0a080a0a080
+a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000000000000000000000000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffffffffff000000000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000000000000000
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff000000
+000000000000ffffff000000000000ffffff000000000000ffffff000000ffffff000000
+000000000000ffffffffffffffffffffffffffffff000000000000000000000000000000
+ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff000000
+000000000000ffffff000000000000ffffff000000000000ffffffffffffffffff000000
+000000000000000000ffffffffffff000000000000000000ffffffffffff000000000000
+000000ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffff000000000000000000000000000000ffffffffffff000000
+000000000000ffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffffffffffffffff000000000000000000ffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080000000000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000000000000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000000000000000a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+000000000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000000000000000000000a0a080a0a080a0a080000000000000a0a080
+000000000000a0a080000000000000a0a080000000000000a0a080000000000000000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080000000000000a0a080
+000000000000a0a080000000000000a0a080a0a080a0a080a0a080000000000000000000
+a0a080a0a080a0a080000000000000a0a080000000000000000000a0a080a0a080a0a080
+000000000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000000000a0a080a0a080000000000000a0a080a0a080a0a080a0a080
+000000000000000000000000a0a080a0a080a0a080a0a080000000a0a080000000000000
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000000000000000000000a0a080a0a080a0a080000000000000a0a080000000000000
+000000a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080000000000000
+a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080000000
+000000a0a080000000000000a0a080000000000000a0a080a0a080a0a080000000000000
+000000000000a0a080a0a080a0a080000000000000000000a0a080a0a080000000000000
+000000a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000000000000000a0a080
+a0a080a0a080000000000000a0a080000000000000000000a0a080a0a080a0a080a0a080
+a0a080000000000000000000a0a080000000000000a0a080a0a080a0a080a0a080000000
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+000000000000000000a0a080a0a080a0a080000000000000000000000000000000000000
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000000000
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+000000000000000000000000000000000000a0a080a0a080a0a080000000000000a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080000000000000a0a080a0a080000000
+000000a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000a0a080000000a0a080000000000000000000000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000
+000000a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080000000000000000000000000000000000000a0a080a0a080a0a080000000000000
+a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000000000a0a080a0a080
+000000000000a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000
+a0a080000000000000a0a080000000a0a080a0a080000000000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000
+a0a080000000000000a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080000000a0a080a0a080000000a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000000000a0a080000000000000a0a080000000a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080000000a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080000000a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+000000000000000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000000000000000000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000000000000000000000000000000000000000a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080000000000000000000000000000000
+000000000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000000000000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000000000000000000000000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+000000000000000000000000000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000000000000000000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080a0a080a0a080000000000000000000000000000000000000000000a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080000000a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000000000000000000000000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000000000
+000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000000000000000a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000000000000000000000
+000000000000a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000000000000000000000000000000000a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000000000000000000000000000000000000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+000000000000000000000000000000000000a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000000000000000
+000000000000000000a0a080a0a080a0a080000000000000000000000000000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000000000000000000000
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080000000a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080000000000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080000000000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000
+a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080a0a080000000000000000000000000a0a080a0a080a0a080000000000000000000
+a0a080000000000000a0a080000000000000a0a080000000a0a080000000000000000000
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080
+a0a080a0a080a0a080000000000000000000000000a0a080a0a080000000000000000000
+a0a080000000000000a0a080000000000000a0a080a0a080a0a080000000000000000000
+000000a0a080a0a080000000000000000000a0a080a0a080000000000000000000a0a080
+a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080
+000000000000000000000000a0a080000000a0a080000000000000a0a080000000000000
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080000000000000000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000000000000000000000a0a080000000a0a080000000000000000000a0a080a0a080
+000000000000000000a0a080a0a080a0a080000000000000000000a0a080000000a0a080
+a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080000000
+000000000000a0a080000000000000a0a080000000000000a0a080a0a080000000000000
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080000000000000000000000000a0a080
+000000a0a080000000000000000000a0a080a0a080000000000000000000a0a080a0a080
+a0a080000000000000000000a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000000000000000000000a0a080a0a080a0a080a0a080a0a080000000
+000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000
+000000a0a080a0a080a0a080a0a080000000000000000000000000a0a080000000a0a080
+a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080000000
+000000000000a0a080000000000000a0a080a0a080000000000000000000000000000000
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080000000a0a080000000000000
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080
+a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080a0a080
+a0a080000000000000000000000000a0a080a0a080a0a080a0a080a0a080000000000000
+000000000000a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000
+000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+000000000000a0a080a0a080a0a080a0a080000000000000000000000000a0a080000000
+a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080
+000000000000000000a0a080000000000000a0a080a0a080000000000000000000000000
+000000a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080ffffff404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080ffffff404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffff404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080ffffffffffffffffff
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080000000ffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080ffffffffffffffffff
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080ffffffffffffffffffffffff
+ffffffa0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080ffffffffffffffffffffffff
+ffffffa0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+000000000000000000ffffff000000ffffffffffffffffff000000000000000000000000
+000000000000ffffffffffff000000000000000000000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080ffffffffffffffffffffffffffffff
+ffffffffffffa0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffff000000
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080ffffffffffffffffffffffffffffff
+ffffffffffffa0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffff000000000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000ffffff000000ffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000000000000000ffffffffffffffffff000000000000ffffffffffffffffff
+000000000000ffffffffffffffffff000000ffffff000000000000000000ffffffffffff
+ffffff000000000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffff000000000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000000000ffffff000000000000000000ffffffffffff
+ffffffffffff000000ffffff000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffff000000ffffffffffff000000ffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffff000000000000
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffff000000000000ffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+000000000000000000ffffff000000ffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffff000000000000ffffff000000000000000000ffffffffffff
+ffffff000000000000000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffff000000ffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffff000000000000000000ffffff000000000000000000ffffff
+ffffffffffffffffffffffff000000000000000000ffffff000000ffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffff000000000000ffffff000000000000
+000000ffffffffffffffffff000000000000000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffff000000000000000000ffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffff000000000000000000ffffffffffffffffffffffff000000
+000000ffffffffffff000000000000ffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000ffffff000000ffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffff000000ffffff000000000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000000000000000000000000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff000000000000
+000000ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000000000ffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffff000000ffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000000000000000000000000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffff000000000000000000000000000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+000000000000000000000000000000000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000000000000000ffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffff000000000000000000000000
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000ffffff000000000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffff000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffff000000000000000000ffffffffffffffffffffffff000000000000000000000000
+ffffffffffffffffffffffff000000000000000000000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffff000000000000000000000000ffffff000000000000000000000000
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffffffffff000000000000000000ffffff000000000000ffffffffffffffffff
+000000000000000000000000ffffff000000ffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+ffffff000000000000ffffff000000000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000ffffff000000000000ffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffff000000ffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffff000000ffffff000000000000000000ffffffffffff
+ffffff000000000000000000ffffffffffff000000000000000000ffffffffffffffffff
+000000000000000000000000ffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffff000000000000000000ffffffffffff
+000000000000000000ffffffffffffffffff000000000000000000000000ffffff000000
+ffffffffffffffffff000000000000000000000000000000ffffffffffff000000000000
+000000ffffffffffff000000000000000000ffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffff000000000000000000ffffffffffff
+000000000000000000ffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffff000000
+ffffffffffffffffff000000000000000000000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+000000000000000000ffffff000000000000ffffffffffffffffffffffff000000000000
+000000000000ffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffff000000000000000000000000ffffff000000
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffff000000000000000000ffffff000000000000000000ffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffffffffff000000ffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffff000000000000000000ffffffffffffffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffff000000000000000000ffffff000000000000
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffff000000000000000000000000000000000000000000
+000000ffffff000000000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000ffffff000000000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080000000ffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000000000000000ffffff000000000000000000000000000000
+000000ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+ffffff000000ffffffffffffffffffffffffffffff000000000000000000ffffff000000
+ffffff000000000000000000ffffffffffff000000000000000000ffffffffffff000000
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000000000ffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffff000000000000000000ffffffffffff000000000000000000ffffffffffff
+ffffff000000000000000000000000000000000000000000000000ffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000000000000000
+ffffff000000000000000000000000000000000000ffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffff000000000000000000ffffffffffffffffff000000
+000000000000000000000000000000000000ffffffffffff000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000ffffff000000ffffff
+ffffffffffff000000000000000000000000000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffffffffff000000ffffffffffffffffff000000000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000000000ffffffffffff
+ffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffff000000000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffff000000000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000ffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffff000000000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffff000000000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffff000000000000ffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffff000000000000000000ffffffffffffffffff
+ffffff000000000000ffffff000000000000ffffff000000000000ffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffff000000000000ffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000ffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff000000ffffff
+000000ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffff000000ffffff000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000ffffff000000000000ffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff000000ffffff
+000000ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffffffffff000000000000000000000000000000ffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000000000000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000000000000000000000000000000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffff000000000000000000000000ffffff000000
+ffffffffffffffffff000000000000000000ffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffff000000000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffff000000
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffff000000000000000000ffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffff000000000000000000000000000000
+000000ffffffffffff000000000000000000000000ffffff000000000000000000000000
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffff000000000000000000ffffffffffff000000000000000000ffffffffffff000000
+000000000000000000ffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000ffffff000000ffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffff000000000000000000ffffff000000000000ffffff000000000000ffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffff000000000000000000000000ffffff000000
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffff000000000000000000ffffff000000ffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000ffffffffffffffffff000000000000000000ffffffffffff
+ffffff000000000000000000000000000000000000000000000000000000000000000000
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffff000000000000000000000000000000000000ffffffffffff000000000000000000
+000000ffffff000000000000000000000000ffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffff000000
+000000000000000000000000000000000000000000000000000000000000ffffff000000
+000000000000000000ffffffffffff000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff000000
+000000000000000000000000000000ffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffff000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000000000000000000000
+000000000000ffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffff000000000000000000000000
+000000000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000
+000000ffffffffffffffffff000000000000ffffffffffffffffff000000000000ffffff
+ffffffffffff000000ffffff000000000000000000ffffffffffffffffff000000000000
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffff
+ffffff000000000000ffffff000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000000000ffffff000000000000000000ffffffffffffffffffffffff000000
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffff000000ffffffffffff000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffff000000000000ffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000000000ffffff000000
+ffffffffffffffffffffffff000000000000000000ffffff000000ffffffffffff000000
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffff000000000000ffffffffffff000000000000ffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffff000000000000
+ffffff000000000000ffffff000000000000ffffff000000000000000000ffffffffffff
+000000000000000000ffffffffffffffffffffffff000000000000000000ffffff000000
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000ffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffff000000000000
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffff000000000000000000ffffff000000ffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffff000000000000ffffffffffff000000000000ffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffff000000000000
+ffffff000000000000000000ffffffffffffffffff000000000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+000000000000000000ffffff000000ffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffff000000000000ffffff000000000000000000ffffffffffff
+ffffff000000000000000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffff000000000000000000000000000000
+000000ffffffffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000000000ffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000000000
+ffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000ffffff000000000000ffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000000000
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffff000000ffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000000000000000ffffff000000ffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000000000000000000000000000000000000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000000000
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000000000000000ffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000000000ffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000ffffffffffffffffff000000000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffff000000000000000000000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000ffffff000000000000
+ffffff000000000000ffffff000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000ffffff000000000000ffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffff000000000000000000ffffffffffff000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffff000000ffffff000000000000000000ffffffffffffffffff000000000000
+000000ffffffffffff000000000000000000ffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffffffffff000000000000000000000000ffffff000000ffffffffffffffffff
+000000000000000000000000000000ffffffffffff000000000000000000ffffffffffff
+000000000000000000ffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffff000000000000
+000000ffffffffffff000000000000000000ffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffff000000000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffff000000000000
+000000ffffff000000000000ffffff000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffff000000000000000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffff000000ffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffff000000
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffff000000000000000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff000000
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+000000000000000000ffffff000000000000ffffffffffffffffffffffff000000000000
+000000000000ffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffff000000000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000000000ffffff
+ffffffffffff000000000000000000000000000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffff000000000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffff000000000000
+000000ffffffffffff000000000000000000ffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffff000000000000ffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffff000000000000ffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffff000000ffffffffffffffffff000000000000000000000000ffffffffffff
+ffffff000000000000ffffff000000000000000000ffffffffffffffffff000000000000
+000000000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffff
+ffffff000000000000ffffff000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000000000000000000000000000000000ffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffff000000000000ffffff000000000000
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffff000000000000ffffff000000000000ffffffffffffffffff
+000000000000000000000000ffffffffffffffffff000000000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000000000ffffff000000
+ffffffffffff000000ffffff000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffff000000000000ffffff000000000000
+000000ffffffffffffffffff000000000000000000000000000000000000ffffffffffff
+ffffffffffff000000000000ffffffffffff000000000000ffffffffffff000000000000
+000000ffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000000000000000000000ffffffffffff
+ffffff000000000000ffffffffffffffffff000000000000ffffffffffffffffff000000
+ffffff000000000000000000ffffffffffffffffff000000000000ffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffff000000000000
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffff000000000000ffffffffffffffffff
+000000000000ffffffffffffffffff000000ffffff000000000000000000ffffffffffff
+ffffff000000000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffff000000000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000ffffff000000
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000ffffff000000000000ffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000000000
+ffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffff000000ffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000000000000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000000000000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000000000ffffffffffffffffff000000000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffff000000ffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffff000000000000000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+000000000000000000000000ffffff000000ffffffffffff000000ffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffff000000000000000000ffffffffffff000000000000000000ffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffffffffff000000000000000000000000000000ffffff
+ffffff000000000000000000ffffffffffff000000000000000000ffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000ffffff000000ffffff000000000000000000ffffff000000
+000000ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffff000000000000ffffff000000000000ffffffffffff
+000000000000000000000000ffffff000000ffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffff000000000000000000ffffffffffff000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffff000000000000000000ffffffffffff
+000000000000000000ffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000ffffff000000000000ffffff000000000000
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+ffffff000000000000ffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffff000000000000
+000000ffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff
+ffffffffffff000000000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+ffffff000000000000ffffff000000000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000ffffff000000000000ffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000000000ffffffffffff000000000000000000ffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffff000000
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffff000000000000ffffffffffffffffff000000000000ffffff
+ffffffffffff000000000000ffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffff000000ffffffffffff000000ffffffffffff000000
+000000000000000000000000000000000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000000000000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffff000000000000000000ffffff000000000000
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000000000000000ffffff000000000000
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffff000000000000
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000000000
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+ffffff000000ffffffffffffffffffffffff000000000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000000000000000000000000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000000000000000000000000000000000000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffff000000000000000000000000000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000ffffff000000ffffff
+ffffffffffffffffffffffff000000000000000000000000ffffffffffff000000000000
+000000ffffffffffff000000000000000000ffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000ffffffffffff000000000000000000ffffffffffffffffff000000000000000000
+ffffff000000000000ffffffffffffffffff000000000000000000000000ffffff000000
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffa0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080ffffffffffffffffffffffffffffff
+ffffffffffffa0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080ffffffffffffffffffffffffffffff
+ffffffffffffa0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080ffffffffffffffffffffffff
+ffffffa0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080ffffffffffffffffffffffff
+ffffffa0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080ffffffffffffffffff
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080ffffffffffffffffff
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080ffffffa0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+000000000000000000a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080000000000000000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080000000
+000000a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000
+000000000000a0a080000000a0a080a0a080a0a080a0a080000000000000000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080000000
+a0a080a0a080a0a080a0a080000000000000a0a080000000000000000000a0a080a0a080
+a0a080000000000000a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080000000000000000000a0a080a0a080
+000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080000000a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080000000
+a0a080a0a080a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000000000000000
+000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+000000a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080
+a0a080a0a080000000000000a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080
+000000000000000000a0a080a0a080a0a080000000000000a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+a0a080a0a080a0a080a0a080000000000000000000000000a0a080000000a0a080000000
+000000000000a0a080a0a080000000000000000000a0a080a0a080a0a080a0a080000000
+000000000000000000a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+a0a080a0a080a0a080a0a080000000000000000000000000000000a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080000000000000000000000000a0a080000000
+000000000000000000a0a080a0a080000000a0a080000000000000000000a0a080a0a080
+a0a080a0a080000000a0a080000000000000000000a0a080a0a080a0a080a0a080a0a080
+000000000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080000000000000000000000000a0a080a0a080a0a080a0a080
+a0a080000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffffa0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffffa0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000ffffff404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080ffffff
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080ffffff404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040404040404040
+404040404040404040404040404040404040404040404040404040404040a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080a0a080
+a0a080a0a080a0a080a0a080a0a080200000c020002000c0
+c020c0c02000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000200000200000200000200000200000200000
+200000200000200000200000200000200000c020002000c0
+c020c0c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000
+c02000c02000c02000c02000c02000c02000c020002000c0
+c020c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c02000c0
+2000c02000c02000c02000c02000c02000c02000c02000c0
+
+showpage
+
+% stop using temporary dictionary
+end
+
+% restore original state
+origstate restore
+
+%%Trailer
diff --git a/docs/feature_edit.gif b/docs/feature_edit.gif
new file mode 100644
index 0000000..a31014b
Binary files /dev/null and b/docs/feature_edit.gif differ
diff --git a/docs/feature_list.sgml b/docs/feature_list.sgml
new file mode 100644
index 0000000..48da025
--- /dev/null
+++ b/docs/feature_list.sgml
@@ -0,0 +1,119 @@
+ <SECT1 ID="FEATURELIST">
+ <TITLE>The Feature List</TITLE>
+ <PARA>
+The feature list is bit at the bottom of the main window of &art; (see <XREF
+LINKEND="MAINWINDOW-OVERVIEW">). <![ %artemis-only; [ The feature list can be
+toggled on and off from the display menu (see <XREF
+LINKEND="DISPLAYMENU-SHOW-FEATURE-LIST">). ]]> The same component is used by
+the two items in the view menus that show a sub-set of components (see <XREF
+LINKEND="VIEWMENU-SHOW-CDS-GENES-PRODUCTS"> and <XREF
+LINKEND="VIEWMENU-FEATURE-FILTERS">). The list contains information about each
+feature in each active entry (see <XREF LINKEND="CONCEPTS-ACTIVEENTRY">), one
+per line.
+ </PARA>
+
+ <PARA>
+The lines show this information about the feature: the colour (see <XREF
+LINKEND="CONCEPTS-COLOUR">), the key, start base, end base, the note field (if
+any) and then the remaining qualifiers in EMBL/GENBANK format. If the
+correlation scores option is on then the scores will be shown just before the
+note in each CDS line.
+ </PARA>
+
+ <SECT2 ID="FEATURELIST-SELECTION">
+ <TITLE>Changing the Selection from the Feature List</TITLE>
+ <PARA>
+To select a feature from the list just click on it with the first mouse
+button. This will unselect anything that is currently selected. To add a
+feature to the selection rather than replacing the current selection, hold the
+<LITERAL>shift</LITERAL> key while clicking. A single feature can be removed
+from the selection in the same way, because shift-clicking acts as a toggle.
+ </PARA>
+ <PARA>
+See <XREF LINKEND="CONCEPTS-SELECTION"> for more about the selection.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="FEATURELIST-OTHERMOUSE">
+ <TITLE>Other Mouse Controlled Functions</TITLE>
+ <PARA>
+Double clicking on a feature with the first mouse button causes both views and
+the feature list to centre themselves on that feature.
+ </PARA>
+ <PARA>
+A double click of the middle mouse button on a feature will open an edit
+window for that feature. This is the same as clicking once and then choosing
+the Edit Selected Features menu item (see <XREF
+LINKEND="EDITMENU-EDIT-SELECTED-FEATURES">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="FEATURELIST-POPUPMENU">
+ <TITLE>The Pop-up Menu</TITLE>
+ <PARA>
+The pop-up menu is activated by pressing the third mouse button (see <XREF
+LINKEND="MOUSEBUTTONS">) anywhere on the feature list. The menu contains
+shortcuts to some of the main window menus and three toggle buttons:
+ </PARA>
+
+ <SECT3>
+ <TITLE>Save List to File</TITLE>
+ <PARA>
+Save the list of features to a file.
+ </PARA>
+ </SECT3>
+
+ <SECT3>
+ <TITLE>Show Selected Qualifier</TITLE>
+ <PARA>
+A qualifier can be selected to be displayed in the feature list.
+ </PARA>
+ </SECT3>
+
+
+ <SECT3>
+ <TITLE>Show Correlation Scores</TITLE>
+ <PARA>
+If this toggle is on the correlation scores for each feature will be show
+between the end base and the note.
+ </PARA>
+ </SECT3>
+
+ <SECT3>
+ <TITLE>Show Gene Names</TITLE>
+ <PARA>
+This button controls whether the key or gene name is shown for each feature.
+The "gene name" is actually the <LITERAL>/label</LITERAL> qualifier of the
+feature. If there is no <LITERAL>/label</LITERAL> then the
+<LITERAL>/gene</LITERAL> qualifier is used. If neither of the qualifiers
+exists then the key is used.
+ </PARA>
+ </SECT3>
+
+ <SECT3>
+ <TITLE>Show Qualifiers</TITLE>
+ <PARA>
+If this option is on the feature qualifiers will be displayed after the note.
+ </PARA>
+ </SECT3>
+
+ <SECT3>
+ <TITLE>Show Product</TITLE>
+ <PARA>
+This button controls whether the <LITERAL>/note</LITERAL> or
+<LITERAL>/product</LITERAL> is shown for each feature. [default:
+<LITERAL>/note</LITERAL> ]
+ </PARA>
+ </SECT3>
+
+ </SECT2>
+ <SECT2 ID="FEATURELIST-SCROLLING">
+ <TITLE>Scrolling The List</TITLE>
+ <PARA>
+The scrollbar on the right of the list controls which part of the list of
+active features is visible. The list will also move if the user double clicks
+on a feature in overview or DNA view areas, in which case the list will scroll
+to that feature.
+ </PARA>
+ </SECT2>
+ </SECT1>
diff --git a/docs/file_menu.sgml b/docs/file_menu.sgml
new file mode 100644
index 0000000..57f6c19
--- /dev/null
+++ b/docs/file_menu.sgml
@@ -0,0 +1,274 @@
+<SECT1 ID="FILEMENU">
+ <TITLE>The File Menu</TITLE>
+ <PARA>
+Most of the items in this menu are used to read and write entries and parts of
+entries, the exceptions are Clone and Close.
+ </PARA>
+
+ <SECT2 ID="FILEMENU-SHOW-FILEMANAGER">
+ <TITLE>Show File Manager ...</TITLE>
+ <PARA>
+This will open the file manager, or if it is already open will bring it
+to the foreground. Entries can be dragged from the file manager into
+the artemis main window and dropped. When dropped the entry is then read
+in and displayed.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="FILEMENU-READ-AN-ENTRY">
+ <TITLE>Read An Entry ...</TITLE>
+ <PARA>
+Read an entry (see <XREF LINKEND="CONCEPTS-ENTRY">), but keep it separate from
+the others. A new button will be created on the entry button line for the new
+entry. The new entry will be marked as active (see <XREF
+LINKEND="CONCEPTS-ACTIVEENTRY">) and will be the new default entry (see <XREF
+LINKEND="CONCEPTS-DEFAULTENTRY">).
+ </PARA>
+ <PARA>
+See <XREF LINKEND="FILETYPES">.
+This function only reads the feature section of the input file - the sequence
+(if any) is ignored.
+ </PARA>
+
+ <SECT2 ID="FILEMENU-READ-ENTRY-INTO">
+ <TITLE>Read Entry Into</TITLE>
+ <PARA>
+Read the features from an entry (see <XREF LINKEND="CONCEPTS-ENTRY">)
+chosen by the user and then insert them into the entry selected by
+the user.
+ </PARA>
+ </SECT2>
+
+ &nextgen;
+
+ <SECT2 ID="FILEMENU-SAVE-DEFAULT-ENTRY">
+ <TITLE>Save Default Entry</TITLE>
+ <PARA>
+Save the default entry to the file it came from, unless the entry has been
+given a new name, in which case the entry is saved to a file with that name.
+If the entry has no name, &prog; will prompt the user for a new name.
+[shortcut key: S]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="FILEMENU-SAVE-AN-ENTRY">
+ <TITLE>Save An Entry</TITLE>
+ <PARA>
+This item will do the same as "Save Default Entry" for the chosen entry.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="FILEMENU-SAVE-ENTRY-AS-MENU">
+ <TITLE>Save An Entry As</TITLE>
+ <PARA>
+This sub-menu contains the less frequently used save functions.
+ </PARA>
+
+ <SECT3 ID="FILEMENU-SAVE-ENTRY-AS">
+ <TITLE>New File</TITLE>
+ <PARA>
+Ask for the name of file to save the given entry to. The name of entry (as
+displayed in the entry button line) will change to the new name.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="FILEMENU-SAVE-ENTRY-AS-EMBL">
+ <TITLE>EMBL Format</TITLE>
+ <PARA>
+This does the same as "Save An Entry As -> New File ...", but will write the
+features and sequence of the entry in EMBL format. Note that currently the
+header of a GENBANK entry can't be converted to the equivalent EMBL header (it
+will be discarded instead).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="FILEMENU-SAVE-ENTRY-AS-GENBANK">
+ <TITLE>GENBANK Format</TITLE>
+ <PARA>
+This does the same as "Save An Entry As -> New File ...", but will write the
+features and sequence of the entry in GENBANK format. Note that currently the
+header of a EMBL entry can't be converted to the equivalent GENBANK header (it
+will be discarded instead).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="FILEMENU-SAVE-ENTRY-AS-SEQUIN">
+ <TITLE>Sequin Table Format</TITLE>
+ <PARA>
+This saves a file in <ULINK
+URL="http://www.ncbi.nlm.nih.gov/Sequin/table.html" TYPE="external">Sequin table format
+</ULINK> which is used by <ULINK
+URL="http://www.ncbi.nlm.nih.gov/Sequin/" TYPE="external">Sequin
+</ULINK>.
+ </PARA>
+ </SECT3>
+
+
+ <SECT3 ID="FILEMENU-SAVE-ENTRY-AS-GFF">
+ <TITLE>GFF Format</TITLE>
+ <PARA>
+Writes the features in GFF format and sequence of the entry in FASTA format to
+a file selected by the user. Note that if you use this function on an EMBL or
+GENBANK entry the header will discarded.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="FILEMENU-SAVE-EMBL-SUBMISSION">
+ <TITLE>EMBL Submission Format</TITLE>
+ <PARA>
+This does the same as "Save An Entry As -> EMBL Format ...", but
+will write an entry/tab file that contains only valid EMBL qualifiers (see
+<XREF LINKEND="OPTIONS-EXTRAQUALIFIERS">) and valid EMBL keys (see <XREF
+LINKEND="OPTIONS-EXTRAKEYS">). It will also check that the start and stop
+codons of each CDS are sensible, that no two features have the same key and
+location and that all required EMBL qualifiers are present.
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+ <SECT2 ID="FILEMENU-SAVE-ALL-ENTRIES">
+ <TITLE>Save All Entries</TITLE>
+ <PARA>
+This acts like "Save Default Entry", but save all the entries.
+ </PARA>
+ </SECT2>
+
+
+ <SECT2 ID="WRITEMENU">
+ <TITLE>Write</TITLE>
+ <PARA>
+
+ </PARA>
+
+ <SECT3 ID="WRITEMENU-AMINO-ACIDS-OF-SELECTED-FEATURES">
+ <TITLE>Amino Acids Of Selected Features</TITLE>
+ <PARA>
+Prompt for a file name and then write the translation of the bases of the
+selected features to that file. The file is written in FASTA format.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-PIR-DATABASE-OF-SELECTED-FEATURES">
+ <TITLE>PIR Database Of Selected Features</TITLE>
+ <PARA>
+Prompt for a file name and then write the translation of the bases of the
+selected features to that file. The file is written in PIR format (similar to
+FASTA, but with a * as the last line of each record).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-BASES-OF-SELECTION">
+ <TITLE>Bases Of Selection</TITLE>
+ <PARA>
+Prompt for a file name and then write the bases of the selection to that
+file in the selected format. If the selection consists of features (rather
+than a base range) then the bases of each feature
+will be written to the file as a separate record. If the selection is a range
+of bases, then those bases will be written.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-UPSTREAM-BASES-OF-SELECTION">
+ <TITLE>Upstream Bases Of Selection</TITLE>
+ <PARA>
+Prompt for a number and a file name, then write that many bases upstream of
+each selected feature to the file in the selected format. For example if the
+selected feature has a
+location of "<LITERAL>100..200</LITERAL>", then asking for 50 upstream will
+write the bases in the range 50 to 99. Writing upstream bases of a feature on
+the complementary strand will work in the expected way.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-DOWNSTREAM-BASES-OF-SELECTION">
+ <TITLE>Downstream Bases Of Selection</TITLE>
+ <PARA>
+Prompt for a number and a file name, then write that many bases downstream of
+each selected feature to the file in the selected format.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-ALL-BASES">
+ <TITLE>All Bases</TITLE>
+ <PARA>
+Prompt for a file name, then write the complete sequence to that file in the
+selected format.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="WRITEMENU-CODON-USAGE">
+ <TITLE>Codon Usage of Selected Features</TITLE>
+ <PARA>
+Prompt for a file name, then write a codon usage table for the selected
+features. The file in written in the same format as the data at <ULINK
+URL="http://www.kazusa.or.jp/codon/" TYPE="external">Kazusa codon usage
+database site</ULINK>. In the output file each codon is followed by it's
+occurrence count (per thousand) and it's percentage occurrence. (See <XREF
+LINKEND="GRAPHMENU-USAGE-PLOTS"> to find out how to plot a usage graph).
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+
+ <SECT2 ID="FILEMENU-CLONE-WINDOW">
+ <TITLE>Clone This Window</TITLE>
+ <PARA>
+Make a new main window with the same contents as the current window. All
+changes in the old window will be reflected in the new window, and vice
+versa. The exception to this rule is the selection (see <XREF
+LINKEND="CONCEPTS-SELECTION">), which is not shared between the
+old and new window.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="FILEMENU-IMAGE-WINDOW">
+ <TITLE>Save As Image Files (png/svg)</TITLE>
+ <PARA>
+Print out the contents of the current window. All or some of the
+window panels can be selected for printing to an image file.
+ </PARA>
+ <PARA>
+SVG (scalable vector graphics) is an XML based vector image format.
+These images can be converted to a raster image (e.g. png, tiff) at any resolution by
+exporting it from applications such as Inkscape (http://inkscape.org/) or gimp (http://www.gimp.org/).
+Therfore the SVG format can be useful for creating publication quality figures.
+ </PARA>
+ <PARA>
+The other formats available (png, jpeg etc) and are raster or bitmap images.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="FILEMENU-PRINT-WINDOW">
+ <TITLE>Print</TITLE>
+ <PARA>
+This option can be used to print the contents of the current window
+to a file as PostScript or to a printer.
+ </PARA>
+ </SECT2>
+
+
+ <SECT2 ID="FILEMENU-PRINT-PREVIEW-WINDOW">
+ <TITLE>Print Preview</TITLE>
+ <PARA>
+This opens the print image in a preview window. This shows
+what the image will look like when printed to a file.
+ </PARA>
+ </SECT2>
+
+
+ <SECT2 ID="FILEMENU-PREFERENCES">
+ <TITLE>Preferences</TITLE>
+ <PARA>
+This enables the user to define their own shortcut preferences.
+ </PARA>
+ </SECT2>
+
+
+
+ <SECT2 ID="FILEMENU-CLOSE">
+ <TITLE>Close</TITLE>
+ <PARA>
+Close this window.
+ </PARA>
+ </SECT2>
+</SECT1>
diff --git a/docs/filetypes.sgml b/docs/filetypes.sgml
new file mode 100644
index 0000000..a9e09cf
--- /dev/null
+++ b/docs/filetypes.sgml
@@ -0,0 +1,100 @@
+ <SECT1 ID="FILETYPES">
+ <TITLE>Sequence and Annotation File Formats</TITLE>
+ <PARA>
+&prog; reads in the common sequence and annotation file formats. As larger data sets become more common it is now
+possible to index some of these formats (FASTA and GFF3) to speed up and improve the performance of &prog;.
+&prog; can read the following sequence and annotation file formats. As discussed in the next
+section these can be read individually or as a combination of different annotation files
+being read in and overlaid on the same sequence.
+ </PARA>
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <PARA>
+ <ULINK URL="http://www.ebi.ac.uk/embl/Documentation/">EMBL</ULINK> format.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+ <ULINK URL="http://www.ncbi.nlm.nih.gov/genbank/">GenBank</ULINK> format.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+ <ULINK URL="http://www.sequenceontology.org/gff3.shtml">GFF3</ULINK> format. The
+ file can contain both the sequence and annotation or the
+ GFF can just be the feature annotation and be read in as a seperate entry and overlaid
+ onto another entry containing the sequence.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+ FASTA nucleotide sequence files can be one of the following:
+ </PARA>
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <PARA>
+ Single FASTA sequence.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+ Multiple FASTA sequence. The sequences are concatenated together when opened in &prog;.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+ Indexed FASTA files can be read in. The files are indexed
+using <ULINK URL="http://samtools.sourceforge.net/">SAMtools</ULINK>:
+ </PARA>
+ <SYNOPSIS>
+samtools faidx ref.fasta
+ </SYNOPSIS>
+ <PARA>
+A drop down menu of the sequences in the Entry toolbar (see <XREF
+LINKEND="MAINWINDOW-BREAKDOWN">) at the top can then used to select the sequence to view.
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Indexed GFF3 format. This can be read in and overlaid onto an indexed FASTA file. The indexed GFF3 file contains
+the feature annotations. To index the GFF first sort and bgzip the file and then use tabix with
+"-p gff" option (see the <ULINK URL="http://samtools.sourceforge.net/tabix.shtml">tabix manual</ULINK>):
+ </PARA>
+ <SYNOPSIS>
+(grep ^"#" in.gff; grep -v ^"#" in.gff | sort -k1,1 -k4,4n) | bgzip > sorted.gff.gz;
+tabix -p gff sorted.gff.gz
+ </SYNOPSIS>
+ <PARA>
+A drop down menu of the sequences in the Entry toolbar (see <XREF
+LINKEND="MAINWINDOW-BREAKDOWN">) at the top can then used to select the sequence to view.
+Using indexed FASTA and indexed GFF files improves the memory management
+and enables large genomes to be viewed. Note that as it is indexed the sequence and annotation are
+read-only and cannot be edited. When there are many contigs to select from it can be easier
+to display the one of interest by typing the name into the drop down list.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+The output of <ULINK
+URL="http://sonnhammer.sbc.su.se/download/software/MSPcrunch+Blixem/"><COMMAND>MSPcrunch</COMMAND></ULINK>.
+<COMMAND>MSPcrunch</COMMAND> must be run with the <COMMAND>-x</COMMAND> or
+<COMMAND>-d</COMMAND> flags.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+The output of <ULINK URL="http://www.ncbi.nlm.nih.gov/blast/">blastall version
+2.2.2</ULINK> or better. <COMMAND>blastall</COMMAND> must be run with the
+<COMMAND>-m 8</COMMAND> flag which generates one line of information per HSP.
+Note that currently &prog; displays each Blast HSP as a separate feature
+rather than displaying each BLAST hit as a feature.
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+
+ </SECT1>
diff --git a/docs/fm.gif b/docs/fm.gif
new file mode 100644
index 0000000..baa89e7
Binary files /dev/null and b/docs/fm.gif differ
diff --git a/docs/fm_login.gif b/docs/fm_login.gif
new file mode 100644
index 0000000..ca29b90
Binary files /dev/null and b/docs/fm_login.gif differ
diff --git a/docs/fm_popup.gif b/docs/fm_popup.gif
new file mode 100644
index 0000000..b3306cb
Binary files /dev/null and b/docs/fm_popup.gif differ
diff --git a/docs/getting_java.sgml b/docs/getting_java.sgml
new file mode 100644
index 0000000..bf25b22
--- /dev/null
+++ b/docs/getting_java.sgml
@@ -0,0 +1,32 @@
+<SECT2 ID="INSTALLINGJAVA">
+ <TITLE>Installing Java</TITLE>
+ <PARA>
+Before installing &prog; you will need to make sure you have Java installed.
+ </PARA>
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <PARA>
+Linux, UNIX users should get the current Java version from the <ULINK
+URL="http://www.java.com/">Oracle</ULINK>.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+Windows users should get latest stable release from <ULINK
+URL="http://www.java.com/">Java version</ULINK>
+from Oracle.
+On some versions of Windows (such as Windows 2000) you will need be have
+administrator privileges for your machine to successfully install Java.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+Macintosh users with MacOS X should have Java installed. You can get the
+latest Java 1.6 version from Apple.
+
+Java 7 for Intel Mac running Mac OSX 10.7.3 or above can be downloaded from
+<ULINK URL="http://www.java.com/en/download/help/mac_install.xml">Oracle</ULINK>.
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+</SECT2>
diff --git a/docs/intro_chapter.sgml b/docs/intro_chapter.sgml
new file mode 100644
index 0000000..af799fc
--- /dev/null
+++ b/docs/intro_chapter.sgml
@@ -0,0 +1,137 @@
+<CHAPTER ID="INTRODUCTION">
+ <TITLE>Introduction to &prog;</TITLE>
+
+ <SECT1 ID="WHATIS">
+ <TITLE>What is &prog;?</TITLE>
+ <PARA>
+ &prog; is a DNA sequence viewer and annotation tool that allows visualisation
+of sequence features and the results of analyses within the context of the
+sequence, and its six-frame translation. &prog; is written in Java, reads
+EMBL or GENBANK format sequences and feature tables, and can work on sequences
+of any size.
+ </PARA>
+
+ <PARA>
+On Unix and GNU/Linux systems, given an EMBL accession number &prog; also can
+read an entry directly from the
+<ULINK URL="http://www.ebi.ac.uk" TYPE="external">EBI</ULINK> using <ULINK
+URL="http://www.ebi.ac.uk/cgi-bin/dbfetch" TYPE="external">Dbfetch</ULINK>. This
+feature is still experimental, but copes well with straightforward entries
+(see <XREF LINKEND="LAUNCH-WINDOW-FILE-MENU-READ-FROM-EBI">).
+ </PARA>
+
+ <PARA>
+For more information on &prog; go to
+<ULINK URL="http://www.sanger.ac.uk/resources/software/artemis/" TYPE="external">the &prog;
+web pages</ULINK>.
+ </PARA>
+ </SECT1>
+
+&requirements;
+
+ <SECT1 ID="INSTALLATION">
+ <TITLE>Getting and Installing &prog;</TITLE>
+ <PARA>
+The most up to date version of &prog; is always available from the <ULINK
+URL="http://www.sanger.ac.uk/resources/software/artemis/">&art; web pages</ULINK>.
+ </PARA>
+
+&gettingjava;
+
+ <SECT2 ID="UNIXINSTALLATION">
+ <TITLE>Installation Instructions for UNIX and GNU/Linux</TITLE>
+ <PARA>
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <PARA>
+Change directory to the directory you wish to install &prog; in. We will use
+<LITERAL>~/</LITERAL> in this example and in the next chapter.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+uncompress and untar the <LITERAL>artemis_compiled.tar.gz</LITERAL> file. On
+UNIX the command is:
+ </PARA>
+ <PARA>
+ <COMMAND>
+tar zxf artemis_compiled.tar.gz
+ </COMMAND>
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+This will create a directory called <LITERAL>~/artemis</LITERAL> which will
+contain all the files necessity for running &prog;.
+ </PARA>
+
+ <PARA>
+For instructions on how to run &prog; on UNIX and GNU/Linux once the archive
+is unpacked see <XREF LINKEND="RUNNINGUNIX">.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="MACINSTALLATION">
+ <TITLE>Installation Instructions for MacOSX</TITLE>
+ <PARA>
+For MacOSX users an archive artemis_act.dmg.gz disk image is provided.
+This contains both Artemis and ACT applications.
+This can be uncompressed using gunzip:
+ </PARA>
+ <PARA>
+<ComputerOutput>gunzip artemis_act.dmg.gz</ComputerOutput>
+ </PARA>
+ <PARA>
+Alternatively on OS X an easier solution is provided by StuffIt Expander.
+Double-click on any file ending in ".gz" and StuffIt Expander will be
+launched to uncompress that file.
+
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="mac_osx_dmg.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+The uncompressed disk image file "artemis_act.dmg" can be mounted by double
+clicking on it. The mounted image, "artemis_act", can then be opened
+and the software contents displayed by double clicking on it. Open an read the
+"Readme.txt" file.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DOSINSTALLATION">
+ <TITLE>Installation Instructions for Windows</TITLE>
+ <PARA>
+On Windows systems with Java (version 1.6 or higher), installing &prog;
+is as simple as downloading the the artemis.jar file to an appropriate
+place (such as the desktop or the Programs folder). When downloading
+some versions of windows mistake this file for a zip file ensure it
+does not name this artemis.zip, if it does rename it to artemis.jar
+(DO NOT UNZIP THIS FILE).
+ </PARA>
+
+ <PARA>
+For instructions on how to run &prog; on Windows once it is unpacked see <XREF
+LINKEND="RUNNINGPC"> .
+ </PARA>
+ </SECT2>
+ </SECT1>
+
+&filetypes;
+&concepts;
+
+ <SECT1 ID="CONTRIBUTIONS">
+ <TITLE>Contributions and Suggestions</TITLE>
+ <PARA>
+We welcome contributions to &prog;, bug reports from users and suggestions for
+new features. An email discussion list has been set up for this purpose. To
+join, send a message to 'artemis-users-join at sanger.ac.uk' with 'subscribe
+artemis-users' in the body (not the subject). Announcements will also be sent
+to this list.
+ </PARA>
+ </SECT1>
+
+&acknowledgments;
+
+©right;
+
+</CHAPTER>
diff --git a/docs/jvm_options.sgml b/docs/jvm_options.sgml
new file mode 100644
index 0000000..242ae1f
--- /dev/null
+++ b/docs/jvm_options.sgml
@@ -0,0 +1,132 @@
+ <SECT2 ID="JVMOPTS-MEM">
+ <TITLE><LITERAL>-Xmsn -Xmxn</LITERAL></TITLE>
+ <PARA>
+Use <LITERAL>-Xmsn</LITERAL> to specify the initial size, in bytes, of the
+memory allocation pool. This value must be a multiple of 1024 greater than
+1MB. Append the letter k or K to indicate kilobytes, or m or M to indicate
+megabytes.
+ </PARA>
+ <PARA>
+Use <LITERAL>-Xmxn</LITERAL> to specify the maximum size, in bytes, of the
+memory allocation pool. This value must a multiple of 1024 greater than
+2MB. Append the letter k or K to indicate kilobytes, or m or M to indicate
+megabytes.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="BB-MODE">
+ <TITLE><LITERAL>-Dblack_belt_mode=false</LITERAL></TITLE>
+ <PARA>
+If this is set to false then warning messages are kept to a minimum.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="JVM-OFFSET">
+ <TITLE><LITERAL>-Doffset=10000</LITERAL></TITLE>
+ <PARA>
+This sets the base position that &prog; opens at, e.g. 10000.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="JVM-USERPLOT">
+ <TITLE><LITERAL>-Duserplot=pathToFile</LITERAL></TITLE>
+ <PARA>
+This can be used to open userplots from the command line. The paths are comma
+separated to open multiple plots (-Duserplot='/pathToFile/userPlot1,/pathToFile/userPlot2').
+ </PARA>
+
+<![ %act-only; [
+ <PARA>
+In ACT numbers are used to associate the file with a particular sequence. For example to add a
+userplot to the second sequence:
+</PARA>
+<PARA><COMMAND>act -Duserplot2=/pathToFile/userPlot</COMMAND></PARA>
+
+]]>
+ </SECT2>
+
+ <SECT2 ID="JVM-LOGUSERPLOT">
+ <TITLE><LITERAL>-Dloguserplot=pathToFile</LITERAL></TITLE>
+ <PARA>
+This is the same as the above userplot option except that the log transform of the data is plotted.
+ </PARA>
+ </SECT2>
+
+
+<![ %artemis-only; [
+ <SECT2 ID="JVM-NG">
+ <TITLE><LITERAL>-Dbam=pathToFile</LITERAL></TITLE>
+ <PARA>
+This can be used to open BAM files and/or VCF/BCF files (see <XREF LINKEND="FILEMENU-READ-BAM"> for more about the
+using these file types). This can take a path name to a file or an HTTP address.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="JVM-NG2">
+ <TITLE><LITERAL>-Dbam1=pathToFile1 -Dbam2=pathToFile2</LITERAL></TITLE>
+ <PARA>
+The following will open BAM files in separate panels (using -Dbam[1,2,3...]):
+ </PARA>
+ <PARA>
+<COMMAND>art -Dbam1=fileA.bam -Dbam2=fileB.bam</COMMAND>
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="JVM-NG3">
+ <TITLE><LITERAL>-DbamClone=n</LITERAL></TITLE>
+ <PARA>
+Multiple BAM panels can be opened using the bamClone flag this is used with the -Dbam flag:
+ </PARA>
+ <PARA>
+<COMMAND>art -Dbam='/pathToFile/file1.bam,/pathToFile/file2.bam' -DbamClone=n</COMMAND>
+ </PARA>
+ <PARA>
+(where n is an integer greater than 1). All BAM files are then shown in each panel.
+ </PARA>
+ </SECT2>
+]]>
+
+<![ %act-only; [
+ <SECT2 ID="JVM-NG2">
+ <TITLE><LITERAL>-Dbam1=pathToFile1 -Dbam2=pathToFile2</LITERAL></TITLE>
+ <PARA>
+This can be used to open BAM files and/or VCF/BCF files (see <XREF LINKEND="FILEMENU-READ-BAM"> for more about the
+using these file types). This can take a path name to a file or an HTTP address.
+Numbers are used to associate the file with a particular sequence. For example to add a BAM to the
+first (top) sequence in ACT:
+</PARA>
+<PARA><COMMAND>act -Dbam1=/pathToFile/file.bam </COMMAND></PARA>
+ </SECT2>
+]]>
+
+ <SECT2 ID="JVM-CHADO">
+ <TITLE><LITERAL>-Dchado="hostname:port/database?username"</LITERAL></TITLE>
+ <PARA>
+This is used to get &prog; to look for the database. The address of the database
+(hostname, port and name) can be conveniently included
+(e.g. -Dchado="genedb-db.sanger.ac.uk:5432/snapshot?genedb_ro") and
+these details are then the default database address in the popup login window.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="JVM-CHADO-READONLY">
+ <TITLE><LITERAL>-Dread_only</LITERAL></TITLE>
+ <PARA>
+For a read only chado connection -Dread_only is specified on the command line
+(e.g. art -Dchado="genedb-db.sanger.ac.uk:5432/snapshot?genedb_ro" -Dread_only).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="JVM-FWD-LINES">
+ <TITLE><LITERAL>-Dshow_forward_lines=false</LITERAL></TITLE>
+ <PARA>
+Hide/show forward frame lines.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="JVM-BWD-LINES">
+ <TITLE><LITERAL>-Dshow_reverse_lines=false</LITERAL></TITLE>
+ <PARA>
+Hide/show reverse frame lines.
+ </PARA>
+ </SECT2>
diff --git a/docs/login.gif b/docs/login.gif
new file mode 100644
index 0000000..df7da4e
Binary files /dev/null and b/docs/login.gif differ
diff --git a/docs/mac_osx_dmg.gif b/docs/mac_osx_dmg.gif
new file mode 100644
index 0000000..9052236
Binary files /dev/null and b/docs/mac_osx_dmg.gif differ
diff --git a/docs/main_window_1.eps b/docs/main_window_1.eps
new file mode 100644
index 0000000..d530147
--- /dev/null
+++ b/docs/main_window_1.eps
@@ -0,0 +1,1464 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: /home/kmr/powmap/docs/main_window_1.ps
+%%Creator: XV Version 3.10a Rev: 12/29/94 (PNG patch 1.2) - by John Bradley
+%%BoundingBox: 36 396 313 410
+%%Pages: 1
+%%DocumentFonts:
+%%EndComments
+%%EndProlog
+
+%%Page: 1 1
+
+% remember original state
+/origstate save def
+
+% build a temporary dictionary
+20 dict begin
+
+% define string to hold a scanline's worth of data
+/pix 1662 string def
+
+% define space for color conversions
+/grays 554 string def % space for gray scale line
+/npixls 0 def
+/rgbindx 0 def
+
+% lower left corner
+36 396 translate
+
+% size of image (on paper, in 1/72inch coords)
+276.98400 14.47200 scale
+
+% define 'colorimage' if it isn't defined
+% ('colortogray' and 'mergeprocs' come from xwd2ps
+% via xgrab)
+/colorimage where % do we know about 'colorimage'?
+ { pop } % yes: pop off the 'dict' returned
+ { % no: define one
+ /colortogray { % define an RGB->I function
+ /rgbdata exch store % call input 'rgbdata'
+ rgbdata length 3 idiv
+ /npixls exch store
+ /rgbindx 0 store
+ 0 1 npixls 1 sub {
+ grays exch
+ rgbdata rgbindx get 20 mul % Red
+ rgbdata rgbindx 1 add get 32 mul % Green
+ rgbdata rgbindx 2 add get 12 mul % Blue
+ add add 64 idiv % I = .5G + .31R + .18B
+ put
+ /rgbindx rgbindx 3 add store
+ } for
+ grays 0 npixls getinterval
+ } bind def
+
+ % Utility procedure for colorimage operator.
+ % This procedure takes two procedures off the
+ % stack and merges them into a single procedure.
+
+ /mergeprocs { % def
+ dup length
+ 3 -1 roll
+ dup
+ length
+ dup
+ 5 1 roll
+ 3 -1 roll
+ add
+ array cvx
+ dup
+ 3 -1 roll
+ 0 exch
+ putinterval
+ dup
+ 4 2 roll
+ putinterval
+ } bind def
+
+ /colorimage { % def
+ pop pop % remove 'false 3' operands
+ {colortogray} mergeprocs
+ image
+ } bind def
+ } ifelse % end of 'false' case
+
+
+
+554 29 8 % dimensions of data
+[554 0 0 -29 0 29] % mapping matrix
+{currentfile pix readhexstring pop}
+false 3 colorimage
+
+000000010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101
+000000ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccccccccccccccccccccc
+cccccc000000000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000000000000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000000000
+000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+000000000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000000000000000000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccc000000000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000000000000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000000000000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000000000000000
+cccccccccccccccccc000000000000000000cccccccccccc000000000000000000000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc000000
+000000cccccccccccccccccccccccc000000000000000000cccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccc000000000000000000cccccccccccc000000000000000000000000cccccccccccc
+cccccc000000000000000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000000000000000000000cccccccccccc000000000000cccccc
+cccccccccccc000000000000000000000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccc000000000000cccccccccccccccccc000000
+000000000000cccccccccccccccccc000000000000000000cccccccccccc000000000000
+000000000000cccccccccccccccccc000000000000000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccc000000cccccc000000000000cccccccccccc
+cccccc000000000000cccccccccccccccccc000000000000000000000000cccccccccccc
+cccccc000000000000000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccc000000cccccccccccccccccc000000cccccc000000cccccc000000000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccc000000
+000000cccccccccccccccccccccccc000000000000000000cccccccccccc000000000000
+000000000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+000000000000cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000000000000000000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+000000cccccc000000cccccccccccccccccc000000cccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000000000
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000000000cccccccccccc000000cccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccc000000cccccc000000cccccc000000000000cccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000000000
+cccccccccccc000000cccccccccccccccccc000000cccccc000000000000cccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc000000cccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccc000000cccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccc000000000000000000000000
+000000cccccccccccccccccc000000cccccccccccccccccc000000000000000000000000
+000000cccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000000000000000000000000000cccccc000000cccccc
+000000cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccc000000000000cccccc
+000000cccccccccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000000000
+000000000000000000cccccccccccc000000000000000000000000cccccccccccc000000
+cccccccccccccccccccccccc000000000000000000000000000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccc000000cccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc000000cccccc
+cccccccccccc000000cccccccccccccccccc000000cccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccc000000000000cccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc000000
+000000000000000000cccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+000000cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+000000cccccccccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccc000000cccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccc000000cccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccc000000cccccc000000cccccccccccc000000000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccc000000cccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+000000cccccc000000cccccccccccccccccc000000cccccccccccc000000cccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc000000cccccc
+000000cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+000000cccccccccccccccccc000000cccccccccccc000000cccccccccccc000000cccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccc000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000000000cccccc000000000000cccccc000000cccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc000000cccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccc000000cccccccccccc000000000000cccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc000000000000
+000000000000cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccc000000000000cccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccc000000000000000000cccccccccccc
+cccccc000000000000000000cccccccccccccccccc000000000000000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccc000000000000000000
+cccccccccccccccccc000000000000000000cccccccccccccccccc000000000000000000
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccc000000000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+000000000000cccccccccccccccccc000000000000000000cccccccccccccccccc000000
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000000000000000cccccccccccc
+cccccc000000000000000000cccccccccccccccccccccccc000000000000cccccccccccc
+cccccc000000000000000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000000000
+000000cccccccccccc000000000000000000000000cccccccccccc000000000000000000
+cccccccccccccccccccccccc000000000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+000000000000cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+000000000000cccccccccccccccccc000000000000000000000000cccccccccccccccccc
+000000000000cccccccccccccccccc000000000000000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000000000000000cccccccccccccccccccccccc000000000000cccccccccccc
+cccccc000000000000000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccc000000000000cccccc000000cccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000000000000000000000cccccccccccccccccc000000
+000000000000cccccccccccccccccc000000000000000000cccccccccccc000000cccccc
+cccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccc000000
+000000000000000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc666666
+666666000000
+000000ffffcc666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000
+
+showpage
+
+% stop using temporary dictionary
+end
+
+% restore original state
+origstate restore
+
+%%Trailer
diff --git a/docs/main_window_1.gif b/docs/main_window_1.gif
new file mode 100644
index 0000000..0810889
Binary files /dev/null and b/docs/main_window_1.gif differ
diff --git a/docs/main_window_1.png b/docs/main_window_1.png
new file mode 100644
index 0000000..d357dd2
Binary files /dev/null and b/docs/main_window_1.png differ
diff --git a/docs/main_window_2.eps b/docs/main_window_2.eps
new file mode 100644
index 0000000..8678d56
--- /dev/null
+++ b/docs/main_window_2.eps
@@ -0,0 +1,900 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: /home/kmr/powmap/docs/main_window_2.ps
+%%Creator: XV Version 3.10a Rev: 12/29/94 (PNG patch 1.2) - by John Bradley
+%%BoundingBox: 36 402 313 410
+%%Pages: 1
+%%DocumentFonts:
+%%EndComments
+%%EndProlog
+
+%%Page: 1 1
+
+% remember original state
+/origstate save def
+
+% build a temporary dictionary
+20 dict begin
+
+% define string to hold a scanline's worth of data
+/pix 1665 string def
+
+% define space for color conversions
+/grays 555 string def % space for gray scale line
+/npixls 0 def
+/rgbindx 0 def
+
+% lower left corner
+36 402 translate
+
+% size of image (on paper, in 1/72inch coords)
+277.48800 8.49600 scale
+
+% define 'colorimage' if it isn't defined
+% ('colortogray' and 'mergeprocs' come from xwd2ps
+% via xgrab)
+/colorimage where % do we know about 'colorimage'?
+ { pop } % yes: pop off the 'dict' returned
+ { % no: define one
+ /colortogray { % define an RGB->I function
+ /rgbdata exch store % call input 'rgbdata'
+ rgbdata length 3 idiv
+ /npixls exch store
+ /rgbindx 0 store
+ 0 1 npixls 1 sub {
+ grays exch
+ rgbdata rgbindx get 20 mul % Red
+ rgbdata rgbindx 1 add get 32 mul % Green
+ rgbdata rgbindx 2 add get 12 mul % Blue
+ add add 64 idiv % I = .5G + .31R + .18B
+ put
+ /rgbindx rgbindx 3 add store
+ } for
+ grays 0 npixls getinterval
+ } bind def
+
+ % Utility procedure for colorimage operator.
+ % This procedure takes two procedures off the
+ % stack and merges them into a single procedure.
+
+ /mergeprocs { % def
+ dup length
+ 3 -1 roll
+ dup
+ length
+ dup
+ 5 1 roll
+ 3 -1 roll
+ add
+ array cvx
+ dup
+ 3 -1 roll
+ 0 exch
+ putinterval
+ dup
+ 4 2 roll
+ putinterval
+ } bind def
+
+ /colorimage { % def
+ pop pop % remove 'false 3' operands
+ {colortogray} mergeprocs
+ image
+ } bind def
+ } ifelse % end of 'false' case
+
+
+
+555 17 8 % dimensions of data
+[555 0 0 -17 0 17] % mapping matrix
+{currentfile pix readhexstring pop}
+false 3 colorimage
+
+000000010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccccccccc000000000000000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000000000
+000000000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccc000000000000000000cccccc
+cccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000000000cccccccccccccccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccccccccccccccc000000000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000000000cccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+000000000000000000000000cccccccccccccccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+000000000000000000cccccc000000cccccc000000000000000000000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccc000000000000000000cccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000000000000000000000000000
+000000cccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccc000000000000000000000000cccccccccccccccccc000000000000000000
+000000000000000000cccccccccccccccccc000000000000cccccccccccccccccc000000
+000000cccccccccccccccccc000000000000cccccccccccc000000000000cccccccccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccc000000
+000000000000000000000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000000000000000000000000000000000cccccccccccccccccccccccccccccc000000
+000000000000000000cccccccccccccccccc000000000000000000000000000000000000
+cccccccccccccccccccccccccccccc000000000000000000000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccc000000000000000000
+cccccccccccccccccccccccccccccc000000000000000000000000cccccccccccccccccc
+cccccccccccc000000000000000000000000000000cccccccccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccc000000000000000000000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000000000000000000000000000000000
+cccccccccccccccccccccccccccccc000000000000000000000000cccccccccccccccccc
+000000000000000000000000000000000000cccccccccccccccccccccccccccccc000000
+000000000000000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000000000000000000000cccccccccccccccccc000000000000cccccc
+000000000000cccccc000000000000cccccccccccccccccc000000000000000000cccccc
+cccccccccccccccccc000000000000cccccc000000000000000000cccccccccccccccccc
+cccccccccccc000000000000000000000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000cccccc000000cccccc
+cccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc000000
+000000000000cccccc000000cccccccccccccccccccccccc000000000000000000000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccc000000000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccc000000000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccc000000cccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000000000cccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000000000cccccccccccccccccccccccc000000cccccccccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000000000
+cccccc000000000000cccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000000000cccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccc000000000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000000000000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccc000000000000cccccccccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccc000000000000000000cccccc
+000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc000000000000
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+000000000000000000cccccccccccccccccccccccc000000000000000000000000000000
+000000000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000000000000000000000000000000000000000cccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000000000000000000000000000000000000000cccccc
+000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000000000000000000000000000000000000000cccccc
+cccccccccccc000000000000000000000000000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccc000000000000000000000000000000000000000000cccccccccccccccccc000000
+000000000000000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000000000000000000000000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccc000000000000000000000000000000cccccccccccc
+cccccccccccc000000000000000000000000cccccccccccccccccccccccc000000000000
+000000000000000000000000000000cccccccccccccccccc000000000000000000000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000
+000000000000000000000000cccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000000000000000000000000000cccccccccccccccccc000000cccccc
+cccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000000000
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccc000000000000000000000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000000000000000000000000000000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccc000000000000000000000000000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccc000000cccccccccccccccccc000000000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000000000cccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccc000000000000000000000000000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000000000
+000000000000000000cccccccccccccccccccccccccccccccccccc000000000000000000
+000000cccccccccccccccccccccccc000000000000000000000000000000cccccccccccc
+cccccccccccccccccc000000000000000000000000cccccccccccccccccccccccccccccc
+000000000000000000000000cccccccccccccccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000000000cccccccccccc
+cccccccccccc000000000000000000cccccc000000000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000000000000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000000000cccccccccccc
+cccccccccccc000000000000000000000000cccccc000000cccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccc000000000000000000cccccc
+000000000000cccccccccccc000000000000000000000000000000cccccccccccccccccc
+cccccccccccccccccc000000000000000000000000cccccccccccccccccc000000000000
+000000000000000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccc000000
+000000000000000000cccccccccccccccccccccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccc000000000000000000000000cccccc000000cccccc
+cccccccccccc000000000000000000000000000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000000000cccccc000000000000000000
+cccccccccccccccccccccccccccccc000000000000000000000000cccccc000000cccccc
+cccccc000000000000000000000000000000cccccccccccccccccccccccccccccccccccc
+000000000000000000000000cccccccccccccccccc000000000000000000000000000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000000000000000000000000000cccccccccccccccccccccccccccccc000000
+000000000000000000cccccccccccccccccccccccccccccc000000000000000000cccccc
+cccccccccccccccccccccccc000000000000000000000000000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccc000000000000000000000000cccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccc000000
+000000000000000000cccccc000000cccccccccccccccccc000000000000000000000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000000000000000000000cccccc000000cccccc000000000000000000
+cccccc000000000000cccccc000000000000cccccccccccc000000000000000000000000
+000000cccccccccccc000000000000000000cccccccccccc000000000000000000cccccc
+cccccccccccc000000000000000000000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000000000
+cccccc000000cccccccccccccccccccccccc000000000000000000000000cccccccccccc
+cccccccccccc000000000000000000000000000000cccccccccccccccccccccccc000000
+000000000000cccccc000000000000cccccccccccc000000000000000000000000000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccccccccccccccc000000000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000
+
+showpage
+
+% stop using temporary dictionary
+end
+
+% restore original state
+origstate restore
+
+%%Trailer
diff --git a/docs/main_window_2.gif b/docs/main_window_2.gif
new file mode 100644
index 0000000..0a53c5d
Binary files /dev/null and b/docs/main_window_2.gif differ
diff --git a/docs/main_window_2.png b/docs/main_window_2.png
new file mode 100644
index 0000000..599910f
Binary files /dev/null and b/docs/main_window_2.png differ
diff --git a/docs/main_window_3.eps b/docs/main_window_3.eps
new file mode 100644
index 0000000..440f5fb
--- /dev/null
+++ b/docs/main_window_3.eps
@@ -0,0 +1,1746 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: /home/kmr/powmap/docs/main_window_3.ps
+%%Creator: XV Version 3.10a Rev: 12/29/94 (PNG patch 1.2) - by John Bradley
+%%BoundingBox: 36 393 313 410
+%%Pages: 1
+%%DocumentFonts:
+%%EndComments
+%%EndProlog
+
+%%Page: 1 1
+
+% remember original state
+/origstate save def
+
+% build a temporary dictionary
+20 dict begin
+
+% define string to hold a scanline's worth of data
+/pix 1665 string def
+
+% define space for color conversions
+/grays 555 string def % space for gray scale line
+/npixls 0 def
+/rgbindx 0 def
+
+% lower left corner
+36 393 translate
+
+% size of image (on paper, in 1/72inch coords)
+277.48800 17.49600 scale
+
+% define 'colorimage' if it isn't defined
+% ('colortogray' and 'mergeprocs' come from xwd2ps
+% via xgrab)
+/colorimage where % do we know about 'colorimage'?
+ { pop } % yes: pop off the 'dict' returned
+ { % no: define one
+ /colortogray { % define an RGB->I function
+ /rgbdata exch store % call input 'rgbdata'
+ rgbdata length 3 idiv
+ /npixls exch store
+ /rgbindx 0 store
+ 0 1 npixls 1 sub {
+ grays exch
+ rgbdata rgbindx get 20 mul % Red
+ rgbdata rgbindx 1 add get 32 mul % Green
+ rgbdata rgbindx 2 add get 12 mul % Blue
+ add add 64 idiv % I = .5G + .31R + .18B
+ put
+ /rgbindx rgbindx 3 add store
+ } for
+ grays 0 npixls getinterval
+ } bind def
+
+ % Utility procedure for colorimage operator.
+ % This procedure takes two procedures off the
+ % stack and merges them into a single procedure.
+
+ /mergeprocs { % def
+ dup length
+ 3 -1 roll
+ dup
+ length
+ dup
+ 5 1 roll
+ 3 -1 roll
+ add
+ array cvx
+ dup
+ 3 -1 roll
+ 0 exch
+ putinterval
+ dup
+ 4 2 roll
+ putinterval
+ } bind def
+
+ /colorimage { % def
+ pop pop % remove 'false 3' operands
+ {colortogray} mergeprocs
+ image
+ } bind def
+ } ifelse % end of 'false' case
+
+
+
+555 35 8 % dimensions of data
+[555 0 0 -35 0 35] % mapping matrix
+{currentfile pix readhexstring pop}
+false 3 colorimage
+
+000000010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000000000000000
+000000000000000000000000cccc99000000000000000000cccc99cccc99cccc99000000
+000000000000000000000000000000000000000000000000cccc99cccc99cccc99000000
+000000000000000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99000000000000000000000000000000000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900999900999900999900999900
+999900999900999900999900999900ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666666666666666666666666666666666666666666666666666
+666666cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+cccc99cccc99000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99000000cccc99cccc99000000000000cccc99cccc99cccc99000000
+000000cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900999900999900999900999900
+999900999900999900999900cccc00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666666666666666666666666666666666666666666666666666
+ffffcccccc99cccc99cccc99cccc99000000000000000000000000cccc99cccc99cccc99
+000000000000000000000000000000000000cccc99cccc99cccc99cccc99000000000000
+000000000000000000000000000000cccc99cccc99cccc99000000000000000000cccc99
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffccffffccffffccffffccffffccffffccffffccffffccffffcc666666
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99000000cccc99cccc99000000cccc99000000cccc99000000cccc99
+000000cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99
+000000cccc99000000000000cccc99000000000000000000cccc99cccc99cccc99000000
+000000000000000000000000000000cccc99cccc99cccc99cccc99000000000000cccc99
+cccc99000000000000cccc99cccc99000000000000000000cccc99cccc99000000000000
+000000cccc99cccc99cccc99cccc99000000000000cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00cccc00cccc00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000
+ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666999999999999999999999999999999999999999999ffffcc
+ffffcccccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99cccc99000000cccc99cccc99000000cccc99cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffcccccc99cccc99cccc99cccc99cccc99cccc99cccc99666666666666
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99000000cccc99cccc99cccc99cccc99000000cccc99000000cccc99000000cccc99
+000000cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99000000cccc99
+cccc99cccc99cccc99000000000000cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+000000cccc99cccc99000000cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99cccc99cccc99000000000000cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900ffff00cccc00cccc00cccc00
+cccc00cccc00ffff00cccc00cccc00ffff00ffff00ffff00ffff00000000000000000000
+000000000000ffff00ffff00000000000000ffff00ffff00ffff00000000000000ffff00
+ffff00ffff00000000ffff00000000000000000000ffff00ffff00ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000000000ffff00ffff00
+ffff00000000000000ffff00000000000000ffff00000000000000ffff00ffff00000000
+ffff00000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666999999999999999999999999999999999999999999ffffcc
+ffffcccccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99cccc99000000cccc99cccc99000000cccc99cccc99cccc99cccc99
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffcccccc99cccc99cccc99cccc99cccc99cccc99cccc99666666666666
+cccc99cccc99cccc99000000cccc99000000000000000000cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99000000000000
+000000000000cccc99cccc99cccc99cccc99cccc99000000000000000000000000000000
+cccc99cccc99000000000000000000000000000000000000cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000000000000000000000
+000000000000cccc99cccc99cccc99cccc99cccc99000000000000000000000000cccc99
+cccc99cccc99cccc99000000cccc99000000000000000000cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000000000
+000000000000cccc99cccc99cccc99cccc99000000cccc99cccc99000000cccc99cccc99
+000000cccc99cccc99000000000000000000000000000000cccc99cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000000000000000000000cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000000000
+cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900ffff00cccc00cccc00cccc00
+cccc00cccc00ffff00cccc00cccc00ffff00ffff00ffff00000000ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00000000ffff00
+ffff00ffff00000000000000ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00000000ffff00
+ffff00ffff00000000000000ffff00000000000000ffff00000000ffff00ffff00000000
+000000ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666999999999999999999999999999999999999999999ffffcc
+ffffcccccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffcccccc99cccc99cccc99cccc99cccc99cccc99cccc99666666666666
+cccc99cccc99cccc99000000000000cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99000000cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99000000000000cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99000000cccc99cccc99
+000000cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99000000cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900ffff00cccc00cccc00cccc00
+cccc00cccc00ffff00cccc00cccc00ffff00ffff00ffff00000000ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00000000ffff00
+ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00000000ffff00ffff00000000ffff00ffff00000000ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666999999999999999999999999999999999999999999ffffcc
+ffffcccccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99000000cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000
+000000000000000000cccc99cccc99cccc99cccc99cccc99000000000000000000000000
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffcccccc99cccc99cccc99cccc99cccc99cccc99cccc99666666666666
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99000000cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99
+000000cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99
+000000cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900ffff00cccc00cccc00cccc00
+cccc00cccc00ffff00cccc00cccc00ffff00ffff00ffff00ffff00000000000000000000
+000000ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00000000ffff00
+ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00000000000000000000000000000000000000000000
+ffff00ffff00000000ffff00ffff00000000ffff00ffff00000000ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666999999999999999999999999999999999999999999ffffcc
+ffffcccccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99000000000000000000000000cccc99cccc99cccc99cccc99cccc99cccc99000000
+cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffcccccc99cccc99cccc99cccc99cccc99cccc99cccc99666666666666
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99000000000000
+000000000000000000cccc99cccc99cccc99cccc99000000000000000000000000cccc99
+cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000000000000000000000000000
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99000000cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99
+000000cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99
+000000cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99000000cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000000000cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99000000000000cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900ffff00cccc00cccc00cccc00
+cccc00cccc00ffff00cccc00cccc00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00000000ffff00
+ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000ffff00ffff00000000ffff00ffff00000000ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666999999999999999999999999999999999999999999ffffcc
+ffffcccccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99000000cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffcccccc99cccc99cccc99cccc99cccc99cccc99cccc99666666666666
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000000000000000
+000000000000000000000000cccc99000000000000000000cccc99cccc99cccc99000000
+000000000000000000000000000000000000000000000000cccc99cccc99cccc99000000
+000000000000000000000000000000000000000000cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99000000000000000000000000000000000000
+000000cccc99000000000000000000cccc99cccc99000000000000000000cccc99cccc99
+cccc99cccc99000000000000000000cccc99cccc99cccc99cccc99000000000000000000
+000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99000000000000cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00cccc00cccc00ffff00ffff00ffff00000000ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00000000ffff00ffff00ffff00000000000000ffff00
+ffff00ffff00000000000000ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00
+ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+ffff00ffff00ffff00ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00000000
+ffff00ffff00000000ffff00ffff00000000ffff00ffff00000000ffff00ffff00000000
+000000ffff00ffff00ffff00000000ffff00ffff00ffff00ffff00ffff00ffff00000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666999999999999999999999999999999999999999999ffffcc
+ffffcccccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99000000cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000000000cccc99cccc99cccc99
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffcccccc99cccc99cccc99cccc99cccc99cccc99cccc99666666666666
+cccc99cccc99cccc99000000000000cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99
+cccc99cccc99000000cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99cccc99000000000000cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99
+cccc99cccc99000000cccc99cccc99cccc99000000cccc99cccc99cccc99cccc99000000
+cccc99cccc99cccc99000000000000cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900999900cccc00cccc00cccc00cccc00
+cccc00cccc00cccc00cccc00cccc00ffff00ffff00ffff00000000000000000000000000
+000000ffff00ffff00ffff00ffff00ffff00000000000000000000ffff00000000000000
+ffff00000000000000ffff00000000000000000000ffff00ffff00ffff00ffff00000000
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000000000000000ffff00
+ffff00000000000000000000ffff00000000000000ffff00000000000000000000000000
+ffff00000000000000000000ffff00ffff00ffff00ffff00ffff00000000000000000000
+000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666666666ffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffcccccc99cccc99cccc99cccc99000000000000000000000000cccc99cccc99cccc99
+000000000000000000cccc99cccc99cccc99cccc99000000cccc99cccc99000000000000
+000000000000cccc99cccc99cccc99cccc99cccc99000000cccc99000000000000000000
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffccffffcc666666666666666666666666666666666666666666666666666666
+cccc99cccc99000000000000cccc99000000000000000000cccc99cccc99cccc99cccc99
+cccc99000000000000000000000000000000cccc99cccc99cccc99cccc99000000000000
+000000000000cccc99000000cccc99cccc99000000000000000000000000000000cccc99
+cccc99cccc99cccc99cccc99cccc99000000000000000000cccc99cccc99cccc99cccc99
+cccc99cccc99000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000
+000000000000cccc99cccc99cccc99cccc99cccc99000000000000000000000000cccc99
+000000cccc99000000000000cccc99000000000000000000cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99000000cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00999900cccc00cccc00cccc00cccc00cccc00
+cccc00cccc00cccc00cccc00cccc00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99666666ffffccffffccffffccffffccffffccffffccffffccffffccffffcc
+ffffcccccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99ffffcc666666666666666666666666666666666666666666666666666666666666
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99000000000000000000000000cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99cccc99
+cccc99cccc99000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000
+
+showpage
+
+% stop using temporary dictionary
+end
+
+% restore original state
+origstate restore
+
+%%Trailer
diff --git a/docs/main_window_3.gif b/docs/main_window_3.gif
new file mode 100644
index 0000000..d2dc9a5
Binary files /dev/null and b/docs/main_window_3.gif differ
diff --git a/docs/main_window_3.png b/docs/main_window_3.png
new file mode 100644
index 0000000..51ee849
Binary files /dev/null and b/docs/main_window_3.png differ
diff --git a/docs/main_window_4.eps b/docs/main_window_4.eps
new file mode 100644
index 0000000..a003f66
--- /dev/null
+++ b/docs/main_window_4.eps
@@ -0,0 +1,14624 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: /home/kmr/powmap/docs/main_window_4.ps
+%%Creator: XV Version 3.10a Rev: 12/29/94 (PNG patch 1.2) - by John Bradley
+%%BoundingBox: 36 256 313 411
+%%Pages: 1
+%%DocumentFonts:
+%%EndComments
+%%EndProlog
+
+%%Page: 1 1
+
+% remember original state
+/origstate save def
+
+% build a temporary dictionary
+20 dict begin
+
+% define string to hold a scanline's worth of data
+/pix 1665 string def
+
+% define space for color conversions
+/grays 555 string def % space for gray scale line
+/npixls 0 def
+/rgbindx 0 def
+
+% lower left corner
+36 256 translate
+
+% size of image (on paper, in 1/72inch coords)
+277.48800 154.51200 scale
+
+% define 'colorimage' if it isn't defined
+% ('colortogray' and 'mergeprocs' come from xwd2ps
+% via xgrab)
+/colorimage where % do we know about 'colorimage'?
+ { pop } % yes: pop off the 'dict' returned
+ { % no: define one
+ /colortogray { % define an RGB->I function
+ /rgbdata exch store % call input 'rgbdata'
+ rgbdata length 3 idiv
+ /npixls exch store
+ /rgbindx 0 store
+ 0 1 npixls 1 sub {
+ grays exch
+ rgbdata rgbindx get 20 mul % Red
+ rgbdata rgbindx 1 add get 32 mul % Green
+ rgbdata rgbindx 2 add get 12 mul % Blue
+ add add 64 idiv % I = .5G + .31R + .18B
+ put
+ /rgbindx rgbindx 3 add store
+ } for
+ grays 0 npixls getinterval
+ } bind def
+
+ % Utility procedure for colorimage operator.
+ % This procedure takes two procedures off the
+ % stack and merges them into a single procedure.
+
+ /mergeprocs { % def
+ dup length
+ 3 -1 roll
+ dup
+ length
+ dup
+ 5 1 roll
+ 3 -1 roll
+ add
+ array cvx
+ dup
+ 3 -1 roll
+ 0 exch
+ putinterval
+ dup
+ 4 2 roll
+ putinterval
+ } bind def
+
+ /colorimage { % def
+ pop pop % remove 'false 3' operands
+ {colortogray} mergeprocs
+ image
+ } bind def
+ } ifelse % end of 'false' case
+
+
+
+555 309 8 % dimensions of data
+[555 0 0 -309 0 309] % mapping matrix
+{currentfile pix readhexstring pop}
+false 3 colorimage
+
+000000010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+000000ffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffff000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffff000000ffffffffffffffffff000000ffffff000000
+000000ffffffffffff000000ffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000000000ffffffffffff000000ffffff000000ffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffff000000000000000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffff000000000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffff000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+000000ffffff000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffcc000000000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffcc000000ccccffffffcc
+000000ffffff000000ffffccccccffffffcc000000000000ccccffffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffff000000ccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ccccccffffff000000cccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ccccccffffffccccccffffff000000ffffcc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffcc
+000000ccccffffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffcc
+000000ffffff000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ccccccffffffccccccffffffccccccffffff
+000000ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc000000
+000000000000ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc
+000000ccccffffffccccccffffffcc000000000000ccccffffffccccccffffffcc000000
+ffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ccccffffffcc000000ccccffffffccccccffffffccccccffffffcc000000
+ffffffccccccffffff000000ffffcc000000000000ccccffffffccccccffffffcc000000
+ffffffccccccffffff000000ffffcc000000ffffffccccccffffff000000ffffcc000000
+000000ccccffffffcc000000ffffff000000ccccccffffff000000ccccccffffffcccccc
+ffffff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ccccffffffccccccffffffcc000000ccccffffffccccccffffffcc000000
+ccccffffffcc000000000000ccccffffffcc000000ffffff000000ffffcc000000ccccff
+ffffccccccffffffccccccffffffcc000000ffffff000000ccccccffffff000000cccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ffffcc000000ffffffcccccc
+ffffff000000ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffcc000000ffffff000000ffffccccccffffffccccccffffffcc000000ffffff
+000000ccccccffffffccccccffffffccccccffffff000000ffffcc000000000000000000
+ccccffffffccccccffffffccccccffffffcc000000ccccffffffcc000000ffffff000000
+ccccccffffff000000ccccccffffff000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ccccccffffffccccccffffff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+ffffff000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffcc000000ccccffffffccccccffffffcc000000ccccffffffcc000000ccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000000000ccccccffffff000000000000
+ccccccffffff000000ccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffcc000000000000ffffffccccccffffffccccccffffffccccccffffff000000cccccc
+ffffffccccccffffffccccccffffff000000ffffcc000000cccccc666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666cccccc000000
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+000000cccccc000000ccccff000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ccccff000000000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffff000000ffffccccccff
+000000cccccc000000ffffffccccccffffff000000000000ffffccccccffffffccccccff
+ffffccccccff000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc000000ffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffcccccc000000ccccff
+000000ffffccccccffffffccccccffffffccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ccccff
+000000ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccccccff
+000000cccccc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff000000
+000000000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+000000ffffccccccffffffccccccff000000000000ffffccccccffffffccccccff000000
+ffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffccccccff000000ffffccccccffffffccccccffffffccccccff000000
+ffffccccccffffffcc000000ccccff000000000000ffffccccccffffffccccccff000000
+ffffccccccffffffcc000000ccccff000000ffffccccccffffffcc000000ccccff000000
+000000ffffccccccff000000cccccc000000ffffffcccccc000000ffffffccccccffffff
+cccccc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffccccccffffffccccccff000000ffffccccccffffffccccccff000000
+ffffccccccff000000000000ffffccccccff000000cccccc000000ccccff000000ffffcc
+ccccffffffccccccffffffccccccff000000cccccc000000ffffffcccccc000000ffffff
+ccccccffffffccccccffffffccccccffffffcccccc000000ccccff000000ffffccccccff
+ffffcc000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffff000000cccccc000000ffffffccccccffffffccccccffffff000000cccccc
+000000ffffffccccccffffffccccccffffffcccccc000000ccccff000000000000000000
+ffffccccccffffffccccccffffffccccccff000000ffffccccccff000000cccccc000000
+ffffffcccccc000000ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+cccccc000000ccccff000000ffffccccccffffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000
+ccccff000000ffffccccccffffffccccccff000000ffffccccccff000000ffffccccccff
+ffffccccccff000000ffffccccccffffffccccccffffffccccccffffffccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000000000ffffffcccccc000000000000
+ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffff
+ccccccffffffccccccffffffcccccc000000ccccff000000cccccc666666999999999999
+999999999999999999999999ffffffcccccc999999999999999999999999999999999999
+ffffff9999cc000000
+000000ffffffccccccffffffffffccccccffffffccffffffccccccffffffccccccffffff
+000000ffffff000000ffffff000000ccccffffffccccccffffffccffffffccccccffffff
+ccccccffffff000000ffffff000000000000ccccccffffffccccccffffffffffccccccff
+ffffccffffffccccccffffffccccccffffff000000ccccffffffccccccffffffccffffff
+ccccccffffffccccccffffff000000ccccccffffffccccccffffffffffccccccffffffcc
+ffffffccccccffffffccccccffffff000000ccccffffffccccccff000000ccccccffffff
+000000ffffff000000ffffffccccccffffff000000000000ffffccffffffccccccffffff
+ccccccffffff000000ccccffffffccccccff000000ffffccccccffffffccffffffcccccc
+ffffffffffccccccffffffccffffffccccccffffff000000ccccccffffff000000ffffcc
+ccccffffffccffffffccccccffffffffffccccccffffffccffffffccccccffffffffffcc
+ccccffffffccccccffffffccffffff000000ccccccffffffccccccffffff000000ffffff
+000000ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff
+ccccccffffffffffccccccffffffccffffffccccccffffffffffccccccffffffccffffff
+ccccccffffffffffccccccffffffccffffffccccccffffffccccccffffff000000ffffff
+000000ffffccccccffffffccccccffffffccffffff000000ccccccffffffccccccffffff
+000000ffffff000000ccccccffffffffffccccccffffffccffffffccccccffffffffffcc
+ccccffffffccffffffccccccffffff000000ffffccccccffffffccccccffffffccffffff
+000000ccccccffffffccccccffffffffffccccccff000000ffffccccccffffffcc000000
+000000000000ffffccffffffccccccffffffccccccffffff000000ccccffffffccccccff
+000000ffffccccccffffffccffffff000000000000ccccccffffffffffccccccff000000
+ccccffffffccccccff000000ccccffffffccccccffffffccffffffccccccffffffcccccc
+ffffff000000ccccccffffff000000ccccccffffffccccccffffffffffccccccff000000
+ffffffccccccffffff000000ffffff000000000000ccccccffffffffffccccccff000000
+ffffffccccccffffff000000ffffff000000ffffffccccccffffff000000ffffff000000
+000000ccccccffffff000000ffffff000000ffffccffffff000000ccccccffffffcccccc
+ffffff000000ffffffccccccffffffffffccccccffffffccffffffccccccffffffcccccc
+ffffff000000ccccccffffffccccccffffff000000ffffccccccffffffccffffff000000
+ccccccffffff000000000000ccccccffffff000000ffffff000000ffffff000000ffffcc
+ffffffccccccffffffccccccffffff000000ffffff000000ccccccffffff000000ffffcc
+ccccffffffccffffffccccccffffffccccccffffff000000ffffff000000ffffffcccccc
+ffffff000000ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffff
+ccccccffffff000000ffffff000000ffffffccccccffffffccccccffffff000000ffffff
+000000ffffccffffffccccccffffffccccccffffff000000ffffcc000000000000000000
+ffffccccccffffffccccccffffffccffffff000000ccccccffffff000000ffffff000000
+ffffccffffff000000ccccccffffff000000ffffccccccffffffccffffffccccccffffff
+ffffccccccffffffccccccffffffccffffff000000ccccccffffffccccccffffff000000
+ffffffccccccffffffffffccccccffffffccffffffccccccffffffccccccffffff000000
+ffffff000000ffffff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffccffffffccccccffffffccccccffffff000000
+ffffff000000ffffccccccffffffccffffff000000ccccccffffff000000ccccccffffff
+ccccccffffff000000ffffffccccccffffffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccffffffccccccffffff000000000000ccccccffffff000000000000
+ccccccffffff000000ccccccffffffccccccffffffffffccccccff000000ffffccccccff
+ffffcc000000000000ffffffccccccffffffccccccffffffccccccffffff000000cccccc
+ffffffccccccffffffccccccffffff000000ffffcc000000cccccc666666999999cccc99
+9999cc999999999999999999cccccccccccc999999cccc999999cc999999999999999999
+ffffcccccccc000000
+000000ffffccccccffffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+000000cccccc000000cccccc000000ffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffcc000000ffffcc000000000000ffffffffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffffccccccffffff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccff000000ffffccccccffffffcc000000ffffffcccccc
+000000cccccc000000ccccffffffccccccff000000000000ccccffffffffccccccffffff
+ccccccffffff000000ffffffccccccffffff000000ffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc000000ffffff
+ccccccffffffccccffffffccccccffffffffccccccffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffccccccff000000ffffffccccccffffffcccccc000000cccccc
+000000ffffffccccccffffffccccffffffccccccffffffccffffffccccffffffccccccff
+ffffffccccccffffffccccccffffffccccffffffccccccffffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffffccccccffffffccccccffffffcccccc000000cccccc
+000000ffffffccccffffffccccccffffffccccccff000000ffffffccccccffffffcccccc
+000000ffffcc000000ffffffccccccffffffccccffffffccccccffffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff000000
+000000000000ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+000000ffffffccccccffffffcccccc000000000000ffffffccccffffffccffffff000000
+ffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccffffffccccccff
+ffffcc000000ffffffcccccc000000ffffffccccffffffccccccffffffccccccff000000
+ffffccccccffffffcc000000ffffcc000000000000ffffffccccffffffccccccff000000
+ffffccccccffffffcc000000cccccc000000ccccffffffccccccff000000ffffcc000000
+000000ffffffcccccc000000cccccc000000ccccffffffcc000000ffffffccccccffffff
+cccccc000000ffffffccccccffffffccccccffffffccccffffffccccccffffffccccccff
+ffffcc000000ffffffccccffffffccccccff000000ffffffccccccffffffcccccc000000
+ffffffcccccc000000000000ffffffcccccc000000cccccc000000cccccc000000ccccff
+ffffffccccccffffffffffccccccff000000ffffcc000000ffffffcccccc000000ffffff
+ccccccffffffccccffffffccccccffffffccccccff000000cccccc000000ccccffffffcc
+ccccff000000ffffffccccccffffffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffff000000cccccc000000ffffffccccccffffffccccccffffff000000cccccc
+000000ccccffffffffccccccffffffffffccccccff000000ffffff000000000000000000
+ffffffccccffffffccccccffffffccccccff000000ffffffcccccc000000cccccc000000
+ccccffffffcc000000ffffffcccccc000000ffffffccccccffffffccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccffffffccccccffffffccccccffffffcc000000
+cccccc000000cccccc000000ffffffccccccffffffccccffffffccccccffffffccffffff
+ccccffffffccccccffffffffccccccffffffccccffffffccccccffffffccccccff000000
+cccccc000000ffffffccccffffffccccccff000000ffffffcccccc000000ffffffccccff
+ffffccccccff000000ffffffccccccffffffccccffffffccccccffffffccccccffffffff
+ffffccccccffffffccccccffffffccccccff000000000000ffffffcccccc000000000000
+ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffff000000000000ffffffccccccffffffffffccccccffffffccccccff000000ffffff
+ccccccffffffffffccccccffffffcc000000ccccff000000cccccc6666669999cc999999
+999999cccc999999ccffffcccccccccccccc666666999999999999cccc999999cc999999
+ccccffcccccc000000
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+000000ffffff000000ffffcc000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ffffff000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffccccccffffffffccccccffffff
+ccccffffffccccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccff
+ffffffccccccffffffccccccffffff000000ffffccccccffffffff000000ccccccffffff
+000000ffffff000000ffffccccccffffffcc000000000000ccccffffffccccccffffffcc
+ccccffffffcc000000ccccccffffffcccccc000000ffffccccccffffffccccccffffffcc
+ccccffffffffccccccffffffccccccffffffffffcc000000ccccccffffff000000cccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffcc000000ffffccccccffffffccffffff000000ffffcc
+000000ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff000000ffffff
+000000ccccccffffffccccccffffffccccccffffff000000ffffccffffffccccccffffff
+000000ccccff000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffff000000ccccccffffffccccffffffccccccffffffff
+000000ccccccffffffccccffffffccccccffffffff000000ffffffccccccffffff000000
+000000000000ccccccffffffccccffffffccccccffffffcc000000ccccffffffccccccff
+000000ccccccffffffccccccffffff000000000000ccccccffffffccccccccccff000000
+ffffccccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ffffffffffff000000ffffccccccffffffccccccffffffccffffff000000
+ccccffffffccccccff000000ffffff000000000000ccccccffffffccccccffffff000000
+ffffffccccccffffff000000ffffcc000000ccccffffffccccccff000000ffffff000000
+000000ccccccffffff000000ffffff000000ffffccccccff000000ccccccffffffccccff
+ffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccffffffccccccff
+ffffff000000ccccffffffccccccffffffcc000000ccccffffffffccccccffffff000000
+ffffffffffff000000000000ccccccffffff000000ffffff000000ffffff000000cccccc
+ffffffccccccffffffccccccffffff000000ccccff000000ccccffffffff000000ffffcc
+ccccffffffccccccffffffccccccffffffccffffff000000ffffff000000ffffccccccff
+ffffcc000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffff000000ffffcc000000ffffffccccccffffffccccccffffff000000ffffff
+000000ccccffffffccccccffffffccccccffffffcc000000ffffcc000000000000000000
+ccccffffffccccccffffffccccccffffffcc000000ccccccffffff000000ffffff000000
+ffffccccccff000000ccccccffffff000000ccccffffffccccccffffffccccccffffffff
+ccccccffffffccccffffffccccccffffffcc000000ccccffffffccccccffffffcc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffff000000ffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff000000
+ffffff000000ccccffffffccccccffffffcc000000ffffccffffff000000ccccffffffcc
+ccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffffccccccffffffffffcc000000000000ffffccffffff000000000000
+ffffccffffff000000ccccccffffffccccffffffccccccffffffff000000ffffccccccff
+ffffcc000000000000ffffffccccccffffffccccffffffccccccffffffcc000000ccccff
+ffffccccccffffffffccccccffffff000000ffffcc000000cccccc666666999999999999
+999999999999999999cccccc9999cccccccccccccc9999cc999999999999cccc99999999
+cccccccccccc000000
+000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+000000ffffff000000ffffff000000ffffccccccffffffccccccffffffccccccffffffff
+ccccccffffff000000cccccc000000000000ccccccffffffccccccffffffccccccffffff
+ccccffffffccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccffffffccccccffffff000000ffffffccccccffffcc000000ffffffcccccc
+000000cccccc000000ffffffccccccffffff000000000000ffffccccccffffffccccccff
+ffffccccccff000000ffffffccccccffffff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffffccccccffffff000000ffffffcccccc000000ffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffff000000ffffffccccccffffffccccff000000ffffff
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccffffffccccffffffccccccff000000cccccc
+000000ffffffffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffcc
+000000ffffff000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffffffccccccffffffccccccffffffcc000000ccccccffffffcccccc000000
+000000000000ffffffccccccffffffccccccffffffccccff000000ffffccccccffffffcc
+000000ffffffccccccffffffcccccc000000000000ffffffccccccffffffffffcc000000
+ccccffffffffcccccc000000ffffffccccccffffffccccccffffffffffccccccffffffff
+cccccc000000ccccccffffff000000ffffffccccccffffffccccccffffffcccccc000000
+ffffffccccccffffff000000cccccc000000000000ffffffccccccffffffcccccc000000
+ccccffffffccccccff000000ffffff000000ffffccccccffffffcc000000cccccc000000
+000000ffffffcccccc000000ccccff000000ffffffcccccc000000ffffffffffccccccff
+ffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+cccccc000000ffffccccccffffffccccccff000000ffffccccccffffffccccccff000000
+ccccccffffff000000000000ffffffcccccc000000ffffcc000000ffffcc000000ffffff
+ccccccffffffccccccffffffcccccc000000ffffcc000000ffffccccccff000000ccccff
+ffffccccccffffffccffffffccccccffffffcccccc000000cccccc000000ffffffcccccc
+ffffff000000ffffccccccffffffffccccccffffffccccff000000ffffccccccffffffcc
+ccccffffffcc000000ccccff000000ffffccccccffffffccccccffffffcc000000ffffcc
+000000ffffccccccffffffffccccccffffffccccff000000ccccff000000000000000000
+ffffccccccffffffffccccccffffffccccff000000ffffffcccccc000000cccccc000000
+ffffffcccccc000000ffffffcccccc000000ffffccccccffffffccccccffffffccccccff
+ffffccffffffccccccffffffccccccffffff000000ffffccccccffffffffcccccc000000
+ccccffffffccccccffffffccccccffffffccccccffffffffccccccffffffcccccc000000
+ccccff000000ccccff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffffccccccffffffcccccc000000
+cccccc000000ffffccccccffffffccccccff000000ccccffffffcc000000ffffccccccff
+ffffccccccff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffccccff000000000000ccccffffffcc000000000000
+ccccffffffcc000000ffffffccccccffffffccccccffffffcccccc000000ccccffffffff
+cccccc000000000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffff
+ccccccffffffccccccffffffcccccc000000ccccff000000cccccc666666cccc999999cc
+999999999999ffffffcccccccccc99cccccccccccc6666669999999999999999cc999999
+ffffffcccccc000000
+000000ccccccffffffccccccffffffffffccccccffffffccccccffffffffccccccffffff
+000000cccccc000000ffffff000000ccccffffffccccccffffffccffffffccccccffffff
+ccccccffffff000000ffffff000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffffffff000000ffffffccccccffffffccccccffffff
+ffffccccccffffffccccccff000000ccccffffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ccccccffffffccccff000000ccccccffffff
+000000ffffff000000ffffffccccccffffff000000000000ffffccffffffccccffffffcc
+ccccffffffcc000000ffffccccccffffffcc000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ccccccffffff000000ffffcc
+ccccffffffccccccffffffccccccffffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ccccccffffffccccccffffcc000000ccccff
+000000ccccffffffccffffffccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffff
+000000ccccffffffffccccccffffffccccccffffff000000ccccffffffffccccccffffff
+000000cccccc000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ccccccffffffffffccccccffffffccffffff
+000000ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffff000000
+000000000000ffffccffffffccccccffffffccccccffffff000000ccccffffffccccccff
+000000ccccffffffffccccccffffff000000000000ccccccffffffccccccffffff000000
+ffffffccccccffffff000000ccccffffffccccccffffffccccccffffffccccccffffffcc
+ffffff000000ffffffcccccc000000ccccffffffccccccffffffccccccffffffcc000000
+ffffffccccccffffff000000ffffff000000000000ccccccffffffccccccffffff000000
+ffffccccccffffffcc000000ccccff000000ffffffccccccffffff000000ffffff000000
+000000ffffccffffff000000ffffcc000000ffffccffffff000000ccccccffffffcccccc
+ffffff000000ccccccffffffccccccffffffccccccffffffffffccccccffffffccccccff
+ffffff000000ffffccffffffccccccffffff000000ccccffffffccccccffffffcc000000
+ffffffcccccc000000000000ccccccffffff000000ccccff000000ccccff000000cccccc
+ffffffccccccffffffccccccffffff000000ffffff000000ccccffffffcc000000cccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ffffff000000ccccffffffcc
+ccccff000000ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffff
+ccccccffffff000000ffffff000000ccccffffffccccccffffffccccccff000000ccccff
+000000ccccffffffccccccffffffccccccffffffcc000000ffffcc000000000000000000
+ffffffccccccffffffccccccffffffffffcc000000ffffccffffff000000ffffff000000
+ffffffffffff000000ccccffffffff000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccffffff000000
+ccccffffffccccccffffffccffffffccccccffffffccccccffffffccccccffffff000000
+ffffcc000000ffffff000000ccccccffffffffffccccccffffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffff000000ccccccffffffccccccffffff000000ccccccffffff000000ccccccffffff
+ccccccffffff000000ffffffccccccffffffffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffffffcc000000000000ccccffffffff000000000000
+ffffffccccff000000ffffccccccffffffccffffffccccccffffff000000ffffccccccff
+ffffff000000000000ccccccffffffccccccffffffccccccffffffccccff000000cccccc
+ffffffccccccffffffccccccffffff000000ffffcc000000cccccc666666999999cccc99
+9999999999999999cccccccc9999cccccc99cccccccccccc999999cccc99999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccffffffccccccffffffccffffffccccccffffffcccccc
+000000ffffff000000cccccc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffcc000000000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcccccccc000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffccccccffffffffccccccffffffccccccffffff
+ccccccffffffccccffffffccccccff000000ffffffccccccffffff000000ffffffcccccc
+000000cccccc000000ccccccffffffcccccc000000000000ccccffffffccccccffffffcc
+ccccffffffff000000ffffffccccccffffff000000ffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc000000ffffff
+ccccccffffffccccccffffffffffccccccffffffccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffffffcc000000ffffffccccccffffffccccff000000ffffcc
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccffffffccccff
+ffffccccccffffffffffffccccccffffffccccccffffffccccccffffffccffffffcccccc
+ffffffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000cccccc
+000000ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccccccff
+000000ffffff000000ffffffccccccffffffccccccffffffffffccccccffffffffffffcc
+ccccffffffccffffffccccccffffff000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff000000
+000000000000ccccffffffccccccffffffccffffffcccccc000000ffffccccccffffffcc
+000000ffffccccccffffffccccccff000000000000ffffffccccccffffffcccccc000000
+ffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffccccccff000000ffffccccccffffffffccccccffffffccccff000000
+ccccccffffffcccccc000000ffffcc000000000000ffffffccccccffffffcccccc000000
+ffffffccccccffffff000000ffffcc000000ffffccccccffffffcc000000ccccff000000
+000000ccccffffffcc000000ccccff000000ccccffffffcc000000ffffffccccccffffff
+cccccc000000ffffffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ccccffffffccccccffffffcc000000ffffccccccffffffccccccff000000
+ffffccccccff000000000000ffffffcccccc000000ffffff000000ffffcc000000ffffff
+ccccffffffccccccffffffccccccff000000cccccc000000ffffccffffff000000ffffff
+ccccccffffffccccffffffccccccffffffccccccff000000cccccc000000ffffccccccff
+ffffcc000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffff000000cccccc000000ffffffccccccffffffccccccffffff000000ffffcc
+000000ffffccccccffffffccccccffffffccccccff000000ccccff000000000000000000
+ffffccccccffffffccccccffffffccccccff000000ccccffffffcc000000cccccc000000
+ccccccffffcc000000ffffccccccff000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffcc000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffffffccccccffffffccccccff000000
+ffffff000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffffffcc
+ccccffffffccffffffccccccffffffccccccffffffccccffffffccccccffffffcc000000
+ffffff000000ffffffccccccffffffcccccc000000ffffffcccccc000000ffffffcccccc
+ffffffcccccc000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffcc000000000000ffffccccccff000000000000
+ffffccccccff000000ffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffcc000000000000ffffffccccccffffffffffccccccffffffccffffff000000ffffff
+ccccccffffffccccccffffffcccccc000000ccccff000000cccccc6666669999cc999999
+999999ffffffcccc99cccccccccccc9999cccccccccccccc666666999999999999cccc99
+ccccffcccccc000000
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+000000ffffff000000ffffcc000000ffffffccccccffffffccccccffffffffffccccccff
+ffffccccccff000000ffffff000000000000ffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffff000000ccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccff000000ffffccccccffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffffffffcc000000ffffccccccffffffcc000000ccccccffffff
+000000ffffff000000ffffffccccccffffff000000000000ccccffffffccffffffcccccc
+ffffffcccccc000000ccccccffffffcccccc000000ffffffccccccffffffccccffffffcc
+ffffffccccccffffffccccccffffffccccccffffff000000ccccffffffff000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffffccccff000000ccccccffffffccccccffffff000000ffffff
+000000ccccffffffccccccffffffccccccffffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffffffccccccffffffccccccffffffcc
+ccccffffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffff
+000000ffffccccccffffffccccccffffffccffffff000000ccccffffffccccccffffffcc
+000000cccccc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ccccccffffffccccccffffffccccccffffff
+000000ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc000000
+000000000000ccccccffffffccccccffffffccccccffffff000000ffffffffffccccccff
+000000ffffffffffccccccffffffcc000000000000ccccffffffccccccffffffff000000
+ffffccccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ffffccffffff000000ccccccffffffccccccffffffccccccffffff000000
+ffffffccccccffffff000000ffffff000000000000ffffccffffffccccccffffff000000
+ccccccffffffcccccc000000ccccff000000ffffffccccccffffff000000ffffcc000000
+000000ccccccffffff000000ffffff000000ffffccccccff000000ccccffffffffcccccc
+ffffff000000ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ccccccffffffccccccffffff000000ccccffffffffccccccffffff000000
+ffffccffffff000000000000ccccccffffff000000ffffff000000ccccff000000ffffcc
+ffffffccccccffffffccccccffffff000000ffffff000000ffffffcccccc000000ccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffff000000ffffffcccccc
+ffffff000000ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffff000000ffffcc000000ffffffccccccffffffccccccffffff000000ccccff
+000000ffffccccccffffffccffffffccccccffffff000000ffffcc000000000000000000
+ccccccffffffccccccffffffccccccffffff000000ccccccffffff000000ffffff000000
+ffffffccccff000000ccccffffffcc000000ccccffffffccffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccccccff000000ccccffffffccccccffffffcc000000
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffcc000000
+ccccff000000ccccff000000ccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccffffffccccccffffffccccccffffffccccccff000000
+cccccc000000ffffccccccffffffccffffff000000ccccffffffff000000ffffccccccff
+ffffccffffff000000ccccffffffccccccffffffccccccffffffffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffff000000000000ccccffffffcc000000000000
+ccccffffffcc000000ccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc000000ccccff
+ffffccccccffffffccccccffffffff000000ffffcc000000cccccc999966999999999999
+999999cccccc9999cccccccccccccccccc99cccccccccccccccccc9999cc999999999999
+cccccccccccc000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+000000cccccc000000ccccff000000ffffccccccffffffffccccccffffffccccccffffff
+ccccccffffff000000cccccc000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffffccccccffffff000000ffffffccccccffffffccccffffffcc
+ccccffffffccffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffcc
+ccccffffffccccccffffffccccccff000000ffffffccccccffffff000000ffffffcccccc
+000000cccccc000000ffffffccccccffffff000000000000ffffccccccffffffccccccff
+ffffccffffff000000ffffffccccccffffff000000ccccccffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffccccccff000000ffffccccccff000000ffffff
+ccccccffffffccccffffffccccccffffffccccccffffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc000000cccccc
+000000ffffffccccccffffffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccff000000cccccc
+000000ffffffccccffffffccccccffffffccccccff000000ffffccccccffffffccccccff
+000000ffffff000000ffffccccccffffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffffffff000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffffffccccccffffffccccccffffffcc000000ccccffffffccccccff000000
+000000000000ffffffccccccffffffccccccffffffcccccc000000ccccccffffffcccccc
+000000ccccffffffccccccffffffcc000000000000ffffccccccffffffccccccff000000
+ffffffccccccffffff000000ffffccccccffffffffccccccffffffccccccffffffcccccc
+ffffff000000ccccffffffcc000000ffffffccccccffffffffffccccccffffffcc000000
+ffffccccccffffffcc000000cccccc000000000000ccccffffffccccccffffffcc000000
+ffffffccccccffffff000000ffffcc000000ccccffffffccccccff000000ffffff000000
+000000ffffffcccccc000000cccccc000000ffffffcccccc000000ffffccccccffffffcc
+ccccff000000ffffccccccffffffccccccffffffccccccffffffffffffccccccffffffcc
+ffffff000000ffffffccccccffffffcccccc000000ffffccccccffffffccccccff000000
+ccccffffffcc000000000000ffffffcccccc000000cccccc000000ffffcc000000ccccff
+ffffccccccffffffccccccffffffcc000000cccccc000000ccccffffffcc000000ffffff
+ccccccffffffccccccffffffccccccffffffccccff000000cccccc000000ffffffcccccc
+ffffff000000ffffccccccffffffccffffffccccccffffff000000ffffccccccffffffff
+ccccccffffff000000ccccff000000ffffccccccffffffccccccffffffcc000000ffffcc
+000000ffffffccccccffffffccccccffffffcccccc000000ccccff000000000000000000
+ffffffccccccffffffccccccffffffcccccc000000ffffffcccccc000000cccccc000000
+ffffccccccff000000ffffccccccff000000ffffffccccccffffffccccccffffffccccff
+ffffccccccffffffccccccffffffccffffff000000ffffccccccffffffccccccff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+ffffff000000ffffcc000000ffffffccccffffffccffffffccccccffffffccccffffffcc
+ccccffffffffccccccffffffccccccffffffccccccffffffccccccffffffffffcc000000
+ffffff000000ffffffccccccffffffcccccc000000ffffccccccff000000ffffffcccccc
+ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccffffffcc000000000000ffffccccccff000000000000
+ffffccccccff000000ffffffccccccffffffccccccffffffcccccc000000ccccffffffcc
+ccccff000000000000ffffffccccffffffccccccffffffccccccffffffcc000000ffffff
+ccccccffffffffffccccccffffffcc000000ccccff000000cccccc6666669999cc999999
+ffffffcccc99cccccccccc999999cccccccc9999cccccc99cccccc666666999999cccccc
+ffffcccccccc000000
+000000ffffccccccffffffccccccffffffffccccccffffffccccccffffffccccccffffff
+000000ffffff000000ffffff000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffff000000000000ccccffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffccccccffffffcc000000ccccffffffccccccffffffccccccff
+ffffccccccffffffccffffff000000ccccffffffccccccffffffccffffffccccffffffcc
+ffffffccccccffffffccccccffffff000000ccccccffffffcccccc000000ccccccffffff
+000000ffffff000000ffffccccccffffffcc000000000000ffffffccccccffffffcccccc
+ffffffcccccc000000ffffffccccccffffff000000ccccffffffccccccffffffccccccff
+ffffffccccccffffffccccffffffccccccffffffcc000000ccccffffffcc000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccccffffff000000ccccccffffffccccccffffff000000ffffff
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccffffffccccccffffffccccccffffffccccccffffff000000ffffff
+000000ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffffffffcc
+000000ccccff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffffcccccc000000ccccffffffccccccffffffffccccccffffff
+000000ccccffffffccccccffffffffccccccffffff000000ffffccccccffffffcc000000
+000000000000ccccccffffffccccccffffffccccccffffff000000ccccffffffffffffcc
+000000ccccffffffccccccffffffff000000000000ccccffffffccccccffffffcc000000
+ccccffffffccccccff000000ccccccffffffffffccccccffffffccccccffffffccccccff
+ffffcc000000ffffccccccff000000ffffccccccffffffccccccffffffccccccff000000
+ffffffccccccffffff000000ffffff000000000000ccccffffffffccccccffffff000000
+ccccffffffccccccff000000ffffff000000ffffccccccffffffcc000000ffffcc000000
+000000ccccccffffff000000ffffff000000ffffccffffff000000ffffccffffffcccccc
+ffffff000000ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ccccccffffffccccccffffff000000ffffffffffccccccffffffcc000000
+ccccccffffff000000000000ccccccffffff000000ffffff000000ffffff000000cccccc
+ffffffccccccffffffccccccffffff000000ffffff000000ccccccffffff000000cccccc
+ffffffccccccffffffffffccccccffffffccffffff000000ffffff000000ccccffffffcc
+ccccff000000ffffffccccccffffffccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffcc000000ffffff000000ccccffffffccccccffffffccccccff000000ccccff
+000000ccccccffffffccccccffffffccccccffffff000000ffffff000000000000000000
+ffffccffffffccccccffffffccccccffffff000000ccccccffffff000000ffffff000000
+ffffccffffff000000ccccffffffcc000000ccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ccccffffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffffccccccffffffccccffffffccccccff000000
+ffffff000000ccccff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffffffccccccffffffccccccffffffccccccff000000
+ccccff000000ccccccffffffccccccffffff000000ccccffffffcc000000ccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000000000ccccffffffcc000000000000
+ccccffffffcc000000ffffffccccccffffffffffffccccccffffff000000ffffccccccff
+ffffcc000000000000ccccffffffccccccffffffccccccffffffccccccff000000cccccc
+ffffffccccccffffffccccccffffff000000ffffcc000000cccccc666666999999999999
+cccccc9999cccccccccccccccccc99cccccccccccccccccccccccccccccc999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccccffffffccccccffffffffffffccccccffffffcccccc
+000000cccccc000000cccccc000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000cccccc000000000000ffffccccccffffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccccccff000000ffffccffffffccccccffffffcccccc
+ffffffccccccffffffcccccc000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffffccccccffffffcccccc000000ffffffccccccffffff000000ffffffcccccc
+000000cccccc000000ffffffccccccffffff000000000000ccccffffffccccccffffffcc
+ccccffffffff000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccff000000ffffccccccff000000ffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffcccccc000000cccccc
+000000ffffccccccffffffccccccffffffccffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffffccccccffffffccccccffffff000000cccccc
+000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccff
+000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff000000
+000000000000ffffffccccccffffffccccffffffccffffff000000ffffccccccffffffcc
+000000ffffffccccccffffffcccccc000000000000ffffccccccffffffccccccff000000
+ffffffccccccffffff000000ffffffccccccffffffccccccffffffffffccccccffffffcc
+ccccff000000ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc000000
+ffffffccccccffffff000000ffffcc000000000000ffffccccccffffffccccccff000000
+ffffccccccffffffcc000000cccccc000000ffffffccccccffffff000000ccccff000000
+000000ffffffcccccc000000cccccc000000ccccffffffcc000000ccccffffffccccccff
+ffffcc000000ffffccccccffffffccffffffccccccffffffccccccffffffccccccffffff
+ffffcc000000ffffffccccccffffffcccccc000000ccccccffffffccccccffffff000000
+ffffffcccccc000000000000ffffffcccccc000000cccccc000000ffffcc000000ffffff
+ccccccffffffffffccccccffffffcc000000ffffcc000000ffffffcccccc000000ffffff
+ccccccffffffccccccffffffccccccffffffcccccc000000cccccc000000ffffccccccff
+ffffcc000000ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffff
+ccccccffffff000000cccccc000000ffffffccccccffffffccccccffffff000000ffffcc
+000000ffffffccccffffffccccccffffffccccccff000000ffffcc000000000000000000
+ccccffffffffccccccffffffffffccccccff000000ffffffcccccc000000ccccff000000
+ccccffffffcc000000ffffccccccff000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffccccccffffffccccccff000000
+ffffccccccffffffccffffffccccccffffffffffccccccffffffccccccffffffcc000000
+cccccc000000ffffcc000000ccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccffffffccccccff000000
+ffffcc000000ffffffccccccffffffcccccc000000ffffccccccff000000ffffffcccccc
+ffffffccccff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000000000ffffccccccff000000000000
+ffffccccccff000000ffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffff
+ccccccffffffccccccffffffcccccc000000ccccff000000cccccc666666999999ffffff
+cccccccccc999999cccccccc9999cccccccc999999cccccccccccccccccc6666669999cc
+ffffcccccccc000000
+000000ccccffffffccccccffffffccffffffccccccffffffccccccffffffccccccffffff
+000000ffffff000000ffffff000000ccccccffffffccccccffffffffffffccccccffffff
+ccccccffffff000000ffffff000000000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffff
+ccccccffffffccccccffffff000000ccccffffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffffccccccffffff000000ccccccffffff
+000000ffffff000000ffffccccccffffffcc000000000000ffffccccccffffffccccccff
+ffffccccccff000000ccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffccffffff000000ccccffffffcc000000ffffcc
+ccccffffffccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffff000000ffffccccccffffffccffffff000000ffffff
+000000ffffffccccccffffffccccffffffccccccffffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffffccccccffffff000000ffffff
+000000ffffffffffccccccffffffccccccffffffcc000000ccccffffffccccccccffffff
+000000ccccff000000ccccffffffccccccffffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccffffffccffffff000000ffffccccccffffffccffffffccccccffffff
+000000ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffff000000
+000000000000ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+000000ccccccffffffccccccffffff000000000000ccccffffffccccccffffffcc000000
+ccccccffffffcccccc000000ffffffccccccffffffccccccffffffccccffffffccccccff
+ffffcc000000ccccffffffff000000ffffccccccffffffccccccffffffccffffff000000
+ccccffffffccccccff000000ffffff000000000000ccccccffffffccccccffffff000000
+ffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000ffffff000000
+000000ccccccffffff000000ffffff000000ccccffffffff000000ffffccccccffffffcc
+ccccff000000ccccccffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ccccff000000ccccffffffccccccffffffff000000ffffccccccffffffccccccff000000
+ccccccffffff000000000000ccccccffffff000000ffffff000000ccccff000000ffffff
+ccccccffffffccccffffffccccccff000000ccccff000000ccccccffffff000000ccccff
+ffffccccccffffffffccccccffffffccccccffffff000000ffffff000000ccccffffffcc
+ccccff000000ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffff000000ffffcc000000ccccffffffccffffffccccccffffff000000ccccff
+000000ffffccccccffffffccffffffccccccffffff000000ffffff000000000000000000
+ccccffffffccccccffffffccccccffffffcc000000ccccccffffff000000ffffcc000000
+ccccccffffff000000ccccccffffff000000ccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccffffff000000ffffccffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffccccccffffffccffffffccccccffffff000000
+ffffff000000ffffff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff000000
+ccccff000000ccccffffffffccccccffffff000000ccccffffffcc000000ccccccffffff
+ccccccffffff000000ffffccccccffffffccffffffccccffffffccccccffffffccffffff
+ccccccffffffccccccffffffccccccffffff000000000000ccccccffffff000000000000
+ccccccffffff000000ccccffffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffcc000000000000ffffffccccffffffccccccffffffffccccccffffff000000ccccff
+ffffffccccccffffffccccccffffff000000ffffcc000000cccccc6666669999999999cc
+cccccccccccccccccccccc99cccccccccccccccccccccc999999cccccc99cccccc9999cc
+cccccccccccc000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+000000cccccc000000ccccff000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffcc000000000000ffffffccccccffffffffffccccccffffffcc
+ccccffffffccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccff000000ffffccccccffffffcc000000ffffffcccccc
+000000ccccff000000ccccffffffccccccff000000000000ffffffccccccffffffcccccc
+ffffffffffcc000000ffffffccccffffffcc000000ffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffffccccccccccff000000ffffccccccff000000ffffff
+ccccccffffffccccffffffccccccffffffccffffffccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc000000cccccc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000cccccc
+000000ccccffffffccccccffffffccccccffffffff000000ffffccccccffffffffcccccc
+000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffffccccccccccff000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffffffccccccffffffccccccffffffcc000000ccccffffffccccccff000000
+000000000000ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc
+000000ffffffccccccffffffcccccc000000000000ffffccccccffffffccccccff000000
+ffffffccccccffffff000000ccccccffffffccccffffffccccccffffffccccccffffffcc
+ccccff000000ffffccccccff000000ffffffccccccffffffccccffffffccccccff000000
+ffffccccccffffffcc000000cccccc000000000000ffffffccccccffffffcccccc000000
+ffffffccccccffffff000000cccccc000000ccccffffffccccccff000000ffffcc000000
+000000ffffffcccccc000000ffffcc000000ffffccccccff000000ffffffccccccffffff
+ffffcc000000ffffffccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ffffffccccccffffffcccccc000000ffffffccccccffffffffffcc000000
+ffffffcccccc000000000000ffffffcccccc000000cccccc000000ffffcc000000ffffff
+ccccccffffffccccccffffffffffcc000000ffffff000000ffffffcccccc000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000cccccc000000ffffffcccccc
+ffffff000000ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffcc000000ffffff000000ffffccccccffffffffccccccffffff000000ffffcc
+000000ffffffccccccffffffccccccffffffcccccc000000cccccc000000000000000000
+ffffccccccffffffccccccffffffffcccccc000000ffffffcccccc000000ffffff000000
+ffffffcccccc000000ffffffcccccc000000ffffffccccccffffffccccffffffccccccff
+ffffffccccccffffffccccccffffffcccccc000000ccccffffffccccccffffffcc000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+cccccc000000cccccc000000ffffffccccccffffffffffccccccffffffccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffcc000000ffffccccccffffffccccccff000000ffffccccccff000000ffffffcccccc
+ffffffcccccc000000ffffffccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffffccccccffffffccccccffffff000000000000ffffffcccccc000000000000
+ffffffcccccc000000ffffffccccccffffffccccccffffffccccff000000ccccffffffcc
+ccccff000000000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffcc
+ccccffffffccccccffffffffcccccc000000ccccff000000cccccc666666ffffcccccccc
+cccc999999cccccccc9999cccccccc999999cccccccccccccccccccccccc9999cc666666
+ffffcccccccc000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffffccccccffffff
+000000ffffff000000ffffcc000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ffffff000000000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffffffccccccff
+ffffccccccffffffccccccff000000ffffccccccffffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccccffffff000000ccccffffffccccccff000000ccccccffffff
+000000ffffcc000000ffffffccccccffffff000000000000ffffccccccffffffccccccff
+ffffccccccff000000ccccffffffccccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccffffff000000ccccccffffff000000cccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccff000000ccccffffffffccccccffffff000000ffffff
+000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffffcc
+ccccffffffccccccffffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccffffffccccccffffffccccccffffffccccccffffff000000ffffff
+000000ffffccccccffffffccccccffffffccccccff000000ccccccffffffccccccffffff
+000000ffffff000000ccccccffffffffffccccccffffffccccccffffffffccccccffffff
+ccccccffffffccccccffffffffffcc000000ccccccffffffccccccffffffccccccffffff
+000000ccccffffffccccccffffffffccccccffffff000000ffffccccccffffffcc000000
+000000000000ccccccffffffccccccffffffccccccffffff000000ccccffffffccccccff
+000000ffffffccccccffffffffffff000000000000ffffffccccccffffffffffcc000000
+ffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ccccffffffcc000000ccccccffffffccccccffffffccccccffffff000000
+ffffffccccccffffff000000ffffff000000000000ccccffffffccccccffffffcc000000
+ffffffccccccffffff000000ffffff000000ffffffccccccffffff000000ffffff000000
+000000ccccccffffff000000ccccff000000ccccffffffcc000000ffffffccccccffffff
+ccccff000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ccccccffffffccccccffffff000000ffffccccccffffffccccccff000000
+ccccffffffff000000000000ffffffffffff000000ffffff000000ccccff000000ffffcc
+ccccffffffccccccffffffccccccff000000ffffcc000000ccccccffffff000000ffffcc
+ccccffffffccccccffffffccccccffffffccffffff000000ffffcc000000ffffffcccccc
+ffffff000000ccccccffffffccccccffffffccccccffffff000000ccccffffffffcccccc
+ffffffccccff000000ccccff000000ccccffffffccccccffffffccccccff000000ccccff
+000000ccccccffffffccccccffffffccccccffffff000000ffffff000000000000000000
+ffffffccccccffffffccccccffffffffffff000000ccccccffffff000000cccccc000000
+ccccffffffff000000ccccccffffff000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffffccccccffffff000000ffffffccccccffffffccccff000000
+ffffccccccffffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffff000000ffffff000000ccccffffffccccccffffffffccccccffffffccccccffffff
+ccccffffffccccccffffffffccccccffffffffffccccccffffffccccccffffffcc000000
+ccccff000000ffffccccccffffffccffffff000000ccccffffffcc000000ffffccffffff
+ccccccffffff000000ccccccffffffccccccffffffccccccffffffccccffffffccccccff
+ffffccffffffccccccffffffccccccffffff000000000000ffffccffffff000000000000
+ccccffffffcc000000ffffffccccccffffffffffccccccffffffcc000000ffffffcccccc
+ffffff000000000000ffffffccccccffffffffffccccccffffffccccccff000000ffffcc
+ffffffccccccffffffccccccffffff000000ffffcc000000cccccc666666ffffff666666
+666666666666666666666666666666666666666666666666666666999966666666666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+000000ffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffff000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffff000000ffffffffffffffffff000000ffffff000000
+000000ffffffffffff000000ffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000000000ffffffffffff000000ffffff000000ffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffff000000000000000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffff000000000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffff000000cccccc666666999999999999
+999999cccc999999999999999999cc9999999999999999999999999999999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc999999cc
+9999999999cc999999cccc99999999cccccc9999999999cc999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccc99999999999999999999999999999999999999cccc999999999999cc999999999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+9999999999cc999999cccccc9999999999999999cc999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+999999999999999999999999999999cccc999999999999999999cc9999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccccc999999cccc999999cc9999999999cc999999999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+999999999999999999999999cccc99999999cccc999999cc999999999999cccccc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc999966999999999999
+cccc999999cc9999999999999999cc999999999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff9999cc6666669999999999cc
+999999999999cccc99999999999999999999cccccc999999999999cccccc999999999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+9999cc999999999999cccccc999999999999999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999999999999999999999999999cccccc9999999999cccccc999999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc999966999999999999
+cccccc999999cccccc999999999999999999999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+999999999999999999999999cccccc999999cccc999999cccccc999999cc999999cccc99
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccc999999cc999999999999999999999999999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc999999cc
+999999999999cccc999999cc9999999999cccccc999999999999cccccc999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999999999999999cccc99999999999999999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccccc
+9999999999cc9999999999999999cccccc999999cc999999cccc999999999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999999999cccc999999999999999999999999999999cc999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffff000000ffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffff000000ffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffff000000000000000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000000000
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+000000ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffff000000000000000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffff000000ffffff000000ffffff
+ffffffffffff000000000000ffffffffffffffffffffffff000000ffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+000000ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000000000ffffffffffffffffff000000000000ffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffcccccc666666999999cccccc
+9999999999999999cc999999cccc99999999999999cccc999999999999cc999999999999
+ffffffcccccc000000
+000000ccccccffffff000000ffffccccccffffffcc000000ffffff000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000ffffff000000ffffcc000000ccccffffffcc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ccccccffffffccccccffffffccccccffffff
+000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffff000000000000ffffccccccffffffcc000000ccccffffffccccccff
+ffffcc000000000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff000000cccccc
+ffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ffffff000000ffffccccccffffffcc
+000000000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+ccccffffffccccccffffffcc000000ffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffcc000000ccccffffffccccccffffffccccccffffffcc000000ccccffffffcc
+ccccffffffccccccffffffcc000000000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000cccccc
+ffffff000000ffffcc000000ccccffffffccccccffffffcc000000ccccffffffccccccff
+ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000ccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffcc000000000000000000ffffff
+ccccccffffff000000ccccccffffffccccccffffff000000ccccccffffff000000000000
+ffffccccccffffffccccccffffffcc000000000000000000ccccffffffccccccffffffcc
+000000ccccffffffcc000000000000ffffffccccccffffffccccccffffffccccccffffff
+000000ffffcc000000ccccffffffcc000000000000000000ccccccffffffccccccffffff
+000000ffffccccccffffffccccccffffffcc000000ffffff000000ffffcc000000ffffff
+ccccccffffff000000000000ccccccffffffccccccffffff000000ffffcc000000ccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ffffff
+ccccccffffffccccccffffff000000ccccccffffffccccccffffffccccccffffff000000
+ccccccffffffccccccffffffccccccffffffccccccffffff000000000000ccccccffffff
+000000ffffccccccffffffcc000000000000ccccffffffccccccffffffccccccffffffcc
+000000ccccffffffcc000000000000ffffccccccffffffcc000000000000ffffcc000000
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ffffffcccccc
+ffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffcc000000
+000000ffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000ffffff
+000000ffffcc000000ffffffccccccffffffccccccffffff000000ffffcc000000000000
+ccccccffffffccccccffffffccccccffffffccccccffffff000000000000ffffcc000000
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffcc000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ccccffffffccccccffffffccccccffffffcccccccc666666999999999999
+999999cccc999999999999cc9999999999999999cc999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ccccffffffccccccff000000cccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000cccccc000000ccccff000000ffffccccccff
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffcccccc000000000000ffffffccccccffffff000000ffffccccccffffffcc
+ccccff000000000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffff
+ccccccffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000000000cccccc000000ccccffffffccccccff
+000000000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffccccccffffffccccccff000000cccccc000000ffffffccccccffffffccccccffffff
+ccccccffffff000000ffffccccccffffffccccccffffffccccccff000000ffffccccccff
+ffffccccccffffffccccccff000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffff
+cccccc000000ccccff000000ffffccccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffccccccff000000ffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ccccff000000000000000000ffffcc
+ccccffffffcc000000ffffffccccccffffffcccccc000000ffffffcccccc000000000000
+ffffffccccccffffffccccccffffff000000000000000000ffffccccccffffffccccccff
+000000ffffccccccff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+000000ccccff000000ffffccccccff000000000000000000ffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffff000000cccccc000000ccccff000000cccccc
+ffffffcccccc000000000000ffffffccccccffffffcccccc000000ccccff000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000000000ffffffcccccc
+000000ffffffccccccffffff000000000000ffffccccccffffffccccccffffffccccccff
+000000ffffccccccff000000000000ffffffccccccffffff000000000000ccccff000000
+ffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffccccccff
+ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ffffccccccffffffccccccffffffccccccffffffccccccff000000
+000000ccccccffffffcccccc000000ffffffccccccffffffccccccffffff000000cccccc
+000000ccccff000000ffffccccccffffffccccccffffffcc000000ccccff000000000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000000000ccccff000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ccccff000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000ffffffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ffffccccccffffffccccccffffffccccccffcccccc666666cccccc999999
+9999999999cc999999cccc99999999999999cccc999999999999cc9999999999cccccc99
+ccccffcccccc000000
+000000ccccccffffff000000ffffffccccccffffff000000ffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffff000000ffffff000000ccccccffffff
+000000ccccffffffccccccffffffccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccff000000ccccccffffffffffccccccffffffccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccffffffccccccffffffccccccffffff
+000000ccccffffffccccccffffffccccccffffffccccccff000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ccccccffffffffffccccccffffffccffffffcccccc
+ffffffccccccffffff000000000000ccccffffffccccccff000000ccccccffffffcccccc
+ffffff000000000000ccccffffffccccccffffffccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ccccffffffccccccff000000cccccc
+ffffffccccccffffff000000ccccccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ffffff000000ffffffccccccffffff
+000000000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000
+ccccccffffffccccccffffff000000ffffff000000ccccffffffccccccffffffccccccff
+ffffccccccff000000ccccccffffffccccccffffffccccccffffff000000ccccccffffff
+ccccccffffffccccccffffff000000000000ccccccffffffffffccccccffffffccffffff
+ccccccffffffffffccccccffffffccffffffccccccffffffccccccffffff000000cccccc
+ffffff000000ffffff000000ffffccccccffffffccffffff000000ccccccffffffcccccc
+ffffffffffccccccff000000ccccffffffccccccffffffccccccff000000ccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffcc000000000000000000ccccff
+ffffccccccff000000ffffccccccffffffccffffff000000ccccccffffff000000000000
+ffffccccccffffffccccccffffffcc000000000000000000ffffccccccffffffccffffff
+000000ccccffffffcc000000000000ffffffccccccffffffccccccffffffccccccffffff
+000000ffffff000000ccccccffffff000000000000000000ccccccffffffccccccffffff
+000000ffffffccccccffffffccccccffffff000000ffffff000000ffffff000000ffffff
+ccccccffffff000000000000ccccccffffffccccccffffff000000ffffff000000ffffff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ccccff
+ffffccccccffffffccccccff000000ccccccffffffccccccffffffccccccffffff000000
+ffffccffffffccccccffffffccccccffffffccccccffffff000000000000ccccccffffff
+000000ffffffccccccffffff000000000000ffffccffffffccccccffffffccccccffffff
+000000ccccccffffff000000000000ffffccccccffffffcc000000000000ffffff000000
+ffffffccccccffffffffffccccccffffffccccccffffffcc000000000000ccccffffffcc
+ccccffffffccccccff000000ccccffffffccccccffffffccccccff000000ccccccffffff
+ccccccffffffffffccccccffffffccffffffccccccffffffffffccccccffffffccffffff
+ccccccffffff000000ffffffccccccffffffffffccccccffffffccccccffffffcc000000
+000000ffffffccccccffffff000000ffffffccccccffffffccccccffffff000000ffffff
+000000ffffff000000ffffffccccccffffffccccccffffff000000ffffcc000000000000
+ccccccffffffccccccffffffccccccffffffccccccffffff000000000000ffffcc000000
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffff000000ccccffffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ccccffffffcc
+ccccffffffccccccffffffccccccff000000ccccccffffff000000ccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ccccff
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ccccffffffccccccffffffccccccffffffcccccccc666666999999999999
+cccc999999999999999999cc9999999999cc999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ffffccccccffffffcc000000cccccc000000ffffffcccccc
+ffffffccccffffffccccccffffffcc000000cccccc000000cccccc000000ffffffcccccc
+000000ffffffccccccffffffccccffffffccccccffffffccccccffffffffccccccffffff
+ccccccffffff000000ffffffccccccffffffccccffffffccccccffffffffccccccffffff
+ffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffccccccffffffcc
+000000ffffffccccccffffffffffccccccffffffccccccff000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccff000000000000ffffccccccffffffcc000000ffffffccccffffffcc
+ccccff000000000000ffffccccccffffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffffffffccccccffffffccffffff000000ffffccccccffffffcc000000ffffff
+ccccccffffffcccccc000000ffffffccccccffffffccccffffffccccccffffffffcccccc
+ffffffffffccccccffffffffcccccc000000000000cccccc000000ffffccccccffffffcc
+000000000000ffffccccccffffffffccccccffffffffffffccccccffffffffffcc000000
+ffffffccccccffffffcccccc000000cccccc000000ffffffffffccccccffffffccffffff
+ccccccffffff000000ffffffccccccffffffffffccccccffffffcc000000ffffffccccff
+ffffccffffffccccccffffff000000000000ffffffccccccffffffccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffff
+cccccc000000cccccc000000ffffffccccffffffccccccff000000ffffffccccccffffff
+ccccccffffffffffcc000000ffffccccccffffffccccccffffffcc000000ffffffccccff
+ffffccccccffffffccffffffccccccffffff000000ccccff000000000000000000ffffcc
+ccccffffffcc000000ffffffccccccffffffcccccc000000ffffffcccccc000000000000
+ffffffccccccffffffccccffffffcc000000000000000000ffffffccccffffffccccccff
+000000ffffccccccff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+000000ffffcc000000ffffffcccccc000000000000000000ffffffccccccffffffcccccc
+000000ffffccccccffffffccccccffffffcc000000cccccc000000ffffcc000000ffffff
+ccccccffffff000000000000ffffffccccccffffffcccccc000000cccccc000000cccccc
+ffffffccccccffffffffffccccccffffffffccccccffffffccccff000000000000ffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffffffccccccffffffcc000000
+ccccffffffffccccccffffffccccccffffffffffccccccff000000000000ffffffcccccc
+000000ccccccffffffcccccc000000000000ccccffffffccccccffffffccffffffcccccc
+000000ffffffcccccc000000000000ccccffffffccccccff000000000000cccccc000000
+ffffffccccccffffffccccccffffffccccffffffccccccff000000000000ffffccccccff
+ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ffffccccccffffffffccccccffffffccccccffffffccccff000000
+000000ffffccccccffffffcc000000ffffccccccffffffccccccffffffcc000000cccccc
+000000cccccc000000ffffffccccccffffffccccccffffff000000ffffff000000000000
+ffffffccccccffffffffffccccccffffffccccccffffffcc000000000000ccccff000000
+000000ffffffccccccffffffccccffffffccffffffccccccffffffffffffccccccffffff
+ccccccffffff000000ffffcc000000ffffccccccffffffccffffffccccccffffffcccccc
+ffffffffffffccccccffffffffffccccccffffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffffcccccc000000ffffffccccccffffff
+ccccccffffffffffffccccccffffffccccccffffffffffccccccffffffcc000000ffffcc
+ccccffffffccffffffccccccffffffccccccffffffffffccccccffffffccccccffffffcc
+ccccff000000ffffccccccffffffccccccffffffccccccffcccccc6666669999999999cc
+999999999999cccc99999999cccc99999999999999cccc999999cccccc99999999999999
+ffffffcccccc000000
+000000ccccccffffff000000ffffffccccccffffff000000ffffcc000000ccccffffffcc
+ffffffccccccffffffccccccffffff000000ffffff000000ffffff000000ccccccffffff
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ffffffcccccc000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffff000000ffffccccccffffffffccccccffffffcccccc
+000000ccccffffffccccccffffffffccccccffffffffffcc000000ffffffccccccffffff
+ffffffccccccffffffcccccc000000ccccccffffffccccccffffffccccccffffffccccff
+ffffccccccffffffcc000000000000ffffffccccccffffff000000ccccffffffccccccff
+ffffcc000000000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffff000000ccccff
+ffffccccccffffffff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffff000000000000ffffff000000ffffffccccccffffff
+000000000000ccccccffffffccccccffffffccccccffffffccccccffffffccccff000000
+ccccccffffffccccccffffff000000ffffcc000000ffffffccccccffffffccccffffffcc
+ccccffffffcc000000ccccccffffffccccffffffccccccffffffff000000ccccffffffcc
+ccccffffffccccccffffffcc000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffffccccccffffffffffcc000000cccccc
+ffffff000000ffffff000000ccccffffffccccccffffffcc000000ffffffccccffffffcc
+ccccffffffccccccff000000ffffccccccffffffffccccccffffff000000ccccccffffff
+ccccccffffffccccffffffccccccffffffcc000000ffffcc000000000000000000ccccff
+ffffccccccff000000ccccffffffccccccffffffff000000ccccccffffff000000000000
+ffffccccccffffffccccccffffffff000000000000000000ccccccffffffccccccffffff
+000000ccccccffffff000000000000ccccccffffffccccffffffccccccffffffccccccff
+000000ffffff000000ccccccffffff000000000000000000ccccccffffffccccccffffff
+000000ffffffccccccffffffccccccffffff000000ffffff000000ccccff000000ffffcc
+ccccffffffcc000000000000ffffccffffffccccccffffff000000ffffff000000ffffcc
+ccccffffffffccccccffffffccccccffffffffffccccccccffffff000000000000ffffcc
+ccccffffffffccccccffffff000000ccccffffffccccccffffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ccccccffffff
+000000ffffffccccccffffff000000000000ccccffffffccccccffffffffccccccffffff
+000000ccccccffffff000000000000ffffffccccccffffff000000000000ffffff000000
+ffffccccccffffffccccccffffffccffffffccccccffffff000000000000ccccffffffcc
+ccccffffffccccccff000000ffffccccccffffffccccccffffffcc000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccffffffccccccffffffffccccccffffff
+ccccccffffff000000ffffffccccccffffffccccccffffffccccccffffffffffcc000000
+000000ccccffffffccccccff000000ffffffccccccffffffccccccffffff000000ffffff
+000000ffffff000000ccccccffffffccccccffffffcccccc000000ffffcc000000000000
+ccccccffffffccccccffffffccccffffffccccccffffffff000000000000ffffcc000000
+000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffcccccc000000ccccff000000ffffccccccffffffccccccffffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ccccccffffff
+ffffccccccffffffccffffffcccccc000000ccccffffffcc000000ccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccffffffccccccffffffccccccff000000ffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ffffff000000ccccffffffccccccffffffccccccffffffcccccccc666666999999cccc99
+9999999999cc9999999999999999999999cc9999999999999999999999cc999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ccccffffffccccccff000000ccccff000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000ffffcc000000ffffff000000ffffffcccccc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffffffccccccffffffff
+000000ffffccccccffffffccccccffffffccccccffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffff000000000000ffffffccccccffffff000000ffffccccccffffffcc
+ccccff000000000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff000000ffffcc
+ccccffffffccccccff000000ffffffccccccffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccffffffcccccccc000000000000cccccc000000ccccffffffccccccff
+000000000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffffccccccffffffcccccc000000ccccff000000ccccccffffffccccccffffffcccccc
+ffffffccccff000000ffffffccccccffffffccccccffffffcccccc000000ffffccccccff
+ffffccccccffffffccccccff000000000000ccccccffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccffffffccffffffccccccffffffccccff000000ffffff
+cccccc000000cccccc000000ffffccccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffffcccccc000000ffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccff000000ffffff000000000000000000ffffff
+ccccccffffff000000ffffccccccffffffccccccff000000ffffffcccccc000000000000
+ffffffccccccffffffccccccffffff000000000000000000ffffffccccccffffffcccccc
+000000ffffffcccccc000000000000ffffffccccccffffffffffccccccffffffffcccccc
+000000ccccff000000ffffffcccccc000000000000000000ffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffff000000cccccc000000ffffcc000000ffffff
+ccccccffffff000000000000ccccffffffffccccccffffff000000cccccc000000ffffff
+ccccccffffffccccccffffffccccccffffffccccffffffccccccff000000000000ffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccff000000
+ffffccccccffffffccccccffffffffccccccffffffccccff000000000000ffffffcccccc
+000000ffffffccccccffffff000000000000ffffccccccffffffccccccffffffccccccff
+000000ffffffcccccc000000000000ffffccccccffffffcc000000000000cccccc000000
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ffffffcccccc
+ffffffffffccccccff000000ffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffcccccc000000ccccccffffffccccccffffffccccccffffffccccccccccff000000
+000000ffffffccccccffffff000000ffffffccccccffffffccccccffffff000000cccccc
+000000cccccc000000ffffffccccccffffffccccffffffcc000000ccccff000000000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000000000ccccff000000
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffffffff000000ffffffcccccc
+ffffffccccffffffccccccffffffcc000000ffffffcccccc000000ffffffffffccccccff
+ffffccccccffffffccffffffccccccffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffffccccccffffffccccffffffccccccffffffccffffffccccccffffffcccccc
+ccccff000000ffffffccccccffffffccccccffffffcccccccccccc6666669999cc999999
+999999cccc999999999999cccccc99999999cccc99999999999999cccc999999999999cc
+ffffcccccccc000000
+000000ccccccffffff000000ffffccccccffffffcc000000ffffff000000ccccffffffcc
+ccccffffffccccccffffffccccccff000000ccccff000000cccccc000000ccccccffffff
+000000ccccffffffccccccffffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccff000000ccccccffffffccccccffffffffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccccffffff000000ccccccffffffccccccffffffccccccffffff
+000000ccccffffffccccccffffffccccccffffffccffffff000000ccccccffffffcccccc
+ffffffccccccffffffcccccc000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000000000ccccffffffccccccff000000ffffccccccffffffcc
+ffffff000000000000ccccccffffffccccccffffffccccccffffffccccffffffccccccff
+ffffccccccffffffffccccccffffffcccccc000000ccccffffffccccccff000000ccccff
+ffffccccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffffffffff000000000000ffffff000000ffffccccccffffffcc
+000000000000ffffccffffffccccccffffffccccccffffffccccccffffffcccccc000000
+ffffffccccccffffffffffff000000ffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffcc000000ccccccffffffccccccffffffccccccffffff000000ccccccffffff
+ccccffffffccccccffffffcc000000000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000cccccc
+ffffff000000ffffff000000ccccccffffffccccccffffff000000ccccccffffffcccccc
+ffffffccccccffffff000000ccccccffffffccccccffffffcccccc000000ffffffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffcc000000000000000000ccccff
+ffffccccccff000000ccccffffffccccccffffffcc000000ccccccffffff000000000000
+ffffccccccffffffccccccffffffcc000000000000000000ccccccffffffccccccffffff
+000000ccccccffffff000000000000ccccffffffccccccffffffccccccffffffccffffff
+000000ffffcc000000ffffccffffff000000000000000000ccccccffffffccccccffffff
+000000ffffffccccccffffffccccccffffff000000ffffff000000ccccff000000ffffcc
+ccccffffffcc000000000000ccccccffffffccccccffffff000000ffffff000000ccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ccccff
+ffffccccccffffffccccccff000000ffffccccccffffffccccccffffffccffffff000000
+ccccffffffccffffffccccccffffffccccccffffffffffcc000000000000ccccccffffff
+000000ffffccccccffffffcc000000000000ccccffffffccccccffffffccccccffffffcc
+000000ffffccffffff000000000000ffffffccccccffffff000000000000ffffff000000
+ccccffffffffccccccffffffccccccffffffccccccffffff000000000000ffffccccccff
+ffffccccccffffffcc000000ffffffffffccccccffffffccccccff000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccffffff000000ccccccffffffccccffffffccffffffccccccffffffffffcc000000
+000000ccccccffffffcccccc000000ffffffccccccffffffccccccffffff000000ffffff
+000000ffffff000000ffffccccccffffffccccccffffffff000000ffffff000000000000
+ccccccffffffccccccffffffccccccffffffccccccffffff000000000000ffffcc000000
+000000ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffff
+ffffccccccff000000ccccff000000ccccffffffccccccffffffccffffffccccccffffff
+ccccccffffffccccffffffccccccffffffccccccffffffcccccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ccccffffffff000000ccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ccccff
+ffffccccccffffffccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ffffcc000000ffffccffffffccccccffffffccccccffffffcccccc666666cccc99999999
+9999cc9999999999999999999999999999cc9999999999999999cc999999999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ffffffccccccffffff000000cccccc000000ffffffcccccc
+ffffffccccffffffccccccffffffcc000000ffffcc000000ffffff000000ffffffcccccc
+000000ffffccccccffffffccccccffffffccffffffccccccffffffffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffffcccccc
+000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffcccccc000000000000ffffccccccffffffcc000000ffffffccccccffffff
+cccccc000000000000ffffffccccccffffffffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffcc
+ccccffffffccccccff000000ffffccccccffffffccccccffffffccccccffffffffcccccc
+ffffffccccccffffffccccccffffcc000000000000cccccc000000ffffffccccccffffff
+000000000000ccccffffffccccccffffffccccccffffffccccccffffffccffffff000000
+ffffccccccffffffcccccccc000000cccccc000000ffffffccccccffffffccccccffffff
+ccccccffffff000000ffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffffccccccffffffccccff000000000000ffffffffffccccccffffffccccccffffffcc
+ccccffffffccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffff
+cccccc000000ffffff000000ffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffffcccccc000000ffffffffffccccccffffffccccccff000000ccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ccccff000000000000000000ffffcc
+ccccffffffcc000000ffffccccccffffffccffffff000000ffffffcccccc000000000000
+ffffffccccccffffffccccccffffff000000000000000000ffffffccccccffffffcccccc
+000000ffffffcccccc000000000000ffffccccccffffffccccccffffffccccccffffffcc
+000000ccccff000000ccccffffffcc000000000000000000ffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffff000000ffffcc000000ffffcc000000ccccff
+ffffffcccccc000000000000ffffffccccccffffffcccccc000000cccccc000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000000000ffffffcccccc
+000000ccccffffffccccccff000000000000ffffccccccffffffccccccffffffccccccff
+000000ccccffffffcc000000000000ffffccccccffffffcc000000000000ffffcc000000
+ffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffffcccccc
+ffffffccccccffffff000000ccccccffffffccccffffffccffffff000000ffffffcccccc
+ffffffffffccccccffffffccffffffccccccffffffffffccccccffffffccccccffffffff
+ccccccffffff000000ffffffccccccffffffccccccffffffccccccffffffccccff000000
+000000ffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000cccccc
+000000cccccc000000ffffffccccccffffffccccccffffcc000000ffffcc000000000000
+ffffffccccccffffffffffccccccffffffccccccffffffcc000000000000ccccff000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccff000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000ffffcccccccc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffffffccccccff000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ccccffffffccccccffffffffffffccccccffcccccc666666999999999999
+cccc999999999999cccccc99999999999999cccc99999999cccc99999999cccccc999999
+ffffffcccccc000000
+000000ccccccffffff000000ffffffccccccffffff000000ffffcc000000ffffffcccccc
+ffffffffffccccccffffffccccccff000000ffffff000000cccccc000000ccccccffffff
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ccccccffffffccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000ccccccffffffccccccffffffccccccffffff
+000000ccccccffffffccccccffffffccccffffffccccccff000000ffffffccccccffffff
+ccccccffffffffffccccccff000000ffffccccccffffffffffffccccccffffffccccccff
+ffffccccccffffffff000000000000ccccccffffffccccff000000ccccccffffffcccccc
+ffffff000000000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ccccffffffccccccff000000ffffcc
+ccccffffffccffffff000000ccccffffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffff000000000000ffffff000000ffffffccccccffffff
+000000000000ffffffccccccffffffffffccccccffffffccccccffffffccccccff000000
+ccccffffffccccccffffffff000000ffffff000000ffffffccccffffffccffffffcccccc
+ffffffcccccc000000ffffccccccffffffccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffffffcc000000000000ccccffffffccccccffffffccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffffffff000000cccccc
+ffffff000000ffffcc000000ccccccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccffffff000000ccccffffffccccccffffffccffffff000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffcc000000000000000000ffffff
+ffffccccccff000000ffffffccccccffffffcccccc000000ccccffffffff000000000000
+ffffccccccffffffccccccffffffcc000000000000000000ffffffccccccffffffffffff
+000000ffffffffffff000000000000ffffffccccccffffffccccccffffffccccccffffff
+000000ffffcc000000ccccffffffff000000000000000000ccccffffffccccccffffffcc
+000000ccccffffffccccccffffffccccccff000000ccccff000000ffffff000000ffffcc
+ccccffffffff000000000000ccccccffffffccccccffffff000000ffffff000000ccccff
+ffffccffffffccccccffffffccccccffffffffffccccccffffffcc000000000000ffffff
+ffffccccccffffffccccccff000000ccccffffffccccccffffffccccccffffffcc000000
+ccccffffffccccccffffffccccccffffffccccccffffffff000000000000ccccccffffff
+000000ffffffccccccffffff000000000000ccccffffffccffffffccccffffffccffffff
+000000ccccccffffff000000000000ffffffccccccffffff000000000000ffffff000000
+ccccccffffffccccccffffffccccffffffccccccffffffcc000000000000ffffffcccccc
+ffffffccccccffffff000000ccccccffffffccccccffffffcccccc000000ccccccffffff
+ccccccffffffccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffcc000000
+000000ffffffffffccccccff000000ffffffccccccffffffccccccffffff000000ffffff
+000000ffffff000000ccccccffffffccccccffffffccccff000000ffffff000000000000
+ffffffccccffffffccccccffffffccffffffccccccffffff000000000000ffffff000000
+000000ccccccffffffffffccccccffffffccffffffccccffffffccccccffffffccccccff
+ffffccccccff000000ffffff000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccffffffccccccffffffccccccffffff000000ccccccffffff
+ccccccffffffccccffffffccccccff000000ccccffffffff000000ffffccccccffffffff
+ccccccffffffccccffffffccffffffccccffffffccccccffffffccffffff000000cccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccffffffccccccff
+ffffcc000000ccccffffffccccccffffffccccccffffffcccccccc6666669999999999cc
+9999999999999999999999999999999999cc9999999999cc999999999999999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ccccccffffffcccccc000000ccccff000000ffffccccccff
+ffffccccccffffffffccccccffffff000000cccccc000000ffffff000000ffffffcccccc
+000000ffffccccccffffffccccccffffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffffffffccccccffffffccccccff
+ffffccffffffccccccffffffccccff000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffccccffffffccffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffcccccc000000000000ffffffccccccffffff000000ffffffccccccffffff
+cccccc000000000000ffffccccccffffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc000000ffffff
+ccccccffffffcccccc000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000000000cccccc000000ccccccffffffcccccc
+000000000000ffffccccccffffffffccccccffffffccccccffffffccccccffffff000000
+ffffccccccffffffccccccff000000cccccc000000ffffccccccffffffccccccffffffcc
+ccccffffffff000000ffffffccccccffffffccccccffffffcccccc000000ffffccccccff
+ffffccccccffffffccccccff000000000000ffffccccccffffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffff
+cccccc000000ccccff000000ffffffffffccccccffffffcc000000ffffffccccffffffcc
+ccccffffffccccccff000000ffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffffffccccccffffffccccccffffffff000000ffffff000000000000000000cccccc
+ffffffcccccc000000ffffffccccccffffffccccff000000ffffccccccff000000000000
+ffffffccccccffffffccccccffffff000000000000000000ffffccccccffffffcccccccc
+000000ccccccffffff000000000000ffffffccccccffffffccccccffffffccccccffffff
+000000ffffff000000ffffccccccff000000000000000000ffffffccccccffffffccccff
+000000ffffccccccffffffccccccffffffcc000000ffffcc000000cccccc000000ffffff
+ccccccffffcc000000000000ffffffccccccffffffcccccc000000ffffcc000000ffffff
+ccccccffffffccccffffffccccccffffffccccccffffffccccccff000000000000ccccff
+ffffccccccffffffccffffff000000ffffffccccccffffffccccccffffffccccff000000
+ffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffffcccccc
+000000ffffffccccccffffff000000000000ffffccccccffffffccccccffffffccccccff
+000000ffffffcccccc000000000000ccccffffffccccccff000000000000cccccc000000
+ffffffccccccffffffffffccccccffffffccccccffffffff000000000000ffffffcccccc
+ffffffccccccffffff000000ffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ffffccccccffffffccccccffffffccccccffffffccccccff000000
+000000ccccccffffffcccccc000000ffffffccccccffffffccccccffffff000000cccccc
+000000ccccff000000ffffffccccccffffffccccccffffff000000cccccc000000000000
+ffffccccccffffffccccccffffffccccccffffffffcccccc000000000000cccccc000000
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000cccccc000000ffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffcccccccc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffff
+ccccccffffffccccccffffffccccffffffccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffccccccffffffccccccffffffccccccffcccccc666666cccc99999999
+999999cccccc999999999999cccc99999999999999999999cccc999999cc999999999999
+ffffffcccccc000000
+000000ccccccffffff000000ffffffccccccffffff000000ffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffff000000ffffcc000000ccccccffffff
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffccccccff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffffffffcc000000ccccffffffccffffffccccccffffffffffff
+000000ccccffffffccccccffffffccccccffffffccffffff000000ccccccffffffcccccc
+ffffffccccccffffffcccccc000000ffffccccccffffffccccccffffffccffffffcccccc
+ffffffccccccffffff000000000000ffffccffffffcccccc000000ccccffffffccccccff
+ffffff000000000000ffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffffccccccffffffccccccffffff000000ffffccffffffccccff000000cccccc
+ffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffff000000ffffffccccccffffff
+000000000000ccccccffffffccccccffffffccccffffffccccccffffffccccccff000000
+ccccccffffffccccccffffff000000ffffff000000ccccccffffffccccccffffffcccccc
+ffffffcccccc000000ccccffffffccccccffffffccccccffffffff000000ccccccffffff
+ccccccffffffccccccffffff000000000000ffffccccccffffffccccccffffffccccccff
+ffffffccccccffffffffffffccccccffffffccccccffffffccccccffffff000000cccccc
+ffffff000000ffffcc000000ffffffccccccffffffccccff000000ccccffffffccccccff
+ffffccccccffffffcc000000ccccccffffffccccccffffffcccccc000000ffffccccccff
+ffffffccccccffffffccccccffffffcccccc000000ffffcc000000000000000000ffffff
+ccccffffffcc000000ffffffccccccffffccffffff000000ccccccffffff000000000000
+ffffccccccffffffccccccffffffcc000000000000000000ccccffffffccccccffffffff
+000000ccccffffffcc000000000000ccccccffffffccccccffffffccccccffffffcccccc
+000000ccccff000000ccccffffffcc000000000000000000ffffccffffffccccccffffff
+000000ccccffffffccccccffffffccccccff000000ffffff000000ffffff000000ffffcc
+ccccffffffff000000000000ffffccffffffccccccffffff000000ccccff000000cccccc
+ffffffccccccffffffccccccffffffccccffffffccccccffffffcc000000000000cccccc
+ffffffccccccffffffcccccc000000ffffccccccffffffccccccffffffccffffff000000
+ccccccffffffffffffccccccffffffccccccffffffffffcc000000000000ccccccffffff
+000000ffffccccccffffffcc000000000000ccccffffffccccccffffffccccccffffffcc
+000000ccccccffffff000000000000ffffccccccffffffcc000000000000ffffff000000
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ccccccffffff
+ccccccffffffcccccc000000ccccffffffccccccffffffccccccff000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccffffffccccccffffff
+ccccccffffff000000ffffccccccffffffffccccccffffffffffccccccffffffcc000000
+000000ffffffccccccffffff000000ffffffccccccffffffccccccffffff000000ffffff
+000000ffffcc000000ffffffccccccffffffccccccffffff000000ffffff000000000000
+ccccccffffffccccccffffffccccccffffffccccccffffff000000000000ffffff000000
+000000ccccccffffffccccffffffccccccffffffccccccffffffccffffffccccccffffff
+ccccccffffff000000ffffff000000ccccffffffccccccffffffccffffffccccffffffcc
+ccccffffffccccccffffffffccccccffffffccccccffffffffffcc000000ccccffffffcc
+ffffffccccccffffffccccccffffff000000ccccffffffff000000ccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffffcccccc
+ffffff000000ccccffffffccccccffffffccccccffffffcccccccc6666669999cc999999
+9999999999999999999999cc999999cccc999999cc999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ffffffccccccffffff000000cccccc000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000cccccc000000ccccff000000ffffffcccccc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ccccffffffccccccffffffccccccffffffffcccccc
+ffffffccccccffffff000000000000ccccffffffccffffff000000ffffccccccffffffcc
+cccccc000000000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccffffffccccccffffffcccccc000000ccccffffffccffffff000000ffffff
+ccccccffffffcccccc000000ffffffccccccffffffffffffccccccffffffccccccffffff
+ffffffccccccffffffccccccffffff000000000000cccccc000000ffffffccccccffffff
+000000000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffffccccccffffffcccccc000000cccccc000000ffffffccccccffffffccccccffffff
+ffffccccccff000000ffffccccccffffffffccccccffffffcccccc000000ffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffff
+cccccc000000ccccff000000ccccccffffffccccccffffcc000000ffffccccccffffffff
+ccccccffffffccccff000000ffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffff000000000000000000ffffcc
+ccccffffffcc000000ffffffccccffffffccccccff000000ffffffcccccc000000000000
+ffffffccccffffffccccccffffffff000000000000000000ffffffccccccffffffcccccc
+000000ffffccccccff000000000000ffffffccccccffffffccccffffffccccccffffffcc
+000000ffffcc000000ffffccccccff000000000000000000ccccffffffccccccffffffcc
+000000ffffffccccccffffffffffccccccff000000cccccc000000cccccc000000ccccff
+ffffccccccff000000000000ccccffffffccccccffffffcc000000ffffcc000000ffffff
+ccccccffffffffffccccccffffffccccccffffffccccccffffffff000000000000ffffff
+ccccccffffffccccccffffff000000ffffffccccccffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffccccff000000000000ffffffcccccc
+000000ccccffffffccccccff000000000000ffffccccccffffffccccccffffffccccccff
+000000ffffffcccccc000000000000ffffffccccccffffff000000000000ffffcc000000
+ffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffffcccccc
+ffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccffffffccccccffffffccccccff
+ffffccccccff000000ffffffccccccffffffccccccffffffccccccffffffccccff000000
+000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000ffffcc
+000000ccccff000000ffffccccccffffffccccccffffffcc000000cccccc000000000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000000000cccccc000000
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffcc000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000ffffccccccff000000ffffffccccccffffff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffff
+ccccccffffffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ffffccccccffffffccccccffffffccccccffcccccc666666cccc99999999
+9999cccccc999999999999999999999999999999999999999999cc9999999999cccccc99
+ccccffcccccc000000
+000000ccccffffffcc000000ffffffccccccffffff000000ffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffff000000ffffcc000000ccccffffffcc
+000000ffffffccccccffffffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ccccffffffccccccffffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ccccccffffffccccccffffffccccccffffff
+000000ccccffffffccccccffffffccccccffffffccccccff000000ccccffffffccffffff
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffcc000000000000ccccffccccccffffff000000ccccffffffccccccff
+ffffff000000000000ccccffffffccccccffffffccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ccccccffffffcccccc000000cccccc
+ffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ffffff000000ffffccccccffffffcc
+000000000000ccccccffffffccccccffffffffffccccccffffffccffffffcccccc000000
+ffffccccccffffffccffffff000000ffffff000000ffffffccccccffffffccccccffffff
+ccccccffffff000000ffffccffffffccccccffffffccccccffffff000000ffffffccccff
+ffffccffffffccccccffffff000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccffffffccffffffccccccffffff000000ffffff
+ffffff000000ffffff000000ccccffffffccffffffccccff000000ffffffccccccffffff
+ccccccffffffffffcc000000ccccccffffffccccccffffffcccccc000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffff000000ffffcc000000000000000000ccccff
+ffffccccccff000000ccccffffffccccccffffffcc000000ccccccffffff000000000000
+ffffccccccffffffccccccffffffcc000000000000000000ffffccccccffffffccffffff
+000000ccccffffffcc000000000000ffffccccccffffffccccccffffffccffffffccccff
+000000ffffff000000ccccffffffcc000000000000000000ccccffffffccccccffffffcc
+000000ccccffffffccccccffffffccffffff000000ffffff000000ffffff000000ffffcc
+ccccffffffcc000000000000ffffffccccccffffffccccff000000ccccff000000ffffff
+ccccccffffffccccccffffffccccccffffffffffccccccffffffcc000000000000cccccc
+ffffffccccffffffccccccff000000ccccccffffffccccccffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ccccccffffff
+000000ffffffccccccffffff000000000000ccccccffffffccccccffffffccccccffffff
+000000ccccccffffff000000000000ffffffccccccffffff000000000000ccccff000000
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ccccffffffcc
+ffffffccccccffffff000000ccccffffffccccccffffffccccccff000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ccccccffffffccccccffffffccccccffffffccccccffffff000000
+000000ccccccffffffcccccc000000ccccffffffccccccffffffccccccff000000ffffff
+000000ffffff000000ffffffccccccffffffccccccffffff000000ffffff000000000000
+ffffffccccffffffccccccffffffccccccffffffccffffff000000000000ffffff000000
+000000ccccccffffffccccccffffffccccccffffffccccccffffffccccffffffccccccff
+ffffccccccff000000ccccff000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccffffffccccccffffffccccccffffffccccccffffff000000ccccffffffcc
+ccccffffffccccccffffffccccccff000000ccccffffffcc000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffccccccffffffccccccffffffffccccccffffffccccffffffccccccffffffcc
+ccccff000000ccccffffffccccccffffffccccccffffffcccccccc666666999999999999
+9999999999cccccc999999cc999999cccccc999999999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffccccccff000000ffffccccccffffffcc000000cccccc000000ffffffcccccc
+ffffffffffccccccffffffccccccff000000cccccc000000ffffff000000ffffffcccccc
+000000ffffffccccccffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ffffffcccccc000000ffffccccccffffffccccccffffffccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffffccccccffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccff000000000000ffffccffffffcccccc000000ffffffccccccffffff
+cccccc000000000000ffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffffcccccc000000ffffffccccccffffff000000ffffff
+ccccccffffffcccccc000000ccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccff000000000000cccccc000000ccccffffffccccccff
+000000000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffffccccccffffffcccccc000000cccccc000000ffffffccccccffffffccccccffffff
+ccccccffffff000000ccccffffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffccccccffffffccccccff000000000000ffffccccccffffffffffffccccccffffffcc
+ccccffffffccccccffffffccffffffccccccffffffccccffffffccccccff000000cccccc
+ffffcc000000ffffcc000000ffffffccccccffffffcccccc000000ffffccccccffffffcc
+ccccffffffccccccff000000ffffffccccccffffffffffccccccff000000ffffccccccff
+ffffccffffffccccccffffffccccccffffff000000ccccff000000000000000000ffffcc
+ccccffffffcc000000ffffccccccffffffccccccff000000ffffffcccccc000000000000
+ffffffccccccffffffccccccffffff000000000000000000ffffffccccccffffffcccccc
+000000ffffffcccccc000000000000ffffffccccccffffffccccccffffffccccccffffff
+000000cccccc000000ffffffcccccc000000000000000000ffffccccccffffffccccccff
+000000ffffccccccffffffccccccffffffcc000000cccccc000000ffffff000000ffffff
+ccccccffffff000000000000ffffccccccffffffccffffff000000ffffcc000000ccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ffffff
+ccccccffffffccccccffffff000000ffffffccccccffffffccccccffffffcccccc000000
+ffffccccccffffffccffffffccccccffffffccccccffffff000000000000ffffffcccccc
+000000ffffccccccffffffcc000000000000ffffffccccffffffccccccffffffccccccff
+000000ffffffcccccc000000000000ccccccffffffcccccc000000000000ffffcc000000
+ffffffccccccffffffffffccccccffffffccccccffffffff000000000000ffffffcccccc
+ffffffccccccffffff000000ffffffffffccccccffffffccffffff000000ffffffcccccc
+ffffffffffccccccffffffccffffffccccccffffffccccccffffffccccffffffccffffff
+ccccccffffff000000ffffffccccccffffffccccccffffffccccccffffffcccccc000000
+000000ffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000cccccc
+000000cccccc000000ffffccccccffffffccccccffffffcc000000cccccc000000000000
+ffffccccccffffffccccccffffffccccccffffffffcccccc000000000000ffffcc000000
+000000ffffffccccccffffffffffccccccffffffccccccffffffccccccffffffccffffff
+ccccccffffff000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccffffffffccccccffffffcccccc000000ffffffcccccc
+ffffffffffccccccffffffccffffff000000ffffccccccff000000ffffffccccccffffff
+ccccccffffffccccccffffffffffccccccffffffccffffffccccccffffff000000ffffff
+ccccccffffffccccccffffffccccccffffffffffccccccffffffccccccffffffccccccff
+ffffcc000000ffffffccccccffffffffffccccccffffffcc9999cc666666cccccc999999
+9999999999999999999999999999999999999999999999cc999999999999cccccc999999
+ffffffcccccc000000
+000000ccccccffffff000000ffffffccccccffffff000000ffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffff000000cccccc000000ccccccffffff
+000000ffffffccccccffffffccccccffffffffffccccccffffffccccccffffffccccccff
+ffffccffffff000000ffffffffffccccccffffffccccccffffffccccccffffffffcccccc
+ffffffffffffccccccffffffcccccc000000ccccccffffffccccccffffffccccccffffff
+000000ffffccccccffffffffccccccffffffccccccffffff000000ccccffffffccccccff
+ffffccccccffffffccccccff000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000000000ffffffccccccffffff000000ffffccccccffffffcc
+ffffff000000000000ffffffccccccffffffccccccffffffccccffffffccccccffffffff
+ccccccffffffffffccccccffffffccffffff000000ffffffccccccffffff000000cccccc
+ffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffff000000ffffffccccccffffff
+000000000000ccccffffffccccccffffffccccccffffffffccccccffffffcccccc000000
+ccccffffffffccccccffffff000000ffffff000000ffffffccccccffffffccccffffffcc
+ccccffffffcc000000ccccccffffffffffccccccffffffccccccff000000ccccffffffcc
+ccccffffffccccccffffffcc000000000000ffffffccccccffffffccccccffffffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffff
+ccccff000000ccccff000000ffffccccccffffffccffffff000000ccccffffffccccccff
+ffffccccccffffffcc000000ccccccffffffccccffffffccffffff000000ffffccccccff
+ffffffccccccffffffffffccccccffffffcc000000ffffcc000000000000000000ccccff
+ffffccccccff000000ffffffccccccffffffffffcc000000ccccccffffff000000000000
+ffffccccccffffffccccccffffffcc000000000000000000ccccffffffccccccffffffcc
+000000ccccffffffff000000000000ffffffccccccffffffccccccffffffccccccffffff
+000000ffffff000000ffffffffffff000000000000000000ccccccffffffccccccffffff
+000000ffffffccccccffffffccccccffffff000000ffffff000000cccccc000000ffffff
+ccccccffffff000000000000ffffccccccffffffccccccff000000ffffcc000000ccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffff000000000000cccccc
+ffffffffffccccccffffffcc000000ccccffffffffccccccffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ccccccffffff
+000000ffffffccccccffffff000000000000ccccffffffccffffffccccccffffffffffcc
+000000ccccccffffff000000000000ffffffccccccffffff000000000000ffffff000000
+ccccccffffffccccffffffccccccffffffccccccffffffcc000000000000ccccccffffff
+ccccccffffffcccccc000000ccccccffffffccccccffffffcccccc000000ccccffffffcc
+ccccffffffffccccccffffffccccccffffffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ccccccffffffffffccccccffffffccccccffffffccffffff000000
+000000ccccffffffccccccff000000ffffffccccccffffffccccccffffff000000ffffff
+000000ffffff000000ffffccccccffffffffccccccffffff000000ffffff000000000000
+ffffccccccffffffccffffffccccccffffffccccccffffff000000000000ffffff000000
+000000ffffccccccffffffccccccffffffffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ccccff000000ccccffffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccffffffccccccffffff000000ccccccffffff
+ccccffffffccccccffffffccccccff000000ccccffffffcc000000ccccffffffccccccff
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000cccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffcccccc
+ffffff000000ccccccffffffccccccffffffccccccffffffcccccc666666999999cccc99
+9999cc999999cccc999999999999cccccc99999999cccc99999999999999999999999999
+cccccccccccc000000
+000000ffffffffffff000000ffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffff000000ffffff000000ffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffff000000000000000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000000000
+ffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+000000ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffff000000000000000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffff000000ffffff000000ffffff
+ffffffffffff000000000000ffffffffffffffffffffffff000000ffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+000000ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffff000000000000ffffffffffffffffff000000000000ffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+000000ffffff000000ffffffffffffffffffffffffffffff000000ffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999999999cc9999999999999999999999cc9999999999999999cccccc99999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccccc
+999999999999cccc99999999cccc99999999999999cccc99999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999cc9999999999cc9999999999cc9999999999cc999999cccc999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc9999669999cc999999
+cccc99999999999999cccc99999999999999cccc99999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999cc9999999999999999cc9999999999999999cccccc999999cc999999cccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+cccc99999999999999cccc99999999cccc99999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc99999999
+9999cc9999999999cc9999999999999999cc999999cccc999999cccccc999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999cccc99999999999999cccc99999999999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cccccc99
+9999999999cc9999999999cc9999999999999999cccccc99999999999999cccccc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999cccc99999999999999cccc999999999999999999cc999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+9999999999999999cc9999999999cc999999cccc99999999cccc999999999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc99999999
+999999cccc99999999cccc999999999999999999999999cc999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+9999cc9999999999999999999999cc9999999999999999999999999999cc999999cccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc99999999
+999999cccccc999999999999cccc99999999cccccc999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999999999999999cc9999999999999999999999999999cc999999cccccc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666699999999999999
+cccccc999999999999cccc999999999999cccccc99999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccc99666666cccc99999999
+9999999999999999cc999999999999999999999999999999cccc999999cc999999999999
+ffffffcccccc000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+cccc99999999999999cccccc999999cccc999999cc999999999999cccc99999999999999
+cccccccccccc000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffff000000ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffff000000ffffffffffff000000ffffffffffffffffffffffff000000000000000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+000000ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000
+000000ffffff000000ffffff000000ffffffffffffffffffffffff000000ffffff000000
+ffffffffffffffffff000000ffffff000000ffffffffffffffffff000000000000000000
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000000000ffffffffffffffffffffffff
+000000ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999999999999999999999999999999999999999999999cc9999999999cccccc99
+ccccffcccccc000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000ccccccffffffcccccc
+ffffff000000ffffcc000000ccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffff000000ccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffcc000000ccccffffffcc000000ccccffffffccccccffffffcc000000000000000000
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ccccccffffffccccccffffff000000
+000000ccccccffffff000000ffffcc000000ccccffffffccccccffffffccccccffffffcc
+000000000000ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+ccccffffffccccccffffffcc000000ffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000000000ccccffffffccccccffffffcc000000ffffff
+ccccccffffff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffff000000000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000000000
+000000ffffff000000ffffcc000000ccccffffffccccccffffffcc000000ffffff000000
+ffffccccccffffffcc000000ffffff000000ffffccccccffffffcc000000000000000000
+ffffcc000000ffffffccccccffffffccccccffffff000000000000ffffccccccffffffcc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffcc000000ccccccffffff000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000ccccffffffcc000000000000ccccffffffccccccffffffcc
+000000ccccffffffcc000000000000ffffffccccccffffffccccccffffffccccccffffff
+000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffff000000ffffcc000000ffffffccccccffffffccccccffffff000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ffffffccccccffffffccccccffffffccccccffffff000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffff000000ffffcc
+ccccffffffccccccffffffcc000000ccccffffffcc000000ffffffccccccffffff000000
+ccccccffffffccccccffffffccccccffffffccccccffffff000000000000ffffccccccff
+ffffcc000000000000ffffffccccccffffffccccccffffffccccccffffff000000000000
+ccccccffffffccccccffffff000000ccccccffffff000000ffffccccccffffffcc000000
+000000ccccffffffccccccccffffffccccccffffffcccccccccccc666666999999cccccc
+9999999999cccccc999999cc9999999999cccccc99999999cccc99999999999999999999
+cccccccccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000000000ffffffccccccffffff
+cccccc000000ccccff000000ffffccccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffcccccc000000
+ccccff000000ffffccccccff000000ffffccccccffffffccccccff000000000000000000
+ffffccccccff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc000000
+000000ffffffcccccc000000ccccff000000ffffccccccffffffccccccffffffccccccff
+000000000000ffffccccccffffffccccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000
+ffffccccccffffffccccccff000000ccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000000000ffffccccccffffffccccccff000000cccccc
+ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffff000000ffffccccccffffffccccccffffffcc000000000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000000000
+000000cccccc000000ccccff000000ffffccccccffffffccccccff000000cccccc000000
+ccccffffffccccccff000000cccccc000000ffffffccccccffffff000000000000000000
+ccccff000000ffffccccccffffffccccccffffffcc000000000000ffffffccccccffffff
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ccccff
+ffffccccccff000000ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffff000000ffffccccccff000000000000ffffccccccffffffccccccff
+000000ffffccccccff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+cccccc000000ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffcc000000ccccff000000ffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000cccccc000000ffffff
+ccccccffffffccccccffffff000000ffffccccccff000000ccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000000000ffffffcccccc
+ffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc000000000000
+ffffffccccccffffffcccccc000000ffffffcccccc000000ccccffffffccccccff000000
+000000ffffccccccffffffffccccccffffffccccccffffffcccccc666666999999999999
+999999999999999999999999cccc999999999999999999cc999999999999cccc999999cc
+ffffcccccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ffffcc000000ffffffcccccc
+ffffff000000ffffff000000ccccccffffff000000ccccffffffccccccffffffccccccff
+ffffccccccff000000ffffccccccffffffccffffffccccccffffffccccccffffff000000
+ffffff000000ccccccffffff000000ccccffffffccccccffffffcc000000000000000000
+ccccccffffff000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ccccccffffffccccccffffff000000
+000000ccccccffffff000000ffffff000000ffffccccccffffffccffffffccccccffffff
+000000000000ccccccffffffccccccffffff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ccccffffffccffffff
+ccccccffffffffffccccccffffffccccccffffffccccccff000000ffffffccccccffffff
+ffffccccccffffffccccccffffffccccccff000000ffffffccccffffffccffffffcccccc
+ffffffccccccffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000ffffff
+ccccccffffff000000ccccffffffccccccffffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ccccffffffccccccff
+ffffccccccff000000ffffffccccccffffffccccccffffff000000000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ccccff
+ffffccccccffffffccccccffffffccccccff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffccccccff000000ffffccccccffffffccccccffffffcc000000000000
+000000ffffff000000ffffff000000ccccccffffffccccccffffff000000ffffff000000
+ffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000000000000000
+ffffff000000ffffffccccccffffffccccccffffff000000000000ccccffffffccccccff
+000000ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffcc000000ccccccffffff000000ffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccff000000ccccccffffff000000000000ccccccffffffccccccffffff
+000000ccccffffffcc000000000000ccccffffffccccccffffffccccccffffffccccccff
+000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffffccccccffffffffffccccccffffffccccccff000000ffffffcccccc
+ffffff000000ffffff000000ccccffffffccccccffffffccccccff000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffffffffccccccffffffccccccffffffccffffffcccccc000000ccccffffffccccccff
+ffffccffffffccccccffffffccccccffffffccccccffffff000000ffffff000000ccccff
+ffffccccccffffffccccccff000000ccccccffffff000000ccccffffffccffffff000000
+ccccccffffffccccccffffffccccccffffffccccccffffff000000000000ffffccccccff
+ffffcc000000000000ffffffccccccffffffccccccffffffccccccffffff000000000000
+ccccccffffffccccccffffff000000ccccffffffcc000000ffffffccccccffffff000000
+000000ffffccccccffffffccccccffffffccccccffffffcccccccc666699999999cccc99
+9999cccccc999999999999999999cc999999cccc999999999999cc999999999999999999
+cccccccccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ccccffffffcc000000ffffff
+cccccc000000cccccc000000ffffffcccccc000000ffffffccccccffffffffffccccccff
+ffffccccccff000000ffffffccccccffffffccccffffffccccccffffffccccccff000000
+ffffcc000000ffffffcccccc000000ffffffccccccffffffccccff000000000000000000
+ffffffcccccc000000ffffffccccccffffffccccccffffffffffccccccffffffccccccff
+ffffccffffffccccccffffffccccccffffff000000ffffffccccccffffffcccccc000000
+000000ffffffcccccc000000cccccc000000ffffffccccffffffccccccffffffccccccff
+000000000000ffffffccccffffffccccccff000000ffffccccccffffffccccccffffffcc
+ffffffccccccffffffcccccc000000ffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ccccccffffffccccff
+ffffccccccffffffccccccffffffccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000000000ffffffccccffffffccffffffccccccffffffccccff
+ffffccffffffccccccffffffffffffccccccffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccffffffccffffffccccffffffccccccffffffccffffffcccccc000000
+ffffffccccffffffccccccff000000ffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000000000ffffffccccffffffccccccff000000ffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffffccccccffffff000000ffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffff000000ffffccccccffffffccccccffffffcc000000000000ffffffcccccc
+ffffffffffccccccffffffccccccffffffccffffffccccccffffffcccccc000000ffffff
+ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffffffffccccccff
+ffffccccccffffffccccccff000000ffffffccccccffffffccccffffffcc000000000000
+000000ffffcc000000ffffcc000000ffffffccccffffffccccccff000000cccccc000000
+ffffccccccffffffcc000000cccccc000000ccccffffffccccccff000000000000000000
+cccccc000000ffffffccccccffffffccccccffffff000000000000ffffccccccffffffcc
+000000ffffffccccccffffffccccffffffccffffffccccccffffffffffccccccffffffff
+ccccccffffffffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffff
+ccccccffffff000000ffffffcccccc000000ffffccccccffffffffccccccffffffcccccc
+ffffffccccccffffff000000ffffffcccccc000000000000ffffffccccffffffccccccff
+000000ffffccccccff000000000000ffffffccccccffffffffffccccccffffffccffffff
+000000ffffffccccccffffffffffccccccffffffffccccccffffffffffccccccffffffcc
+ccccff000000ffffccccccffffffffccccccffffffccccccffffff000000ccccccffffff
+cccccc000000cccccc000000ffffffccccccffffffccccccffffff000000ffffccccccff
+ffffffccccccffffffffffffccccccffffffffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffccccccffffffccccccffffffffccccccffffffffffff000000ffffccccccffffffff
+ccccccffffffccccccffffffccccffffffccccccffffffcc000000cccccc000000ffffff
+ccccccffffffccccccffffff000000ffffffcccccc000000ffffccccccffffffcc000000
+ffffffccccccffffffffffccccccffffffccccccffffffcc000000000000ccccffffffcc
+ccccff000000000000ffffffccccccffffffccccccffffffccccccffffff000000000000
+ffffffccccccffffffcccccc000000ffffccccccff000000ffffccccccffffffcc000000
+000000ccccffffffccccccffffffccccccffffffccccccffcccccc666666999999999999
+9999999999999999cc999999999999999999999999999999cccc999999999999cc999999
+ffffffcccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ccccffffffffcccccc000000
+ffffff000000ffffff000000ffffffffffcc000000ccccffffffccccccffffffffcccccc
+ffffffffffcc000000ffffccccccffffffccccccffffffccffffffccccccffffff000000
+ffffff000000ccccffffffcc000000ffffffccccccffffffffffcc000000000000000000
+ccccccffffff000000ffffccccccffffffccccccffffffccccccffffffffccccccffffff
+ccccccffffffccccffffffccffffffcccccc000000ffffccccccffffffccffffff000000
+000000ccccccffffff000000ffffff000000ffffffccccccffffffccccccffffffffffcc
+000000000000ffffccffffffccccccffffff000000ffffccccccffffffffccccccffffff
+ccccccffffffccccccffffff000000ccccffffffccccccff000000ccccccffffffcccccc
+ffffffccccccffffffccccffffffccffffffccccccffffff000000ccccccffffffcccccc
+ffffffccccffffffccccccffffffccccccff000000ffffccccccffffffccccccffffffff
+ffffccccccffffffcc000000000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffffccccccffffff000000
+ffffccffffffccccccffffff000000ccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000000000ccccccffffffccccccffffff000000cccccc
+ffffffcccccc000000ccccffffffccffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccff000000ccccccffffffcccccc000000ccccffffffccccccff
+ffffccccccff000000ffffccccccffffffffccccccffffff000000000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccffffffccccccffffffcc000000ccccff
+ffffccffffffccccffffffccccccffffffcc000000ccccffffffccccccffffffccccccff
+ffffffccccccffffffffffcc000000ffffccccccffffffccccccffffffff000000000000
+000000ccccff000000ffffff000000ccccffffffccccccffffffcc000000ffffff000000
+ffffffccccccffffff000000ffffff000000ffffffccccccffffff000000000000000000
+ffffff000000ffffccccccffffffccccccffffffcc000000000000ccccffffffccccccff
+000000ffffccccccffffffccccccffffffccccccffffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccffffffccccccffffffffccccccffffff000000ccccff
+ffffccccccff000000ccccccffffff000000ccccffffffccccccffffffccccccffffffff
+ffffccccccffffffcc000000ccccccffffff000000000000ffffccccccffffffccffffff
+000000ccccffffffcc000000000000ccccffffffccccccffffffffccccccffffffcccccc
+000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffff000000ffffcc000000ffffffccccccffffffffffccccccff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffffffffccccccffffff
+cccccc000000ffffffccccccffffffffffccccccffffffccccccff000000ffffffccccff
+ffffccccccffffffccccccffffffccccccffffffccccccff000000ccccffffffccccccff
+ffffccccccffffffccffffffccccccffffffccccccffffff000000ffffff000000ffffcc
+ffffffccccccffffffcccccc000000ffffccffffff000000ffffffccccccffffff000000
+ccccccffffffccccffffffccccccffffffffccccccffffff000000000000ffffccccccff
+ffffcc000000000000ffffffccccccffffffccccffffffccccccffffffcc000000000000
+ffffccccccffffffccffffff000000ccccccffffff000000ffffffccccccffffff000000
+000000ffffffccccccffffffccccccffffffccccccffffcccccccc666666cccc999999cc
+999999cccc99999999cccc999999cccccc999999999999cc999999cccc99999999999999
+cccccccccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ffffccccccffffffcc000000
+ccccff000000cccccc000000ccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccccccff000000ffffffccccccffffffccccccffffffccccccffffffcccccc000000
+cccccc000000ffffffcccccc000000ffffffccccccffffffcccccc000000000000000000
+ffffffcccccc000000ffffffccccccffffffccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffff000000ffffffccccccffffffcccccc000000
+000000ffffffcccccc000000cccccc000000ffffccccccffffffccccccffffffccccccff
+000000000000ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccffffffccccccffffffcc000000ffffffccccccffffff000000ffffffccccccffffff
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000
+ccccffffffccccccffffffcc000000ffffccccccffffffcc000000ffffffccccccffffff
+ccccffffffccccccffffffcc000000000000ffffffccccccffffffcccccc000000ffffff
+ccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ffffccccccffffffcc
+ffffffcccccc000000ffffffccccccffffffccccccffffff000000000000ffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccccccff000000ffffffccccccffffffccccccffffcc000000000000
+000000ffffcc000000cccccc000000ffffccffffffccccccffffff000000cccccc000000
+ffffffccccccffffff000000cccccc000000ffffffccccccffffff000000000000000000
+cccccc000000ffffffccccccffffffccccccffffff000000000000ffffccccccffffffcc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffcc
+ccccffffffcc000000ffffffcccccc000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffff000000ffffffcccccc000000000000ffffffccccccffffffcccccc
+000000ffffccccccff000000000000ffffccccccffffffccccccffffffccccccffffffff
+000000ffffffccccccffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ccccff000000ccccccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffff000000ccccff000000ffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ffffffccccccffffffccccccffffffccccccffffff000000ccccccffffff
+ccccccffffffffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000cccccc000000ccccff
+ffffccccccffffffccffffff000000ccccffcccccc000000ffffffccccccffffff000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000000000ffffffccccff
+ffffcc000000000000ffffccccccffffffccccccffffffccccccffffffff000000000000
+ffffffccccccffffffcccccc000000ffffffcccccc000000ccccffffffccccccff000000
+000000ffffccccccffffffccffffffccccffffffccccccffcccccc666666999999999999
+9999999999cc9999999999999999999999999999999999999999999999cc999999cccc99
+ccccffcccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ccccccffffffccccffffffcc
+000000000000ffffff000000ffffccccccff000000ccccffffffccccccffffffccccccff
+ffffffffffcc000000ccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffff000000ccccccffffff000000ccccffffffccccccffffffff000000000000000000
+ccccccffffff000000ffffccccccffffffccccccffffffccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ccccffffffccccccffffffff000000
+000000ccccccffffff000000ffffff000000ccccccffffffccccffffffccccccffffffcc
+000000000000ffffccccccffffffccccccff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffccccccff000000ccccccffffffcccccc000000ccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffccccccff
+ffffccccccccffffff000000000000ccccccffffffccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff
+ccccccffffffccccccffffffffffccccccffffffccccccffffffccccccffffffcc000000
+ffffccccccffffffccccccff000000ccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffccccccffffffff000000000000ffffccccccffffffccffffff000000ccccff
+ffffccccccff000000ffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ccccffffffccccccff000000ffffccccccffffffff
+ccccccffffff000000ffffffccccccffffffccccccffffff000000000000ccccccffffff
+ccccccffffffffffccccccffffffccccccffffffccccccffffffccffffff000000ccccff
+ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffffffccccccff
+ffffccccccffffffffffffcc000000ffffccccccffffffccccccffffffff000000000000
+000000ffffff000000ffffff000000ccccccffffffccccccffffff000000ffffff000000
+ffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000000000000000
+ffffff000000ffffccccccffffffffccccccffffff000000000000ffffffffffccccccff
+000000ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffffccccccffffff000000ffffff
+ffffccccccff000000ccccccffffff000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000ccccccffffff000000000000ccccccffffffccccccffffff
+000000ccccccffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+000000ccccffffffffccccccffffffffffccccccffffffccccccffffffccccccffffffcc
+ffffff000000ccccffffffccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffcc000000ffffff000000ccccffffffccccccffffffccccccff000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffff000000ffffccccccffffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffff000000ffffcc
+ccccffffffffccccccffffff000000ffffccffffff000000ccccccffffffcccccc000000
+ccccffffffccccccffffffccccccffffffccccccffffffff000000000000ffffccccccff
+ffffff000000000000ccccccffffffffffccccccffffffccccccffffffcc000000000000
+ccccffffffccccccffffffff000000ccccffffffcc000000ffffffccccccffffff000000
+000000ffffccccccffffffccccccffffffccccccffffffcccccccc6666669999cccccc99
+999999999999cccc999999999999cc999999cccccc999999cccc99999999999999999999
+cccccccccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ffffffccccccffffffcccccc
+ffffff000000cccccc000000ffffffcccccc000000ffffccccccffffffccccccffffffcc
+ccccffcccccc000000ffffffccccccffffffccccffffffccccccffffffccccccff000000
+ffffcc000000ffffffcccccc000000ffffccccccffffffccccccff000000000000000000
+ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccffffff000000ffffccccccffffffccccccff000000
+000000ffffffcccccc000000ccccff000000ffffffccccccffffffccccccffffffccccff
+000000000000ffffffccccccffffffffffcc000000ffffccccccffffffccccccffffffcc
+ffffffccccccffffffffffcc000000ffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffffcccccc000000000000ffffffccccccffffffffffccccccffffffffcccccc
+ffffffccccffffffccffffffccccffffffccffffffccccccffffffccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffffccccccffffffcccccc000000
+ffffffccccccffffffffffcc000000ffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffccccccffffff000000000000ffffffccccccffffffcccccc000000ffffcc
+ccccffffffcc000000ccccffffffccccccffffffccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ffffffccccccffffff
+ccccccffffff000000ffffccccccffffffccccccffffffcc000000000000ffffffcccccc
+ffffffccccccffffffccccccffffffffffccccccffffffffccccccccccff000000ffffcc
+ccccffffffccccccffffffccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccccccff000000ffffffccccccffffffccccccffffff000000000000
+000000cccccc000000ccccff000000ffffffccccccffffffcccccc000000cccccc000000
+ccccccffffffcccccc000000cccccc000000ccccffffffccccccff000000000000000000
+cccccc000000ffffffccccccffffffccccccffffff000000000000ccccffffffccccccff
+000000ffffccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffccccccffffffccccccffffffcc000000ccccff
+ffffccffffff000000ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffff000000ffffffcccccc000000000000ffffffccccccffffffcccccc
+000000ffffffcccccc000000000000ffffffccccccffffffccccccffffffccccccffffff
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ffffccccccffffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffff000000cccccc000000ffffffccccccffffffccccccffffff000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff
+cccccc000000ffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccffffffccccccffffffcc000000cccccc000000ffffff
+ccccccffffffccccccffffff000000ccccffcccccc000000ffffffccccccffffff000000
+ffffccccccffffffccffffffccccccffffffccccccffffff000000000000ccccffffffcc
+cccccc000000000000ffffffccccccffffffccccccffffffccccccffffff000000000000
+ffffccccccffffffcccccccc000000ffffffcccccc000000ffffffccccccffffff000000
+000000ccccffffffccccccffffffccccccffffffccccccffcccccc666666999999999999
+9999cc999999999999999999cccc999999999999999999999999cc999999cccccc999999
+ffffffcccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ffffccffffffccccccffffff
+000000000000ffffff000000ffffccffffff000000ffffccccccffffffccffffffcccccc
+ffffffffffff000000ffffccccccffffffccccccffffffccffffffccccccffffff000000
+ccccff000000ccccccffffff000000ccccffffffccccccffffffcc000000000000000000
+ccccccffffff000000ccccccffffffccccccffffffccccccffffffccccccffffffccccff
+ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffffffcc000000
+000000ffffccffffff000000ffffcc000000ccccffffffccccccffffffccccccffffffcc
+000000000000ccccffffffccccccffffffff000000ccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccff000000ffffffccccccffffff000000ccccffffffccffffff
+ccccccffffffccccccffffffffffffccccccffffffccccff000000ffffccccccffffffcc
+ccccffffffffccccccffffffccccccffffff000000ccccffffffffccccccffffffffffcc
+ccccffffffccffffff000000000000ffffccccccffffffccccccffffffccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffff000000
+ffffccccccffffffccccccff000000ccccccffffffcccccc000000ccccccffffffffffcc
+ccccffffffccccccffffffcc000000000000ccccffffffccccccffffffff000000ffffcc
+ccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffccffffff
+ccccffffffccccccffffffcc000000ccccccffffffcccccc000000ccccffffffccccccff
+ffffccccccff000000ffffffccccccffffffccccccffffff000000000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff000000ffffcc
+ccccffffffccccccffffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ffffffccccccffffffffffcc000000ffffccccccffffffccccccffffffcc000000000000
+000000ffffff000000ffffcc000000ccccccffffffccccccffffff000000ffffff000000
+ffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000000000000000
+ffffff000000ffffccccccffffffccccccffffffcc000000000000ccccffffffccffffff
+000000ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000cccccc
+ffffffcccccc000000ccccffffffff000000ccccffffffccccccffffffffccccccffffff
+ccccccffffffcccccc000000ccccccffffff000000000000ffffffccccccffffffffffff
+000000ccccccffffff000000000000ccccffffffccccccffffffccccccffffffccccccff
+000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ccccccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffff000000ffffcc000000ffffffccccccffffffffffccccccff000000ffffffcccccc
+ffffffffffccccccffffffccccccffffffffffffccccccffffffccccccffffffffcccccc
+ffffff000000ffffccccccffffffccffffffccccccffffffcccccc000000ccccffffffcc
+ccccffffffffccccccffffffccccccffffffccccccffffff000000ccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffccccccffffffff000000ffffff000000ffffcc
+ccccffffffccccccffffffcc000000ffffccffffff000000ffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ffffccccccff
+ffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc000000000000
+ccccffffffccccccffffffff000000ccccccffffff000000ffffffccccccffffff000000
+000000ffffccccccffffffccccccffffffccccccffffffcccccccc666666cccc99999999
+999999cccc999999cc9999999999999999cccccc99999999999999999999999999999999
+cccccccccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ccccffffffccccccff000000
+ffffcc000000ccccff000000ccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccccccff000000ffffffccccccffffffccccccffffffccccccffffffcccccc000000
+ffffcc000000ffffffcccccc000000ffffffccccccffffffccccff000000000000000000
+ffffffcccccc000000ffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffccccccffffffccccccff000000
+000000ccccffffffcc000000ffffff000000ffffccccccffffffccffffffccccccffffff
+000000000000ffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccffffffccccccffffffffccccccffffffcccccc000000
+ccccffffffccccccffffffcc000000ffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffccccccffffff000000000000ffffccccccffffffccccccff000000ffffff
+ccccccffffff000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffff000000000000ffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffff
+ccccccffffffccccccffffffccccccffffff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff000000000000
+000000cccccc000000ffffff000000ffffffccccccffffffcccccc000000cccccc000000
+ffffffccccccffffff000000cccccc000000ffffffccccccffffff000000000000000000
+cccccc000000ffffffccccccffffffccccccffffff000000000000ffffccccccffcccccc
+000000ffffccccccffffffccccccffffffccffffffccccffffffccccccffffffccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffff
+ccccccffffff000000ffffcccccccc000000ffffffffffccccccffffffccccccffffffcc
+ccccffffffccffffff000000ffffffcccccc000000000000ffffccccccffffffccccccff
+000000ffffffcccccc000000000000ffffccccccffffffccccccffffffccccccffffffcc
+000000ffffffccccccffffffccccffffffccffffffccccccffffffccccffffffccccccff
+ffffcc000000ffffffccccccffffffffffccccccffffffccccccff000000ffffccccccff
+ffffcc000000ccccff000000ffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ccccffffffffccccccffffffccccccffffffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000cccccc000000ccccff
+ffffccccccffffffccccccff000000ccccffcccccc000000ffffccccccffffffcc000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000000000ffffffcccccc
+ffffff000000000000ffffffccccccffffffccccccffffffccccccffffff000000000000
+ffffffccccccffffffffffcc000000ffffffcccccc000000ccccccffffffcccccc000000
+000000ffffffccccffffffccffffffccccccffffffcccccccccccc6666669999cc999999
+9999999999cc999999999999cccc999999999999999999cccccc999999999999cc999999
+ffffffcccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ffffffccccccffffff000000
+ffffff000000ffffcc000000ffffffccccff000000ccccccffffffccccccffffffcccccc
+ffffffffffcc000000ccccffffffccccccffffffffccccccffffffccccccffffff000000
+ffffff000000ccccffffffcc000000ffffffccccccffffffffffcc000000000000000000
+ccccccffffff000000ffffffccccccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ccccccffffffccccccffffff000000
+000000ffffccccccff000000ccccff000000ccccffffffccccccffffffccccccffffffcc
+000000000000ccccffffffccccccffffffcc000000ffffffffffccccccffffffccccccff
+ffffccccccffffffccccccff000000ccccffffffccccccff000000ffffccccccffffffff
+ccccccffffffccccccffffffccccccffffffffffccccccff000000ffffffccccccffffff
+ccccccffffffffffccccccffffffccccccff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff000000
+ccccffffffffccccccffffff000000ccccccffffffcccccc000000ffffffccccccffffff
+ccccffffffccccccffffffcc000000000000ccccffffffccccccffffffcc000000ffffcc
+ccccffffffcc000000ccccffffffccccccffffffccccccffffffffccccccffffffcccccc
+ffffffccccccffffffcccccc000000ffffffccccccffffff000000ccccccffffffcccccc
+ffffffcccccc000000ffffffccccccffffffccccccffffff000000000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffccccccffffffccccccffffffcc
+ccccffffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000000000
+000000ffffff000000cccccc000000ffffffccccccffffffffffff000000ffffcc000000
+ffffffccccccffffff000000ffffff000000ffffffccccccffffff000000000000000000
+ffffff000000ffffccccccffffffccccccffffffcc000000000000ccccffffffccffffff
+000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffffccccccffffffccccccffffffcccccc000000ccccff
+ffffccccccff000000ccccccffffff000000ffffffccccccffffffccccccffffffffffcc
+ccccffffffccccccff000000ccccccffffff000000000000ccccffffffccccccffffffcc
+000000ccccffffffff000000000000ccccccffffffffffffccccccffffffffffccccccff
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffff000000ffffff000000ccccffffffffccccccffffffcccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ccccccffffffccccccffffffccccffffffccccccff000000ffffffcccccc
+ffffffffffccccccffffffccccccffffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffff000000ffffcc
+ccccffffffccccccffffffcc000000ffffccffffff000000ccccffffffccccccff000000
+ccccccffffffccccffffffccccccffffffccccccffffffff000000000000ffffccccccff
+ffffcc000000000000ffffffccccccffffffccccffffffccccccffffffcc000000000000
+ffffccccccffffffccccccff000000ccccccffffff000000ffffffccccccffffff000000
+000000ffffffccccccffffffccccccffffffccccccffffffcccccc666666cccc99999999
+999999cccc999999999999999999cc999999999999999999999999cccc99999999999999
+cccccccccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ffffccccccff000000cccccc
+ffffff000000ccccff000000ffffccccccff000000ffffffccccccffffffccccccffffff
+ccccccffffff000000ffffccccccffffffccccccffffffccccccffffffccccccff000000
+cccccc000000ffffffcccccc000000ffffffccccccffffffcccccc000000000000000000
+ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccffffffccffffffccccffffffccccccff000000ffffffccccccffffffcccccc000000
+000000ccccffffffcc000000ffffcc000000ffffffccccccffffffccccccffffffccccff
+000000000000ffffccccccffffffccccccff000000ccccffffffccccccffffffffcccccc
+ffffffccccccffffffffffcc000000ffffccccccffffffcc000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffccccccffffffffccccccffffff
+ccccccffffffccccff000000000000ffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffffccccccffffffffffccccccffffffccccccffffffcc
+ccccffffffccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffccccccffffffccccccff000000ffffffccccccffffff000000ccccccffffffcccccc
+ffffffccccccffffffccccff000000000000ffffccccccffffffccccccff000000ffffff
+ccccccffffff000000ffffffccccccffffffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccffffff000000ccccccffffffcccccc000000ffffffccccccffffff
+ffffffcccccc000000ffffffccccccffffffccccccffffff000000000000ffffffcccccc
+ffffffccccccffffffffffffccccccffffffccccccffffffccccccffffff000000ffffff
+ccccccffffffccccccffffffccccccffffff000000ffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccffffffcc000000000000
+000000cccccc000000ffffff000000ffffccccccffffffccccccff000000ccccff000000
+ffffccccccffffffcc000000cccccc000000ccccffffffccccccff000000000000000000
+cccccc000000ffffffccccccffffffccccccffffff000000000000ffffccccccffccccff
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffffffccccccffffffccccccffffffccffffff000000ffffcc
+ccccffffffcc000000ffffffcccccc000000ccccccffffffccccccffffffccccccffffff
+ccccffffffccccccff000000ffffffcccccc000000000000ffffccccccffffffccccccff
+000000ffffccccccff000000000000ffffffccccccffffffccccccffffffccccccffffff
+000000ffffffccccccffffffccccccffffffccccccffffffffffccccccffffffccccccff
+ffffcc000000ccccffffffccccccffffffffccccccffffffcccccc000000ccccffffffcc
+cccccc000000cccccc000000ffffccccccffffffccccccffffffff000000ccccccffffff
+ccccccffffffffffccccccffffffccccccffffffccccccffffffccffffffccccccffffff
+cccccc000000ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffccccccffffffccccccffffffccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000ccccff000000ffffff
+ccccccffffffccccccffffff000000ccccffffffcc000000ffffccccccffffffcc000000
+ffffffccccccffffffffffccccccffffffccccccffffffcc000000000000ccccffffffcc
+ccccff000000000000ffffccccccffffffccccccffffffccccccffffffff000000000000
+ffffffccccccffffffcccccc000000ffffffcccccc000000ffffccccccffffffcc000000
+000000ccccccffffffccccccffffffccccccffffffcccccccccccc6666669999cc999999
+9999999999cc999999cccc99999999999999cccccc9999999999999999cc999999cccc99
+ccccffcccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ffffff000000ffffccccccff
+ffffcc000000ffffcc000000ffffffffffcc000000ccccccffffffccccffffffccccccff
+ffffccccccff000000ffffccccccffffffccffffffccccccffffffccccccffffff000000
+ffffff000000ccccccffffff000000ccccffffffccccccffffffff000000000000000000
+ccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccffffff000000ccccccffffffccccccffffff000000
+000000ccccccffffff000000cccccc000000ccccffffffccccccffffffccccccffffffcc
+000000000000ccccffffffccccccffffffcc000000ccccffffffccccccffffffccccccff
+ffffccccccffffffccccccff000000ccccccffffffccccff000000ffffccccccffffffcc
+ffffffccccffffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffffccccccffffffccccccffffff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffffffffccccccffffffccccccffffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccffffffccccccffffffccccccffffffcccccc000000
+ccccffffffccccccffffffcc000000ffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000cccccc
+ffffffcccccc000000ffffccccccffffffccccccffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ffffffccccccffffff
+ccccccffffff000000ffffccccccffffffccccccffffffcc000000000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000cccccc
+ffffffccccccffffffccccccffffffcccccc000000ccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccff000000ffffccccccffffffccccccffffffff000000000000
+000000ffffff000000ccccff000000ccccffffffccccccffffffcc000000ffffff000000
+ffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000000000000000
+ffffff000000ffffffccccccffffffccccccffffff000000000000ccccccffffffffffcc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffcc
+ccccffffffff000000ccccccffffff000000ffffffffffccccccffffffffccccccffffff
+ccccccffffffffffcc000000ccccccffffff000000000000ffffccccccffffffccffffff
+000000ffffffffffcc000000000000ccccccffffffccccccffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffcc000000ccccffffffccccccffffffccffffffccccccffffff000000ffffccccccff
+ffffff000000ffffff000000ffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffffffffccccccffffffccccccffffffccccffffffcc
+ffffff000000ccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccff000000ccccccffffffcccccc
+ffffffccccccffffffccccffffffccffffffccccccffffff000000ffffcc000000ffffcc
+ccccffffffffccccccffffff000000ffffccccccff000000ffffffccccccffffff000000
+ccccccffffffccccccffffffccccccffffffccccccffffff000000000000ffffffcccccc
+ffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc000000000000
+ffffffccccccffffffffffff000000ccccccffffff000000ffffffccccccffffff000000
+000000ccccccffffffccccffffffccccccffffffccccccffcccccc999966999999999999
+cccc999999999999999999cc999999999999999999999999cccc99999999999999999999
+cccccccccccc000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000000000ccccffffffccccccff
+ffffff000000ccccff000000ccccccffffff000000ffffffffffccccccffffffccccccff
+ffffccccccff000000ffffffccccccffffffccccccffffffccccccffffffcccccc000000
+ffffcc000000ffffffcccccc000000ffffccccccffffffccccccff000000000000000000
+ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffffffcc000000
+000000ffffffcccccc000000ffffff000000ffffffccccccffffffccccccffffffcccccc
+000000000000ffffffccccccffffffffffcc000000ffffffccccccffffffffffccccccff
+ffffccccccffffffccccccff000000ffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccffffffcccccc
+ffffffccccccffffff000000000000ffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff000000
+ffffccccccffffffccccccff000000ffffccccccffffffcc000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000000000ffffffccccccffffffcccccc000000ffffff
+ccccccffffff000000ffffffccccccffffffccccccffffffffffccccccffffffccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffff000000000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffffccccccffffff000000ffffff
+ccccccffffffffffffccccccffffffffffff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffcc000000ffffffccccccffffffccccccffffff000000000000
+000000ffffcc000000ffffcc000000ffffccccccffffffccccccff000000cccccc000000
+ccccccffffffcccccc000000cccccc000000ffffffccccccffffff000000000000000000
+cccccc000000ffffffccccccffffffccccccffffff000000000000ffffffccccccccccff
+000000ffffccccccffffffccccccffffffccffffffccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffffccccccffffffcccccc000000ffffff
+ccccccffffff000000ffffffcccccc000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffccccff000000ffffffcccccc000000000000ffffffccccccffffffcccccc
+000000ccccccffffff000000000000ffffffccccccffffffccccffffffccccccffffffcc
+000000ffffffccccccffffffccccffffffccccccffffffccccccffffffffccccccffffff
+ccccff000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffff000000cccccc000000ffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffccccccffffffccffffffccccccffffffcccccc000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffccccccffffff
+ccccccffffffffffccccccffffffccccccffffffccccccff000000ccccff000000ffffff
+ccccccffffffccccccffffff000000ffffffcccccc000000ffffffccccccffffff000000
+ffffffccccccffffffccccccffffffffffccccccffffffcc000000000000ffffccccccff
+ffffcc000000000000ffffffccccccffffffccccccffffffccccccffffff000000000000
+ccccccffffffccccccffffcc000000ffffffcccccc000000ffffffccccccffffff000000
+000000ffffffccccccffffffffffccccccffffffccccccffcccccc6666669999cc999999
+9999999999cccccc99999999999999cccccc9999999999cc999999999999cccccc999999
+ffffffcccccc000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000ccccffffffccccccff
+ffffcc000000ffffff000000ffffffcccccc000000ffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccffffffccffffffccccccffffff000000
+ccccff000000ccccccffffff000000ffffffccccccffffffffffcc000000000000000000
+ccccccffffff000000ccccffffffccffffffccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ccccffffffffccccccffffff000000
+000000ccccffffffff000000cccccc000000ffffffccccccffffffffffccccccffffffff
+000000000000ffffccccccffffffccccccff000000ccccffffffccccccffffffccccccff
+ffffffccccccffffffffffcc000000ccccccffffffcccccc000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccffffffcccccc000000ffffffccccccffffff
+ffffccccccffffffccccccffffffccccccff000000ccccccffffffccccccffffffcccccc
+ffffffccccccffffff000000000000ccccccffffffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccffffffccccccffffffccccccff000000
+ccccffffffffccccccffffff000000ccccffffffccccccff000000ccccffffffccffffff
+ccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000cccccc
+ffffffcccccc000000ccccffffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffcccccc000000ccccffffffccccccff000000ccccffffffccccccff
+ffffccccccff000000ffffffccccffffffccccccffffffcc000000000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffffccccccffffffccccccffffff000000cccccc
+ffffffccccccffffffccccccffffffcccccc000000ccccccffffffffffccccccffffffcc
+ffffffccccccffffffccccff000000ffffccccccffffffccccccffffffcc000000000000
+000000ccccff000000ccccff000000ccccccffffffccccccffffff000000ffffff000000
+ffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000000000000000
+ffffff000000ffffccccccffffffccccccffffffcc000000000000ccccffffffccffffff
+000000ccccffffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffcc000000ccccffffffff000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000ccccffffffff000000000000ccccccffffffccccccffffff
+000000ccccccffffff000000000000ccccccffffffccccccffffffccccccffffffccccff
+000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ffffff000000ccccccffffffccccccffffffccccffffffccccccff000000ffffffcccccc
+ffffff000000ffffff000000ccccccffffffccccccffffffcccccc000000ffffccccccff
+ffffccccccffffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ccccffffffccccccffffffccccccffffffccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffffffcc000000ffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffcc000000cccccc
+ffffffccccccffffffcccccc000000ccccccffffff000000ccccccffffffcccccc000000
+ccccffffffccccccffffffccccccffffffffccccccffffff000000000000ffffffcccccc
+ffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc000000000000
+ffffffccccccffffffccccff000000ccccccffffff000000ffffccccccffffffcc000000
+000000ffffffccccccffffffccccccffffffccccccffffcccccccc666666999999cccc99
+9999999999999999cc999999999999999999cccc99999999999999999999999999999999
+cccccccccccc000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffff000000ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffff000000ffffffffffff000000ffffffffffffffffffffffff000000000000000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+000000ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000
+000000ffffff000000ffffff000000ffffffffffffffffffffffff000000ffffff000000
+ffffffffffffffffff000000ffffff000000ffffffffffffffffff000000000000000000
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000000000ffffffffffffffffffffffff
+000000ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+999999cccc999999999999999999cc9999999999999999cccccc999999cc999999999999
+ffffffcccccc000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc99999999
+999999999999999999cccc99999999cccc99999999999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+cccccc9999999999cc9999999999999999cc9999999999999999cc9999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999999999cccc99999999999999999999cccc99999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccccc
+9999999999999999cc999999cccccc9999999999cc999999999999999999cccccc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999cccc99999999999999999999999999cccc999999cc999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffff000000000000000000000000000000ffffffffffff000000
+000000ffffff000000000000000000ffffffffffffffffff000000000000ffffff000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccccc
+9999999999cc999999999999999999999999999999999999cccc999999cc999999999999
+ffffffcccccc000000
+000000ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999999999cccccc999999cccccc999999cc999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccccc
+999999cccc999999999999999999999999999999999999999999cc9999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999999999999999999999cccccc99999999999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccccc
+9999999999cccccc999999999999999999999999cc999999999999999999cccccc999999
+ffffffcccccc000000
+000000ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999999999999999cc999999cccc99999999cccccc999999999999999999999999
+cccccccccccc000000
+000000ffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+000000ffffff000000000000000000ffffffffffffffffff000000000000000000ffffff
+ffffff000000000000000000ffffffffffffffffff000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccccc999999
+cccc999999cc999999cccc999999999999999999999999999999999999cccccc99999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999999999999999cc9999999999999999cccccc99999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccccc
+999999cccc99999999999999cccc999999999999999999999999cc9999999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999cc9999999999999999cc9999999999cc999999cccc99999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+999999999999cccc99999999cccc999999999999999999999999cc999999999999cccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666699999999999999
+9999cc9999999999cc999999999999cccccc999999999999cccc999999cc999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+999999999999999999999999999999999999999999999999999999999999cccc999999cc
+ffffcccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc6666669999999999cc
+cccc99999999cccccc999999cccccc9999999999cccccc999999cc999999999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666999999999999
+999999999999999999999999999999999999999999999999999999999999cccccc999999
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666cccc99999999
+9999cccccc999999999999cc999999cccc99999999cccccc999999999999999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc6666669999cc999999
+999999999999999999cccc999999999999cc9999999999999999999999cc999999999999
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc999966999999999999
+cccccc9999999999cc999999999999999999999999cccc99999999cccc99999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc9999cc666666ffffffffffcc
+ccccffffffccffffffffffccffffffffffffccccffffffffccccffffffffffffffffffcc
+ccccffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666cccccccccccc
+cccccccccccc9999cccccccc9999cccccccccccc99cccccccccc999999cccccccc666666
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666ffffff9999cc
+cccc99cccccccccc99cccccccccc99cccccc9999cccccccccccccccccccccccccc666666
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666ffffcccccccc
+9999cccccccc9999cccccccc9999cccccc99cccccccccccc999999cccccccccccc666666
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666ccccffcccccc
+cccc99cccccccccccccccc99cccccccccccc9999cccccccccccccccccccc999999666666
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666cccccccccccc
+9999cccccccccccccc9999cccccccccccc99cccccc999999cccccccccccccccccc666666
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666ffffffcccc99
+cccccccccc999999cccccccccccccc9999cccccccccccccccccccccccccccccccc666666
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000cccccc000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666ffffcc9999cc
+cccccccccccccccc99cccccccccc99cccccccccc999999cccccc999999cccccccc666666
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff000000000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666ccccffcccccc
+cccc999999cccccccc9999cccccccccccccccccccccccccccccccccccccccccc99666666
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666cccccccccccc
+cccccccccc99cccccccccccccccc999999cccccccc999999cccccc9999cccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffff9999cc
+cccccc9999cccccccc9999cccccccccccccccccc99cccccccccccccccccccccccc666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffcccccc99
+cccccccccc99cccccccccc99cccccccccccc9999cccccccccccc999999cccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ccccffcccccc
+9999cccccccc9999cccccccc9999cccccc99cccccccccccc9999cccccccccccc99666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ccccffcccc99
+cccccccccccccccc99cccccccccccc9999cccccccccccc99cccccccccccccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000000000000000ffffffffffffffffffffffffffffff000000
+000000000000000000000000000000000000ffffff000000000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffcccccccc
+9999cccccccccccccc999999cccccccccc99cccccc9999cccccccc9999cccccccc666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccccccccccc
+999999cccccccccccccccccc9999cccccccccccc99cccccccccc99cccccccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffffcccccc
+cccccccccccc999999cccccccccccccccccc9999cccccccc9999cccccccccccc99666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffcc999999
+cccccccccccccccccc9999cccccc99cccccccccccccccc99cccccc9999cccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ccccffcccccc
+cccccccccc99cccccccccccccccccc999999cccccccccccccccccccccccccccccc666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccccccccccc
+9999cccccccc999999cccccccccccccccccccccc999999cccccc999999cccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffffcccccc
+cccc99cccccccccccc9999cccccc999999cccccccccccccccccccccccccccccc99666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccccccccccc
+9999cccccc99cccccccccccccccccccccccc9999cccccc99cccccc9999cccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000000000000000000000000000000000ffffffffffffffffff
+000000000000000000000000000000ffffffffffff000000000000000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffffcccccc
+cccccccccccc9999cccccc99cccccccccc99cccccccccccc9999cccccccccccccc666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffcc9999cc
+cccc99cccccccccccc9999cccccccc9999cccccc99cccccccccc99cccccccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff9999cc666666ffffffcccccc
+9999cccccccccccc99cccccccccccccccccc9999cccccccc9999cccccccccccc99666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccccccccc99
+cccccc9999cccccccc9999cccccc99cccccccccccccccc99cccccc9999cccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffffcccccc
+cccccccccc99cccccccccccccccccc999999cccccccccccccccccccccccccccccc666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666ffffcc9999cc
+cccccc9999cccccc999999cccccccccccccccccc999999cccccc999999cccccccc666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000000000000000
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000000000000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000000000000000000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000cccccc666666ccccffcccc99
+cccccccccccccccccccccccccccc999999cccccccccccccccccccccccccccccc99666666
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc666666ffffff666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc666666999999999999
+9999999999999999999999cc9999999999999999999999999999cc9999999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc666666999999cccc99
+9999cc999999cccc99999999cccc999999cccccc99999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc6666669999cc999999
+9999999999999999cc9999999999999999999999999999cc9999999999999999999999cc
+ffffcccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc999966999999999999
+cccc99999999999999cccc999999cc999999999999cccc999999cccccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+000000000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffff9999cc6666669999999999cc
+9999999999999999cc999999999999cccc999999999999999999999999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc999966999999999999
+cccc99999999cccc999999999999cc9999999999999999cccccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc6666669999cc999999
+9999cc999999999999999999999999cccc999999999999999999999999cccccc99999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000000000000000ffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffff000000000000000000000000000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc666666999999cccc99
+9999999999cccccc999999999999cc999999cccccc999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc666666999999999999
+999999999999999999999999999999999999999999999999cccccc9999999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffff000000000000000000000000000000ffffffffffffffffffffffffffffffffffff
+000000000000000000ffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000000000000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc999999999999999999
+cccccc9999999999cccccc99999999cccccc999999999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffff9999cc666666999999999999
+9999999999999999999999999999999999999999999999cc999999999999999999cccccc
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc666666cccc999999cc
+cccc999999999999cccccc999999cc999999cccc99999999cccccc999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc666666999999999999
+999999999999999999999999999999999999999999999999999999999999cccccc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc666666cccc999999cc
+999999cccc999999cccccc999999999999cccccc999999cc999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc666666999999999999
+999999999999999999999999999999999999999999999999cccc999999cc999999999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffcccccc6666669999999999cc
+cccc999999cc999999cccccc999999cccc999999cc999999999999cccc99999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666cccc99999999
+9999999999999999999999999999999999999999999999999999cc9999999999cccccc99
+ccccffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc00000000000099ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666699999999999999
+9999cc999999cccc999999cc9999999999cccccc99999999cccc99999999999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccc00000066ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+99ccff66ccff99ccff99ccff66ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666999999cccc99
+999999999999999999999999cccc999999999999999999cc999999999999cccccc999999
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccc00000099ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff66ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc6666669999999999cc
+999999cccccc9999999999999999cc999999cccc99999999999999999999999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccc00000099ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+66cccc99ccff99ccff99ccff66ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666999999cccc99
+9999999999999999999999999999999999999999cc999999cccc999999cc999999999999
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccc00000066ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff
+99ccff66ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff
+99ccff66ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff
+66ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff
+99ccff66ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff
+99ccff66ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff
+66ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff
+99ccff66ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff
+99ccff66ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff
+66ccff99ccff66cccc99ccff99ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc6666669999cc999999
+cccc999999cc999999cccccc999999cccc99999999999999999999cccc99999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccc00000099ccff66ccff99ccff99ccff99ccff99ccff99ccff99ccff
+66ccff99ccff66ccff99ccff99ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff
+99ccff99ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff
+99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff99ccff99ccff99ccff
+66ccff99ccff66ccff99ccff99ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff
+99ccff99ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff
+99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff99ccff99ccff99ccff
+66ccff99ccff66ccff99ccff99ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff
+99ccff99ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff
+99ccff66ccff99ccff66ccff99ccff66ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666999999999999
+9999999999999999999999999999999999cc9999999999cc9999999999999999cccccc99
+ccccffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc00000099ccff66ccff99ccff66ccff99ccff66ccff66ccff99ccff
+99ccff99ccff66ccff99ccff66ccff66ccff99ccff66ccff99ccff99ccff99ccff66ccff
+99ccff66ccff66ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff66ccff
+99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff66ccff99ccff66ccff99ccff
+99ccff99ccff66ccff99ccff66ccff66ccff99ccff66ccff99ccff99ccff99ccff66ccff
+99ccff66ccff66ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff66ccff
+99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff66ccff99ccff66ccff99ccff
+99ccff99ccff66ccff99ccff66ccff66ccff99ccff66ccff99ccff99ccff99ccff66ccff
+99ccff66ccff66ccff99ccff66ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff
+99ccff99ccff99ccff66ccff99ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc9999669999999999cc
+cccc999999999999cccccc99999999999999cccc99999999cccc99999999999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccccccccccccccc00000099ccff99ccff66cccc99ccff99ccff99cccc99ccff66ccff
+99cccc66ccff99ccff99cccc99ccff99ccff66cccc99ccff66ccff99cccc66ccff99ccff
+99cccc99ccff99ccff66cccc99ccff66ccff99cccc66ccff99ccff99cccc99ccff99ccff
+66cccc99ccff66ccff99cccc66ccff99ccff99cccc99ccff99ccff66cccc99ccff66ccff
+99cccc66ccff99ccff99cccc99ccff99ccff66cccc99ccff66ccff99cccc66ccff99ccff
+99cccc99ccff99ccff66cccc99ccff66ccff99cccc66ccff99ccff99cccc99ccff99ccff
+66cccc99ccff66ccff99cccc66ccff99ccff99cccc99ccff99ccff66cccc99ccff66ccff
+99cccc66ccff99ccff99cccc99ccff99ccff66cccc99ccff66ccff99cccc66ccff99ccff
+99cccc99ccff99ccff66cccc99ccff66ccff99cccc66ccff99ccff66cccc99ccff99ccff
+66ccff99ccff66cccc99ccff66ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc9999cc666666999999999999
+9999999999999999999999999999cc9999999999999999cc9999999999cccccc99999999
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccc00000066ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff
+99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff99ccff66ccff99ccff
+66ccff99ccff66ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff
+99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff
+99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff99ccff66ccff99ccff
+66ccff99ccff66ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff
+99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff
+99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff99ccff99ccff66ccff99ccff
+66ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff
+66ccff99ccff99ccff99ccff66ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666cccc999999cc
+999999cccccc999999cccc99999999999999cccc99999999999999999999999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccc00000099ccff99ccff99ccff66ccff99ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff99ccff
+66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff99ccff
+66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff99cccc
+99ccff66ccff99ccff66ccff99ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666999999999999
+9999999999999999999999cc9999999999cc999999999999cccccc9999999999cc999999
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccc00000099ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff
+66ccff99ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff66ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff
+66ccff99ccff99ccff99ccff66ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc999966999999999999
+cccccc999999999999cccc99999999cccc99999999999999999999cccc99999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccc00000099ccff99ccff99cccc66ccff99ccff99cccc66ccff99ccff
+99cccc66ccff99ccff99cccc66ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+99cccc66ccff99ccff99cccc66ccff99ccff99cccc66ccff99ccff99cccc66ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff99cccc66ccff99ccff99cccc66ccff99ccff
+99cccc66ccff99ccff99cccc66ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+99cccc66ccff99ccff99cccc66ccff99ccff99cccc66ccff99ccff99cccc66ccff99ccff
+66cccc99ccff99ccff66cccc99ccff99ccff99cccc66ccff99ccff99cccc66ccff99ccff
+99cccc66ccff99ccff99cccc66ccff99ccff66cccc99ccff99ccff66cccc99ccff99ccff
+99cccc66ccff99ccff99cccc66ccff99ccff66ccff99cccc99ccff66ccff99ccff99ccff
+99ccff66cccc99ccff66ccff99ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc9999cc6666669999cc999999
+9999999999999999cc9999999999999999cc9999999999999999999999cc999999cccc99
+ccccffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc00000000000099ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff99ccff
+99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff99ccff
+99ccff66ccff99ccff66ccff99ccff66ccff99ccff99ccff66ccff99ccff99ccff66ccff
+99ccff99ccff66ccff99ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff
+99ccff99ccff99ccff66ccff99ccff99ccff99ccff66ccff99ccff66ccff99ccff66ccff
+99ccff66ccff99ccff99ccff66ccff99ccff000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc999966999999999999
+cccc99999999cccc99999999999999999999cccccc999999cccc99999999999999999999
+cccccccccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000cccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc6666669999999999cc
+9999999999999999cc999999cccc999999999999999999999999cc999999cccccc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccc999999999999999999cc9999999999cccccc99999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccccc999999
+9999cc999999cccc999999999999999999999999999999cccccc999999999999999999cc
+ffffcccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999999999999999cc999999cccc999999999999999999cccccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccccc
+999999999999cccc999999999999999999cc9999999999999999999999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999cc9999999999cccccc99999999999999cccc999999cccccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+9999999999999999999999999999cc9999999999999999999999999999cccccc99999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffff000000000000ffffff000000000000
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000ffffffffffffffffffffffffffffff000000000000
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffff000000000000000000000000000000000000ffffffffffff
+ffffff000000000000ffffffffffffffffff000000000000ffffffffffffffffff000000
+000000ffffffffffff000000000000ffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+cccc999999cc999999cccc99999999999999cccccc999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffff000000000000ffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffff000000ffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999999999999999999999cc999999999999999999999999cccccc9999999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc999966999999999999
+cccccc999999999999cccc99999999cccccc999999999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000000000000000000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+000000000000000000000000000000000000ffffffffffffffffff000000000000000000
+000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000000000000000000000
+000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+9999999999999999999999cc9999999999999999999999cc9999999999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccccc999999999999cccc99999999999999cccc99999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc99999999
+9999999999999999cc9999999999cc9999999999999999cc999999999999cccc99999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffff000000000000ffffff000000
+000000ffffffffffff000000000000000000000000000000ffffffffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000ffffff000000ffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffff000000000000000000ffffff000000000000ffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+999999cccc99999999cccc99999999999999cccc999999999999999999cc999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+9999999999cc9999999999999999cc9999999999999999cccccc999999999999999999cc
+ffffcccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+999999999999cccc99999999999999cccc999999999999999999cccccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+9999cc9999999999cc9999999999cc9999999999999999999999999999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccc99999999999999cccc99999999999999cccc999999cccccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+9999999999999999cc9999999999cc9999999999999999999999999999cccccc99999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffff000000ffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+000000ffffff000000ffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000ffffff000000ffffffffffff000000ffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffcccccc666666cccc999999cc
+999999cccc99999999cccc99999999999999cccccc999999999999999999999999999999
+cccccccccccc000000
+000000ffffccccccffffffccccccffffffccccccff000000ffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffccccccffffffccccccff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000ffffffcccccc000000ffffff000000ffffcc000000ffffff
+ccccccffffffccccccffffff000000ffffccccccff000000ffffccccccff000000ffffcc
+ccccffffffccccccff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffffccccccffffff
+cccccc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccff
+000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000
+000000ffffccccccffffffccccccff000000ffffcc000000ffffffccccccffffff000000
+ffffccccccffffffccccccffffffcc000000ffffff000000ffffccccccffffffcc000000
+ffffffcccccc000000ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc000000ffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccff
+ffffccccccff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ffffccccccff000000ffffccccccffffffccccccffffffcc000000ffffff
+000000ffffcc000000ffffffccccccffffff000000ffffcc000000ffffffccccccffffff
+cccccc000000ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffff
+cccccc000000ffffffccccccffffffcccccc000000ffffffcccccc000000ffffff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffff000000ffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffccccccffffffccccccffffffccccccff000000000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000000000000000ffffccccccff
+ffffccccccffffffccccccff000000000000ffffccccccffffffcc000000ffffff000000
+ffffccccccff000000ffffccccccff000000ffffccccccff000000ffffccccccffffffcc
+ccccffffffcc000000000000ffffcc000000ffffccccccff000000ffffcc000000ffffcc
+ccccffffffccccccff000000ffffccccccffffffccccccffffffccccccffffffcc000000
+ffffccccccffffffccccccffffffccccccccffffffccccccffffff000000ffffccccccff
+ffffccccccffffffcc000000ccccccffffffccccccffffcccccccc666666999999999999
+9999999999cc9999999999999999cc999999999999999999cccccc9999999999cc999999
+ffffffcccccc000000
+000000ccccccffffffccccccffffffccccccffffff000000ccccff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000ccccccffffff000000ccccff000000ccccff000000ccccff
+ffffccccccffffffccccccff000000ccccccffffff000000ccccccffffff000000ccccff
+ffffccccccffffffcc000000000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ccccccffffff000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffff000000ccccffffffccccccff
+ffffccccccffffffccccccff000000ccccffffffccccccff000000ccccccffffffcccccc
+ffffff000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffccccccffffffccccccff000000ccccffffffcc
+ccccffffffccccccffffffccccccff000000ccccffffffccccccff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffffcc
+ccccffffffccccccffffffccccccff000000ffffccccccffffffccccccffffffccffffff
+000000ccccff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff000000
+000000ccccccffffffccccccffffff000000ccccff000000ccccffffffccccccff000000
+ccccffffffccccccffffffccccccff000000ccccff000000ccccffffffccccccff000000
+ccccccffffff000000ccccccffffff000000ffffffccccccffffffccccccffffffffffcc
+ccccffffffccccccffffffccccccff000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccffffff000000ccccffffffccccccff000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccffffff000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ccccccffffffccccccffffff
+ccccccffffff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ffffff000000ccccccffffff000000ccccccffffffccccccffffffcccccc000000ccccff
+000000ccccff000000ccccffffffccccccff000000ccccff000000ffffccccccffffffcc
+ffffff000000ccccccffffff000000ffffccccccffffffccccccffffffccccccffffffcc
+ffffff000000ccccccffffffccccccffffff000000ccccccffffff000000ccccff000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccccccff
+ffffccccccffffffccffffff000000000000ccccff000000ffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccffffff000000ffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccff000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ccccffffffccccccffffffccccccffffffcc000000000000ccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000000000000000ccccccffffff
+ccccccffffffccccccffffff000000000000ccccccffffffcccccc000000ccccff000000
+ccccccffffff000000ccccccffffff000000ccccccffffff000000ffffffccccccffffff
+ccccccffffff000000000000ccccff000000ccccccffffff000000ccccff000000cccccc
+ffffffccccccffffff000000ccccffffffccccccffffffccccccffffffccccccff000000
+ccccccffffffccccccffffffccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffffccccccffffff000000ffffccccccffffffffcccccccccccc6666669999cc999999
+cccc99999999999999cccc99999999cccccc999999999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccccffffffcccccc000000ffffcc000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ffffffccccccffffffcccccc000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccffffffccccccffffff
+ccccccffffffffffcc000000ffffffcccccc000000ffffcc000000ffffcc000000ffffcc
+ccccffffffccccccffffffcc000000ffffffcccccc000000ffffffcccccc000000ffffcc
+ccccffffffccccccff000000000000ffffccccccffffffccffffffccccccffffffffffcc
+ccccffffffcc000000ffffffcccccc000000ffffffccccccffffffffffccccccffffffcc
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffccccccffffffcc
+ffffffccccccffffffffffcc000000ffffffccccccffffff000000ffffffccccccffffff
+cccccc000000ffffccccccffffffccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccffffffcccccc000000ffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffffffccccccff000000ffffccccccff
+ffffccffffffccccccffffffffffcc000000ffffccccccffffffcc000000ffffccccccff
+ffffccffffffccccccffffffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+000000ffffcc000000ffffffccccccffffffffffccccccffffffccffffffccccccffffff
+ffffccccccffffffccffffffccccccffffffffffccccccffffffccccccffcccccc000000
+000000ffffffccccccffffffcccccc000000ffffcc000000ffffccccccffffffcc000000
+ffffccccccffffffccccccffffffcc000000ffffcc000000ffffffccccccffffff000000
+ffffffcccccc000000ffffffcccccc000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccffffffcccccc000000ffffffccccffffffccffffffccccccffffff
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc000000ffffff
+ccccccffffffffffccccccffffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffccffffffccccccffffffffffccccccffffffcc000000ffffffccccccffffffffffcc
+ccccffffffcc000000ffffffccccccffffffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+cccccc000000ffffffcccccc000000ffffffccccffffffccccccffffffff000000ffffcc
+000000ffffcc000000ffffccccccffffffcc000000ffffcc000000ccccffffffccccccff
+ffffcc000000ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffff
+cccccc000000ffffffccccccffffffcccccc000000ffffffcccccc000000ffffcc000000
+ffffccccccffffffccffffffccccccffffffffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffffffffccccccffffffcc000000ffffffccccccffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffcc000000ffffffccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccccccff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccffffffcccccc000000ffffccccccffffffccffffffccccccffffffcccccc
+ffffffffffcc000000ffffffccccccffffffccccccffffffccccff000000000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000000000000000ffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffffccccccffffff000000ffffcc000000
+ffffffcccccc000000ffffffcccccc000000ffffffcccccc000000ffffccccccffffffcc
+ccccffffffcc000000000000ffffff000000ffffffcccccc000000ffffff000000ffffff
+ccccccffffffcccccc000000ffffccccccffffffccccccffffffccccccffffffcc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ccccccffffff
+ccccccffffffcccccc000000ffffffccccccffffffcccccccccccc666666cccc99999999
+9999cc9999999999999999999999999999999999999999cc9999999999999999cccccc99
+ccccffcccccc000000
+000000ccccccffffffffffccccccffffffccffffff000000ccccff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000
+ccccffffffffffffccccccffffffccffffffccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000ccccccffffff000000ffffff000000ccccff000000ffffff
+ccccccffffffccccccffffff000000ccccccffffff000000ccccccffffff000000ffffcc
+ccccffffffccffffff000000000000ffffffccccccffffffccccccffffffccccffffffcc
+ccccffffffff000000ccccccffffff000000ffffccccccffffffccccccffffffffcccccc
+ffffffccccccffffffffffffccccccffffffccccccffffff000000ccccffffffccccccff
+ffffccccccffffffccccccff000000ffffccccccffffffcc000000ccccccffffffcccccc
+ffffff000000ffffffccccccffffffccccffffffccccccffffffccccccffffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffffffcc
+ccccffffffffccccccffffffccccffffffccccccffffffccffffff000000ccccffffffcc
+ccccffffffccccccffffffccccccff000000ccccffffffccccccff000000ccccffffffcc
+ccccffffffccccccffffffffccccccffffffffffccccccffffffffccccccffffffccccff
+ffffccccccffffffccccccffffffff000000ccccccffffffccccccffffffccccccffffff
+000000ccccff000000ffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffffccccccffffffffffff000000
+000000ccccccffffffccccccffffff000000ffffff000000ccccffffffccccccff000000
+ffffffccccccffffffccccccffffff000000ccccff000000ffffccccccffffffcc000000
+ccccccffffff000000ccccccffffff000000ffffffccccccffffffccccccffffffccccff
+ffffccccccffffffccccccffffffff000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffffccccccffffffccccccffffff000000ccccffffffccccccff000000ffffff
+ccccccffffffccccccffffffccccffffffccccccffffffccccccff000000ccccffffffff
+ccccccffffffccccffffffccccccffffffccccccff000000ffffccccccffffffccccccff
+ffffccccccff000000ffffffccccccffffffccccffffffccffffffccccccffffffcccccc
+ffffffffffccccccffffffffffffccccccffffffccffffffccccffffffccccccffffffcc
+ffffff000000ffffccffffff000000ccccccffffffffffccccccffffffcc000000ccccff
+000000ffffff000000ccccffffffccccccff000000ffffff000000ffffccccccffffffcc
+ccccff000000ccccccffffff000000ffffccccccffffffccffffffccccffffffccccccff
+ffffff000000ffffccccccffffffccffffff000000ffffccffffff000000ccccff000000
+ccccffffffffccccccffffffccccccffffffccccffffffccccccffffffffccccccffffff
+ccccccffffffccccffffffccccccffffffccccccff000000ccccccffffffccccccffffff
+ccccccffffffccccccffffff000000000000ccccff000000ffffffccccccffffffffffcc
+ccccffffffccffffffccccffffffccffffffccccccffffffccccffffffccccccffffffff
+ffffccccccffffffccccccffffffffffffccccccffffffffffffccccccffffffccccccff
+ffffccffffff000000ffffffccccccffffffffffffccccccffffffccccccffffffccccff
+ffffccccccffffffccffffff000000ccccffffffccccccffffffffccccccffffffcccccc
+ffffffcccccc000000ffffffccccccffffffccccccffffffffffcc000000000000ccccff
+ffffccccccffffffffffffccccccffffffccffffff000000000000000000ffffccccccff
+ffffccccccffffffccffffff000000000000ccccffffffccccccff000000ffffff000000
+ccccccffffff000000ccccccffffff000000ccccccffffff000000ffffffccccccffffff
+ccccccffffff000000000000cccccc000000ccccccffffff000000cccccc000000cccccc
+ffffffccccccffffff000000ffffccccccffffffccccccffffffccffffffccccff000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffff000000ccccccffffffccccccffffffcccccc666666999999999999
+cccc999999cc999999cccc999999cc999999cccc99999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccccffffffcccccc000000ffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffffccccccffffffcccccc000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffffccccccffffffccccff
+ffffccccccffffffcc000000ffffffcccccc000000cccccc000000ffffcc000000ffffff
+ccccccffffffccccccffffff000000ffffffcccccc000000ffffffcccccc000000ffffff
+ccccffffffccccccff000000000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffcc000000ffffffcccccc000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ffffffccccccffffff
+cccccc000000ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffffcccccc000000ffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffffccccccffffffccccccffffffcccccc
+000000ffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+000000ffffffccccccffffffcccccc000000cccccc000000ffffffccccccffffff000000
+ffffffccccccffffffccccccffffff000000ffffcc000000ffffffccccccffffff000000
+ffffffcccccc000000ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffcc000000ffffccccccffffffccccccffffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff000000cccccc
+ffffffccccffffffccffffffccccccffffffccccccffffffffffcc000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccffffffccccccff
+ffffffcccccc000000ffffccccccffffffccccccffffffccccccffffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffffccccccff
+cccccc000000ccccffffffcc000000ffffffccccccffffffccccccffffff000000ffffcc
+000000cccccc000000ffffffccccccffffff000000cccccc000000ffffffccccffffffcc
+ffffff000000ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffff
+cccccc000000ffffffccccccffffffcccccc000000ccccffffffcc000000ffffcc000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffffccccccffffff000000ffffffccccccffffffffffff
+ccccccffffffccccffffffcc000000000000ffffcc000000ffffccccccffffffccccccff
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffff
+ccccccffffff000000ffffccccccffffffccccccffffffccccccff000000000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000000000000000ffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffccccccffffffcc000000cccccc000000
+ffffffcccccc000000ffffffcccccc000000ffffffcccccc000000ffffffccccccffffff
+ccccccffffff000000000000ffffff000000ffffffcccccc000000ffffff000000ffffff
+ccccccffffffcccccc000000ffffffccccffffffccccccffffffccccccffffffcc000000
+ffffccccccffffffccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffffccccffffffcc000000ffffffccccccffffffcccccccccccc6666669999cc999999
+9999999999999999999999999999999999999999cc9999999999999999cccccc99999999
+ffffffcccccc000000
+000000ccccccffffffccccccffffffccccccffffff000000ccccff000000ffffffcccccc
+ffffffccccffffffccccccffffffcc000000000000ffffccccccffffffccffffff000000
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffff000000ccccccffffff000000ffffff000000ccccff000000cccccc
+ffffffccccccffffffcccccc000000ffffccffffff000000ccccffffffff000000ccccff
+ffffccccccffffffcc000000000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccff000000ccccccffffff000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000ccccffffffccccccff
+ffffffccccccffffffcccccc000000ccccffffffccccccff000000ffffccccccffffffcc
+ffffff000000ccccccffffffccccccffffffccccffffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccccffffffccccccffffff000000ccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffccccccff
+ffffffccccccffffffccccccffffff000000ccccffffffccccccff000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffffccccccffffff
+000000ffffff000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+000000ffffccccccffffffccffffff000000ffffff000000ccccccffffffcccccc000000
+ccccffffffccccccffffffccccccff000000ccccff000000ccccccffffffcccccc000000
+ffffffffffff000000ccccccffffff000000ffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffffccccff000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffcc000000ffffffccccccffffff000000ffffff
+ccccccffffffccccccffffffccccccffffffccccffffffccccccff000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccff000000ffffccccccffffffccccccff
+ffffccffffff000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ffffff000000ffffccccccff000000ccccffffffccccccffffffccccccff000000ffffff
+000000ffffcc000000ffffffccccccffffff000000ffffff000000ffffffccccccffffff
+cccccc000000ccccffffffff000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ccccffffffccccccffffffff000000ffffccccccff000000ccccff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ccccccffffffccccccffffff
+ccccccffffffccccccffffff000000000000ccccff000000ccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccffffffcc
+ccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccff000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ccccccffffffccccccffffffccccccffffff000000000000ccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000000000000000ccccffffffcc
+ccccffffffffccccccffffff000000000000ffffffccccccffffff000000ffffff000000
+ccccffffffff000000ffffffffffff000000ccccccffffff000000ffffccccccffffffcc
+ccccffffffcc000000000000ffffcc000000ccccccffffff000000ffffcc000000ccccff
+ffffccccccffffffff000000ccccffffffccccccffffffccccccffffffccccccff000000
+ccccccffffffccccccffffffccccccffffffffffccccccffffffcc000000ccccffffffcc
+ccccffffffccccccff000000ffffffccccccffffffcccccccccccc999966999999999999
+cccc999999cc999999cccccc999999999999cccc99999999999999999999999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccffffffccccccff000000ffffcc000000ffffccccccff
+ffffccccccffffffccccccffffffff000000000000ffffffccccccffffffccccff000000
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000ffffffcccccc000000ffffff000000ffffcc000000ffffff
+ccccffffffccccccffffffff000000ccccffffffcc000000ffffcccccccc000000ffffcc
+ccccffffffccccccff000000000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffffcccccc000000ffffffccccccffffffccccccffffffccccff
+ffffccccccffffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffff000000ffffffccccccffffff
+cccccc000000ffffffccccccffffffccccccffffffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccffffffccccccffffff000000ffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffccffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccff000000ffffccccccffffffccffffffccccccffffff
+000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccffffffccccccffffffccccccffffffccccccffffffccccccff000000
+000000ffffffccccccffffffcccccc000000ffffcc000000ffffffccccccffffff000000
+ffffccccccffffffccccccffffffcc000000ffffcc000000ffffffccccccffffff000000
+ccccccffffff000000ffffffcccccc000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffffffffccccccffffffcc000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffffcccccc000000ffffffccccccffffff000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffffccccccffffffccccffffffccffffffcccccc000000ffffffccccccffffffcccccc
+ffffffcccccc000000ccccccffffffccccccffffffccccccffffffffffccccccffffffff
+ccccccffffffccccccffffffccccffffffccccccffffffccccccffffffffccccccffffff
+cccccc000000ffffffcccccc000000ffffccccccffffffccccccffffffcc000000ccccff
+000000ccccff000000ffffccccccffffffcc000000cccccc000000ccccccffffffcccccc
+ffffff000000ffffccccccff000000ffffffccccccffffffccccccffffffccccccffffff
+cccccc000000ffffccccccffffffccccccff000000ffffffcccccc000000ffffff000000
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffffffffcc
+ccccffffffccccccffffffccffffffccccccffffff000000ffffffccccccffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffcc000000ffffffccccffffffccccccff
+ffffccccccffffffccccccffffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffccccccffffffccccccffffffffffffccccccffffffccccccff
+ffffffccccccffffffffffcc000000ffffffccccccffffffffffccccccffffffccffffff
+ccccccffffff000000ffffffccccccffffffccccccffffffcccccc000000000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000000000000000ffffccccccff
+ffffccccccffffffccccccff000000000000ffffccccccffffffcc000000ffffcc000000
+ffffcccccccc000000ccccccffffff000000ffffffcccccc000000ffffffccccccffffff
+ccccffffffcc000000000000ccccff000000ffffffcccccc000000ccccff000000ffffcc
+ffffffccccccffffcc000000ffffccccccffffffccccccffffffccccccffffffcc000000
+ffffffccccffffffccccccffffffccccccffffffccccccffffffff000000ffffccccccff
+ffffccccccffffffcc000000ffffffccccccffffffffffcc9999cc6666669999cc999999
+9999999999999999999999999999999999cc9999999999cccccc999999999999cc999999
+ffffffcccccc000000
+000000ffffccccccffffffccccccffffffccffffff000000ccccff000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ccccccffffffccccccffffcc000000
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000ccccffffffcc000000ccccff000000ffffff000000cccccc
+ffffffffffccccccffffffcc000000ffffccccccff000000ccccffffffff000000ccccff
+ffffccccccffffffcc000000000000ffffffccccccffffffccccffffffccffffffcccccc
+ffffffcccccc000000ffffccffffff000000ffffccffffffccccccffffffffffccccccff
+ffffccffffffccccccffffffccccccffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffffffccccccff000000ccccccffffffcccccc000000ccccccffffffcccccc
+ffffff000000ffffffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffffccccccffffffffffcc000000ccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffccccccffffffcc000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccff
+000000ccccff000000ccccccffffffffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccffffffccccccffffffffffccccccffffffccccccffffffcc000000
+000000ccccffffffffccccccffffff000000ccccff000000ffffffccccccffffff000000
+ccccffffffccffffffccccccffffff000000ccccff000000ffffffccccccffffff000000
+ffffffcccccc000000ccccccffffff000000ccccccffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffccccccff000000ffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ccccffffffccccccff000000ffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffffffcc000000ccccccffffff
+ffffccccccffffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffcc
+ccccffffffff000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffccffffff000000ffffffccccccffffffccccccffffff000000ffffcc
+000000ffffff000000ffffffccccccffffff000000ffffff000000ffffccccccffffffcc
+ccccff000000ccccffffffcc000000ffffccccccffffffccccccffffffccffffffcccccc
+ffffff000000ccccffffffccccccffffffcc000000ffffccffffff000000cccccc000000
+ffffffccccccffffffccccccffffffccccccffffffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ccccccffffffccccccffffff
+ccccccffffffccccccffffff000000000000ccccff000000ffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ffffccccccffffffccccccffffffccccccffffffccffffffcccccc
+ffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ccccccffffffffffccccccffffffccffffff000000000000ccccff
+ffffccccccffffffffccccccffffffccccccffffff000000000000000000ffffccffffff
+ccccccffffffccccccffffff000000000000ccccccffffffccccff000000ffffff000000
+ccccffffffff000000ffffffcccccc000000ccccccffffff000000ffffccccccffffffcc
+ccccffffffff000000000000ffffcc000000ffffccffffff000000ffffff000000ffffff
+ccccccffffffccccff000000ffffffccccffffffccccccffffffccffffffccccff000000
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffffffcc
+ccccffffffccccccff000000ccccccffffffccccccffffffcccccc666666cccc999999cc
+cccc999999999999cccccc99999999cccc99999999999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccccffffffcccccc000000ffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffffccccccffffffccccff000000
+ffffccccccffffffccffffffccccccffffffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000ffffffcccccc000000ffffcc000000cccccc000000ffffff
+ccccccffffffccccccffffff000000ccccffffffcc000000ffffccccccff000000ffffff
+ccccccffffffccccff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ccccffffffcc000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffccccccffffff000000ffffffccccccffffff
+cccccc000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffccccff000000ffffffcccccc
+ffffffffffccccccffffffccccccff000000ffffffccccccffffff000000ffffffcccccc
+ffffffffffccccccffffffccccccffffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffffffcc
+000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffffccccccffffffccccffffffccccccffffffff000000
+000000ffffccccccffffffccccccff000000ffffcc000000ffffccccccffffffcc000000
+ffffffccccccffffffccccccffffff000000ffffcc000000ccccffffffccccccff000000
+ffffccccccff000000ffffffffffcc000000ffffffccccffffffccccccffffffccffffff
+ccccccffffffccccccffffffffffcc000000ccccffffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccffffffcc000000ffffccccccffffffcc000000ffffcc
+ccccffffffccccccffffffccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccff
+ffffccffffff000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccccccffffffccffffffccccccffffffccccccffffff
+cccccc000000ccccffcccccc000000ffffffccccccffffffccccccffffff000000ccccff
+000000cccccc000000ccccccffffffcccccc000000ccccff000000ffffffccccccffffff
+ffffcc000000ffffccccccff000000ffffffccccccffffffccccccffffffccccccffffff
+cccccc000000ffffffccccccffffffccccff000000ccccffffffcc000000ffffff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc
+ffffffffffccccccffffffcc000000000000ffffcc000000ffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffccccccffffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccffffffccffffffccccccffffffcccccc
+ffffffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffffccccffffffccccccffffffccccccff000000000000ffffcc
+ccccffffffccffffffccccccffffffccccccffffff000000000000000000ccccffffffcc
+ccccffffffccccccffffffcc000000000000ffffffccccccffffff000000cccccc000000
+ffffccccccff000000ffffccccccff000000ffffffcccccc000000ffffffccccccffffff
+ccccccffffff000000000000ccccff000000ccccffffffcc000000cccccc000000ffffff
+ccccccffffffcccccc000000ffffccccccffffffccccccffffffccccccffffffcc000000
+ffffccccccffffffffccccccffffffccccccffffffccccccffffff000000ccccccffffff
+ccccccffffffffffcc000000ffffffccccccffffffcccccccccccc666666999999999999
+9999999999999999999999999999cc9999999999999999cc9999999999cc999999cccc99
+ccccffcccccc000000
+000000ccccccffffffccccccffffffccccccffffff000000ccccff000000ccccffffffcc
+ffffffccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffccccccffffff
+ffffccccccffffffcc000000ccccffffffff000000ffffff000000ffffff000000ffffcc
+ccccffffffccccccffffffcc000000ccccccffffff000000ccccffffffcc000000ffffff
+ffffccccccffffffcc000000000000ffffffccccccffffffccccccffffffccccffffffcc
+ccccffffffff000000ccccccffffff000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffff
+ccccffffffccccccffffffcc000000ccccffffffccccccff000000ccccccffffffcccccc
+ffffff000000ccccffffffccccccffffffccccccffffffffffffccccccffffffccffffff
+ccccccffffffccccccffffffccccccffffffffffff000000ffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffffffffcc000000ccccffffffcc
+ccccffffffffccccccffffffffffcc000000ccccccffffffcccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffff000000ffffccccccffffffccccccffccccccffffff
+000000ccccff000000ccccccffffffccccccffffffccccccffffffccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+000000ccccccffffffccccccffffff000000ccccff000000ccccffffffccccccff000000
+ccccffffffccccccffffffccccccff000000ffffcc000000ffffffccccccffffff000000
+ffffccffffff000000ffffccccccff000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffff000000ccccccffffffccccccffffffffffccccccff
+ffffccffffffccccccffffffccccccffffff000000ccccffffffccccccff000000ffffcc
+ccccffffffccccccffffffffccccccffffffccccccffffffcccccc000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffcccccc
+ffffffcccccc000000ccccffffffccffffffccccffffffccccccffffffccccccffffffcc
+ccccffffffccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffff000000ffffccffffff000000ffffccccccffffffccccccffffffcc000000ffffcc
+000000ffffff000000ffffffccccccffffff000000ffffcc000000ffffccccccffffffcc
+ccccff000000ccccffffffcc000000ccccffffffccccccffffffccccccffffffffcccccc
+ffffff000000ccccccffffffccccccffffcc000000ffffccccccff000000ccccff000000
+ccccffffffccccccffffffccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ccccccffffffccccccffffff
+ccccffffffccccccffffffff000000000000ccccff000000ccccffffffccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffffcccccc
+ffffffccccff000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccff000000ccccffffffffccccccffffffccccffffffccccccff
+ffffccccccff000000ccccccffffffccccccffffffccccccffffff000000000000cccccc
+ffffffccccccffffffccccccffffffccccccffffff000000000000000000ccccccffffff
+ccccccffffffccccccffffff000000000000ffffffccccccffffff000000ffffff000000
+ccccccffffff000000ffffccffffff000000ccccccffffff000000ffffccccccffffffcc
+ccccffffffcc000000000000ffffcc000000ccccccffffff000000ffffff000000ffffcc
+ccccffffffccffffff000000ffffffccccccffffffccccccffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffccffffffccccccffffff000000ccccffffffcc
+ccccffffffccccccff000000ccccffffffccccccffffffcccccccc6666669999999999cc
+cccc999999cc999999cccc99999999999999cccc99999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccccffffffcccccc000000ffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffffccccccffffffffffcc000000
+ffffccccccffffffffccccccffffffccccccffffffccccccffffffffffccccccffffffcc
+ccccffffffffcccccc000000ffffccccccff000000cccccc000000cccccc000000ccccff
+ffffccccccffffffccccccff000000ffffffcccccc000000ffffccccccff000000cccccc
+ffffffccccccffffff000000000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffffcccccc000000ffffffccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffffccccffffffcc
+ffffff000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccccccccc000000ffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccccccff000000ffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccff
+ffffccffffffccccccffffffcccccc000000ffffffccccffffffccffffffccccccffffff
+000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000
+000000ffffffccccccffffffcccccc000000ffffcc000000ffffffccccccffffff000000
+ffffccccccffffffffccccccffffff000000ccccff000000ffffccccccffffffcc000000
+ccccffffffcc000000ccccffffffcc000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff000000ffffff
+ccccccffffffccccccffffffccccccffffffccccffffffccffffff000000ffffccccccff
+ffffccffffffccccccffffffccccffffffccffffff000000ffffffccccccffffffcccccc
+ffffffffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccffffffffccccccffffffccccccffffffccccccffffff
+cccccc000000ccccffcccccc000000ffffffccccccffffffccccccffffff000000ccccff
+000000ffffcc000000ffffffccccccffffff000000ccccff000000ccccffffffccccccff
+ffffcc000000ffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffff
+cccccc000000ffffffffffccccccffffffff000000ffffffcccccc000000ffffcc000000
+ffffffccccccffffffccccffffffccccccffffffffccccccffffffccccccffffffccccff
+ffffccffffffccccffffffccccccffffffffcccccc000000ffffffffffccccccffffffcc
+ccccffffffccccccffffffcc000000000000ffffcc000000ffffffccccccffffffccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccffffffccccffffffcc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffcccccc000000ffffffccccccffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffffccccccffffffcccccc000000000000ffffff
+ccccccffffffccccffffffccccccffffffccccccff000000000000000000ffffffccccff
+ffffccffffffccccccffffff000000000000ccccccffffffcccccc000000ffffcc000000
+ffffffcccccc000000ccccffffffcc000000ffffffcccccc000000ffffffccccccffffff
+ccccccffffff000000000000ffffff000000ffffffcccccc000000cccccc000000ffffff
+ccccccffffffcccccc000000ffffccccccffffffccccccffffffccccccffffffcc000000
+ffffccccccffffffccffffffccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffffccccccffffff000000ffffffccccccffffffcccccccccccc666666cccc99999999
+9999999999999999999999cc9999999999cc9999999999999999cc999999cccccc999999
+ffffffcccccc000000
+000000ffffccccccffffffccccccffffffccffffff000000ccccff000000ffffffcccccc
+ffffffccccffffffccccccffffffcc000000000000ffffccccccffffffccccccff000000
+ffffffccccccffffffccccccffffffccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffff000000ccccccffffff000000ffffff000000ffffff000000ccccff
+ffffccccccffffffccffffff000000ffffccffffff000000ccccccffffff000000cccccc
+ffffffccccccffffff000000000000ffffffccccccffffffccccccffffffffffccccccff
+ffffccccccff000000ccccccffffff000000ccccffffffccccccffffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ccccffffffccccccff
+ffffccccccffffffccccccff000000ffffffffffccccccff000000ffffffccccccffffff
+cccccc000000ffffffccccccffffffccccccffffffccccccffffffccccffffffccccccff
+ffffffffffccccccffffffffccccccffffffffffff000000ccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffffccccccffffffffffccffffff000000ccccccffffffcccccc000000ffffffcccccc
+ffffffccccccffffffffffccccccffffffccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccff
+000000ccccff000000ccccffffffccccccffffffccccccffffffccccccffffffccffffff
+ccccccffffffccccccffffffccccccffffffffffccccccffffffccccccffffffff000000
+000000ccccccffffffccccccffffff000000ffffff000000ccccffffffccccccff000000
+ffffccccccffffffccccccffffffcc000000ffffff000000ffffffccccccffffff000000
+ccccccffffff000000ffffccccccff000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccffffffcc000000ccccffffffccccccff000000ffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ccccffffffff
+ccccccffffffccccccffffffccccccffffffcccccc000000ffffccccccffffffccccccff
+ffffccccccff000000ccccffffffccccccffffffccccccffffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffffffffccccccffffff
+ffffff000000ffffccffffff000000ffffffccccffffffccccccffffffcc000000ffffff
+000000ccccff000000ffffccccccffffffcc000000ffffcc000000ffffffccccffffffcc
+ccccff000000ccccccffffff000000ccccccffffffccccccffffffffffccccccffffffcc
+ffffff000000ffffffccccccffffffcccccc000000ffffccffffff000000cccccc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccffffffccffffffccccccffffff000000ccccccffffffccccccffffff
+ccccccffffffccccccffffff000000000000cccccc000000ccccffffffccccccffffffcc
+ccccffffffccccccffffffffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccffffff000000ffffffffffccccccffffffccccccffffffccffffffccccccffffff
+ccccccffffffffffccccccff000000ccccffffffccffffffccccccffffffccccccffffff
+ffffccccccff000000ffffccccccffffffccccccffffffccffffff000000000000ccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000000000000000ccccffffffcc
+ccccffffffccccccffffffcc000000000000ffffffccccccffffff000000ccccff000000
+ccccffffffff000000ffffccccccff000000ccccccffffff000000ffffffccccccffffff
+ccccccffffff000000000000cccccc000000ccccccffffff000000ffffff000000ccccff
+ffffccccccffffffff000000ccccccffffffccccffffffccccccffffffccccccff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffccccccffffffcc000000ccccccffffffccccccffffffcccccc666666999999999999
+cccccc999999999999cccc99999999999999cccc99999999999999999999999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccffffffccccccff000000ffffcc000000ccccccffffff
+ccccccffffffccccccffffffccccff000000000000ffffffccccccffffffcccccc000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000ffffffcccccc000000ffffcc000000ffffcc000000ffffff
+ccccccffffffccccccccccff000000ccccffffffcc000000ffffffcccccc000000ffffff
+ccccccffffffcccccc000000000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffffcccccc000000ffffccccccffffffccffffffccccccffffff
+ccccccffffffffffccccccffffffccffffffccccccffffff000000ffffccccccffffffcc
+ffffffccccccffffffcccccc000000ccccffffffccccccff000000ffffccccccffffffcc
+ccccff000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffcc000000ffffffccccccffffffffffcc
+ccccffffffccffffffccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffccccccffffffffccccccffffff000000ffffffccccccffffff000000ffffccccccff
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ccccffffffccccccffffffccccccffffffcc
+000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000
+000000ffffffccccccffffffcccccc000000cccccc000000ffffccccccffffffcc000000
+ffffffccccccffffffccccccffffff000000cccccc000000ccccffffffccccccff000000
+ffffffcccccc000000ffffffcccccc000000ffffffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffffffffcc000000ffffffccccccffffffccccffffffccccccff
+ffffccccccffffffccffffffccccccffffff000000ffffccccccffffffcc000000ffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccffffffccccccff
+ffffccccccff000000ffffccccccffffffccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffffffccccccffffffccccccffffffccccccffffffcc
+ccccff000000ccccffcccccc000000ffffccccccffffffccccccffffffff000000cccccc
+000000ffffcc000000ffffffccccccffffff000000ccccff000000ffffccccccffffffcc
+ffffff000000ffffffcccccc000000ffffffffffffccccccffffffccccccffffffcccccc
+ccccff000000ffffccccccffffffccccccff000000ccccffffffcc000000ffffff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccffffffccccccff
+ffffccccccffffffccccccff000000000000ffffff000000ffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccffffffccccccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccff000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccff000000ffffccccccffffffccccccffffffccccccffffffff
+ccccccffffff000000ffffffccccccffffffccccffffffccccccff000000000000ffffcc
+ccccffffffccffffffccccccffffffccccccffffff000000000000000000ffffccccccff
+ffffccccccffffffccccccff000000000000ffffccccccffffffcc000000ffffcc000000
+ffffccccccff000000ffffffcccccc000000ffffffcccccc000000ffffffccccccffffff
+ccccccffffff000000000000ffffff000000ffffffcccccc000000cccccc000000ffffcc
+ccccffffffccccccff000000ffffffccccccffffffffffccccccffffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffff000000ffffffccccccffffffcccccccccccc6666669999cc999999
+9999999999999999cc9999999999999999cc999999999999cccc999999cc999999999999
+ffffffcccccc000000
+000000ccccccffffffccccccffffffccccccffffff000000ffffff000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ccccccffffffccccccffffff000000
+ffffffccccccffffffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000ffffccccccff000000ccccff000000ffffff000000cccccc
+ffffffccccccffffffffffcc000000ffffffccccff000000ffffffffffff000000cccccc
+ffffffccccccffffff000000000000ffffffccccffffffccccccffffffccccccffffffcc
+ccccffffffff000000ccccccffffff000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccff000000ccccccffffffcccccc
+ffffffccccccffffffffffcc000000ccccffffffccffffff000000ccccffffffccccccff
+ffffcc000000ffffccccccffffffccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccff000000ccccccffffffccccccffffff
+ccccccffffffccccccffffffccccffffffccccccffffffccccccff000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000ccccffffffccccccff000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffcc000000ccccccffffffccccccffffffffffccccccff
+000000ffffff000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffffffffccccccffffffccccccffffffccccccffffffffffcc000000
+000000ffffccffffffccccccffffff000000ffffff000000ccccffffffccccccff000000
+ffffffccccccffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000
+ccccffffffff000000ffffccffffff000000ffffffccccffffffccccccffffffccffffff
+ccccccffffffccccccffffffccccff000000ccccffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffffccccccffffff000000ccccffffffccccccff000000cccccc
+ffffffffffccccccffffffccccccffffffccccccffffffccffffff000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccff000000ffffccccccffffffccccccff
+ffffccffffff000000ffffccccccffffffccccccffffffccffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ffffccffffff000000ffffccccccffffffccccccccffffff000000ffffff
+000000ccccff000000ffffccccccffffffcc000000ffffff000000ccccffffffccccccff
+ffffcc000000ccccccffffff000000ffffccccccffffffccccccffffffccccccffffffcc
+ffffff000000ffffccccccffffffccffffff000000ffffccccccff000000ccccff000000
+ffffffccccccffffffccccccffffffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000000000ccccff000000ffffffccccccffffffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffff
+ccccccffffffccccccffffffccccccffffffccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccffffff000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffffcccccc000000ccccccffffffccccccffffffccccccffffff000000000000ffffcc
+ccccffffffccccccffffffccffffffccccccffffff000000000000000000ccccffffffcc
+ccccffffffffccccccffffff000000000000ccccffffffccccccff000000ccccff000000
+ffffffcccccc000000ffffffffffff000000ccccccffffff000000ffffccccccffffffcc
+ccccffffffcc000000000000ccccff000000ccccccffffff000000ffffff000000ccccff
+ffffccccccffffffcc000000ccccccffffffccccccffffffccccccffffffffffff000000
+ccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ccccffffffff
+ccccccffffffcccccc000000ffffffccccffffffccccccffcccccc999966999999999999
+cccc99999999999999cccc99999999cccc999999cc999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccccffffffcccccc000000cccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffffccccccffffffcccccc000000
+ccccffffffccccccffffffccccccffffffccffffffccccffffffccccccffffffccffffff
+ccccccffffffffffcc000000ffffffffffcc000000ffffcc000000cccccc000000ffffff
+ccccccffffffccccccffffff000000ffffcccccccc000000ccccccffffff000000ffffff
+ccccccffffffcccccc000000000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffcc000000ffffffcccccc000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffff000000ffffffccccccffffff
+ccccffffffccccccffffffff000000ffffccccccffcccccc000000ffffccccccffffffcc
+ccccff000000ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffccccccff
+ffffccccccffffffffccccccffffffccccccffffffccccffffffccccccffffffccffffff
+ccccccffffffccccccffffffccccff000000ffffffccccccffffffccccffffffccccccff
+000000ffffcc000000ffffffccccffffffccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccccccff000000
+000000ccccffffffccccccffffffcc000000ffffcc000000ffffffccccccffffff000000
+ffffccccccffffffccccccffffffcc000000cccccc000000ffffffccccccffffff000000
+ffffccccccff000000ccccffffffcc000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc000000ffffff
+ccccffffffccccccffffffccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffffffccccccffffffccccccffffffccffffcc000000ffffffccccccffffffcccccc
+ffffffcccccc000000ffffffccccffffffccccccffffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccccffffffccccffffffccccccffffffccccccffffffccccccff
+ffffcc000000ccccffcccccc000000ffffffccccccffffffccccffffffcc000000ffffcc
+000000ffffcc000000ccccffffffccccccff000000cccccc000000ffffccccccffffffcc
+ccccff000000ffffffcccccc000000ccccffffffccccccffffffccccccffffffccccccff
+cccccc000000ffffffccccffffffccccccff000000ffffffcccccc000000ffffcc000000
+ffffffccccccffffffccccccffffffccccffffffccccccffffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccffffffcc000000ffffccccccffffffccccccff
+ffffffccccccffffffccccff000000000000ffffcc000000ffffffccccccffffffccccff
+ffffccccccffffffccccccffffffffccccccffffffffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccff000000ffffccccccffffffffccccccffffffccccffffffccccccffffffff
+ccccccffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffff000000ffffffccccccffffffccccccffffffcccccc000000000000ffffff
+ccccccffffffccccffffffccccccffffffccccccff000000000000000000ffffccccccff
+ffffccccccffffffccccccff000000000000ffffffccccccffffff000000ffffcc000000
+ffffccccccff000000ccccccffffff000000ffffffcccccc000000ffffffccccccffffff
+ccccccffffff000000000000ffffcc000000ffffffcccccc000000cccccc000000ffffff
+ccccccffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffcc000000
+ffffffccccccffffffccccccffffffffffffccccccffffffffffcc000000ffffccccccff
+ffffccccccffffffff000000ffffccccccffffffccccccffcccccc6666669999999999cc
+9999999999999999cc9999999999999999999999999999999999cc9999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffff000000ffffff000000ffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+000000ffffff000000ffffffffffffffffff000000ffffff000000ffffffffffffffffff
+ffffff000000ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff000000
+ffffffffffff000000ffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffff000000000000ffffff000000ffffffffffff000000ffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffcccccc999966999999999999
+cccc99999999999999cccccc9999999999cccccc99999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff9999cc6666669999cc999999
+9999999999cc9999999999999999999999999999999999cc999999999999cccc999999cc
+ffffcccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+999999cccc99999999cccccc999999999999cccc999999999999cc999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+9999999999999999999999999999999999cc999999999999cccc999999999999cc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+9999cc9999999999cccccc99999999cccc999999cc999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999cccc999999999999999999999999999999999999999999cc999999999999cccccc
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc999999cc
+9999999999999999cc999999999999cccccc999999999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccc99999999999999cccc999999999999999999999999cc9999999999cccccc999999cc
+ffffcccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+9999999999999999cc999999cccccc999999999999cccc999999999999999999999999cc
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc99999999
+999999cccc999999999999999999999999999999cc999999999999cccc99999999999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+9999cc999999999999cccccc999999999999cccc999999999999999999cc999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+9999999999999999999999999999999999cc9999999999cccccc99999999999999cccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999cc999999cccccc999999999999cccc999999999999999999999999cc999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cccccc99
+9999999999999999999999999999cc999999999999cccc99999999cccc999999999999cc
+ffffcccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999cccccc999999999999cccc999999999999cc9999999999cc999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc999999cc
+9999999999999999999999cc999999999999cccc99999999999999cccc999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccc99999999999999cccc999999cc9999999999999999cc999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+999999999999999999999999999999cccc99999999cccc999999999999cccccc99999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff000000000000
+000000000000ffffff000000ffffffffffff000000ffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+000000ffffffffffff000000000000000000000000ffffff000000000000ffffff000000
+ffffffffffffffffff000000ffffffffffffffffff000000ffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffff000000000000000000ffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffff000000ffffffffffff
+000000ffffffffffff000000ffffff000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffff000000ffffffcccccc999966999999999999
+cccc999999cccccc999999999999cc9999999999cc999999999999999999999999999999
+cccccccccccc000000
+000000ffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ffffffccccccffffffcccccc000000
+ffffff000000000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffff000000ffffccccccffffffcc000000000000
+000000000000ffffcc000000ffffffcccccc000000ffffccccccffffffcc000000000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc000000
+000000ffffccccccff000000000000000000000000ffffcc000000000000ffffff000000
+ffffccccccffffffcc000000ffffffccccccffffff000000ffffcc000000ffffffcccccc
+ffffff000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffcccccc000000000000ffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffccccccffffffccccccff000000ffffcc
+ccccffffffccccccffffffcc000000ffffffcccccc000000000000000000ffffccccccff
+000000ffffccccccff000000ffffccccccff000000ffffcc000000ffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffcccccc000000000000000000000000ffffccccccffffffccccccff000000
+ffffccccccffffffcc000000ffffffccccccffffff000000ffffcc000000ffffffcccccc
+ffffffcccccc000000ffffffccccccffffffcccccc000000000000000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffcc
+ccccffffffccccccffffffcc000000ffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000ffffccccccffffffcc000000ffffff000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffccccccff000000ffffcc
+ccccff000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffcc
+ccccffffffccccccffffffcc000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000000000000000ffffccccccff000000ffffccccccff
+000000ffffccccccff000000ffffcc000000000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffcccccc000000
+000000ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+000000ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000000000
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+ffffccccccff000000ffffccccccffffffcc000000ccccffcccccc6666669999999999cc
+999999999999999999999999999999cccc99999999999999cccccc9999999999cc999999
+ffffffcccccc000000
+000000ccccffffffccccccffffffccccccff000000ccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ccccffffffcc
+ccccffffffccccccff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000
+ffffff000000000000ccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000000000ccccccffffffcccccc
+ffffffccccccffffffcccccc000000ccccff000000ffffffccccccffffff000000000000
+000000000000ccccff000000ccccccffffff000000ffffffccccccffffff000000000000
+ffffccccccffffffccccccffffffccccccffffffccffffff000000ccccccffffff000000
+000000ccccffffffcc000000000000000000000000ffffff000000000000ccccff000000
+ccccffffffccccccff000000ccccffffffccccccff000000ccccff000000ccccffffffcc
+ccccff000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+000000ccccffffffccccccff000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ccccccffffff000000000000ccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000000000000000
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffccccccff
+ffffccccccffffffccffffff000000000000ccccccffffffccccccffffff000000ffffff
+ccccccffffffccccccffffff000000ccccccffffff000000000000000000ccccccffffff
+000000ccccccffffff000000ccccccffffff000000ccccff000000ccccccffffff000000
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffff000000000000000000000000ccccccffffffccccccffffff000000
+ccccffffffccccccff000000ccccffffffccccccff000000ccccff000000ccccccffffff
+ccccccffffff000000ffffccccccffffffccffffff000000000000000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccffffff000000ccccff
+ffffccccccffffffccccccff000000ccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccffffff000000ccccccffffffcccccc000000ccccff000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffff000000ccccccffffff000000cccccc
+ffffff000000ccccffffffccccccff000000ffffccccccffffffccccccffffffcc000000
+000000ffffffccccccffffffccccccffffffccccccffffffffffcc000000000000ccccff
+ffffccccccffffffccccccff000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000000000000000ccccccffffff000000ccccccffffff
+000000ccccccffffff000000ffffff000000000000ccccccffffffccccccffffff000000
+ccccccffffffccccccffffffccccccffffff000000ffffccccccffffffccffffff000000
+000000ccccffffffccccccffffffccccccff000000ccccffffffccccccffffffccccccff
+000000ccccffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000000000
+ccccccffffffccccccffffffccccccffffff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ccccccffffffccccccffffff000000
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000
+ccccccffffff000000ffffccccccffffffcc000000ffffcccccccc666666999999cccc99
+999999999999cccccc9999999999999999cc999999999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffccccccffffffccccccffffffcc000000ffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffffccccccffffffcccccc000000
+cccccc000000000000ffffffccccccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffcc000000ccccffffffccccccff000000000000
+000000000000ffffcc000000ffffffcccccc000000ccccffffffccccccff000000000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc000000
+000000ffffccccccff000000000000000000000000cccccc000000000000ffffcc000000
+ffffffccccccffffff000000ffffccccccffffffcc000000ffffcc000000ffffccccccff
+ffffcc000000ffffffccccccffffffffffccccccffffffccccccffffffccffffffcccccc
+000000ffffccccccffffffcc000000ffffccccccffffffccffffffccccccffffffffffcc
+ccccffffffccffffffccccccffffffccccccffffffffffcc000000ffffffccccccffffff
+ffffccccccffffffccccccff000000ffffffcccccc000000000000ffffffccccccffffff
+ffffccccccffffffccccccffffffccffffffccccccffffffcccccc000000000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffffccccccffffffcccccc000000ffffcc
+ccccffffffccccccffffffcc000000ffffffcccccc000000000000000000ffffffcccccc
+000000ffffffcccccc000000ffffffcccccc000000ffffcc000000ffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffcccccc000000000000000000000000ffffffccccccffffffcccccc000000
+ffffccccccffffffcc000000ffffccccccffffffcc000000ffffcc000000ffffffcccccc
+ffffffcccccc000000ffffffccccccffffffcccccc000000000000000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffcc
+ccccffffffccccccffffffcc000000ffffcc000000ffffffccccffffffccccccffffffcc
+ccccffffffccccccff000000ffffffccccccffffff000000ffffcc000000ffffffcccccc
+ffffffccccccffffffffffccccccffffffccccccff000000ffffffcccccc000000ffffff
+cccccc000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ffffccccccffffffccccccff000000000000000000ffffffcccccc000000ffffffcccccc
+000000ffffffcccccc000000cccccc000000000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffcccccc000000
+000000ffffccccccffffffccccccffffffcc000000ffffccccccffffffccccccffffffcc
+000000ffffccccccffffffccccccffffffcc000000ffffccccccffffffccccccffffffcc
+ccccffffffccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffffffccccccffffffccccccffffffcc000000000000
+ffffffccccccffffffffffccccccffffffcc000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+ffffffcccccc000000ffffffccccccffffff000000ccccffcccccc6666669999cc999999
+9999cc999999999999999999cccc99999999999999cccccc9999999999999999cc999999
+ffffffcccccc000000
+000000ccccccffffffffffccccccffffffff000000ccccccffffffccccccffffffccccff
+ffffccccccffffffffffffccccccffffffccffffffccccffffffccccccffffffccffffff
+ccccccffffffccccccffffffffffffccccccffffffccccccffffff000000ccccffffffcc
+ccccffffffccccccff000000ffffccccccffffffccccccffffffccffffffccccccffffff
+ffffccccccffffffccccccffffffcc000000000000ccccccffffffccccccffffff000000
+ffffff000000000000ffffffccccccffffffccccffffffccccccffffffffffffccccccff
+ffffccffffffccccffffffccffffffccccccffffff000000000000ffffccccccffffffcc
+ccccffffffffffffccccccff000000ffffff000000ffffccccccffffffcc000000000000
+000000000000ccccff000000ccccccffffff000000ffffccccccffffffcc000000000000
+ccccccffffffccccccffffffccccffffffccccccffffffff000000ccccccffffff000000
+000000ccccffffffcc000000000000000000000000ffffff000000000000ffffcc000000
+ccccffffffccccccff000000ffffffccccccffffff000000ccccff000000ffffccccccff
+ffffff000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+000000ffffffccccccffffff000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccff000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000ccccccffffff000000000000ffffccccccffffffcc
+ccccffffffccccccffffffffccccccffffffccccffffffccffffff000000000000000000
+ccccffffffccffffffccccffffffccffffffccccccffffffccccff000000ccccccffffff
+ccccffffffccccccffffffff000000000000ccccccffffffccccccffffff000000ccccff
+ffffffccccccffffffccccff000000ccccccffffff000000000000000000ccccffffffff
+000000ccccccffffff000000ccccccffffff000000ccccff000000ccccccffffff000000
+ffffffccccccffffffffffccccccffffffccccccffffffffffffccccccffffffccccccff
+ffffffffffccccccffffffff000000ffffccffffffccccccffffffccccffffffccffffff
+ccccffffffccffffff000000000000000000000000ccccccffffffccccccffffff000000
+ccccffffffccccccff000000ccccffffffccccccff000000ffffff000000ccccccffffff
+ccccccffffff000000ccccffffffccccccffffffff000000000000000000ccccccffffff
+ccccffffffccffffffccccccffffffffffffccccccffffffccccccffffff000000ccccff
+ffffccffffffccccccffffff000000ccccff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000ccccffffffccccccff000000ccccff000000ffffffccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ccccccffffff000000ffffcc
+ffffff000000ccccffffffccccccff000000ffffffccccccffffffccccccffffff000000
+000000ccccffffffccccccffffffffccccccffffffccccccffffff000000000000ffffff
+ccccccffffffccccccffffff000000ccccffffffccffffffccccccffffffffffccccccff
+ffffccccccffffffffccccccffffffffffcc000000ffffccccccffffffccccccffffffcc
+ccccffffffffccccccffffff000000000000000000ccccccffffff000000ffffccffffff
+000000ccccccffffff000000ffffff000000000000ffffccccccffffffccffffff000000
+ffffccccccffffffccffffffccccccffffff000000ccccccffffffccccccffffff000000
+000000ffffffccccccffffffffffccccccff000000ffffffccccccffffffffffccccccff
+000000ffffffffffccccccffffffccccccff000000ccccccffffffffffccccccffffffff
+ccccccffffffccccffffffccffffffccccccffffffffffccccccffffffccffffffccccff
+ffffccffffffccccffffffccccccffffffccccccffffffffccccccffffff000000000000
+ccccccffffffccccccffffffccccccffffff000000ccccffffffccccccffffffccccccff
+ffffffffffccccccffffffccccccffffffff000000ccccccffffffccccccffffff000000
+ccccffffffccccccffffffffffffccccccffffffccccccffffffccffffffccccff000000
+ccccffffffff000000ffffccccccffffffcc000000ffffcccccccc666666cccc99999999
+999999cccc999999cc9999999999cc999999999999999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccccffffcc000000ffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffffccccccffffff000000ffffccccccff
+ffffccffffffcccccc000000ffffffccccccffffffccccffffffccccccffffffffcccccc
+ffffffccccffffffccccccffffffff000000000000ffffffccccccffffffcccccc000000
+cccccc000000000000ffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000000000ffffffccccccffffff
+ccccccffffffccccccffffff000000cccccc000000ffffffccccccffffff000000000000
+000000000000ffffcc000000ffffffcccccc000000ffffffccccccffffff000000000000
+ffffffccccccffffffffffccccccffffffccccccffffffcc000000ffffffcccccc000000
+000000ffffffcccccc000000000000000000000000cccccc000000000000ccccff000000
+ffffccccccffffffcc000000ccccccffffffcccccc000000ffffff000000ccccffffffcc
+cccccc000000ffffffccccffffffccccccffffffffccccccffffffccccccffffffcccccc
+000000ccccffffffccccccff000000ffffffccccccffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffcc000000ccccffffffccccccff
+ffffffccccccffffffccccff000000ffffffcccccc000000000000ffffffccccccffffff
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffffccccccffffffcccccc000000ffffcc
+ccccffffffccccccffffffcc000000ffffffcccccc000000000000000000ffffccccccff
+000000ffffffcccccc000000ffffffcccccc000000ffffcc000000ffffffcccccc000000
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcccccccc000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000000000000000000000ffffffccccccffffffffffcc000000
+ffffffccccccffffff000000ffffccccccffffffcc000000cccccc000000ffffffccccff
+ffffccccccff000000ffffccccccffffffccccccff000000000000000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffff
+ccccccffffffccccffffffcc000000ffffcc000000ffffccccccffffffccffffffcccccc
+ffffffccccccffffff000000ffffccccccffffffcc000000ffffcc000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffff000000ffffffcccccc000000ccccff
+ffffcc000000ffffffccccccffffff000000ffffffccccccffffffccccccffffff000000
+000000ffffccccccffffffccffffffccccccffffffccccccffffff000000000000ffffff
+ccccccffffffccccccffffff000000ffffccccccffffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffccccccffccccff000000ffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffcc000000000000000000ffffffcccccc000000ccccffffffcc
+000000ffffffcccccc000000cccccc000000000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccffffffccccccff000000ffffffccccccffffffcccccc000000
+000000ffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccffffffcc
+000000ccccccffffffccccffffffccffffff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000000000
+ffffffccccffffffccccccffffffccccccff000000ffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffcccccc000000
+ffffffffffccccccffffffccccccffffffccccccffffffccccccffffffccffffcc000000
+ffffccccccff000000ccccffffffccccccff000000ccccffcccccc666666999999999999
+9999cc999999999999cccc99999999999999cccccc9999999999999999cc999999cccc99
+ccccffcccccc000000
+000000ccccccffffffccccccffffffccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ffffffccccffffffccffffff000000
+ffffff000000000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000000000
+000000000000ffffff000000ccccccffffff000000ffffffccccccffffff000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccff000000ffffccffffff000000
+000000ffffffffffff000000000000000000000000ffffff000000000000ffffff000000
+ffffffccccccffffff000000ffffffccccccffffff000000ffffcc000000ffffccccccff
+ffffff000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccffffffccccccffffffffccccccffffff000000ccccccffffffcccccc
+ffffffccccccffffffffffcc000000ccccccffffff000000000000ccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000000000000000
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffccccccff
+ffffccccccffffffccffffff000000000000ffffffffffffccccccffffff000000ffffff
+ccccccffffffccccccffffff000000ccccffffffff000000000000000000ccccffffffcc
+000000ffffccffffff000000ccccffffffff000000ffffff000000ccccffffffcc000000
+ffffffccccccffffffccccccffffffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffff000000ffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffff000000000000000000000000ffffffccccffffffccccccff000000
+ffffffffffccccccff000000ffffffccccccffffff000000ffffff000000ffffccffffff
+ccccccffffff000000ccccccffffffccccccffffff000000000000000000ccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffff
+ccccccffffffccccccffffff000000ccccff000000ffffffccccccffffffccccffffffcc
+ccccffffffccccccff000000ffffffccccccffffff000000ccccff000000ffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffcc000000ccccccffffff000000cccccc
+ffffff000000ccccccffffffcccccc000000ffffccccccffffffccccccffffffcc000000
+000000ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ccccff
+ffffccccccffffffccccccff000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000000000000000ccccccffffff000000ffffffccccff
+000000ccccccffffff000000ffffff000000000000ccccffffffccccccffffffff000000
+ccccffffffccccccffffffccccccffffffcc000000ccccccffffffccccccffffff000000
+000000ccccffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+000000ccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000000000
+ccccffffffccccccffffffccccccffffffcc000000ccccccffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffcc000000ffffccccccffffffccffffff000000
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccff000000
+ccccffffffcc000000ccccccffffffffffcc000000cccccccccccc999966999999999999
+cccc999999999999999999cc999999999999999999cccc99999999999999999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccccffffff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffccccccffffffccccccff000000
+cccccc000000000000ffffffccccccffffffccccccffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffffccccccffffffccccff000000000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000cccccc000000ccccffffffffcccccc000000000000
+000000000000cccccc000000ffffffcccccc000000ccccffffffccccccff000000000000
+ffffffccccccffffffccccccffffffccccccffffffffffcc000000ccccffffffcc000000
+000000ccccccffffff000000000000000000000000ffffcc000000000000cccccc000000
+ffffffccccccffffff000000ffffccccccffffffcc000000ccccff000000ffffffcccccc
+ffffcc000000ffffffccccccffffffffffccccccffffffccccccffffffffffffccccccff
+000000ffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffcccccc000000000000ffffccccccffffffcc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000000000000000
+ffffccccccffffffccccccffffffffccccccffffffffffccccccff000000ffffffcccccc
+ffffffccccccffffffcccccc000000000000ccccccffffffccccccffffff000000ffffcc
+ccccffffffccccccffffffcc000000ffffccccccff000000000000000000ffffccccccff
+000000ccccffffffcc000000ffffccccccff000000cccccc000000ffffffcccccc000000
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000000000000000000000ffffccccccffffffccccccff000000
+ccccccffffffcccccc000000ffffffccccccffffff000000cccccc000000ccccffffffcc
+ccccffffffcc000000ffffffccccccffffffcccccc000000000000000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffffffffccccccffffff000000cccccc
+ffffffccccccffffffcccccc000000ffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffffffcc000000ffffffccccccffffff000000ffffcc000000ffffccccccff
+ffffccccccffffffccffffffccccccffffffcccccc000000ffffffcccccc000000ffffff
+cccccc000000ffffffccccccffffff000000ffffffccccccffffffccccccffffff000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffcc
+ccccffffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccccccff000000ffffccccccffffffccccccffffffcc
+ffffffccccccffffffcccccc000000000000000000ffffffcccccc000000ffffccccccff
+000000ffffffcccccc000000ccccff000000000000ffffccccccffffffcccccccc000000
+ffffffccccccffffffccccccffffffccccff000000ffffffccccccffffffcccccc000000
+000000ffffffccccccffffffccccccffffff000000ccccccffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffffccccccffffffccccccffffffccccccffffff000000000000
+ffffccccccffffffffccccccffffffccccff000000ffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccffffffff000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccffffffccccccffffffccccccffffffcc000000
+ffffccccccff000000ffffffccccccffffff000000ffffff9999cc6666669999cc999999
+9999999999cccccc999999999999999999cc9999999999cc999999cccc999999cc999999
+ffffffcccccc000000
+000000ccccccffffffccccccffffffcccccc000000ccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccffffffccccccffffffcccccc000000ccccccffffff
+ccccccffffffcccccc000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000
+ffffff000000000000ccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffff000000ffffccccccffffffff000000000000
+000000000000ffffff000000ccccccffffff000000ffffccccccffffffcc000000000000
+ccccffffffccccccffffffccccccffffffccccccffffffff000000ccccccffffff000000
+000000ccccffffffcc000000000000000000000000ffffff000000000000ffffcc000000
+ccccffffffccccccff000000ffffffccccccffffff000000ffffcc000000ffffccccccff
+ffffff000000ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+000000ccccffffffccccccff000000ccccffffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000ccccccffffff000000000000ffffccccccffffffcc
+ccccffffffccffffffccccccffffffccccccffffffccccccffffff000000000000000000
+ccccccffffffccccccffffffffffccccccffffffccccccffffffcc000000ccccccffffff
+ccccccffffffccccccffffff000000000000ffffccccccffffffccccccff000000ccccff
+ffffccccccffffffccccccff000000ffffffffffcc000000000000000000ffffccffffff
+000000ccccccffffff000000ccccccffffff000000ffffff000000ccccccffffff000000
+ffffffccccccffffffccccccffffffccccccffffffccccffffffccffffffccccccffffff
+ffffccccccffffffccccccff000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000000000000000000000ffffccccccffffffccffffff000000
+ccccccffffffffffcc000000ffffffccccccffffff000000ffffff000000ffffccccccff
+ffffccccccff000000ffffccccccffffffccffffff000000000000000000ccccffffffcc
+ccccffffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffff
+ccccccffffffccccccffffff000000ccccff000000ffffffccccccffffffccccccffffff
+ccccccffffffcccccc000000ccccffffffccccccff000000ccccff000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffff000000ccccffffffff000000cccccc
+ffffff000000ccccccffffffcccccc000000ffffffccccccffffffccccccffffff000000
+000000ccccffffffccccccffffffffccccccffffffccccccffffff000000000000ffffcc
+ccccffffffccffffffcccccc000000ffffffccccccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffff000000000000000000ccccccffffff000000ffffffffffcc
+000000ccccffffffcc000000ffffff000000000000ccccccffffffccccffffffcc000000
+ffffffccccccffffffffffccccccffffffcc000000ffffffffffffccccccffffff000000
+000000ffffffccccccffffffccccccffffff000000ccccccffffffccccccffffffffffff
+000000ccccffffffccccccffffffccccccff000000ccccffffffccccccffffffccffffff
+ccccccffffffccccffffffccccccffffffccccccffffffffccccccffffffccccccffffff
+ffffccccccffffffccffffffccccccffffffccccffffffccccccffffffcc000000000000
+ccccffffffccccccffffffccccccffffffcc000000ffffccccccffffffccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ccccccffffffccccccffffff000000
+ffffffccccccffffffccccccffffffffffccccccffffffccccccffffffccccccff000000
+ffffccffffff000000ccccccffffffcccccc000000ffffcccccccc666666999999cccc99
+999999999999999999cccc99999999999999cccc99999999999999999999999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccccffffff000000ffffccccccffffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffffffff000000ffffffcccccc
+ffffffccccccffffff000000ffffccccccffffffccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ffffffccccccffffffcccccc000000
+ffffcc000000000000ffffccffffffccccccffffffccccccffffffccccccffffffffffcc
+ccccffffffccccccffffffccffffffccccccffffff000000000000ffffffccccccffffff
+ccccccffffffccccccffffff000000cccccc000000ffffffccccccffffff000000000000
+000000000000ffffcc000000ffffffcccccc000000ffffffccccccffffff000000000000
+ffffccccccffffffffccccccffffffccccccffffffcccccc000000ffffffcccccc000000
+000000ffffccccccff000000000000000000000000cccccc000000000000ccccff000000
+ffffccccccffffffcc000000ccccffffffccccccff000000ccccff000000ffffffcccccc
+ffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+000000ffffffccccccffffff000000ffffccccccffffffccffffffccccccffffffccccff
+ffffccccccffffffccccccffffffccccccffffffcccccccc000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffffcccccc000000000000ffffffccccccffffff
+ccccccffffffccccffffffccccccffffffffccccccffffffcccccc000000000000000000
+ffffffccccffffffccccccffffffccccccffffffccccccffffffff000000ffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffffccccccffffffffffcc000000ffffcc
+ccccffffffccccccffffffcc000000ccccccffffff000000000000000000ccccffffffcc
+000000ffffffcccccc000000ffffffcccccc000000ffffcc000000ffffffcccccc000000
+ffffccccccffffffccccccffffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffffffcc000000ffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccff000000000000000000000000ccccffffffccccccffffffcc000000
+ffffffccccccccccff000000ffffccccccffffffcc000000cccccc000000ffffffcccccc
+ffffffcccccc000000ffffffccccccffffffcccccc000000000000000000ffffccccccff
+ffffccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffffccccccffffff000000ffffcc000000ffffccccccffffffccccccffffffff
+ccccccffffffccccff000000ffffccccccffffffcc000000ffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffcc000000ffffccccccff000000ffffff
+cccccc000000ffffffffffccccccff000000ffffffccccccffffffccccccffffff000000
+000000ffffccccccffffffccffffffccccccffffffccccccffffff000000000000ffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffff
+ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000000000000000ffffffcccccc000000ccccccffffff
+000000ffffffcccccc000000ccccff000000000000ffffffccccccffffffccccff000000
+ffffccccccffffffccccccffffffccccccff000000ccccccffffffccccccffffff000000
+000000ffffccccccffffffffccccccffffff000000ffffffccccccffffffccccccffffcc
+000000ffffffccccccffffffccccccffffff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffffffccccccffffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccff000000000000
+ffffccccccffffffccffffffccccccffffff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ccccffffffcc000000ffffffccccccffffff000000ffffffcccccc6666669999999999cc
+9999999999999999cc9999999999999999cc9999999999cccccc99999999cccccc999999
+ffffffcccccc000000
+000000ffffffccccccffffffffffccccccff000000ffffffccccccffffffffffccccccff
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffffffffccccccffffffccccccffffffccccccffffffcccccc000000ccccccffffff
+ccccffffffccccccff000000ccccffffffccccccffffffccccccffffffffccccccffffff
+ffffffccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000
+ffffff000000000000ffffccccccffffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000000000
+000000000000ccccff000000ccccccffffff000000ffffffccccccffffff000000000000
+ffffccccccffffffccffffffccccccffffffccccccffffff000000ccccccffffff000000
+000000ccccccffffff000000000000000000000000ffffff000000000000ffffff000000
+ffffffccccccffffff000000ffffccccccffffffcc000000ffffcc000000ccccffffffcc
+ccccff000000ccccffffffccccccffffffffccccccffffffccccccffffffccccccffffff
+000000ccccffffffccccccff000000ffffccccccffffffccccccffffffccffffffcccccc
+ffffffccccccffffffffffccccccffffffccccccffffffff000000ccccffffffccccccff
+ffffccccccffffffffcccccc000000ccccffffffff000000000000ffffffccccffffffcc
+ccccffffffccccccffffffccffffffccccccffffffccccccffffff000000000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ccccccffffff
+ccccffffffccccccffffffff000000000000ffffccccccffffffccccccff000000cccccc
+ffffffffffccccccffffffff000000ffffccccccff000000000000000000ffffccccccff
+000000ccccccffffff000000ccccccffffff000000ccccff000000ccccffffffff000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffffccccccffffffccccff
+ffffccccccffffffccccccff000000ffffccccccffffffccffffffccccccffffffcccccc
+ffffffccccccffffff000000000000000000000000ffffccccccffffffccccccff000000
+ccccffffffccffffff000000ffffffccccccffffff000000ffffff000000ffffffcccccc
+ffffffffffff000000ccccccffffffccccccffffff000000000000000000ffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccff000000cccccc
+ffffffccccccffffffcccccc000000ccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccffffff000000ffffffccccccffffff000000cccccc000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffff000000ccccffffffcc000000ffffff
+ffffff000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000
+000000ccccffffffccccccffffffccccccffffffccccccffffffcc000000000000ccccff
+ffffccccccffffffccccccff000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffcc000000000000000000ccccccffffff000000ccccffffffcc
+000000ccccccffffff000000ffffcc000000000000ccccffffffccccccccffffff000000
+ccccffffffffccccccffffffccccccffffff000000ccccccffffffccccccffffff000000
+000000ffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffccccccff
+000000ffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffffcccccc
+ffffffffffccccccffffffccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffffffccccccffffffccccccffffffcc000000000000
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffffffccccccff000000ccccccffffffccccffffffcc000000
+ccccffffffccccccffffffccccccffffffccccccffffffffffffccccccffffffcc000000
+ffffccccccff000000ccccccffffffcccccc000000cccccccccccc666666cccc99999999
+999999cccc99999999cccc99999999cccc99999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffccccccffffffccccccffffffcc000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffffccccccffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccffffff000000ffffffcccccc
+ffffffffffccccccff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000000000ffffffccccccffffffcccccc000000
+cccccc000000000000ccccffffffccccccffffffccccccffffffccccccffffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000000000ffffccccccffffffff
+ccccccffffffccccccffffff000000cccccc000000ccccffffffccccccff000000000000
+000000000000ffffcc000000ffffffcccccc000000ccccccffffffcccccc000000000000
+ffffffccccccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc000000
+000000ffffffcccccc000000000000000000000000ffffff000000000000cccccc000000
+ffffccccccffffffcc000000ffffffccccccffffff000000ccccff000000ffffffcccccc
+ffffff000000ffffccccccffffffccccccffffffccffffffccccccffffffccccccffffff
+000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffffccccccffffff
+ffffccccccffffffccffffff000000ffffccccccff000000000000ccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000000000000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffffffccccccffffffcc000000000000ccccffffffccccccffffffff000000ffffff
+ccccccccccffffffccccccff000000ffffffcccccc000000000000000000ffffffcccccc
+000000ffffffcccccc000000ffffffcccccc000000ffffcc000000ffffccccccff000000
+ffffffccccccffffffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccffffffcc
+ffffffccccccffffff000000000000000000000000ffffffccccccffffffffffcc000000
+ffffffccccccccccff000000ccccccffffffcccccc000000cccccc000000ffffffcccccc
+ffffffcccccc000000ffffffccccffffffccccccff000000000000000000ffffccccccff
+ffffccccccffffffccccccffffffccffffffccccccffffffccccccffffff000000ffffff
+ccccccffffffccccccffffff000000ffffcc000000ffffffccccccffffffffffccccccff
+ffffccccccffcccccc000000ffffccccccffffffcc000000ffffff000000ffffffcccccc
+ffffffffffffccccccffffffccccccffffffcccccc000000ffffccccccff000000cccccc
+ffffff000000ccccffffffccccccff000000ffffccccccffffffccccccffffffcc000000
+000000ffffccccccffffffccccccffffffccccccffffffccccccff000000000000ffffcc
+ccccffffffccccccffffffcc000000ffffffccccffffffccccccffffffccccccffffffcc
+ccccffffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccff000000000000000000ffffffcccccc000000ffffccccccff
+000000ffffffcccccc000000ffffff000000000000ffffccccccffffffffcccccc000000
+ffffccccccffffffccccccffffffccffffff000000ffffffccccccffffffcccccc000000
+000000ffffffccccccffffffccccccffffff000000ffffffccccccffffffccccccffffff
+000000ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000000000
+ffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ffffffcccccc000000ffffffccccccffffff000000ffffffcccccc6666669999cc999999
+9999cc9999999999cc9999999999999999cc999999999999cccccc999999999999999999
+ffffffcccccc000000
+000000ccccffffffccccccffffffccccccff000000ccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffffccccccffffffccccccffffffcccccc000000ccccccffffff
+ccccccffffffffffcc000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ccccffffffffccccccffffff000000
+ffffff000000000000ffffccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffcc000000000000ccccffffffccffffff
+ccccccffffffccccccffffff000000ffffff000000ffffffccccccffffff000000000000
+000000000000ccccff000000ccccccffffff000000ffffffccccccffffff000000000000
+ccccffffffccccccffffffffccccccffffffccccccffffff000000ccccffffffff000000
+000000ccccccffffff000000000000000000000000ffffcc000000000000ffffff000000
+ccccffffffccccccff000000ffffccccccffffffcc000000ffffcc000000ffffffcccccc
+ffffff000000ccccffffffccccccffffffccccccffffffffccccccffffffccccccffffff
+000000ccccffffffccccccff000000ccccccffffffccccffffffccccccffffffccccccff
+ffffccccccffffffffccccccffffffccccccffffffcccccc000000ccccffffffccccccff
+ffffccccccffffffccccccff000000ccccffffffcc000000000000ccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ccccccffffff
+ccccccffffffccccccffffff000000000000ccccffffffccccccffffffcc000000cccccc
+ffffffffffccccccffffffcc000000ffffccffffff000000000000000000ffffccffffff
+000000ffffffffffff000000ccccccffffff000000ffffff000000ccccffffffcc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccff000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000000000000000000000ffffccccccffffffffcccccc000000
+ccccffffffccffffff000000ffffffccccccffffff000000ffffff000000ccccccffffff
+ccccccffffff000000ccccffffffccccccffffffcc000000000000000000ccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ccccff
+ffffccffffffccccccffffff000000ccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccffffff000000ccccffffffccccccff000000cccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ccccccffffff000000ccccff
+ffffcc000000ffffffccccccffffff000000ffffffccccccffffffccccccffffff000000
+000000ccccccffffffccccffffffccccccffffffffccccccffffff000000000000cccccc
+ffffffccccffffffccccccff000000ccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffcc000000000000000000ffffffffffff000000ccccccffffff
+000000ccccccffffff000000ffffff000000000000ccccccffffffccccccffffff000000
+ffffffccccccffffffccccccffffffcccccc000000ccccccffffffccccccffffff000000
+000000ccccccffffffccccccffffffcccccc000000ccccccffffffccccccffffffcccccc
+000000ccccffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccffffffccccccffffffccccccffffffccccccffffffccccccffffffff
+ccccccffffffccccffffffccccccffffffccccccffffffccccccffffffff000000000000
+ffffffccccccffffffccccccffffffffffcc000000ccccccffffffccccffffffccffffff
+ccccffffffccccccffffffccccccffffffcc000000ccccccffffffccccccffffff000000
+ffffccccccffffffffffffccccccffffffccccccffffffccccccffffffccccccff000000
+ffffccffffff000000ccccccffffffcccccc000000cccccccccccc666666cccc99999999
+999999cccc99999999999999999999cccc99999999999999999999cccccc999999999999
+cccccccccccc000000
+000000ffffffccccccffffffccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffffffccccccffffffccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffffccccff000000ffffffcccccc
+ffffffccccccccccff000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffff000000000000ffffccccccffffffccccccff000000
+ffffff000000000000ccccffffffccccccffffffccccccffffffccccccffffffccffffff
+ccccffffffccccccffffffccccccffffffccccccff000000000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000cccccc000000ffffffccccccffffff000000000000
+000000000000ffffcc000000ffffffcccccc000000ffffffccccccffffff000000000000
+ffffccccccffffffccccccffffffccffffffccccffffffcc000000ffffccccccff000000
+000000ffffffcccccc000000000000000000000000ccccff000000000000cccccc000000
+ffffffccccccffffff000000ffffffccccccffffff000000ccccff000000ffffccccccff
+ffffcc000000ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffccccccffffffcc
+ccccffffffccccccffffffcc000000ffffccccccff000000000000ffffccffffffcccccc
+ffffffccccccffffffccccccffffffffffffccccccffffffccccff000000000000000000
+ffffffccccccffffffccccffffffccffffffccccccffffffccccff000000ffffffcccccc
+ffffffccccccffffffcccccc000000000000ffffccccccffffffccccccff000000ffffff
+ccccccffffffccccccffffff000000ccccffffffcc000000000000000000ccccffcccccc
+000000ccccccffffff000000ffffffcccccc000000cccccc000000ffffccccccff000000
+ccccccffffffccccccffffffffffccccccffffffccccccffffffccffffffccccccffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000000000000000000000ccccffffffccccccffffffcc000000
+ffffffccccccccccff000000ffffffccccccffffff000000cccccc000000ffffffcccccc
+ffffffcccccc000000ffffccccccffffffccccccff000000000000000000ffffffcccccc
+ffffffffffccccccffffffccccccffffffffccccccffffffccccccffffff000000ffffcc
+ccccffffffccccccffffffcc000000ffffff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffcc000000ffffffccccccffffff000000ffffff000000ccccccffffff
+ccccccffffffccccccffffffffffccccccffffffcc000000ffffffcccccc000000ffffcc
+ccccff000000ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000
+000000ffffffccccccffffffffffccccccffffffccccccffffffcc000000000000ffffff
+ccccccffffffffffccccccff000000ffffccccccffffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccffffff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000000000000000ccccccffffff000000ffffffcccccc
+000000ffffffcccccc000000cccccc000000000000ffffffffffccccccffffffcc000000
+ffffccccccffffffccccccffffffccffffff000000ffffffccccccffffffcccccc000000
+000000ffffffccccccffffffccccccffffff000000ffffffccccccffffffccccccffffff
+000000ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000000000
+ffffffccccccffffffccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+ccccffffffcc000000ffffffccccccffffff000000ffffffcccccc6666669999999999cc
+9999999999999999cccccc999999cc9999999999999999cc999999999999999999cccccc
+cccccccccccc000000
+000000ccccffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffffffccccccff
+ffffccccccffffffccffffffccccccffffffccccccffffffffffcc000000ccccccffffff
+ccccccffffffffffcc000000ffffffccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffcc000000000000ffffffffffccccccffffffcc000000
+ffffcc000000000000ccccffffffccccccffffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ffffff000000ffffccccccffffffcc000000000000
+000000000000ffffff000000ccccccffffff000000ffffffccccccffffff000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffffffcc000000
+000000ccccccffffff000000000000000000000000ffffcc000000000000ffffff000000
+ccccccffffffcccccc000000ffffffccccffffffcc000000ffffff000000ffffffcccccc
+ffffff000000ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff
+000000ccccccffffffccccff000000ccccccffffffccccccffffffffffccccccffffffff
+ccccccffffffffffccccccffffffccffffffccccccffffff000000ccccffffffccccccff
+ffffccccccffffffccccccff000000ffffffffffcc000000000000ccccffffffffcccccc
+ffffffccccffffffccccccffffffccccccffffffccccccffffffcc000000000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ccccccffffff
+ccccccffffffccccccffffff000000000000ccccccffffffccccccffffff000000ffffcc
+ccccffffffccccccffffffcc000000ccccffffffff000000000000000000ffffccffffff
+000000ccccffffffcc000000ccccccffffff000000ffffff000000ccccccffffff000000
+ccccffffffccccccffffffffccccccffffffccccccffffffccccccffffffccccccffffff
+ffffccccccffffffccccccff000000ccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffff000000000000000000000000ccccccffffffccccccffffff000000
+ccccccffffffffffcc000000ffffffccccccffffff000000ffffff000000ccccccffffff
+ccccccffffff000000ffffffccccccffffffffffcc000000000000000000ffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ccccff
+ffffccccccffffffccccccff000000ffffcc000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccff000000ccccccffffffcccccc000000ffffff000000ffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffff000000ccccccffffff000000cccccc
+ffffff000000ccccffffffccccccff000000ffffccccccffffffccccccffffffcc000000
+000000ccccccffffffccccccffffffccccccffffffccccccffffff000000000000ccccff
+ffffccccccffffffccffffff000000ffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffffccccccffffffcccccc000000ffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffcc000000000000000000ffffffcccccc000000ffffccffffff
+000000ccccccffffff000000ffffff000000000000ffffccccccffffffccccccff000000
+ffffccccccffffffffccccccffffffcccccc000000ccccffffffccccccffffffff000000
+000000ffffccccccffffffccffffffcccccc000000ffffffccccffffffccffffffcccccc
+000000ffffffccccccffffffffffccccccff000000ccccffffffccccccffffffccccccff
+ffffccccccffffffccffffffccccccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffffffffccccccffffffccccccffffffcc000000000000
+ccccffffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffccccccff
+ffffccccccffffffffccccccffffffcccccc000000ccccccffffffccccccffffff000000
+ffffffccccccffffffccccccffffffccccccffffffccccffffffccccccffffffcc000000
+ccccffffffff000000ccccccffffffcccccc000000cccccccccccc666666999999999999
+cccc99999999999999999999999999cccc99999999999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffffccccccffffffccccccffffffcccccc000000ffffffcccccc
+ffffffccccccccccff000000ffffccccccffffffccccccffffffccffffffccccccffffff
+ffffccccccffffffccccccffffffcc000000000000ccccccffffffccccccffffff000000
+ccccff000000000000ffffccccccffffffccffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffccccccff000000000000ffffccccccffffffff
+ccccccffffffccccccffffff000000cccccc000000ccccffffffccccccff000000000000
+000000000000ccccff000000ffffffcccccc000000ccccccffffffcccccc000000000000
+ffffffccccccffffffccccccffffffccccccffffffffffcc000000ccccccffffff000000
+000000ffffffcccccc000000000000000000000000ffffff000000000000ffffcc000000
+ffffffccccccffffff000000ccccccffffffcccccc000000cccccc000000ccccffffffcc
+ccccff000000ffffffccccccffffffffffccccccffffffccffffffccccccffffffcccccc
+000000ffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffccccccffffff000000ffffffccccccffffff
+ccccccffffffccccccffffff000000ccccccffffff000000000000ffffccccccffffffcc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000000000000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccffffffccccccff000000000000ffffffffffccccccffffffcc000000ffffff
+ccccccffffffccccccffffff000000ffffccccccff000000000000000000ccccffffffcc
+000000ffffccccccff000000ffffffcccccc000000ffffcc000000ffffffcccccc000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccffffff000000ffffffccccccffffffffffccccccffffffffcccccc
+ffffffccccccffffff000000000000000000000000ffffffccccccffffffcccccc000000
+ffffffccccccccccff000000ffffccccccffffffcc000000cccccc000000ffffffcccccc
+ffffffcccccc000000ffffffccccccffffffccccff000000000000000000ffffffcccccc
+ffffffccccffffffccccccffffffccffffffccccccffffffccccccffffff000000ffffff
+ccccccffffffccccccffffff000000ccccff000000ffffffccccccffffffffffffcccccc
+ffffffccccccffffff000000ffffffccccccffffff000000cccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffcc000000ffffffcccccc000000ffffff
+cccccc000000ffffccccccffffffcc000000ffffffccccccffffffccccffffffcc000000
+000000ffffffccccccffffffccccccffffffccccccffffffcccccc000000000000ffffcc
+ccccffffffccccccffcccccc000000ffffccccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffff000000000000000000ffffccccccff000000ccccffffffcc
+000000ffffffcccccc000000ffffcc000000000000ccccffffffccccccffffffcc000000
+ffffffccccccffffffccccccffffffccccff000000ffffccffffffccccccffffff000000
+000000ffffffccccccffffffccccffffffcc000000ffffccccccffffffccccccffffffcc
+000000ffffffccccccffffffccccccffffff000000ffffffccccccffffffffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc000000000000
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccccccffffffcc
+ccccffffffccffffffccccccffffffffffff000000ffffffccccccffffffcccccc000000
+ccccccffffffccccccffffffccccffffffccffffffccccccffffffccccccffffff000000
+ffffccccccff000000ffffffccccccffffff000000ffffffcccccc666666cccc999999cc
+9999999999999999cc9999999999cc9999999999999999cc9999999999cccccc999999cc
+ffffcccccccc000000
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000
+ffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffff000000000000
+000000000000ffffff000000ffffffffffff000000ffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000
+000000ffffffffffff000000000000000000000000ffffff000000000000ffffff000000
+ffffffffffffffffff000000ffffffffffffffffff000000ffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffff000000000000000000ffffffffffff
+000000ffffffffffff000000ffffffffffff000000ffffff000000ffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffff000000ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffff000000ffffffffffff
+000000ffffffffffff000000ffffff000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffff000000ffffffcccccc666666999999cccc99
+999999999999cccc99999999999999cccc99999999cccc999999999999999999999999cc
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+9999cc9999999999999999cc9999999999cc999999999999999999999999cccc99999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+999999999999cccc99999999cccc99999999999999cccccc9999999999cc999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+9999999999cc9999999999999999cc999999999999999999999999cccc999999999999cc
+ffffcccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc999966999999999999
+cccc99999999999999cccc999999999999cccccc999999999999cc999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff9999cc6666669999999999cc
+9999999999999999cc999999999999999999999999999999cccc999999999999cccccc99
+ccccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc99999999
+cccc99999999999999cccc99999999cccccc9999999999cc999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cc999999
+9999cc9999999999999999cc999999999999999999cccc99999999cccccc999999999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccc99999999999999cccc999999999999999999cc999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccc999999cc
+9999999999cc9999999999999999cccccc99999999999999cccccc999999cccccc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cccc99
+999999999999cccc99999999999999999999999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+9999999999999999cc999999cccc999999cc999999cccc999999cc999999999999999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+999999cccc99999999999999999999999999999999999999999999cccccc999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cccccc999999
+9999cc9999999999cccccc999999cc999999cccc999999cc999999999999999999cccccc
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+cccc99999999999999999999999999999999999999999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+9999cc999999cccc999999cccccc999999cc9999999999999999cc999999cccccc999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666699999999cccc99
+999999999999999999999999999999cccc99999999cccc99999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccc996666669999999999cc
+9999999999cc9999999999999999cc9999999999cc999999999999cccccc999999999999
+ffffffcccccc000000
+000000ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff000000
+000000000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffff000000000000ffffffffffffffffffffffffffffff
+000000ffffff000000000000ffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffff000000000000000000ffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc999966999999999999
+cccc99999999cccc99999999999999cccc99999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffccccccff000000ffffcc000000ffffffccccccffffffccccccffffff000000
+000000000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffff000000
+ffffccccccffffffcc000000ffffff000000000000ffffccccccffffffccccccffffffcc
+000000ffffff000000000000ffffcc000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000
+000000ffffffccccccffffffcccccc000000000000000000ffffccccccffffffccccccff
+ffffcc000000ffffffccccccffffffccccccffffff000000ffffccccccff000000ffffcc
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000ffffccccccff000000ffffccccccffffffccccccffffffccccccff000000
+ffffcc000000ffffffccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffcc000000ffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffcccccc000000ffffffcccccc000000ffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffccccccffffffcc
+000000ffffff000000ffffcc000000ffffffccccccffffffcccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccff000000000000
+ffffcc000000ffffffccccccffffffcccccc000000ffffccccccff000000ffffcc000000
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+cccccc000000000000ffffffccccccffffff000000000000000000ffffccccccff000000
+ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+000000ffffff000000000000ffffccccccffffffcc000000ffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffcccccc000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ffffccccccff
+ffffcc000000ffffff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000cccccc6666669999999999cc
+9999999999999999cc9999999999999999cc999999cccccc9999999999cccccc99999999
+ffffffcccccc000000
+000000ccccccffffff000000ccccff000000ffffccccccffffffccccccffffffcc000000
+000000000000ccccff000000ccccffffffccccccffffffccccccffffffccccccff000000
+ccccffffffccccccff000000ffffcc000000000000ccccffffffccccccffffffccccccff
+000000ffffcc000000000000ccccff000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff000000
+000000ccccccffffffccccccffffff000000000000000000ccccffffffccccccffffffcc
+ccccff000000ccccffffffccccccffffffccccccff000000ccccccffffff000000ffffff
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ccccccffffff000000ccccccffffffccccccffffffccccccffffff000000
+ccccff000000ccccffffffccccccffffffccccccffffffccccccff000000ccccffffffcc
+ccccff000000ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccffffff000000ccccccffffff000000ccccff
+ffffccccccffffffccccccff000000ccccccffffffccccccffffffccccccffffffcccccc
+000000ccccff000000ccccff000000ffffccccccffffffccffffff000000ccccffffffcc
+ccccffffffccccccffffffccccccff000000ccccffffffccccccffffffcc000000000000
+ccccff000000ccccffffffccccccffffffff000000ccccccffffff000000ccccff000000
+ccccccffffffccccccffffffccccccffffff000000ccccccffffffccccccffffffcccccc
+ffffff000000000000ffffccccccffffffcc000000000000000000ccccccffffff000000
+ccccffffffccccccff000000ffffccccccffffffccccccffffffcc000000000000000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+000000ffffcc000000000000ccccffffffccccccff000000ffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccffffff000000ffffccccccffffffccffffff000000
+ccccccffffffccccccffffff000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffccccccffffffccccccff000000ccccffffffcc
+ccccff000000ccccff000000ffffffccccccffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff00000000000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cccccc666666cccc99999999
+999999cccc99999999999999cccc99999999999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ffffcc000000ffffffccccccffffffccccccffffff000000
+000000000000ffffcc000000ffffccccccffffffccffffffccccccffffffffffcc000000
+ffffffccccccffffff000000ccccff000000000000ffffccccccffffffccccccffffffcc
+000000ccccff000000000000ffffcc000000ffffccccccffffffccccccffffffccccccff
+ffffccffffffccccccffffffffffcc000000ffffffccccccffffffccccccffffff000000
+000000ffffffccccccffffffcccccc000000000000000000ffffccccccffffffccccccff
+ffffcc000000ffffccccccffffffccccccffffffcc000000ffffff000000ccccccffffff
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ffffffcccccc000000ffffffccccccffffffccccccffffffcccccc000000
+ffffcc000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc
+ffffff000000ffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc000000ffffcc
+ccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffffccccffffffcc
+000000ffffff000000ffffcc000000ccccffffffccccccffcccccc000000ffffccccccff
+ffffccffffffccccccffffffffffcc000000ffffccccccffffffccccccff000000000000
+ffffcc000000ffffccccccffffffccccccff000000ffffffcccccc000000ffffcc000000
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+cccccc000000000000ffffffccccccffffff000000000000000000ffffffcccccc000000
+ffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000000000000000
+ffffccccccffffffccccccffffffccffffffccccccffffffccccccffffffcccccc000000
+000000ccccff000000000000ffffccccccffffffcc000000ffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffcccccc000000000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccffffffccccccffffffffffcc000000ffffccccccff
+ffffcc000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffccffffff000000ffffccccccffffffccffffff
+ccccccffffffccccccffffff000000ffffcc00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cc99cc6666669999cc999999
+9999cc9999999999cc9999999999999999cccccc99999999cccccc9999999999cc999999
+ffffffcccccc000000
+000000ccccccffffff000000ccccff000000ffffccccccffffffccccccffffffcc000000
+000000000000ccccff000000ccccffffffccccccffffffccccccffffffccccccff000000
+ffffccccccffffffcc000000ffffff000000000000ffffccccccffffffccccccffffffff
+000000ffffcc000000000000ffffff000000ccccccffffffccccccffffffccccffffffcc
+ccccffffffccccccffffffccccccff000000ffffccccccffffffccccccffffffcc000000
+000000ffffccccccffffffccffffff000000000000000000ccccffffffccffffffcccccc
+ffffff000000ccccffffffccccccffffffccccccff000000000000ffffccccccffffffcc
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ccccffffffff000000ccccccffffffffffccccccffffffccffffff000000
+ccccff000000ffffccccccffffffffccccccffffffffffccccccff000000ffffccccccff
+ffffcc000000ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffff
+ccccccffffffccccccffffffccccffffffccccccff000000ccccccffffff000000ffffcc
+ccccffffffccccccffffffff000000ccccffffffccffffffccccccffffffccccccffffff
+000000ccccff000000ffffff000000ccccccffffffffffccffffff000000ccccffffffcc
+ccccffffffccccccffffffccccccff000000ffffccccccffffffccffffff000000000000
+ccccff000000ffffffccccccffffffffffcc000000ccccccffffff000000ffffff000000
+ccccccffffffffffccccccffffffccffffff000000ccccccffffffffffccccccffffffcc
+ffffff000000000000ccccffffffccccccff000000000000000000ccccccffffff000000
+ccccffffffccccccff000000ffffffccccccffffffccccccffffff000000000000000000
+ffffffccccffffffccccccffffffccccccffffffffccccccffffffccccccffffff000000
+000000ffffff000000000000ccccccffffffccccff000000ffffccccccffffffccffffff
+ccccccffffffccccffffffccccccffffffff000000ccccccffffffccccccffffff000000
+ffffccffffffccccccffffff000000000000ccccffffffccccccffffffffccccccffffff
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffff000000ccccff000000ccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccffffffccccccffffffccccccff000000ffffffccccccffffffcccccc
+ffffffccccffffffcc000000ccccffffffff00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cccccc66666699cc99999999
+cc999999cc99999999cccc99999999999999999999999999999999cccc99999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ffffcc000000ffffffccccffffffccccccffffffff000000
+000000000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffff000000
+ccccffffffccccccff000000cccccc000000000000ffffffccccffffffccccccffffffcc
+000000ccccff000000000000cccccc000000ffffffffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff000000
+000000ffffffccccccffffffcccccc000000000000000000ffffffccccccffffffcccccc
+ffffff000000ffffffccccccffffffffffccccccff000000ffffffccccccffffffcccccc
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ffffccccccff000000ffffffccccccffffffccccccffffffcccccc000000
+ffffff000000ffffffccccccffffffccccccffffffccccccffffff000000ccccffffffcc
+ccccff000000ffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccccffffffffffccccccffffffccccccffffffcc000000ffffffcccccc000000ffffff
+ccccccffffffccccccffffcc000000ffffffccccccffffffccccccffffffccccccffffff
+000000ffffcc000000ffffcc000000ffffffccccffccccccffffff000000ffffffcccccc
+ffffffccccffffffccccccffffffff000000ffffffccccccffffffcccccc000000000000
+ffffcc000000ffffccccccffffffccccccff000000ffffffcccccc000000cccccc000000
+ffffffccccffffffccccccffffffccccccff000000ffffffccccccffffffccccffffffcc
+ccccff000000000000ffffccccccffffffcc000000000000000000ffffffcccccc000000
+ffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffffccccccffffff000000
+000000ffffcc000000000000ffffffccccccffffcc000000ffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffffcccccc000000
+ccccffffffffccccccffffff000000000000ffffccccccffffffccffffffccccccffffff
+ccccccffffffccccffffffccccccffffffffccccccffffffcccccc000000ffffffcccccc
+ffffff000000ffffcc000000ffffffccccccffffffccccffffffccccccffffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ccccffffffccccccffffffcc
+ccccffffffcc000000ffffffccccccffffcc00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cccccc6666669999999999cc
+9999999999999999cc9999999999cc9999999999999999cc9999999999999999cccccc99
+ccccffcccccc000000
+000000ccccccffffff000000ccccff000000ffffccccccffffffccccccffffffcc000000
+000000000000ccccff000000ffffffccccccffffffccccffffffccccccffffffcc000000
+ffffffccccccffffff000000ffffff000000000000ffffffccccccffffffccccccffffff
+000000ffffff000000000000ffffff000000ffffccccccffffffccccccffffffccccccff
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000
+000000ccccffffffccccccffffffff000000000000000000ffffccccccffffffccccccff
+ffffcc000000ccccccffffffccccccffffffffffcc000000ccccffffffccccccffffffff
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ffffffffffcc000000ccccffffffffccccccffffffccccccffffff000000
+cccccc000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffcc000000ffffffffffccccccffffffff000000ccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ccccffffffff000000ccccff
+ffffccccccffffffccccccff000000ccccffffffccccccffffffccccccffffffccccccff
+000000ccccff000000ccccff000000ccccffffffccccccffffffcc000000ccccccffffff
+ccccccffffffccccccffffffcccccc000000ccccccffffffccccccffffff000000000000
+ccccff000000ccccffffffccccccffffffcc000000ccccccffffff000000ffffff000000
+ccccffffffccccccffffffccccccffffffcc000000ccccffffffccccccffffffccccccff
+ffffcc000000000000ffffffccccccffffff000000000000000000ffffffffffcc000000
+ccccffffffccccccff000000ffffffccccccffffffccccccffffff000000000000000000
+ccccccffffffccccccffffffccccccffffffffffccccccffffffccccccffffffcc000000
+000000ffffff000000000000ccccccffffffccccff000000ccccffffffffccccccffffff
+ccccccffffffccccccffffffccccccffffff000000ccccffffffffccccccffffff000000
+ccccffffffccccccffffffcc000000000000ccccffffffccccccffffffccccccffffffcc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffff000000ccccffffffcc
+ccccff000000ffffff000000ccccccffffffccccccffffffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc
+ffffff000000ccccffffffccccccffffffff00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cccccc999966999999cc9999
+99cc99cc9999999999999999cccc99999999cccc99999999cccc99999999999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ffffcc000000ffffffccccccffffffccccccffffff000000
+000000000000ffffcc000000ffffccccccffffffccccccffffffccccccffffffcc000000
+ffffffccccccffffff000000cccccc000000000000ffffccccccffffffccccccffffffcc
+000000ffffcc000000000000cccccc000000ffffffccccccffffffccccccffffffffffcc
+ccccffffffccccccffffffffcccccc000000ffffffccccffffffccccccffffffff000000
+000000ffffccccccffffffccccccff000000000000000000ffffffccccccffffffcccccc
+ffffff000000ffffffccccccffffffcccccc000000000000ffffffccccccffffffcccccc
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ccccccffffff000000ffffccccccffffffccccccffffffccccccff000000
+ffffff000000ffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffff000000ccccccffffffccccccffffff000000ffffffccccccffffffccccccffffff
+ccccffffffccccccffffffccccccffffffccccccff000000ffffccccccff000000ffffff
+ccccccffffffccccffffffcc000000ffffffccccccffffffccccccffffffffffccccccff
+000000ffffcc000000ffffcc000000ffffccccccffffffccccccff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffffccccccffffffcccccc000000000000
+ffffcc000000ffffffccccccffffffccccff000000ffffffcccccc000000cccccc000000
+ffffccccccffffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ccccff000000000000ffffffccccccffffff000000000000000000ccccffffffcc000000
+ffffffccccccffffff000000ffffffccccccffffffccccccffffff000000000000000000
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffff000000
+000000cccccc000000000000ffffffccccccffffcc000000ffffccccccffffffccccccff
+ffffccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccff000000
+ffffccccccffffffccccccff000000000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffcc000000cccccc000000ffffffccccccffffffccccccffffffccccccffffffffffcc
+ccccffffffccffffffccccccffffffccccccffffff000000ffffccccccffffffccccccff
+000000ffffccccccffffffccccccffffffcc00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff999999cc6666669999999999cc
+9999999999cc99cc999999999999cc9999999999999999cc999999999999cccc999999cc
+ffffcccccccc000000
+000000ccccccffffff000000ccccff000000ffffccccccffffffccccccffffffcc000000
+000000000000ccccff000000ccccffffffccccccffffffccccccffffffccccccff000000
+ffffffccccccffffff000000ffffff000000000000ccccffffffccccccffffffccccccff
+000000ffffff000000000000ffffff000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000
+000000ccccffffffccccccffffffcc000000000000000000ccccffffffffccccccffffff
+cccccc000000ffffffccccccffffff000000ffffff000000ccccccffffffccccccffffff
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ffffccccccff000000ccccffffffccccccffffffccccccffffffcc000000
+cccccc000000ccccffffffccccccffffffffccccccffffffcccccc000000ffffffcccccc
+ffffff000000ffffffccccccffffffcccccc000000ffffccccccffffffccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ccccffffffcc000000ffffcc
+ccccffffffccffffffcccccc000000ccccffffffccccccffffffccccccffffffccffffff
+000000ccccff000000ffffff000000ccccffffffccccccffffffcc000000ffffffcccccc
+ffffffccccccffffffffffccccccff000000ccccccffffffccccccffffff000000000000
+ccccff000000ffffffccccccffffffffffcc000000ccccccffffff000000ffffff000000
+ccccccffffffccccccffffffccccccffffff000000ccccffffffccccccffffffccccccff
+ffffcc000000000000ffffccccccffffffcc000000000000000000ccccccffffff000000
+ccccccffffffcccccc000000ffffffccccccffffffccccccffffff000000000000000000
+ccccccffffffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+000000ffffff000000000000ffffccffffffccccff000000ffffffccccccffffffccccff
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccffffff000000
+ccccccffffffccccccffffff000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffffffccccccffffffccccccff000000ffffffcccccc
+ffffff000000ffffff000000ccccccffffffccccccffffffccccccffffffccccccffffff
+ccccccffffffccccffffffccccccffffffccccccff000000ffffffccccccffffff000000
+ccccccffffffccccccffffffccccccffffff00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cccccc666666999999cccc99
+999999999999cc999999999999cc99cc99999999999999999999cc999999999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ffffcc000000ffffffccccccffffffccccccffffff000000
+000000000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffff000000
+ffffccccccffffffcc000000cccccc000000000000ffffffccccccffffffccccccffffff
+000000cccccc000000000000cccccc000000ffffccccccffffffccccccffffffccccccff
+ffffccffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff000000
+000000ffffccccccffffffccccccff000000000000000000ffffccccccffffffccccccff
+ffffff000000ccccffffffccccccffffffcc000000000000ffffffccccccffffffcccccc
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ffffffcccccc000000ffffccffffffccccffffffccccccffffffff000000
+ffffff000000ffffccccccffffffccffffffccccccffffffccccff000000ffffccccccff
+ffffcc000000ffffccccccffffffccccccff000000ffffffccccccffffffccccccffffff
+ccccccffffffffffffccccccffffffccccccffffff000000ffffccccccff000000ffffff
+ccccccffffffccccccffffff000000ffffccccccffffffffccccccffffffccccccffffff
+000000ffffcc000000cccccc000000ffffffccccccffffffcccccc000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffffccccccffffffcccccc000000000000
+ffffcc000000ffffccccccffffffccccccff000000ffffffcccccc000000ffffff000000
+ffffffccccccffffffccccccffffffcccccc000000ffffccccccffffffccccccffffffcc
+ccccff000000000000ccccffffffccccccff000000000000000000ffffffcccccc000000
+ffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000000000000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+000000ffffcc000000000000ccccffffffcccccccc000000ffffffccccccffffffcccccc
+ffffffccccccffffffffffccccccffffffcc000000ffffffccccccffffffcccccc000000
+ffffffccccccffffffcccccc000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccffffffccccccffffffcc000000ccccffffffcc
+ccccff000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffccccff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffccccccffffffcccccc
+000000ffffffccccccffffffccccccffffff00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cccccc6666669999999999cc
+99999999cc999999cc9999999999cc99999999cc99cc999999999999cc99cc99cc999999
+ffffffcccccc000000
+000000ccccccffffff000000ccccff000000ffffccccccffffffccccccffffffcc000000
+000000000000ccccff000000ffffccccccffffffccffffffccccffffffccccccff000000
+ffffffccccccffffff000000ffffff000000000000ffffffccccccffffffccccccffffff
+000000ffffff000000000000ffffff000000ffffccccccffffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000
+000000ccccffffffccccccffffffcc000000000000000000ffffffccccccffffffcccccc
+ffffcc000000ffffffccccccffffffccccccffffff000000ccccccffffffccccccffffff
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ffffccffffff000000ccccffffffccccccffffffccccccffffffcc000000
+ccccff000000ccccffffffccccccffffffccccccffffffccffffff000000ccccffffffcc
+ccccff000000ffffccccccffffffccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ccccffffffcc000000ffffff
+ccccccffffffccccccffffff000000ccccffffffccccccffffffccccccffffffccccccff
+000000ffffff000000ffffff000000ffffffffffccccccffffffff000000ccccccffffff
+ccccccffffffccccccffffffcccccc000000ffffffccccffffffccffffff000000000000
+ccccff000000ccccccffffffccccccffffff000000ccccccffffff000000cccccc000000
+ccccffffffccffffffccccffffffccffffff000000ffffffccccccffffffffffccccccff
+ffffcc000000000000ffffccccccffffffcc000000000000000000ccccccffffff000000
+ccccffffffccccccff000000ffffffccccccffffffccccccffffff000000000000000000
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffff000000
+000000ffffff000000000000ffffccccccffffffff000000ffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ccccffffffccccccffffffff000000
+ccccccffffffccccccffffff000000000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffffffffccccccffffffccccccffffffccccffffffcc000000ffffffcccccc
+ffffff000000ccccff000000ffffffccccccffffffccccffffffccccccffffffccccccff
+ffffccccccffffffccffffffccccccffffffcccccc000000ccccffffffccffffffcccccc
+ffffff000000ccccffffffccccccffffffcc00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cccccc99666699cc99999999
+cc9999999999cc999999cc99cc99999999cc9999999999cc999999999999999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ffffff000000ffffffccccccffffffccccffffffcc000000
+000000000000ffffcc000000ffffffccccccffffffccccccffffccccccffffffcc000000
+ccccccffffffcccccc000000cccccc000000000000ffffccccccffffffccccccffffffcc
+000000ffffcc000000000000ccccff000000ffffffccccccffffffccccccffffffccccff
+ffffccccccffffffccccccffffffcc000000ffffffccccccffffffccccccffffff000000
+000000ffffffffffccccccffffffff000000000000000000ffffccccccffffffccccccff
+ffffff000000ccccccffffffccccccffffffcccccc000000ffffffffffccccccffffffcc
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ccccffffffcc000000ffffccccccffffffccffffffccccccffffff000000
+ffffcc000000ffffccccccffffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffff000000ccccffffffccccccffcccccc000000ffffffccccffffffccccccffffffcc
+ccccffffffccccccffffffccffffffccccccffffff000000ffffccccccff000000cccccc
+ffffffccccccffffffcccccc000000ffffccccccffffffccccccffffffccccccffffffcc
+000000cccccc000000cccccc000000ccccccffffffccccccffffff000000ffffffcccccc
+ffffffccccccffffffccccccffffff000000ffffccccccffffffccccccff000000000000
+ffffcc000000ffffffccccccffffffcccccc000000ffffffcccccc000000ffffff000000
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffccccccffffffcc
+ccccff000000000000ffffffccccccffffff000000000000000000ffffffcccccc000000
+ffffccffffffcccccc000000ffffffccccccffffffccccccffffff000000000000000000
+ffffccccccffffffccccccffffffffffffccccccffffffccccccffffffccccccff000000
+000000ccccff000000000000ffffffccccccffffcc000000ffffffccccccffffffcccccc
+ffffffccccffffffccccccffffffccffffff000000ffffccccccffffffcccccccc000000
+ffffffffffffccccccffffff000000000000ffffffccccccffffffffffffccccccffffff
+ccccccffffffccccccffffffccccccffffffffffccccccffffffcc000000ffffffcccccc
+ffffff000000ffffcc000000ffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccffffffcc000000ffffffccccccffffffcccccc
+ffffffcccccc000000ffffffccccccffffff00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999cccc6666669999cc999999
+9999cc99cc9999999999999999999999cc99cc999999999999cc99cc99cc99999999cc99
+ffccffcccccc000000
+000000ffffccffffff000000cccccc000000ffffccccccffffffccccccffffffff000000
+000000000000ccccff000000ffffccccccffffffccccccffffffffccccccffffff000000
+ffffffccccccffffff000000ffffff000000000000ccccffffffccccccffffffccccccff
+000000ffffff000000000000ffffcc000000ccccccffffffccccccffffffccccccffffff
+ccccccffffffccccccffffffccccff000000ffffccccccffffffccccccffffffcc000000
+000000ccccffffffccccccffffffcc000000000000000000ffffffccccccffffffcccccc
+ffffff000000ffffffccccccffffffccccccffffff000000000000ffffffccccccffffff
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ffffccccccff000000ffffffccccccffffffccccccffffffcccccc000000
+ffffff000000ccccffffffccccccffffffccccccffffffccccccff000000ffffccccccff
+ffffcc000000ffffccccccffffffccffffff000000ccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffff000000ccccccffffff000000ffffff
+ccccccffffffccccccffffff000000ffffffccccccffffffccccccffffffccccccffffff
+000000ffffff000000ffffff000000ffffffccccccffffffcccccc000000ffffccffffff
+ccccffffffccccccffffffccccccff000000ccccffffffccccccffffffcc000000000000
+ccccff000000ffffccccccffffffccffffff000000ccccccffffff000000ccccff000000
+ccccccffffffccccccffffffccccccffffff000000ccccffffffffccccccffffffcccccc
+ffffff000000000000ffffccccccffffffcc000000000000000000ccccccffffff000000
+ffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000000000000000
+ffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000
+000000ffffff000000000000ccccccffffffccccff000000ccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccccccff000000ffffccccccffffffffffffff000000
+ccccffffffccccccffffffcc000000000000ccccffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ccccffffffcc
+ccccff000000cccccc000000ffffffccccccffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ccccccffffffccccccffffff
+ffffccccccffffffcc000000ccccffffffcc00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cccccc66666699cc99cc9999
+9999999999cc999999cc99cc9999999999999999cc999999999999999999999999999999
+cccccccccccc000000
+000000ccccffffffcc000000ffffff000000ffffffccccccffffffccccccffffcc000000
+000000000000ffffcc000000ccccffffffffccccccffffffffffccccccffffffcc000000
+ffffffccccccffffff000000cccccc000000000000ffffffccccccffffffccccccffffff
+000000cccccc000000000000ccccff000000ffffffccccccffffffffffccccccffffffcc
+ccccffffffccccccffffffccffffff000000ffffffccccccffffffccccffffffcc000000
+000000ffffccccccffffffccccccff000000000000000000ffffccccccffffffccccccff
+ffffcc000000ffffffccccccffffffccccccffffff000000ffffcc000000ffffffcccccc
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ffffffcccccc000000ffffccccccffffffccccccffffffccccccff000000
+ffffcc000000ffffffccccccffffffccccccffffffccccccffffff000000ffffffcccccc
+ffffff000000ffffffccccccffffffcccccc000000ffffffccccccffffffffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffcc000000ffffffcccccc000000ffffff
+ccccccffffffccccccffffff000000ffffccccccffffffccffffffccccccffffffcccccc
+000000cccccc000000cccccc000000ffffffccccccffffffcccccc000000ccccffffffcc
+ccccffffffccffffffccccccffffff000000ffffccccccffffffccccccff000000000000
+ffffcc000000ffffffccccffffffccccccff000000ffffffcccccc000000ffffcc000000
+ffffffccccccffffffccccccffffffcccccc000000ffffccccccffffffccccccffffffcc
+ccccff000000000000ccccffffffccccccff000000000000000000ffffffcccccc000000
+ccccccffffffcccccc000000ffffffccccffffffccccccffffffff000000000000000000
+ffffffccccccffffffccccccffffffccccccffffffffffffccccccffffffcccccc000000
+000000cccccc000000000000ffffffccccccffffff000000ffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffcccccc000000ffffffccccccffffccccccff000000
+ffffccccccffffffccccccff000000000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccccccffffffcc000000ffffccccccff
+ffffcc000000ffffff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffffccccccffffffccccccffffffffffccccccff000000ffffffccccccffffffcccccc
+ffffffccccccffffffcccccc000000ffffff00000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cc99cc666666999999999999
+99cc99cc999999999999cc99999999cccc99999999cccc999999cc99cc99cc99cc999999
+ffffffcccccc000000
+000000ffffccccccff000000cccccc000000ffffccccccffffffccccccffffffff000000
+000000000000ffffff000000ccccccffffffccccccffffffccccccffffffcccccc000000
+ffffffccccccffffff000000ffffff000000000000ccccffffffccffffffccccccffffff
+000000ffffff000000000000ffffcc000000ffffffccccccffffffccccccffffffffffff
+ccccccffffffccccccffffffcccccc000000ffffccccccffffffccccccffffffff000000
+000000ccccccffffffccccccffffff000000000000000000ccccccffffffccccccffffff
+cccccc000000ccccffffffccccccffffffccccccff000000ffffffcccccc000000ffffff
+000000000000000000000000ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00000000000000
+000000000000ccccffffffff000000ffffccccccffffffccccccffffffccffffff000000
+ccccff000000ccccccffffffccccccffffffccccccffffffcccccc000000ccccffffffcc
+ccccff000000ccccccffffffccccccffffff000000ffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffccccccffffffccccccff000000ccccffffffcc000000ccccff
+ffffccccccffffffccccccff000000ffffccccccffffffccccccffffffffccccccffffff
+000000ffffff000000ffffcc000000ffffffccccccffffffffffff000000ccccffffffcc
+ccccffffffccccccffffffccccccff000000ffffccccccffffffccffffff000000000000
+ccccff000000ccccffffffccccccffffffcc000000ffffccffffff000000ffffff000000
+ccccccffffffccccccffffffccccccffffff000000ffffffccccccffffffccccccffffff
+ffffcc000000000000ffffffccccccffffff000000000000000000ccccccffffff000000
+ffffffccccccffffff000000ffffccccccffffffccccccffffffcc000000000000000000
+ccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffffffff000000
+000000ffffff000000000000ccccccffffffcccccc000000ccccffffffccccccffffffcc
+ccccffffffccccccffffffffccccccffffff000000ccccffffffffccccccffffff000000
+ccccccffffffccccccffffff000000000000ffffccccccffffffccccccffffffffffffcc
+ccccffffffccccccffffffccccccffffffccccccffffffccccccff000000ffffffcccccc
+ffffff000000ccccff000000ffffccccccffffffccccccffffffccccccffffffccccccff
+ffffccffffffccccccffffffccccccffffffffffcc000000ccccffffffffccccccffffff
+ccccffffffccccccffffffffcccccc00000000000000000099ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99
+99ff9999ff9999ff9999ff9999ff9999ff9999ff9999ff99cccccc666666cc99999999cc
+9999999999999999999999cc9999999999cc999999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffcccccc000000ffffff000000ffffffccccccffffffccccccffffff000000
+000000000000ffffcc000000ffffffccccccffffffccccccffffffccccffffffcc000000
+ffffffccccccffffff000000ffffcc000000000000ffffccccccffccccccffffffcccccc
+000000ffffcc000000000000ccccff000000ffffccccccffffffccccccffffffccccccff
+ffffccccccffffffccccccffffffff000000ffffffccccccffffffccccccffffcc000000
+000000ffffffccccccffffffcccccc000000000000000000ffffffccccffffffccccccff
+ffffff000000ffffccccccffffffccccccffffffcc000000ffffffcccccc000000ffffff
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000ffffccccccff000000ccccffffffccccccffffffffccccccffffff000000
+ffffcc000000ffffffccccccffffffccccffffffccccccffffffcc000000ffffffcccccc
+ffffff000000ffffffccccccffffffcccccc000000ffffffccccccffffffccccccffffff
+ffffccccccffffffccccccffffffccccccffffffcc000000ffffffcccccc000000ffffff
+ffffccccccffffffccccccff000000ffffffccccccffffffccccccffffffccccccffffff
+000000ccccff000000ccccff000000ffffccccccffffffcccccccc000000ffffccccccff
+ffffccccccffffffccccccffffffcc000000ffffffccccffffffccccccff000000000000
+ffffcc000000ffffccccccffffffccccccff000000ccccffcccccc000000ccccff000000
+ffffffccccccffffffccccccffffffcccccc000000ffffccccccffffffffccccccffffff
+ccccff000000000000ffffccccccffffffcc000000000000000000ffffffcccccc000000
+ffffffccccccffffff000000ffffffccccccffffffccccccffffff000000000000000000
+ffffffccccccffffffccccffffffccccccffffffccccccffffffccccccffffffcc000000
+000000ffffcc000000000000ffffffccccccffffff000000ffffccccccffffffffcccccc
+ffffffffffccccccffffffccccccffffffcc000000ffffccccccffffffccccccff000000
+ffffffccccccffffffcccccc000000000000ffffffccccccffffffccccccffffffcccccc
+ffffffccccffffffccffffffccccccffffffccccccffffffffffcc000000ccccffffffcc
+ccccff000000ffffcc000000ffffffccccffffffccccccffffffffccccccffffffcccccc
+ffffffccccccffffffccccffffffccccccffffffcc000000ffffccccccffffffccccccff
+ffffccccccffffffccffffffccccccffffff000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000cccccc66666699cc99999999
+999999cccccc999999999999cc999999cc99999999cc99cc99cc9999999999cccc999999
+ffffffcccccc000000
+000000ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff000000
+000000000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffff000000000000ffffffffffffffffffffffffffffff
+000000ffffff000000000000ffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000
+000000ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000000000
+ffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffff000000000000000000ffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000ffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666cc99cc999999
+99999999999999cc999999999999cc999999999999999999999999cc9999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc66666699999999cc99
+cc99cc999999cc999999999999cc99cc99999999cc99cc999999999999cc999999cc9999
+ccffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999999999
+99cc999999cc9999999999cc999999999999cc9999999999cc999999cc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999cccc9999
+999999999999cccc9999999999999999cccc9999999999cc999999999999cc99cc99cc99
+ffccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000ffffff000000ffffffffffff
+ffffff000000000000000000000000000000000000ffffffffffff000000000000000000
+000000000000000000ffffffffffffffffffffffffffffffffffff000000000000000000
+ffffff000000ffffffffffffffffff000000000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+000000000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+ffffff000000ffffffffffffffffff000000000000000000000000000000000000ffffff
+ffffff000000000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000ffffff000000ffffffffffffffffff000000000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc66666699999999cc99
+9999999999999999cc999999cc999999999999cc99cc999999cc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffff000000000000ffffffffffff000000ffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cc99cc
+99999999cc99cc999999999999cccc9999999999999999cc999999cc99cc99cc99999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffff000000000000000000
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffff000000000000000000ffffff000000ffffffffffffcccccc66666699cc99999999
+9999999999cc999999999999999999999999cc9999999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000ffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+000000ffffff000000ffffffffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffff000000000000000000ffffffffffff
+ffffffffffffffffffffffff000000ffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffff000000000000ffffffffffffcccccc666666cc99cc999999
+cccc99999999999999cccccc9999999999cc99cc99999999cccccc9999999999cccc9999
+ccffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff000000000000
+000000000000000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffffcccccc666666999999999999
+9999cc999999999999999999999999cc999999999999999999999999cc99999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000000000000000000000000000ffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc66666699cc99cc9999
+999999cccc999999cc99999999cc999999cc999999cc9999999999cc99cc99999999cc99
+ffccffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+999999999999cc9999999999cc999999999999cccc99999999cc99999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffffffffff
+ffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffff
+ffffffffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff
+000000ffffffffffffffffffffffff000000ffffffffffffcccccc66666699999999cc99
+99999999cccc99999999999999cccc999999cc99999999999999cccc999999cccccc9999
+ccffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffff000000000000000000ffffffffffffffffff
+ffffff000000000000000000000000ffffffffffffffffffffffff000000000000000000
+000000000000000000ffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffff000000000000000000000000000000ffffffffffffffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffff000000000000000000
+000000ffffff000000000000000000000000ffffffffffff000000000000000000000000
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffff000000000000ffffffffffffffffffffffffffffff000000000000
+000000000000000000ffffffffffffffffffffffffffffffffffff000000000000000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff000000000000
+000000ffffffffffffffffffffffff000000000000000000000000ffffffffffffffffff
+ffffff000000000000000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffffffffff000000000000000000000000
+000000ffffffffffffffffffffffffffffffffffff000000000000000000ffffffffffff
+ffffff000000000000000000000000ffffff000000000000000000000000ffffffffffff
+000000000000000000000000ffffffffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffff
+ffffffffffffffffff000000000000000000000000ffffffffffffffffffffffffffffff
+ffffff000000000000000000000000ffffffffffffffffffcccccc9966669999cccc9999
+999999999999cc999999999999999999999999cc99999999999999999999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffff99cccc66666699999999cc99
+9999cc99999999cccc999999cc99999999cc999999cc999999cccc999999cc9999999999
+ffffffcccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cc9999
+999999cc999999999999999999999999cc9999999999999999999999cccc999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc66666699cccc999999
+99cc999999cc99cc99cc99cc999999cc99999999cc999999cc999999999999999999cccc
+ffcccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc666666999999cc9999
+99999999999999999999cc9999999999999999cc9999999999cccccc9999999999999999
+cccccccccccc000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffcccccc6666669999999999cc
+999999cc99999999cc999999cc99cc999999999999cc999999999999999999cccc999999
+ffffffcccccc000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc666666cccc99999999
+99cc9999999999999999cc9999999999999999cc999999cc999999cc9999999999999999
+cccccccccccc000000
+000000cccccc666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666cc99cccccccc6666669999cc999999
+cc99cc999999999999cc99cc9999999999cccc999999999999cc999999cc999999cc9999
+ccffffcccccc000000
+000000cccccc666666999999999999999999999999999999999999999999999999999999
+999999999999999999ffffffcccccc999999ffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffffccccccffffffccccccffffffccccccffffffcccccc
+ffffffccccccffffffccccccffffff999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999999999999999ccccccffffff999999999999999999999999999999999999
+999999999999999999999999999999999999ccffcccccccccccccc666666ccffccffffff
+ccffccffffffffffccccffffffffccffffffccffccffffffffccccffffffccffccffffff
+cccccccccccc000000
+000000cccccc6666669999999999cc9999999999999999cc9999999999999999cc999999
+999999ffffffcccccccccccc666666999999ffffff9999cccccccccccccccccccccccccc
+cccccc9999cccccccccccccccccccccccccccccccccccccc9999cccccccccccccccccccc
+cccccc9999cccccccccccccccccccccccccccccccc9999cccccccccccccccccccccccccc
+cccccc9999cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc9999cccccccccccccccccccccccccc9999cccccccccccccccccccccccccc
+cccccc9999cccccccccccccc6666669999999999999999cccccc999999999999cc999999
+999999cccccc9999999999999999cc999999cccc999999cc9999999999999999cccccc99
+9999999999cc999999999999cccccc9999999999999999cc999999cccc999999cc999999
+9999999999cccccc999999999999cc999999999999cccccc9999999999999999cc999999
+cccc999999cc9999999999999999cccccc999999999999cc999999999999cccccc999999
+9999999999cc999999cccc999999cc9999999999999999cccccc999999999999cc999999
+999999cccccc9999999999999999cc999999cccc999999cc9999999999999999cccccc99
+9999999999cc999999999999cccccc9999999999999999cc999999cccc999999cc999999
+9999999999cccccc999999999999cc999999999999cccccc9999999999999999cc999999
+cccc999999cc9999999999999999cccccc999999999999cc999999999999cccccc999999
+9999999999cc999999cccc999999cc9999999999999999cc9999999999999999cc999999
+9999999999cc9999999999999999cc999999cccc999999cc9999999999999999cc999999
+9999999999cc9999999999999999cc999999cccc999999cc9999999999999999cc999999
+9999999999cc9999999999999999cc999999cccc999999cc9999999999999999cccccc99
+9999999999cc9999999999999999cccccc999999999999cc999999999999cccccc999999
+9999999999cc999999cccc999999cc9999999999999999cc999999999999cccccc999999
+9999999999cc999999cccc999999cc9999999999999999cc9999999999999999cc999999
+cccc999999cc9999999999999999cc999999cccc999999cc9999999999999999cccccc99
+9999999999cc999999999999cccccc9999999999999999cc999999cccc999999cc999999
+9999999999cccccc999999999999cc9999999999999999cc999999cccc999999cc999999
+9999999999cc9999999999999999cc9999999999999999cc9999999999999999cc999999
+9999999999cccccc999999999999cc9999999999999999cc9999999999999999cc999999
+9999999999cc9999999999999999cccccc999999999999cc9999999999999999cc999999
+9999999999cc9999999999999999cc9999999999999999cc9999999999999999cc999999
+cccc999999cc9999999999999999cccccc999999999999cc999999cccc999999cc999999
+9999999999999999cc9999999999999999cc9999999999999999cc9999999999999999cc
+999999cccc999999cc9999999999999999cc999999999999cccccc9999999999999999cc
+9999999999999999cc9999999999999999cc9999999999999999cc9999999999999999cc
+9999999999999999cc9999999999999999cc9999999999999999cc999999cccc999999cc
+9999999999999999cccccc999999999999cc999999999999cccccc9999999999999999cc
+999999cccc999999cc9999999999999999cccccc999999999999cc999999999999cccccc
+9999999999999999cc999999cccc999999cc9999999999999999cccccc999999999999cc
+999999999999cccccc9999999999999999cc999999cccc999999cc9999999999999999cc
+cccc999999999999cc999999999999cccccc9999999999999999cc999999cccc999999cc
+9999999999999999cccccc999999999999cc999999999999cccccc9999999999999999cc
+999999cccc999999cc9999999999999999cccccc999999999999cc999999999999cccccc
+9999999999999999cc999999cccc999999cc9999999999999999cccccc999999999999cc
+999999999999cccccc999999ffffffccccccccccccffffcc9999cc9999999999999999cc
+9999999999999999999999cc999999999999ffffffcc999999cccc996666ffccffcccccc
+cc99cc99cccccc99cccccccc9999cccccccccc99cc99cccccccccccccccccc99cc666666
+ffffcccccccc000000
+000000cccccc666666cccc99999999cccc99999999cccc99999999cccc99999999ffffcc
+cccccccccc999999cccccccc666666999999ffffcccccc999999cccccc999999cccccc99
+cccccccccc99cccccc999999cccccc999999cccccccccc99cccccccccc999999cccccc99
+cccccccccc999999cccccc999999cccccc99cccccccccc999999cccccc999999cccccc99
+cccccccccc99cccccc999999cccccc999999cccccc999999cccccc999999cccccc999999
+cccccccccc99cccccccccc999999cccccc99cccccccccc99cccccc999999cccccc999999
+cccccccccc99cccccccccccc666666cccc99999999999999999999999999cccc99999999
+999999999999999999cccc99999999999999999999999999cccc99999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999999999999999999999cccc99999999999999
+999999999999cccc99999999999999999999999999cccc99999999999999999999999999
+cccc99999999999999999999999999cccc99999999999999999999999999cccc99999999
+999999999999999999cccc99999999999999999999999999cccc99999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999999999999999999999cccc99999999999999
+999999999999cccc99999999999999999999999999cccc99999999999999999999999999
+cccc99999999999999999999999999cccc99999999cccc99999999cccc99999999cccc99
+999999cccc99999999cccc99999999999999999999999999999999cccc99999999cccc99
+999999cccc99999999cccc99999999999999999999999999999999cccc99999999cccc99
+999999cccc99999999cccc99999999999999999999999999cccc99999999999999999999
+999999999999cccc99999999999999999999999999cccc99999999999999999999999999
+cccc99999999999999999999999999cccc99999999cccc99999999999999999999999999
+cccc99999999999999999999999999cccc99999999cccc99999999cccc99999999999999
+999999999999999999cccc99999999999999999999999999cccc99999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999cccc99999999999999999999999999cccc99
+999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99
+999999999999999999999999cccc99999999cccc99999999cccc99999999cccc99999999
+cccc99999999cccc99999999999999999999999999cccc99999999cccc99999999cccc99
+999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999999999
+999999999999cccc99999999999999999999cccc999999999999999999999999999999cc
+cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999
+999999999999999999cccc99999999cccc99999999999999999999999999999999cccc99
+999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99
+999999cccc99999999cccc99999999cccc99999999cccc99999999999999999999999999
+cccc99999999999999999999999999cccc99999999999999999999999999cccc99999999
+999999999999999999cccc99999999999999999999999999cccc99999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999999999999999999999cccc99999999999999
+999999999999cccc99999999999999999999999999cccc99999999999999999999999999
+cccc99999999999999999999999999cccc99999999999999999999999999cccc99999999
+999999999999999999cccc99999999999999999999999999cccc99999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccccccccccc9999ccccccccccccccffffff999999cccc99
+9999cccccc99999999cccc99999999999999cccccccccccccccccc666666999999cccc99
+cccccccccc99cccccccccc99cccccccccc99cccccccc99cccccc9999cccccccccc9999cc
+cccccccccccc000000
+000000cccccc6666669999cc9999999999cc9999999999999999ccffffcccccccc9999cc
+cccccccccccccccccccccccc666666999999ffffffcccccccccccccccccccccccc9999cc
+cccccc9999cccccccccccccccccccccccccc9999cccccccc9999cccccccccccccccccccc
+9999cccccccccccccccccccccccccccccccc9999cccccccccccccccccccccccccc9999cc
+cccccc9999cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+9999cccccccc9999cccccccccccccccccccc9999cccccccccccccccccccccccccccccccc
+cccccc9999cccccccccccccc6666669999cc9999999999cccccc999999cc9999999999cc
+cccc999999cc9999999999cc999999cccccc9999999999cc9999999999cccccc999999cc
+9999999999cc999999cccccc9999999999cc9999999999cccccc999999cc9999999999cc
+999999cccccc9999999999cc9999999999cccccc999999cc9999999999cc999999cccccc
+9999999999cc9999999999cccccc999999cc9999999999cc999999cccccc9999999999cc
+9999999999cccccc999999cc9999999999cc999999cccccc9999999999cc9999999999cc
+cccc999999cc9999999999cc999999cccccc9999999999cc9999999999cccccc999999cc
+9999999999cc999999cccccc9999999999cc9999999999cccccc999999cc9999999999cc
+999999cccccc9999999999cc9999999999cccccc999999cc9999999999cc999999cccccc
+9999999999cc9999999999cccccc999999cc9999999999cc999999cccccc9999999999cc
+999999999999cccccc9999999999999999cc9999999999999999cc9999999999999999cc
+9999999999cc9999999999999999cccccc999999cccccc999999cc9999999999999999cc
+9999999999999999cc999999999999cccccc999999cccccc9999999999cc9999999999cc
+9999999999cc9999999999cc999999cccccc9999999999999999cc999999cccccc999999
+cccccc9999999999cc999999cccc999999cc9999999999cc999999cccc999999cc999999
+9999cc999999cccc999999cc9999999999cc9999999999cc999999cccccc9999999999cc
+9999999999cccccc999999cc9999999999cc9999999999999999cc999999999999cccccc
+999999cccccc9999999999cc999999cccccc9999999999cc9999999999cccccc999999cc
+9999999999999999cccccc999999cc9999999999999999cccccc999999cc999999999999
+9999cccccc999999cc9999999999cc9999999999cc999999cccccc9999999999cc999999
+9999999999cc9999999999cc9999999999cc9999999999cc9999999999cc9999999999cc
+999999cccc999999cc9999999999999999cc9999999999cc9999999999999999cc999999
+9999cc9999999999cc999999cccccc9999999999cc9999999999cc9999999999999999cc
+9999999999999999cc9999999999cc9999999999cc9999999999999999cc999999cccccc
+9999999999999999cc9999999999cc9999999999cc999999cccc999999cccccc99999999
+9999999999cc9999999999cc9999999999cc9999999999cc9999999999cc999999999999
+cccccc9999999999cc9999999999999999cc999999cccccc999999cccc999999cc999999
+9999cc9999999999999999cc9999999999999999cc9999999999cc9999999999cc999999
+9999cc9999999999999999cc9999999999cc9999999999cc999999cccccc9999999999cc
+9999999999cccccc999999cc9999999999cc999999cccccc9999999999cc9999999999cc
+cccc999999cc9999999999cc999999cccccc9999999999cc9999999999cccccc999999cc
+9999999999cc999999cccccc9999999999cc9999999999cccccc999999cc9999999999cc
+999999cccccc9999999999cc9999999999cccccc999999cc9999999999cc999999cccccc
+9999999999cc9999999999cccccc999999cc9999999999cc999999cccccc9999999999cc
+9999999999cccccc999999cc9999999999cc999999cccccc9999999999cc9999999999cc
+cccc999999cc9999999999cc999999cccccc9999999999cc9999999999cccccc999999cc
+9999999999cc999999cccccc9999999999cc9999999999cccccc999999cc999999999999
+9999cccccc99999999999999ffffffcccccccccc99cccccccccc99cccccccccccccccccc
+9999999999999999cc9999999999cccccc99ccffffcccccc99cccc666666999999ccffff
+cc99cc99cccccc99cc99cccccc99cc99cccccccccc99cccccccccccc9999666666999999
+ffffffcccccc000000
+000000cccccc6666669999999999999999999999ccffffcccccccc9999cccccccccccccc
+cccccc999999cccccccccccc666666999999cccccccccccc9999cccccc99cccccccccccc
+cccc99cccccccccccc999999cccccccccccccccc99cccccccccccc999999cccccccccccc
+cccc99cccccccccc999999cccccc99cccccccccc99cccccc999999cccccccccccccccc99
+cccccccccccccccc999999cccccccc999999cccccc9999cccccc999999cccccc99cccccc
+cccc99cccccccccc99cccccc9999cccccc99cccccccccccc999999cccccc9999cccccc99
+cccccccccc999999cccccccc666666cccc99999999999999999999999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999999999999999999999cccc99999999999999
+999999999999cccc99999999999999999999999999cccc99999999999999999999999999
+cccc99999999999999999999999999cccc99999999999999999999999999cccc99999999
+999999999999999999cccc99999999999999999999999999cccc99999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999999999999999999999cccc99999999999999
+999999999999cccc99999999999999999999999999cccc99999999999999999999999999
+cccc99999999999999999999999999cccc99999999999999999999999999cccc99999999
+999999999999999999cccc99999999999999cccc99999999999999999999cccc99999999
+999999999999cccc99999999999999999999999999999999999999999999cccc99999999
+999999999999cccc99999999999999999999999999999999999999cccc99999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999999999
+999999999999999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999999999cccc99999999999999999999999999cccc99999999
+999999999999999999999999cccc99999999999999999999cccc99999999999999999999
+999999999999999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999999999999999999999cccc99999999999999
+999999999999999999999999cccc99999999999999999999999999999999cccc99999999
+999999999999cccc99999999999999999999cccc99999999999999999999cccc99999999
+999999999999999999999999cccc99999999999999999999cccc99999999999999999999
+cccc99999999999999999999999999cccc99999999999999cccc99999999999999999999
+cccc99999999999999999999cccc99999999999999999999cccc99999999999999999999
+999999999999cccc99999999999999cccc99999999999999999999999999999999999999
+cccc99999999999999999999cccc99999999999999999999cccc99999999999999999999
+999999cccc99999999999999cccc99999999999999999999999999999999999999999999
+999999cccc99999999999999999999cccc99999999999999999999cccc99999999999999
+999999cccc99999999999999999999cccc99999999999999999999999999cccc99999999
+999999999999999999cccc99999999999999999999999999cccc99999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999999999999999999999cccc99999999999999
+999999999999cccc99999999999999999999999999cccc99999999999999999999999999
+cccc99999999999999999999999999cccc99999999999999999999999999cccc99999999
+999999999999999999cccc99999999999999999999999999cccc99999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999999999999999999999cccc99999999999999
+9999999999cc999999999999cccccccccccc9999cccccccc9999cccccccccccccccccccc
+ccccccffffcc999999999999999999999999ffcccccccccccccccc666666999999999999
+cccccccccc99cccccccccccccccc99cccccccc9999cccccccccccccccccc999999999999
+cccccccccccc000000
+000000cccccc666666cccc99999999ffffffcccccc9999cccccccccccc99cccccc9999cc
+cccccccccccccccccc9999cc666666999999ffffffcccccccccccccccccc9999cccccc99
+cccccc9999cccccccccccccccccccc999999cccccccccccccccccccccccccccccc999999
+cccccc9999cccccccccccccccccccc9999cccccccccccccccccccccccccc9999cccccccc
+cccccc999999cccccccccccccccccccccccccccccccccc99cccccccccccc9999cccccccc
+9999cccccccccccccccccccccccc99cccccc9999cccccccccccccccccccccccc999999cc
+cccccccccccccccccccccc996666669999cc999999cccc999999999999cccccc99999999
+9999999999cc999999cccc999999999999cc999999999999cccc999999cc999999999999
+999999cccccc9999999999999999999999cccccc999999999999999999cc999999cccc99
+9999999999cc999999999999cccc999999cc999999999999999999cccccc999999999999
+9999999999cccccc999999999999999999cc999999cccc999999999999cc999999999999
+cccc999999cc999999999999999999cccccc9999999999999999999999cccccc99999999
+9999999999cc999999cccc999999999999cc999999999999cccc999999cc999999999999
+999999cccccc9999999999999999999999cccccc999999999999999999cc999999cccc99
+9999999999cc999999999999cccc999999cc999999999999999999cccccc999999999999
+9999999999cccccc999999999999999999cc999999cccc999999999999cc9999999999cc
+cccc999999cc9999999999cc9999999999cc9999999999cccccc999999cc999999999999
+cccccc9999999999cc999999999999cccccc999999999999cccccc9999999999cc999999
+cccccc9999999999cc9999999999999999cccccc999999999999999999cc999999cccc99
+9999999999cc999999cccc999999cc9999999999cc999999cccc999999999999cccccc99
+999999cccccc9999999999cccccc999999999999cc9999999999cccccc999999999999cc
+999999cccccc9999999999999999999999cc999999cccc999999999999cc999999999999
+cccc999999cc9999999999999999cc999999cccccc9999999999cc999999999999cccccc
+999999999999cccc999999cc9999999999999999999999cccccc999999999999cc999999
+9999cccccc999999999999cc9999999999cccccc999999999999cc9999999999cccccc99
+999999999999cccccc999999999999999999cccccc9999999999999999cc9999999999cc
+cccc999999999999999999cccccc999999999999999999cccccc999999999999cc999999
+cccccc9999999999cc9999999999999999cccccc999999cc999999999999cccccc999999
+999999999999cccccc9999999999999999cc9999999999999999cc999999cccccc999999
+9999cc999999cccc999999cc999999999999cccccc9999999999cc999999cccc999999cc
+cccc999999999999cc9999999999999999cc9999999999cccccc999999999999cc999999
+9999999999cccccc999999999999999999cccccc999999999999cc999999cccccc999999
+9999999999cc9999999999cc9999999999999999cccccc999999cc999999cccc999999cc
+9999999999cc999999cccccc9999999999999999cccccc999999999999cc999999cccc99
+9999cc999999999999cccccc999999999999999999cccccc9999999999999999999999cc
+cccc999999999999999999cc999999cccc999999999999cc999999999999cccc999999cc
+999999999999999999cccccc9999999999999999999999cccccc999999999999999999cc
+999999cccc999999999999cc999999999999cccc999999cc999999999999999999cccccc
+9999999999999999999999cccccc999999999999999999cc999999cccc999999999999cc
+999999999999cccc999999cc999999999999999999cccccc9999999999999999999999cc
+cccc999999999999999999cc999999cccc999999999999cc999999999999cccc999999cc
+999999999999999999cccccc9999999999999999999999cccccc999999999999999999cc
+999999cccc999999999999cc999999999999cccccc9999999999999999cc999999cccc99
+999999999999cccc999999ccffffcccccccccccccccccc99cccccc999999cccccccccccc
+ccccccccccccccccccffffff999999999999ccffffcc99cccccccc666666999999999999
+ffffff9999cccccccc9999cccccccc99cccccccccc99cccccccc996666669999cc999999
+ffffffcccccc000000
+000000cccccc666699cccccccccccccccccccccc99cccccc9999cccccccccccccccccc99
+9999cccccc99cccccccccccc666666999999ffffcc9999cccccc99cccccc9999cccccccc
+cccc99cccccc999999cccccccccccccccccccccc999999cccccc999999cccccccccccccc
+cccccccccccc999999cccccccccccccccc99cccccc9999cccccc99cccccccccc99cccccc
+cccccccccccc999999cccccccccc999999cccccccc9999cccccccccccccccccc99cccccc
+cccccccccc999999cccccccc9999cccccc99cccccccccc999999cccccccccccccccccccc
+cccc999999cccccccccccccc6666669999999999cc9999999999999999999999cccccc99
+9999999999999999cc9999999999999999999999cc9999999999999999999999cc999999
+9999999999999999cc9999999999999999999999cc9999999999999999999999cc999999
+9999999999999999cc9999999999999999999999cc9999999999999999999999cc999999
+9999999999999999cc9999999999999999999999cc9999999999999999999999cc999999
+9999999999999999cc9999999999999999999999cc9999999999999999999999cc999999
+9999999999999999cc9999999999999999999999cc9999999999999999999999cc999999
+9999999999999999cc9999999999999999999999cc9999999999999999999999cc999999
+9999999999999999cc9999999999999999999999cc9999999999999999999999cc999999
+9999999999999999cc9999999999999999999999cc999999999999999999999999999999
+999999999999cccc99999999999999cccc99999999999999999999999999999999999999
+999999999999cccc99999999999999999999999999999999999999999999999999999999
+999999999999999999cccc999999999999999999cccccc999999999999999999cc999999
+9999999999999999999999999999999999999999999999cc999999999999999999999999
+999999999999999999999999999999999999cccc99999999999999999999999999999999
+999999999999cccccc999999cccc999999999999cc9999999999999999999999cc999999
+999999999999999999cccc99999999999999999999999999cccc99999999999999999999
+9999cc9999999999999999999999cccccc99999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999cc
+9999999999999999999999cc999999999999999999999999999999cccc99999999999999
+9999cc9999999999999999999999cc999999999999999999999999999999999999999999
+999999cccc99999999cccc999999999999999999999999999999999999999999999999cc
+999999999999999999999999cccc99999999999999cccc99999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999999999cccc99999999999999cccc99999999999999999999999999999999cccc99
+9999999999999999cc999999999999999999999999999999999999999999999999999999
+999999cccc99999999999999cccc99999999999999999999999999999999999999999999
+cccc99999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999cc9999999999999999999999cc999999999999999999
+9999cc9999999999999999999999cc9999999999999999999999cc999999999999999999
+9999cc9999999999999999999999cc9999999999999999999999cc999999999999999999
+9999cc9999999999999999999999cc9999999999999999999999cc999999999999999999
+9999cc9999999999999999999999cc9999999999999999999999cc999999999999999999
+9999cc9999999999999999999999cc9999999999999999999999cc999999999999999999
+9999cc9999999999999999999999cc9999999999999999999999cc999999999999999999
+9999cc9999999999999999999999cc9999999999999999999999cc999999999999999999
+9999cc999999999999999999999999999999999999999999cccc999999999999cc999999
+9999cc999999999999999999cccccccccccc9999cccccccccccccccccccccc99cc99cc99
+cc99cc99cccccc99ccccffffcc9999999999ccffcccccccccccccc999966999999999999
+999999cccc99cccccccccc99cccccccc9999cccccccc99cccccccccccc99999999999999
+cccccccccccc000000
+000000cccccc666666ffffcccccccc9999cccccccccccccccccc99cccccc9999cccccccc
+cccccccccccccccccccccccc666666999999ffffffcccccccccccccccccccccc99cccccc
+9999cccccccccccccccccccc999999cccccccccccccccccccccccccccccccccc999999cc
+cccccccccccccccccc9999cccccccc9999cccccc99cccccccccccc9999cccccccc9999cc
+cccc99cccccccccccccccccc9999cccccccccccc99cccccccccccc9999cccccccc9999cc
+cccc99cccccccccccccccccccccccccccccc9999cccccccccccccccccc999999cccccccc
+cccccccccccccccc99cccccc666666999999cccc99999999999999999999999999999999
+999999cccc99999999999999cccc99999999cccc99999999999999cccc99999999cccc99
+999999999999cccc99999999cccc99999999999999cccc99999999cccc99999999999999
+cccc99999999cccc99999999999999cccc99999999cccc99999999999999cccc99999999
+cccc99999999999999cccc99999999cccc99999999999999cccc99999999cccc99999999
+999999cccc99999999cccc99999999999999cccc99999999cccc99999999999999cccc99
+999999cccc99999999999999cccc99999999cccc99999999999999cccc99999999cccc99
+999999999999cccc99999999cccc99999999999999cccc99999999cccc99999999999999
+cccc99999999cccc99999999999999cccc99999999cccc99999999999999cccc99999999
+cccc99999999999999cccc99999999cccc99999999999999cccccc999999cccc99999999
+9999cc9999999999999999cc999999999999999999cccccc999999999999cccccc999999
+9999999999cc9999999999cccccc999999999999cccccc999999999999cccccc99999999
+9999cccccc999999cc999999999999999999999999999999999999cccc99999999999999
+cccccc9999999999cccccc99999999cccc99999999999999cccc999999cc9999999999cc
+cccc999999999999cccccc999999cc9999999999999999cccccc999999cc999999cccc99
+999999999999999999999999999999999999cccc99999999cccc99999999cccc99999999
+cccccc9999999999cc9999999999999999cc9999999999999999cc999999cccc99999999
+cccc99999999cccc999999999999999999999999cccccc99999999cccccc999999999999
+cccccc999999cccccc999999cccc999999cc999999cccc999999cccccc99999999999999
+cccc99999999999999cccc99999999cccccc999999cccc999999cc999999999999999999
+cccc99999999cccc99999999999999cccc999999cc999999cccc999999cc999999999999
+9999cc9999999999cc999999999999cccccc999999999999cccccc999999999999cccc99
+9999999999cccccc999999cc9999999999999999cc999999999999cccccc999999999999
+cccccc9999999999cccccc999999cc999999cccc999999cc999999cccccc9999999999cc
+9999999999999999cc9999999999999999cc999999cccccc9999999999cc999999999999
+999999cccc99999999999999cccccc9999999999cccccc999999999999cccccc999999cc
+9999999999999999cc9999999999cc999999cccc999999cccccc999999cc999999999999
+9999cc999999cccc999999cc999999cccc999999cc999999cccccc9999999999cccccc99
+9999cc999999cccc99999999999999cccc99999999999999cccc99999999cccc99999999
+999999cccc99999999cccc99999999999999cccc99999999cccc99999999999999cccc99
+999999cccc99999999999999cccc99999999cccc99999999999999cccc99999999cccc99
+999999999999cccc99999999cccc99999999999999cccc99999999cccc99999999999999
+cccc99999999cccc99999999999999cccc99999999cccc99999999999999cccc99999999
+cccc99999999999999cccc99999999cccc99999999999999cccc99999999cccc99999999
+999999cccc99999999cccc99999999999999cccc99999999cccc99999999999999cccc99
+999999cccc99999999999999cccc99999999cccc99999999999999cccc99999999cccc99
+999999999999cccccc999999cccccc9999999999999999cc999999999999cccc99999999
+999999cccc999999cc999999ffffffcccccccccc99cccccc9999cccccc99cccccccccccc
+cccccccccc99cccccc666666999999999999ffccffcccccc9999cc6666669999cc999999
+999999ffffff9999cccccccc99cccccccccc99cccccccc996666669999999999999999cc
+ffffcccccccc000000
+000000cccccc666666999999999999666666cccccc9999cccccccccccc99cccccccccc99
+cccccc999999cccccccccccc666666999999ffffcc999999cccccc9999cccccccccccccc
+cccccccccc999999cccccccccccccccccccc9999cccccc999999cccccccccccccccccc99
+9999cccccc99cccccccccc99cccccccccccccccccc9999cccccccccccc99cccccccccccc
+cccccc9999cccccccccccc99cccccccccccc9999cccccc99cccccccccc99cccccccccccc
+cccccc999999cccccc999999cccccccccccccccc999999cccccccccccccccccccc999999
+cccccc9999cccccccccccccc6666669999999999cc999999cccccc999999cccc999999cc
+9999999999999999cc9999999999cc9999999999999999cc9999999999999999cc999999
+9999cc9999999999999999cc9999999999999999cc9999999999cc9999999999999999cc
+9999999999999999cc9999999999cc9999999999999999cc9999999999999999cc999999
+9999cc9999999999999999cc9999999999999999cc9999999999cc9999999999999999cc
+9999999999999999cc9999999999cc9999999999999999cc9999999999999999cc999999
+9999cc9999999999999999cc9999999999999999cc9999999999cc9999999999999999cc
+9999999999999999cc9999999999cc9999999999999999cc9999999999999999cc999999
+9999cc9999999999999999cc9999999999999999cc9999999999cc9999999999999999cc
+9999999999999999cc9999999999999999cc9999999999999999999999cc999999999999
+999999cccc99999999999999cccccc999999999999999999999999999999999999999999
+999999cccc99999999999999999999999999999999999999999999999999999999999999
+999999999999999999cccccc999999999999cccccc9999999999999999cc999999999999
+9999999999999999999999cc9999999999999999cc999999999999cccc99999999999999
+999999999999999999999999cccc999999999999999999999999999999999999cc999999
+9999cc999999cccc999999cc9999999999999999cc9999999999999999cc999999999999
+999999999999cccc99999999999999cccc99999999cccc999999999999cc999999999999
+9999cc9999999999999999cccccc99999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+9999cc9999999999cc999999999999999999999999999999999999cccccc999999999999
+9999cc9999999999999999cc999999999999999999999999999999999999cccc99999999
+999999cccc999999999999999999999999999999999999999999999999999999cc999999
+999999999999999999999999999999999999cccc99999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999cccc99
+999999999999cccc99999999cccc99999999999999999999cccc99999999cccc999999cc
+9999999999cc999999999999999999999999999999999999999999999999999999999999
+999999cccc99999999cccc99999999999999999999999999999999cccc99999999cccc99
+999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999cc9999999999999999cc9999999999cc9999999999999999cc999999999999
+9999cc9999999999cc9999999999999999cc9999999999999999cc9999999999cc999999
+9999999999cc9999999999999999cc9999999999cc9999999999999999cc999999999999
+9999cc9999999999cc9999999999999999cc9999999999999999cc9999999999cc999999
+9999999999cc9999999999999999cc9999999999cc9999999999999999cc999999999999
+9999cc9999999999cc9999999999999999cc9999999999999999cc9999999999cc999999
+9999999999cc9999999999999999cc9999999999cc9999999999999999cc999999999999
+9999cc9999999999cc9999999999999999cc9999999999999999cc9999999999999999cc
+999999999999999999999999999999999999999999cccc999999cc9999999999999999cc
+999999999999999999999999ffffcc9999cccccccccccccccccccc9999cccccc9999cccc
+cc99cc99cccccccccc996666999999999999ccffcccccccccccccc666666cccc99999999
+999999999999cccccccccccccc9999cccccccc99cccccccc9999cc99cc99999999999999
+cccccccccccc000000
+000000cccccc666666999999999999999999999999666666cccccc9999cccccccc9999cc
+cccccccccccccccccc9999cc6666669999ccffffffcccccccccccccccc999999cccccccc
+cccccccccccccccccccccc999999cccccc99cccccccccccccccccccccc999999cccccccc
+cccccccccccc9999cccccccc9999cccccc99cccccccccc99cccccc9999cccccccc999999
+cccccccccc99cccccc9999cccccccccccc99cccccccccccc9999cccccccc9999cccccc99
+cccccccccccccccccccccccccccccc9999cccccccccccc99cccccc999999cccccccccccc
+cccccccccc99cccccccccccc666666999999cccc99999999999999999999999999999999
+cccc99999999999999cccc99999999999999cccc99999999999999cccc99999999999999
+cccc99999999999999cccc99999999999999cccc99999999999999cccc99999999999999
+cccc99999999999999cccc99999999999999cccc99999999999999cccc99999999999999
+cccc99999999999999cccc99999999999999cccc99999999999999cccc99999999999999
+cccc99999999999999cccc99999999999999cccc99999999999999cccc99999999999999
+cccc99999999999999cccc99999999999999cccc99999999999999cccc99999999999999
+cccc99999999999999cccc99999999999999cccc99999999999999cccc99999999999999
+cccc99999999999999cccc99999999999999cccc99999999999999cccc99999999999999
+cccc99999999999999cccc99999999999999cccc99999999999999cccc99999999cccccc
+9999999999999999cc999999999999999999cccccc999999cccccc999999cccc999999cc
+9999999999999999cccccc999999cccccc999999cc999999cccc999999cc999999cccc99
+9999999999cc999999999999999999999999999999999999cccc99999999cccc999999cc
+cccc99999999999999cccc99999999999999cccc999999999999cc999999999999cccccc
+9999999999999999cc9999999999999999cccccc99999999999999cccc99999999999999
+cccc99999999999999999999cccc99999999999999999999cccc99999999999999cccccc
+9999999999999999cc9999999999999999cc999999999999999999999999cccc99999999
+999999999999cccc999999999999999999cc999999cccccc9999999999cccccc99999999
+9999cccccc999999999999cccccc999999cc999999cccc999999cc999999cccccc999999
+cccc99999999999999999999cccccc9999999999cc999999999999999999999999cccc99
+999999999999cccc99999999cccc999999cccccc999999cc9999999999cc999999999999
+9999cc9999999999cccccc999999cccccc999999cc999999cccc99999999cccc99999999
+9999cccccc99999999cccccc9999999999cc9999999999cccccc999999cccccc99999999
+9999cccccc99999999cccccc999999cccccc999999cccc999999cc9999999999999999cc
+9999999999cc9999999999cc9999999999999999cc9999999999999999cc999999999999
+cccc99999999cccc999999cc999999cccc999999cccccc999999999999cccccc99999999
+9999cc9999999999999999cc9999999999cccccc999999999999cc9999999999cc999999
+9999cccccc999999cccccc999999cc999999cccc99999999cccccc999999cccc99999999
+cccc99999999999999cccc99999999cccc99999999999999cccc99999999999999cccc99
+999999999999cccc99999999999999cccc99999999999999cccc99999999999999cccc99
+999999999999cccc99999999999999cccc99999999999999cccc99999999999999cccc99
+999999999999cccc99999999999999cccc99999999999999cccc99999999999999cccc99
+999999999999cccc99999999999999cccc99999999999999cccc99999999999999cccc99
+999999999999cccc99999999999999cccc99999999999999cccc99999999999999cccc99
+999999999999cccc99999999999999cccc99999999999999cccc99999999999999cccc99
+999999999999cccc99999999999999cccc99999999999999cccc99999999999999cccc99
+9999999999cccccc999999999999cccccc99999999999999999999cccc99999999999999
+cccc999999cc999999999999ffffffcccccc999999cccccccccccccccccccccccccc99cc
+cccc996666669999999999cc99cc99cc9999ffccffcccccccccc996666669999cc999999
+999999999999ffffcc9999cccccccccccccc99cc99666666999999cc99999999cc99cc99
+ffccffcccccc000000
+000000cccccc666666cccccc999999cccc99999999999999999999666666cccccccccc99
+9999cccccc99cccccccccccc666666999999cccccc9999cccccccccccccccccc99cccccc
+999999cccccc9999cccccccccccccccccccccccc999999cccccccccccccccccccc999999
+cccccccccccccccc99cccccccccccccccccc9999cccccccccccc99cccccccccccccccccc
+9999cccccccccccc99cccccc9999cccccccc9999cccccccccccc99cccccccccccccccccc
+9999cccccccc999999cccccccccc99cccccc9999cccccccccccccccccccccccc999999cc
+cccccc9999cccccccccccccc6666669999cc999999999999cccccc9999999999cc999999
+9999cc9999999999cc9999999999999999cc9999999999cc9999999999cc999999999999
+9999cc9999999999cc9999999999cc9999999999cc9999999999999999cc9999999999cc
+9999999999cc9999999999999999cc9999999999cc9999999999cc9999999999cc999999
+9999999999cc9999999999cc9999999999cc9999999999999999cc9999999999cc999999
+9999cc9999999999cc9999999999999999cc9999999999cc9999999999cc999999999999
+9999cc9999999999cc9999999999cc9999999999cc9999999999999999cc9999999999cc
+9999999999cc9999999999999999cc9999999999cc9999999999cc9999999999cc999999
+9999999999cc9999999999cc9999999999cc9999999999999999cc9999999999cc999999
+9999cc9999999999cc9999999999cc9999999999cc9999999999cc999999999999999999
+cccc99999999999999cccc99999999999999999999999999999999999999999999999999
+cccc999999999999999999999999999999999999999999999999999999999999cc999999
+cccc99999999999999cccc999999cccccc999999cc9999999999cc999999999999999999
+9999cc9999999999999999cc9999999999cc999999cccc99999999999999999999999999
+cccc99999999999999cccc999999999999999999cc9999999999999999cc999999999999
+9999cc9999999999cc9999999999cccccc999999cc9999999999cc999999999999999999
+999999999999cccc99999999cccc99999999999999cccccc9999999999999999cccccc99
+9999cc9999999999cc999999cccc99999999999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999999999
+999999cccccc999999999999999999999999cccc99999999cccc999999999999cc999999
+9999cc9999999999cc999999999999999999999999999999cccc99999999cccc99999999
+cccc999999999999999999999999999999999999999999999999999999cc999999999999
+999999999999999999999999999999cccc99999999999999999999999999999999999999
+999999999999999999999999999999999999999999999999cccc99999999999999cccc99
+999999999999cccc99999999999999cccc99999999999999cccc99999999999999999999
+9999cc999999999999999999999999999999999999999999999999999999999999999999
+cccc99999999999999cccc99999999999999999999999999999999cccc99999999999999
+9999999999999999999999999999999999cc9999999999999999999999999999cc999999
+9999999999cc9999999999cc9999999999999999cc9999999999cc9999999999cc999999
+9999cc9999999999999999cc9999999999cc9999999999cc9999999999999999cc999999
+9999cc9999999999cc9999999999cc9999999999999999cc9999999999cc9999999999cc
+9999999999999999cc9999999999cc9999999999cc9999999999cc9999999999999999cc
+9999999999cc9999999999cc9999999999999999cc9999999999cc9999999999cc999999
+9999cc9999999999999999cc9999999999cc9999999999cc9999999999999999cc999999
+9999cc9999999999cc9999999999cc9999999999999999cc9999999999cc9999999999cc
+9999999999999999cc9999999999cc9999999999cc9999999999cc9999999999cc999999
+9999999999999999999999999999999999999999999999cc9999999999999999cc999999
+999999999999999999999999cccccccccccccccccc99cc99cc99cc99cc99cccccc666666
+9999cc99cc99999999999999999999999999cccccccccccccccccc666666999999cccc99
+9999999999cc999999cccc99cccccccccccccccccc9999cc999999999999999999999999
+cccccccccccc000000
+000000cccccc6666669999999999999999cc999999cccc99999999999999999999666666
+cccccccccccccccccccccc99666666999999ffffffcccccccccc999999cccccccccccccc
+cccccccccccccccc99cccccc9999cccccccccccccccccccccccc999999cccccccccccccc
+cccccc999999cccccc9999cccccc99cccccccccccc9999cccccccccccccc999999cccccc
+cccccccccccc9999cccccccccccc99cccccccccc99cccccc9999cccccccc999999cccccc
+cccccccccccccccccccccccc9999cccccccccccc99cccccc9999cccccccccccccccccccc
+cccc99cccccccccc99cccccc666666999999999999999999999999999999cccc99999999
+cccc99999999cccc99999999cccc99999999999999cccc99999999cccc99999999cccc99
+999999cccc99999999cccc99999999cccc99999999cccc99999999999999cccc99999999
+cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999
+999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99
+999999cccc99999999999999cccc99999999cccc99999999cccc99999999cccc99999999
+cccc99999999cccc99999999cccc99999999999999cccc99999999cccc99999999cccc99
+999999cccc99999999cccc99999999cccc99999999cccc99999999999999cccc99999999
+cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999
+999999cccc99999999cccc99999999cccc99999999999999cccc999999999999cc999999
+9999cc9999999999999999cc999999cccc999999cc9999999999cccccc999999cc999999
+9999cc999999999999cccccc999999cccc999999cc999999cccc99999999999999999999
+9999cc999999999999999999999999999999999999999999cccc99999999999999cccc99
+999999999999cccc999999999999999999999999999999cc999999cccccc9999999999cc
+9999999999cc9999999999999999cccccc99999999cccc99999999cccc99999999999999
+cccc99999999999999999999999999999999999999cccc99999999999999cccccc999999
+cccccc9999999999cc9999999999cc999999999999999999999999999999999999999999
+9999999999999999999999999999cc999999cccc999999cccccc999999cc999999cccccc
+9999999999cccccc99999999cccccc999999cccccc999999999999cccccc999999999999
+999999999999999999cccccc9999999999999999cc9999999999cc999999cccc99999999
+999999cccc99999999999999cccccc9999999999999999999999cc9999999999999999cc
+999999999999999999cccccc999999999999cccccc999999999999cccc999999999999cc
+cccc999999cc999999cccc999999999999cc999999999999cccccc9999999999cccccc99
+9999999999cc999999cccc999999cc9999999999cc9999999999cc9999999999999999cc
+9999999999999999cc9999999999cc9999999999999999cc999999999999cccccc999999
+999999cccc999999cccccc999999cc999999cccccc9999999999cccccc999999cc999999
+9999cc9999999999999999cc999999cccc999999cc9999999999999999999999cccccc99
+999999999999cccccc999999cccc999999999999999999cccccc99999999999999cccc99
+999999cccc99999999999999cccc99999999cccc99999999cccc99999999cccc99999999
+cccc99999999cccc99999999cccc99999999cccc99999999999999cccc99999999cccc99
+999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999999999
+cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999
+cccc99999999999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99
+999999cccc99999999cccc99999999999999cccc99999999cccc99999999cccc99999999
+cccc99999999cccc99999999cccc99999999cccc99999999999999cccc99999999cccc99
+999999cccc99999999cccc99999999cccc99999999cccc99999999cccc99999999999999
+cccccc9999999999cccccc999999cc999999cccc99999999cccc99999999cccc99999999
+9999cccccc999999cc999999ffffffcccccccc99cccccccccccccc666666999999999999
+cc99999999999999cccccc999999cc99cc99ffccff99cccccccccc6666669999cc999999
+999999cc9999999999ccffffcccccc666666666666cc999999cc99cc99cc99999999cc99
+ffffffcccccc000000
+000000cccccc6666669999999999999999999999cc999999cccc999999cc999999999999
+999999666666cccccccccccc666666999999cccccccccccccccccccccccc9999cccccc99
+9999cccccccc9999cccccc99cccccccccc999999cccccccccccccccccccc999999cccccc
+cccccccccccccccccccccccc9999cccccc99cccccccccc99cccccccccccccccccc999999
+cccccccccc99cccccccccccccccccc9999cccccccccccc99cccccccccccccccccccccc99
+cccccc999999cccccccccc99cccccc9999cccccccccccc99cccccc999999cccccc9999cc
+cccccc9999cccccccccccccc666666cccc999999cccccc999999999999cc999999999999
+9999cc9999999999999999cc9999999999999999cc9999999999999999cc9999999999cc
+9999999999999999cc9999999999999999999999cc9999999999cc9999999999999999cc
+9999999999999999cc9999999999999999cc9999999999999999cc9999999999999999cc
+9999999999999999cc9999999999999999cc9999999999cc9999999999999999cc999999
+9999999999999999cc9999999999cc9999999999999999cc9999999999999999cc999999
+9999999999cc9999999999999999cc9999999999999999cc9999999999999999cc999999
+9999999999cc9999999999cc9999999999999999cc9999999999999999999999cc999999
+9999cc9999999999999999cc9999999999999999cc9999999999999999cc999999999999
+9999cc9999999999999999cc9999999999999999999999cc999999cccc99999999999999
+999999cccc99999999999999999999999999cccc99999999999999999999999999999999
+cccc999999999999999999999999999999999999999999cc9999999999cccccc99999999
+999999cccc999999cc9999999999cccccc999999999999999999cc9999999999cc999999
+9999999999cc999999999999cccccc999999cccc99999999999999999999999999cccc99
+999999999999cccc999999999999999999999999999999999999cc9999999999cc999999
+9999999999cccccc99999999cccccc9999999999999999cc999999999999999999999999
+999999999999999999999999999999cccccc999999999999cccccc9999999999cccccc99
+999999cccccc999999cccc99999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999cccccc99
+9999cc999999999999999999999999999999cccc999999999999999999cc999999999999
+9999999999cc9999999999999999999999cccccc99999999999999cccc99999999999999
+cccccc9999999999999999999999999999999999999999999999cc999999999999999999
+9999999999999999cc999999999999cccc99999999999999999999999999999999999999
+cccc99999999999999999999999999cccc99999999999999cccc99999999cccc99999999
+cccc99999999cccc99999999999999cccc99999999cccc99999999999999999999999999
+999999999999999999999999999999999999999999999999999999999999999999cccc99
+999999999999cccc999999999999999999999999999999cccccc999999999999999999cc
+9999999999999999999999999999cccccc999999999999999999999999999999cc999999
+9999cc9999999999999999cc9999999999cc9999999999999999cc999999999999999999
+9999cc9999999999cc9999999999999999cc9999999999999999cc9999999999999999cc
+9999999999999999cc9999999999999999cc9999999999999999cc9999999999999999cc
+9999999999cc9999999999999999cc9999999999999999999999cc9999999999cc999999
+9999999999cc9999999999999999cc9999999999999999cc9999999999999999cc999999
+9999999999cc9999999999999999cc9999999999999999cc9999999999cc999999999999
+9999cc9999999999999999999999cc9999999999cc9999999999999999cc999999999999
+9999cc9999999999999999cc9999999999999999cc9999999999999999cc999999999999
+9999999999999999999999999999999999999999999999cc9999999999cc999999999999
+999999999999999999999999ffcccc99cc99cccccc6666669999999999999999cc99cc99
+999999999999999999999999999999999999ffcccccccccccccccc669966cc9999999999
+99cccc9999999999999999cccc999999cc99999999999999999999999999999999999999
+cccccccccccc000000
+000000cccccc666666cccccc999999999999cccc99999999999999999999cccc999999cc
+999999999999999999666666666666cccccccccccc666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666666666666666666666666666666666666666666666
+666666666666666666666666666666999999999999999999999999999999cccc99999999
+999999999999cccc99999999999999cccc99999999999999999999cccc99999999999999
+999999999999cccc999999999999cc999999cccc99999999999999cccc99999999999999
+999999999999cccc99999999999999999999999999cccc99999999999999cccc99999999
+999999999999cccc99999999999999999999999999cccc99999999999999999999cccc99
+9999cc999999cccc99999999999999999999cccc99999999999999999999999999cccc99
+999999999999999999cccc99999999999999cccc99999999999999999999999999cccc99
+999999999999999999999999cccc99999999999999cccc999999cc999999999999cccc99
+999999999999999999999999cccc99999999999999999999cccc99999999999999cccc99
+999999999999999999cccc999999999999cccccc999999999999999999cc999999cccc99
+9999cc9999999999cccccc999999cc9999999999999999cccccc999999999999999999cc
+9999999999cccccc999999999999cccccc99999999cccc999999999999999999999999cc
+999999999999999999cccc999999999999999999cccccc99999999999999cccc99999999
+cccc999999999999999999999999999999999999cc999999cccc999999cc999999999999
+9999cc9999999999cc999999cccc999999cc999999999999cccc99999999999999cccc99
+999999999999999999999999999999999999cccc99999999999999cccccc999999999999
+cccc999999cccccc99999999999999999999999999999999999999999999999999999999
+9999999999999999999999cc999999999999cccccc999999999999cccc999999cc999999
+cccc999999cc9999999999cccccc999999999999cccccc99999999999999999999999999
+999999cccc999999cc999999cccccc999999999999999999cccc99999999999999cccccc
+999999999999cccc999999999999999999999999999999cc9999999999cc999999999999
+999999999999cccccc999999cccccc999999999999cccc99999999cccc999999cc999999
+cccc999999999999999999999999cc999999999999cccccc999999999999cccccc999999
+9999999999cc999999cccc999999999999cc9999999999999999cc9999999999999999cc
+9999999999999999cc9999999999999999cc9999999999cc999999cccc999999cc999999
+cccc999999cc999999cccc999999cccccc99999999999999cccccc9999999999999999cc
+9999999999cc999999999999cccccc999999cccc99999999999999999999cccc99999999
+999999cccccc9999999999999999999999999999cccccc999999cc999999cccc99999999
+999999cccc99999999999999999999999999cccc999999999999999999999999cc999999
+cccc99999999999999999999cccc99999999999999cccc99999999999999999999cccc99
+999999999999999999999999cccc99999999999999999999cccc99999999999999cccc99
+999999999999999999999999cccc999999999999cc999999999999cccc99999999999999
+cccc99999999999999999999cccc99999999999999999999999999cccc99999999999999
+999999cccc99999999999999cccc99999999999999999999cccc99999999999999999999
+999999cccc999999cc999999999999cccc99999999999999cccc99999999999999999999
+999999cccc99999999999999999999999999cccc99999999999999cccc99999999cccccc
+999999cccc999999cc999999cccccc999999999999999999cccc9999999999cc99cc99cc
+99cc9999999999cccc999999ffffff666666999999999999cc999999cc99cc9999999999
+9999cccccc999999999999cc99cc99999999ffffff9999cccccccc666666999999999999
+999999cc999999999999cc999999999999cccc999999cccc999999cc99cc999999999999
+ffffffcccccc000000
+000000cccccc666666ccccffffffccffffffccccffffffccccccffffffffccccccffffff
+ffffccccccffffffffffffccffffffccccccffffffffffccccccffffffccffffffffffcc
+ffffffffffccffffffffffffccccffffffccffffffccccccffffffffffccffffffcccccc
+ffffffffffccccccffffffccffffffffffffccccccffffffccccffffffccffffffffffff
+ccccffffffccccccffffffccffffffccccccffffffffffffccccccffffffccccccffffff
+ffffffccccffffffffffffccffffffffffccccccffffffffffffffffffccffffffffffcc
+ffffffccccccffffffffffffffffffccccccffffffffffffffffffffffffccccffffffcc
+ffffffffffffccccffffffccffffffccccffffffffffffffffffffccccccffffffffffff
+ffffccffffffccccffffffccffffffffffccccccffffffccffffffccccffffffccffffff
+ffffffffffffccccffffffffffffffffffffffffffccccccffffffffffffccccffffffcc
+ffffffffffffccccffffffffffffffffffccffffffccccffffffccffffffffffffcccccc
+ffffffffffccccccffffffccffffffffffffccccccffffffffffffffffffffffffccccff
+ffffffffffffffffffccccccffffffffffffccccffffffffffffffffffffffffccccccff
+ffffffffffffffffccffffffccccffffffffffffccccccffffffccffffffffffccccccff
+ffffffffffccffffffffffffccccffffffffffffccffffffccccffffffffffffffcccccc
+ffffffffffffffffffccccccffffffffffccccccffffffccffffffccccccffffffcccccc
+ffffffffffccccccffffffccccccffffffccffffffccccccffffffffffccffffffffffcc
+ccccccffffffccccccffffffffffccccccffffffccccccffffffffffffccffffffffffcc
+ffffffffffccffffffccccffffffccffffffccccccffffffffffccccccffffffffcccccc
+ffffffffffffffffffffffffffffffffffccccccffffffccccccffffffccffffffffffcc
+ffffffffffccffffffffffccccccffffffccffffffffffffccccccffffffffffffcccccc
+ffffffffffffffffffffffccffffffccccffffffffffffccccccffffffccccccffffffcc
+ccccffffffccccccffffffffffffccffffffffffffffffffffffccffffffffffffffffcc
+ffffffccccffffffccffffffffffccffffffccccccffffffffffffccccccffffffffffcc
+ccccffffffccffffccffffffccccccffffffffffccccccffffffffffffffffffccffffff
+ccccffffffccffffccccccffffffccccccffffffffffffccccccffffffffffffcccccccc
+ffffffffffffccccccffffffffffffffffffffffccffffffffffccffffffffffccffffff
+ccccccffffffccccccffffffccccccffffffffffffccccffffffccccccffffffccffffff
+ccccccffffffffffffffffccffffffffffccccccffffffccffffffccccccffffffcccccc
+ffffffffffccffffffccccffffffccffffffffffccffffffffffccffffffffffccccccff
+ffffccffffffffffccffffffffffccffffffffffccffffffccccccffffffffffccccccff
+ffffccccccffffffccccccffffffccccccffffffccffffffccccccffffffffffccffffff
+ffffccffffffffffccffffffccccccffffffccccccffffffccccffffffffccccffffffcc
+ffffffccccccffffffffffffffffccffffffccccccffffccffffffccccccffffffffffcc
+ccccffffffffffffffffffccffffffffffffccccccffffffffffffffffccffffffffffcc
+ccccffffffccffffffffffffccccccffffffffffffccccffffffffffffffffffffccccff
+ffffffffffccffffffffffffccccffffffccffffffffffffccccffffffffffffffcccccc
+ffffffffffffffffccffffffccccffffffccffffffffffccffffffccccccffffffffffff
+ccccccffffffffffffffffffccccffffffffffffffffffffffffffccccccffffffffffff
+ffffffccccffffffffffffffccccccffffffffffffffffffccccccffffffffffffffffff
+ffffccccccffffffccffffffffffccccccffffffffffffccccccffffffffffffffffffff
+ffffccccccffffffffffffffffffffffffccccccffffffffffffffccccccffffffcccccc
+ffffffccccccffffccffffffccccccffffffffffccccffffffccffffffccccccffffffcc
+ffccffffffccffccffffffccccccffffffccccffffffffffccffccffccffccffffffcccc
+ffffffccccccffffffffffccffccffccffccffcccccccccccccccc666666ffffffffffff
+ccffffffffccffffffffccffccffccffccffccffccffccffffffccccffffffffccffffff
+cccccccccccc000000
+000000cccccccccccccccccccccccc9999cccccccccccccccccccccccccccccccc9999cc
+cccccccccccccccccc9999cccccccccccccccccccc9999cccccccccccccc9999cccccccc
+9999cccccccc9999cccccc99cccccccccccc9999cccccccccccccc9999cccccccccccccc
+9999cccccccccccccc9999cccccccccccccccccccccccccccccccc9999cccccccccccc99
+cccccccccccccccccc9999cccccccccccccc9999cccccccccccccccccccccccccc9999cc
+cccc99cccccccccccc9999cccccccccccccccccccc999999cccccc9999cccccccc9999cc
+cccccccccccccccccc999999cccccccccccccccccc999999cccccc9999cccccccccccccc
+9999cccccc99cccccccccccc9999cccccc99cccccc9999cccccccccccccc9999cccccccc
+9999cccccccccccccc9999cccccccccccccccccccc9999cccccccccccccccccccc9999cc
+cccc999999cccccc99cccccc999999cccccc9999cccccccccccccc999999cccccccccccc
+9999cccccc99cccccccccccc9999cccccccc9999cccccccccccccc9999cccccccccccccc
+cccccc9999cccccccccccccc9999cccccccccccccc9999cccccc999999cccccc99cccccc
+999999cccccccccccccccccc9999cccccc99cccccccccc999999cccccccc9999cccccccc
+cccc999999cccccccc9999cccccc99cccccc9999cccccccccccccc9999cccccccccccccc
+cccccc9999cccccccc999999cccccccccccc9999cccccccccccc99cccccc9999cccccccc
+cccccc999999cccccccccccc9999cccccccccccccccccccc9999cccccccccccccccccccc
+cccccc9999cccccccccccccccccccccccccc9999cccccccccccccc9999cccccccccccccc
+cccccccccccccccccc9999cccccccccccccccccccccccccc9999cccccccc9999cccccccc
+9999cccccccc9999cccccccccccccc9999cccccccccccccc9999cccccccccccccccccccc
+9999cccccc999999cccccc999999cccccccccccccccccccccccccc9999cccccccc9999cc
+cccccc9999cccccccc9999cccccccccccccc9999cccccccccccccccccccc9999cccccccc
+cccccc999999cccccc9999cccccccccccc999999cccccccccccccccccccccccccccccccc
+cccccccccccccccccc9999cccccccc9999cccccc999999cccccccc9999cccccccc9999cc
+cccccccccccccccccc9999cccccccc9999cccccccccccccc9999cccccccccccccc9999cc
+cccccccccccc9999cccccccccccccc9999cccccccccccccccccc999999cccccccccccccc
+cccccc9999cccccccccccccccccccccccccc9999cccccccccccccccccccc9999cccccccc
+cccccc9999cccccccccccccc999999cccccc9999cccccccc9999cccccccc9999cccccccc
+cccccccccccccccccccccccccccccc9999cccccc99cccccccccccccccccc9999cccccccc
+cccccccccccc9999cccccccc9999cccccccccccccc9999cccccccccccccccccccccccccc
+9999cccccccc9999cccccccccccccc9999cccccccc9999cccccccc9999cccccccccccccc
+cccccc9999cccccccc9999cccccccc9999cccccccccccccccccccc9999cccccccccccccc
+cccccccccccccccccccccccccccccccccccc9999cccccccccccccc9999cccccccc9999cc
+cccccc9999cccccccc9999cccccccccccccccccccccccccccccc99cccccccccccc9999cc
+cccccccccccc9999cccccccc9999cccccccccccccc9999cccccccccccccc9999cccccccc
+cccccc999999cccccc9999cccccccc9999cccccccccccccc9999cccccccc9999cccccccc
+cccccc9999cccccccccccccccccccc9999cccccc99cccccc999999cccccccccc99cccccc
+9999cccccccc9999cccccc99cccccc9999cccccccccccc99cccccc9999cccccccccccccc
+cccccc9999cccccccc9999cccccccccccccc9999cccccccc9999cccccccccccccc9999cc
+cccccccccccc999999cccccccccc99cccccc999999cccccc9999cccccccccccccc999999
+cccccccccc99cccccc9999cccccccccccccc999999cccccccccccc9999cccccc999999cc
+cccccccccccc9999cccccccccccccccccccc9999cccccccccccccccccc999999cccccccc
+9999cccccccccccc999999cccccccc9999cccccccccccc999999cccccccccccccccccccc
+cccccccccccc9999cccccccccccccccc99cccccccccc99cc99cccccccccccccccc99cccc
+cccccc99cccccccccc99cccccccccccc99cccccccccc99cccccccccccccccccccc99cccc
+cccccccccccccc99cc99cccccccccccccccc99cccccccccccc99cccccccc99cc99cc99cc
+cccccc9999cccccccccccccccccccccccccccccccc99cccccccccccc99cccccccc9999cc
+cccccccccccc000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000
+
+showpage
+
+% stop using temporary dictionary
+end
+
+% restore original state
+origstate restore
+
+%%Trailer
diff --git a/docs/main_window_4.gif b/docs/main_window_4.gif
new file mode 100644
index 0000000..f6047a4
Binary files /dev/null and b/docs/main_window_4.gif differ
diff --git a/docs/main_window_4.png b/docs/main_window_4.png
new file mode 100644
index 0000000..2d09fb6
Binary files /dev/null and b/docs/main_window_4.png differ
diff --git a/docs/main_window_5.eps b/docs/main_window_5.eps
new file mode 100644
index 0000000..2161282
--- /dev/null
+++ b/docs/main_window_5.eps
@@ -0,0 +1,8185 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: /home/kmr/powmap/docs/main_window_5.ps
+%%Creator: XV Version 3.10a Rev: 12/29/94 (PNG patch 1.2) - by John Bradley
+%%BoundingBox: 36 324 313 410
+%%Pages: 1
+%%DocumentFonts:
+%%EndComments
+%%EndProlog
+
+%%Page: 1 1
+
+% remember original state
+/origstate save def
+
+% build a temporary dictionary
+20 dict begin
+
+% define string to hold a scanline's worth of data
+/pix 1665 string def
+
+% define space for color conversions
+/grays 555 string def % space for gray scale line
+/npixls 0 def
+/rgbindx 0 def
+
+% lower left corner
+36 324 translate
+
+% size of image (on paper, in 1/72inch coords)
+277.48800 85.96800 scale
+
+% define 'colorimage' if it isn't defined
+% ('colortogray' and 'mergeprocs' come from xwd2ps
+% via xgrab)
+/colorimage where % do we know about 'colorimage'?
+ { pop } % yes: pop off the 'dict' returned
+ { % no: define one
+ /colortogray { % define an RGB->I function
+ /rgbdata exch store % call input 'rgbdata'
+ rgbdata length 3 idiv
+ /npixls exch store
+ /rgbindx 0 store
+ 0 1 npixls 1 sub {
+ grays exch
+ rgbdata rgbindx get 20 mul % Red
+ rgbdata rgbindx 1 add get 32 mul % Green
+ rgbdata rgbindx 2 add get 12 mul % Blue
+ add add 64 idiv % I = .5G + .31R + .18B
+ put
+ /rgbindx rgbindx 3 add store
+ } for
+ grays 0 npixls getinterval
+ } bind def
+
+ % Utility procedure for colorimage operator.
+ % This procedure takes two procedures off the
+ % stack and merges them into a single procedure.
+
+ /mergeprocs { % def
+ dup length
+ 3 -1 roll
+ dup
+ length
+ dup
+ 5 1 roll
+ 3 -1 roll
+ add
+ array cvx
+ dup
+ 3 -1 roll
+ 0 exch
+ putinterval
+ dup
+ 4 2 roll
+ putinterval
+ } bind def
+
+ /colorimage { % def
+ pop pop % remove 'false 3' operands
+ {colortogray} mergeprocs
+ image
+ } bind def
+ } ifelse % end of 'false' case
+
+
+
+555 172 8 % dimensions of data
+[555 0 0 -172 0 172] % mapping matrix
+{currentfile pix readhexstring pop}
+false 3 colorimage
+
+000000010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6fc0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7e7e7e7a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3c0c0c0c0c0c0a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3e7e7e7
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fe7e7e76f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fe7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fe7e7e76f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000
+000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000000000000000000000000000000000000000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000000000
+000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000
+000000ff0000ff0000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000000000000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000000000000000000000000000000000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000000000000000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000
+000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000000000000000000000ff0000ff0000000000000000000000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000ff0000ff0000000000000000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+000000000000000000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000000000000000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000ff0000ff0000000000000000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000
+000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000000000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000000000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000000000ff0000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000000000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000000000ff0000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000000000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000000000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000000000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000000000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000000000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000000000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000000000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000000000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000000000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000000000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000
+ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000000000000000000000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+000000000000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000000000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000000000000000000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000000000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000000000000000000000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000000000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000000000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000000000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000000000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000000000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000000000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000000000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000000000ff0000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000000000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000000000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000000000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000
+ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000ff0000ff0000ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000ff0000000000000000000000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000000000ff0000
+000000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000
+000000000000000000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000000000ff0000000000000000000000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000
+000000ff0000ff0000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000000000000000000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000000000000000000000000000000000000000000000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000000000000000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000
+000000000000000000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000000000000000000000ff0000ff0000000000000000000000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000ff0000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+000000000000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000000000ff0000000000000000000000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000000000000000000000ff0000ff0000ff0000000000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000000000
+000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000
+000000000000000000000000000000000000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000000000ff0000
+ff0000ff0000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000000000ff0000000000000000
+000000000000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffff000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccc000000000000000000cccccc000000cccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccc000000000000000000000000
+000000000000000000cccccccccccccccccccccccc000000000000000000cccccc000000
+cccccccccccc000000000000000000000000000000000000000000cccccccccccccccccc
+000000000000000000cccccc000000cccccccccccccccccccccccccccccc000000000000
+000000cccccc000000cccccccccccc000000000000000000000000000000000000000000
+cccccccccccc000000000000000000000000000000000000000000cccccccccccc000000
+000000000000000000000000000000000000cccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccc000000000000000000000000000000000000000000
+cccccccccccc000000000000000000000000000000000000000000cccccccccccc000000
+000000000000000000000000000000000000cccccccccccccccccc000000000000000000
+cccccc000000cccccccccccccccccccccccccccccc000000000000000000cccccc000000
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccccccccccccccc000000000000
+000000cccccc000000cccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccccccccccccccc000000000000000000cccccc000000cccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccc000000000000000000000000
+000000000000000000cccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccc000000000000000000000000000000000000000000cccccccccccc000000
+000000000000000000000000000000000000cccccccccccccccccc000000000000000000
+cccccc000000cccccccccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccc000000cccccccccccccccccccccccc
+000000000000000000cccccc000000cccccccccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccccccccc000000000000000000cccccc000000
+cccccccccccc000000000000000000000000000000000000000000cccccccccccc000000
+000000000000000000000000000000000000cccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccccccccc000000000000000000cccccc000000
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+cccccc000000000000000000cccccc000000cccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccccccccccccccc000000000000000000cccccc000000cccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccc000000000000000000000000
+000000000000000000cccccccccccccccccccccccc000000000000000000cccccc000000
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+cccccc000000000000000000cccccc000000cccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccc000000cccccccccccccccccccccccc
+cccccc000000000000000000cccccc000000cccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccc000000000000000000000000000000000000000000cccccccccccccccccc
+cccccc000000000000000000cccccc000000cccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccccccccccccccc000000000000000000cccccc000000cccccccccccc000000
+000000000000000000000000000000000000cccccccccccccccccc000000000000000000
+cccccc000000cccccccccccccccccc000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccc000000cccccccccccccccccc000000000000cccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccc000000000000cccccccccccccccccccccccc000000cccccccccccc
+cccccc000000000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000000000cccccccccccccccccccccccc000000cccccccccccccccccc000000000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000000000cccccccccccccccccc000000
+cccccccccccccccccc000000000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccc000000000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000000000cccccccccccccccccc000000ccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccc000000ccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000000000cccccccccccccccccc000000000000000000000000cccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccc000000000000000000000000cccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000
+000000000000000000cccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000
+000000000000000000cccccccccccccccccc000000000000000000000000000000cccccc
+cccccc000000cccccccccccccccccc000000000000000000000000cccccc000000cccccc
+cccccccccccc000000000000000000000000cccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccc000000cccccccccccccccccc000000000000000000000000cccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000
+000000000000000000ccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000ccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000ccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccc000000000000000000000000cccccccccccccccccc000000000000
+000000000000cccccc000000000000000000000000cccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccccccccc000000000000000000000000cccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000cccccccccccccccccccccccccccccccccccc000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccc000000000000000000000000cccccc
+000000000000000000000000cccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccc000000000000000000
+000000cccccccccccccccccccccccccccccccccccc000000000000000000000000cccccc
+cccccc000000000000000000000000cccccc000000000000000000000000000000000000
+000000000000cccccc000000000000000000000000cccccccccccccccccc000000000000
+000000000000cccccccccccc000000000000000000000000cccccc000000000000000000
+000000cccccccccccccccccc000000000000000000000000cccccccccccc000000000000
+000000000000cccccc000000000000000000000000cccccccccccc000000000000000000
+000000000000cccccccccccc000000000000000000000000cccccc000000000000000000
+000000cccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccc000000000000000000
+000000cccccccccccccccccc000000000000000000000000cccccc000000000000000000
+000000cccccccccccc000000000000000000000000cccccccccccccccccccccccccccccc
+000000000000000000000000cccccccccccccccccc000000000000000000000000cccccc
+000000000000000000000000cccccccccccccccccc000000000000000000000000cccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccc000000000000000000000000cccccc
+000000000000000000000000cccccccccccccccccc000000000000000000000000cccccc
+cccccc000000000000000000000000cccccc000000000000000000000000cccccccccccc
+cccccc000000000000000000000000cccccccccccc000000000000000000000000cccccc
+000000000000000000000000000000000000000000000000cccccc000000000000000000
+000000cccccccccccccccccc000000000000000000000000cccccccccccc000000000000
+000000000000cccccc000000000000000000000000cccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccccccccc000000000000000000000000cccccc
+cccccc000000000000000000000000cccccc000000000000000000000000cccccccccccc
+cccccc000000000000000000000000cccccccccccc000000000000000000000000cccccc
+000000000000000000000000000000000000000000000000cccccc000000000000000000
+000000cccccccccccc000000000000000000000000cccccccccccccccccccccccccccccc
+cccccc000000000000000000000000cccccccccccc000000000000000000000000cccccc
+000000000000000000000000000000000000000000000000cccccc000000000000000000
+000000cccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+cccccc000000000000000000000000cccccccccccc000000000000000000000000cccccc
+000000000000000000000000000000000000000000000000cccccc000000000000000000
+000000cccccccccccccccccc000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccc000000000000000000
+000000cccccccccccccccccccccccccccccc000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000
+000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000000000000000ffffff
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000000000000000ffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000000000000000000000000000ffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffff000000000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000000000000000000000ffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000000000000000000000000000
+000000ffffffffffffffffff000000ffffffffffffffffffffffff000000ffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffff000000ffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffff000000ffffffffffff000000ffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffff000000ffffffffffffffffffffffff000000ffffffffffffffffffffffff000000
+ffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffff000000000000000000000000000000ffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffff000000000000000000000000000000
+ffffffffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000000000000000000000ffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffff000000000000
+000000ffffffffffffffffffffffffffffff000000000000ffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffff000000000000000000000000ffffffffffffffffffffffffffffffffffff
+000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffff000000ffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccc000000000000000000cccccc000000cccccccccccc000000
+000000000000000000000000000000000000cccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000cccccc000000cccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+cccccc000000000000000000cccccc000000cccccccccccccccccc000000000000000000
+cccccc000000cccccccccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccc000000000000000000000000
+000000000000000000cccccccccccccccccc000000000000000000cccccccccccccccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccccccccccccccc000000000000
+000000cccccc000000cccccccccccccccccc000000000000000000cccccc000000cccccc
+cccccccccccc000000000000000000000000000000000000000000cccccccccccc000000
+000000000000000000000000000000000000cccccccccccccccccc000000000000000000
+cccccc000000cccccccccccccccccc000000000000000000000000000000000000000000
+cccccccccccccccccc000000000000000000cccccc000000cccccccccccccccccc000000
+000000000000000000000000000000000000cccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccc000000000000000000000000000000000000000000
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccccccccccccccc000000000000
+000000cccccc000000cccccccccccc000000000000000000000000000000000000000000
+cccccccccccccccccccccccc000000000000000000cccccc000000cccccccccccccccccc
+cccccc000000000000000000cccccc000000cccccccccccc000000000000000000000000
+000000000000000000cccccccccccccccccc000000000000000000cccccc000000cccccc
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccc000000000000000000000000
+000000000000000000cccccccccccccccccc000000000000000000cccccc000000cccccc
+cccccccccccc000000000000000000000000000000000000000000cccccccccccccccccc
+000000000000000000cccccc000000cccccccccccccccccc000000000000000000000000
+000000000000000000cccccccccccc000000000000000000000000000000000000000000
+cccccccccccccccccc000000000000000000cccccc000000cccccccccccccccccc000000
+000000000000000000000000000000000000cccccccccccccccccc000000000000000000
+cccccccccccccccccccccccccccccccccccc000000000000000000cccccc000000cccccc
+cccccccccccc000000000000000000000000000000000000000000cccccccccccccccccc
+000000000000000000cccccc000000cccccccccccccccccc000000000000000000000000
+000000000000000000cccccccccccc000000000000000000000000000000000000000000
+cccccccccccccccccccccccc000000000000000000cccccc000000cccccccccccccccccc
+000000000000000000cccccc000000cccccccccccccccccc000000000000000000000000
+000000000000000000cccccccccccc000000000000000000000000000000000000000000
+cccccccccccccccccc000000000000000000cccccccccccccccccccccccccccccccccccc
+000000000000000000cccccc000000cccccccccccccccccc000000000000000000000000
+000000000000000000cccccccccccc000000000000000000000000000000000000000000
+cccccccccccccccccc000000000000000000cccccc000000cccccccccccccccccccccccc
+000000000000000000cccccccccccccccccccccccccccccccccccccccccc000000000000
+000000cccccc000000cccccccccccccccccc000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccc000000cccccccccccccccccc000000000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000000000cccccccccccc000000cccccccccccccccccc
+000000000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000000000cccccccccccc000000cccccccccccccccccc000000000000cccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccccccccc
+000000000000cccccccccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccc000000000000cccccccccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccc000000cccccccccccccccccc000000000000cccccccccccccccccc
+000000cccccccccccccccccc000000000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccc000000000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccc000000000000cccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccc000000000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccc000000000000cccccccccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000000000cccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccc000000000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccc000000cccccccccccccccccc000000000000cccccccccccc000000
+cccccccccccccccccc000000000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccc000000000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccc000000000000cccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000000000ccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc000000
+cccccccccccc000000cccccccccccc000000cccccccccccc000000cccccccccccc000000
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccc000000cccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccc000000cccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccc000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccc
+cccccc000000cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc000000cccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+000000cccccccccccccccccc000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000ccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccc000000cccccccccccccccccc000000000000000000000000
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc000000
+000000000000000000cccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccc000000000000000000000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000
+000000000000000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccc000000000000000000000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccc000000000000000000000000
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccc000000000000000000000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccc000000000000000000000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccc000000000000000000000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccc000000cccccccccccccccccc000000000000000000000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccc000000000000000000000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000000000000000000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccc000000cccccc
+cccccccccccc000000000000000000000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccc000000000000000000000000cccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccc000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccc000000cccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccc000000cccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccc000000cccccc
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccccccccccccccccccccccccccc000000ccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccc000000cccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc
+000000cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccc000000cccccccccccccccccccccccc000000cccccc
+cccccccccccccccccccccccccccccc000000cccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccccccccc000000cccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccccccccc000000cccccccccccc000000
+cccccccccccccccccccccccc000000cccccccccccccccccccccccccccccccccccc000000
+cccccccccccccccccccccccccccccccccccccccccccccccc000000cccccccccccccccccc
+cccccccccccc000000cccccccccccccccccccccccc000000cccccccccccccccccc000000
+cccccccccccccccccccccccccccccc000000cccccccccccccccccc000000cccccccccccc
+cccccccccccc000000cccccccccccc000000ccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccc000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccc000000000000000000000000cccccc
+000000000000000000000000cccccccccccc000000000000000000000000cccccccccccc
+cccccc000000000000000000000000cccccc000000000000000000000000cccccccccccc
+cccccc000000000000000000000000cccccccccccccccccccccccc000000000000000000
+000000cccccccccccccccccc000000000000000000000000cccccc000000000000000000
+000000000000000000000000000000cccccc000000000000000000000000000000000000
+000000000000cccccc000000000000000000000000cccccccccccc000000000000000000
+000000000000cccccccccccc000000000000000000000000cccccc000000000000000000
+000000000000000000000000000000cccccc000000000000000000000000000000000000
+000000000000cccccc000000000000000000000000cccccccccccccccccc000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000cccccccccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccccccccccccccc000000000000000000
+000000cccccccccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccc000000000000000000000000cccccccccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccc000000000000000000000000cccccc
+000000000000000000000000cccccccccccc000000000000000000000000000000cccccc
+cccccc000000000000000000000000cccccc000000000000000000000000000000000000
+000000000000cccccc000000000000000000000000cccccccccccccccccc000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccccccccc000000000000000000000000cccccccccccccccccccccccc
+cccccc000000000000000000000000cccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000cccccccccccc
+cccccc000000000000000000000000cccccc000000000000000000000000000000000000
+000000000000cccccc000000000000000000000000cccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000cccccccccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000cccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccc000000000000000000000000cccccccccccccccccccccccccccccc
+000000000000000000000000000000cccccccccccc000000000000000000000000cccccc
+000000000000000000000000cccccccccccc000000000000000000000000cccccccccccc
+cccccccccccccccccc000000000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000cccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccccccccc000000000000000000000000cccccccccccccccccccccccc
+000000000000000000000000cccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccc000000000000000000000000cccccc000000000000000000000000cccccccccccc
+000000000000000000000000cccccccccccccccccccccccccccccc000000000000000000
+000000000000cccccccccccccccccccccccc000000000000000000000000000000cccccc
+cccccccccccccccccc000000000000000000000000cccccccccccccccccc000000000000
+000000000000cccccc000000000000000000000000cccccccccccccccccc000000000000
+000000000000cccccccccccc000000000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ccccccccccccccccccccccccccccccccccccccccccccccccc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffc0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6fc0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3e7e7e7e7e7e7a3a3a3e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7e7e7e7a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fe7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0e7e7e7a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fe7e7e7c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0e7e7e7a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0e7e7e7
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3e7e7e7
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0e7e7e7a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0e7e7e7a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fe7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0e7e7e7a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fe7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a36f6f6fc0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a36f6f6fc0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a36f6f6fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3c0c0c0c0c0c0c0c0c0c0c0c0a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a36f6f6f
+c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3e7e7e7e7e7e76f6f6f6f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a36f6f6fc0c0c0c0c0c06f6f6fa3a3a3e7e7e7c0c0c0c0c0c0c0c0c0c0c0c06f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a36f6f6f6f6f6fa3a3a3e7e7e76f6f6f6f6f6f6f6f6f6f6f6f6f6f6f
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3e7e7e76f6f6fa3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3e7e7e7c0c0c0c0c0c06f6f6fa3a3a3a3a3a3
+a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3
+e7e7e7c0c0c0000000
+000000c0c0c06f6f6fe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0c0c0c06f6f6fe7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7c0c0c0000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000
+
+showpage
+
+% stop using temporary dictionary
+end
+
+% restore original state
+origstate restore
+
+%%Trailer
diff --git a/docs/main_window_5.gif b/docs/main_window_5.gif
new file mode 100644
index 0000000..5d51dc6
Binary files /dev/null and b/docs/main_window_5.gif differ
diff --git a/docs/main_window_5.png b/docs/main_window_5.png
new file mode 100644
index 0000000..ccc46a6
Binary files /dev/null and b/docs/main_window_5.png differ
diff --git a/docs/main_window_6.eps b/docs/main_window_6.eps
new file mode 100644
index 0000000..14ecb8b
--- /dev/null
+++ b/docs/main_window_6.eps
@@ -0,0 +1,8138 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: /home/kmr/powmap/docs/main_window_6.ps
+%%Creator: XV Version 3.10a Rev: 12/29/94 (PNG patch 1.2) - by John Bradley
+%%BoundingBox: 36 325 313 411
+%%Pages: 1
+%%DocumentFonts:
+%%EndComments
+%%EndProlog
+
+%%Page: 1 1
+
+% remember original state
+/origstate save def
+
+% build a temporary dictionary
+20 dict begin
+
+% define string to hold a scanline's worth of data
+/pix 1665 string def
+
+% define space for color conversions
+/grays 555 string def % space for gray scale line
+/npixls 0 def
+/rgbindx 0 def
+
+% lower left corner
+36 325 translate
+
+% size of image (on paper, in 1/72inch coords)
+277.48800 85.53600 scale
+
+% define 'colorimage' if it isn't defined
+% ('colortogray' and 'mergeprocs' come from xwd2ps
+% via xgrab)
+/colorimage where % do we know about 'colorimage'?
+ { pop } % yes: pop off the 'dict' returned
+ { % no: define one
+ /colortogray { % define an RGB->I function
+ /rgbdata exch store % call input 'rgbdata'
+ rgbdata length 3 idiv
+ /npixls exch store
+ /rgbindx 0 store
+ 0 1 npixls 1 sub {
+ grays exch
+ rgbdata rgbindx get 20 mul % Red
+ rgbdata rgbindx 1 add get 32 mul % Green
+ rgbdata rgbindx 2 add get 12 mul % Blue
+ add add 64 idiv % I = .5G + .31R + .18B
+ put
+ /rgbindx rgbindx 3 add store
+ } for
+ grays 0 npixls getinterval
+ } bind def
+
+ % Utility procedure for colorimage operator.
+ % This procedure takes two procedures off the
+ % stack and merges them into a single procedure.
+
+ /mergeprocs { % def
+ dup length
+ 3 -1 roll
+ dup
+ length
+ dup
+ 5 1 roll
+ 3 -1 roll
+ add
+ array cvx
+ dup
+ 3 -1 roll
+ 0 exch
+ putinterval
+ dup
+ 4 2 roll
+ putinterval
+ } bind def
+
+ /colorimage { % def
+ pop pop % remove 'false 3' operands
+ {colortogray} mergeprocs
+ image
+ } bind def
+ } ifelse % end of 'false' case
+
+
+
+555 171 8 % dimensions of data
+[555 0 0 -171 0 171] % mapping matrix
+{currentfile pix readhexstring pop}
+false 3 colorimage
+
+000000010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101010101010101010101010101010101010101010101010101010101
+010101010101010101
+000000c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+c0c0c0c0c0c0000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
+7f7f7fe7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0bdbdbdbdbdbdc0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000e7e7e7000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000000000
+000000000000000000000000000000e7e7e7000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7000000000000
+000000000000000000000000000000e7e7e7000000000000000000000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e7000000000000000000
+e7e7e7000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0e7e7e7e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000e7e7e7
+e7e7e7000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000
+000000e7e7e7000000000000000000000000e7e7e7000000000000000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7000000000000e7e7e7000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000e7e7e7000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0bdbdbde7e7e7e7e7e77f7f7fc0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000
+000000e7e7e7000000000000000000000000e7e7e7000000000000000000e7e7e7000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000e7e7e7000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000
+000000e7e7e7000000000000000000000000e7e7e7000000000000000000e7e7e7000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7000000e7e7e7000000000000000000000000000000000000
+000000e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7
+000000000000000000e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000000000
+e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0bdbdbde7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000
+000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000e7e7e7000000000000000000000000000000e7e7e7000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7000000000000000000000000e7e7e7000000000000000000e7e7e7000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7000000000000000000000000000000e7e7e7000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0bdbdbde7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000000000e7e7e7000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000e7e7e7000000000000000000e7e7e7000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7000000000000000000000000e7e7e7000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000e7e7e7000000000000000000000000e7e7e7
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000e7e7e7000000000000000000e7e7e7000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000000000e7e7e7e7e7e7000000
+000000000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000000000
+000000e7e7e7000000000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000
+000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+bdbdbde7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7000000
+000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000e7e7e7
+e7e7e7000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000e7e7e7e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7fc0c0c0bdbdbd
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000e7e7e7
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000e7e7e7000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7fc0c0c0e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7fbdbdbd7f7f7f
+7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7e7e7e7000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000000000e7e7e77f7f7fbdbdbdbdbdbd
+bdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbd
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000000000000000e7e7e7000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7000000000000000000000000e7e7e7e7e7e7000000000000
+000000000000000000000000000000000000e7e7e7000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7000000e7e7e7
+000000000000000000000000000000e7e7e7000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e7000000000000e7e7e7
+000000000000000000000000000000e7e7e7000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7000000000000000000000000000000e7e7e7000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000e7e7e7000000000000000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7000000000000000000e7e7e7000000e7e7e7000000000000
+000000000000000000000000000000000000e7e7e7000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7000000000000000000e7e7e7000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000e7e7e7
+000000000000000000000000000000e7e7e7000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7
+000000000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000e7e7e7000000e7e7e7
+000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000e7e7e7
+000000000000000000000000000000e7e7e7000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7
+000000000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000e7e7e7000000e7e7e7
+000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000e7e7e7000000000000000000000000e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000
+000000000000e7e7e7000000e7e7e7000000000000e7e7e7000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7
+000000000000000000000000e7e7e7000000000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000e7e7e7000000
+000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7000000000000000000e7e7e7
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000
+000000000000e7e7e7e7e7e7000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000e7e7e7000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000000000e7e7e7000000000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000e7e7e7000000
+000000e7e7e7000000000000000000e7e7e7000000000000000000000000e7e7e7000000
+000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000
+000000000000e7e7e7000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000e7e7e7000000000000000000000000e7e7e7000000
+000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000
+000000000000e7e7e7000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000000000000000e7e7e7e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000000000000000000000e7e7e7000000000000e7e7e7000000000000000000000000
+e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000000000000000000000000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000000000000000000000000000e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7000000000000000000000000
+000000e7e7e7000000000000000000e7e7e7000000000000000000000000e7e7e7000000
+000000000000e7e7e7000000000000000000000000e7e7e7000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000000000000000000000000000e7e7e7
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000000000e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7000000000000e7e7e7
+000000000000000000000000000000e7e7e7000000000000000000e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000e7e7e7000000000000
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000e7e7e7000000000000000000000000e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000
+000000000000e7e7e7000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7000000000000000000e7e7e7000000000000000000000000e7e7e7000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7000000000000000000000000000000000000000000000000e7e7e7000000
+000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000e7e7e7e7e7e7000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000e7e7e7
+e7e7e7000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000e7e7e7000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000e7e7e7
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000e7e7e7000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00
+ffff00ffff00ffff00ffff00ffff00e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fbdbdbd7f7f7f
+7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000000000e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000000000e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000000000e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7000000000000000000
+e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000000000e7e7e7000000000000
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e70000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff
+0000ff0000ff0000ff0000ff0000ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+000000000000e7e7e7000000000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7000000000000e7e7e7000000000000000000
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000
+000000000000e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e798fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb9898fb98
+98fb9898fb9898fb9898fb9898fb98e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+000000000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000000000000000e7e7e7000000000000e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000000000e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7000000000000000000e7e7e7000000000000000000e7e7e7e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7000000000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900ff9900
+ff9900ff9900ff9900ff9900ff9900e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7000000000000000000000000e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000ff0000
+ff0000ff0000ff0000ff0000ff0000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+000000000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000000000e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000000000e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000000000e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000000000
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000000000e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7000000000000000000
+e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000000000e7e7e7000000000000
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000000000
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000000000e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7000000000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000000000e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7000000e7e7e7000000000000000000e7e7e7000000000000e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7000000000000000000000000
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fbdbdbdbdbdbd
+bdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbd
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fbdbdbde7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7f
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000000000000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000000000e7e7e7000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7000000000000e7e7e77f7f7fc0c0c0bdbdbd
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7000000000000e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+bdbdbde7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000000000000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000000000000000000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000000000
+000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0bdbdbde7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7
+000000000000e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7000000000000000000
+e7e7e7000000000000e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7000000000000e7e7e7000000000000
+e7e7e7e7e7e7000000000000000000000000e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7000000000000000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000000000000000e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0bdbdbde7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0e7e7e7e7e7e7e7e7e7e7e7e7c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0bdbdbdbdbdbd7f7f7f7f7f7fc0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e787cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa87cefa
+87cefa87cefa87cefa87cefa87cefae7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000
+000000000000000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fc0c0c0c0c0c0
+c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e77f7f7fbdbdbdbdbdbd
+bdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbd
+bdbdbde7e7e7000000
+000000e7e7e7ff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ffff00ff
+ff00ffff00ffff00ffff00ffff00ffe7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000
+000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000000000000000
+000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000000000e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7e7
+e7e7e7e7e7e7000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000
+000000000000000000
+
+showpage
+
+% stop using temporary dictionary
+end
+
+% restore original state
+origstate restore
+
+%%Trailer
diff --git a/docs/main_window_6.gif b/docs/main_window_6.gif
new file mode 100644
index 0000000..2d06a72
Binary files /dev/null and b/docs/main_window_6.gif differ
diff --git a/docs/main_window_6.png b/docs/main_window_6.png
new file mode 100644
index 0000000..c91a0b3
Binary files /dev/null and b/docs/main_window_6.png differ
diff --git a/docs/main_window_chapter.sgml b/docs/main_window_chapter.sgml
new file mode 100644
index 0000000..b670839
--- /dev/null
+++ b/docs/main_window_chapter.sgml
@@ -0,0 +1,269 @@
+<CHAPTER ID="MAINWINDOW-CHAPTER">
+ <TITLE>The &prog; Main Window</TITLE>
+
+ <SECT1 ID="MAINWINDOW-OVERVIEW">
+ <TITLE>Overview of the Entry Edit Window</TITLE>
+ <PARA>
+This window is the main editing and viewing window. See <XREF
+LINKEND="LAUNCH-WINDOW-FILE-MENU-OPEN"> or <XREF
+LINKEND="LAUNCH-WINDOW-FILE-MENU-READ-FROM-EBI"> to find out how to read an
+entry (and hence open one of these windows).
+ </PARA>
+
+ <PARA>
+The following images show a breakdown of the main &prog; edit window. The full
+screen shot <ULINK
+URL="user_manual_screen_shot.gif"
+TYPE="external">is here</ULINK>.
+ </PARA>
+
+ <SECT2 ID="MAINWINDOW-BREAKDOWN">
+ <TITLE>A breakdown of the main &prog; edit window</TITLE>
+
+ <ORDEREDLIST ID="MAINWINDOW-BREAKDOWN-LIST">
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-MENU">
+ <PARA>
+ <SCREENSHOT>
+ <SCREENINFO>
+The menu bar of the main &prog; window
+ </SCREENINFO>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="png" FILEREF="main_window_1.png">
+ </IMAGEOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="main_window_1.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-SELECTIONSTATUS">
+ <PARA>
+ <SCREENSHOT>
+ <SCREENINFO>
+A one line summary of the what is currently selected
+ </SCREENINFO>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="png" FILEREF="main_window_2.png">
+ </IMAGEOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="main_window_2.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-ENTRY-BUTTONS">
+ <PARA>
+ <SCREENSHOT>
+ <SCREENINFO>
+The entry buttons from the main &prog; window
+ </SCREENINFO>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="png" FILEREF="main_window_3.png">
+ </IMAGEOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="main_window_3.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-OVERVIEW">
+ <PARA>
+ <SCREENSHOT>
+ <SCREENINFO>
+The sequence overview
+ </SCREENINFO>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="png" FILEREF="main_window_4.png">
+ </IMAGEOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="main_window_4.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-DNAVIEW">
+ <PARA>
+ <SCREENSHOT>
+ <SCREENINFO>
+The DNA view
+ </SCREENINFO>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="png" FILEREF="main_window_5.png">
+ </IMAGEOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="main_window_5.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM ID="MAINWINDOW-BREAKDOWN-FEATURELIST">
+ <PARA>
+ <SCREENSHOT>
+ <SCREENINFO>
+The feature list
+ </SCREENINFO>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="png" FILEREF="main_window_6.png">
+ </IMAGEOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="main_window_6.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </PARA>
+ </LISTITEM>
+ </ORDEREDLIST>
+ <PARA>
+Key:
+ </PARA>
+ <ORDEREDLIST ID="MAINWINDOW-INFO">
+ <LISTITEM ID="MAINWINDOW-INFO-MENU">
+ <PARA>
+The menus for the main window (described later in this chapter).
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="MAINWINDOW-INFO-SELECTIONSTATUS">
+ <PARA>
+A one line summary of the current selection (see <XREF LINKEND="SELECTMENU">
+and <XREF LINKEND="CONCEPTS-SELECTION"> for more).
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="MAINWINDOW-INFO-ENTRYBUTTONS">
+ <PARA>
+This line contains one button for each entry that has been loaded. These
+buttons allow the user to set the default entry (see <XREF
+LINKEND="CONCEPTS-DEFAULTENTRY">) and to set the active entries
+(see <XREF LINKEND="CONCEPTS-ACTIVEENTRY">).
+For more detail on operating the buttons see <XREF
+LINKEND="ENTRYBUTTONS">.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="MAINWINDOW-INFO-OVERVIEW">
+ <PARA>
+This shows an overview of the sequence and the features from the active
+entries. (see <XREF LINKEND="VIEWS">).
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="MAINWINDOW-INFO-DNAVIEW">
+ <PARA>
+This is called the "DNA view" to distinguish it from the overview, but in fact
+it operates in a very similar way. (see <XREF LINKEND="VIEWS">).
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="MAINWINDOW-INFO-FEATURELIST">
+ <PARA>
+A textual summary of the active features. (See <XREF
+LINKEND="FEATURELIST">).
+ </PARA>
+ </LISTITEM>
+ </ORDEREDLIST>
+ </SECT2>
+ </SECT1>
+
+ &filemenu;
+ &menus;
+ &displaymenu;
+
+ <SECT1 ID="ENTRYBUTTONS">
+ <TITLE>The Entry Button Line</TITLE>
+ <PARA>
+There is one button on this line for each currently loaded entry. The buttons
+indicate which entries are active and allow the user to toggle each entry
+between the active and inactive states. The default entry can be set by
+clicking the right mouse button (see <XREF LINKEND="MOUSEBUTTONS">) on an
+entry.
+ </PARA>
+ <PARA>
+See also <XREF LINKEND="MAINWINDOW-BREAKDOWN-ENTRY-BUTTONS">, <XREF
+LINKEND="CONCEPTS-DEFAULTENTRY"> and <XREF LINKEND="CONCEPTS-ACTIVEENTRY">).
+ </PARA>
+ </SECT1>
+
+ <SECT1 ID="VIEWS">
+ <TITLE>The Overview and DNA Views</TITLE>
+ <PARA>
+The overview and DNA view take up most of the main window (see <XREF
+LINKEND="MAINWINDOW-BREAKDOWN">). Both views act in a very similar way, so
+they are described in the same section. In particular they both show the
+forward and reverse strands of the sequence and a representation of the three
+translation frames in each direction. The forward sequence is read from
+the EMBL entry, the reverse sequence is derived from the forward one by
+complementing it. In the overview shows only the stop codons of each
+translation frame, but in the DNA view the shows the complete six frame
+translation.
+ </PARA>
+ <PARA>
+The horizontal scrollbar controls which part of the sequence is
+currently visible. The scroll bar at the left controls the zoom level.
+ </PARA>
+
+&views-selection;
+
+ <SECT2 ID="VIEWS-OTHERMOUSE">
+ <TITLE>Other Mouse Controlled Functions</TITLE>
+ <PARA>
+Double clicking on a feature with the first mouse button causes both views and
+the feature list to centre themselves on that feature. Similarly, double
+clicking the first mouse button on a base or amino acid will centre both views
+on that base/amino acid.
+ </PARA>
+ <PARA>
+A double click of the middle mouse button on a feature will open an edit
+window for that feature. This is the same as clicking once and then choosing
+the Edit Selected Features menu item (see <XREF
+LINKEND="EDITMENU-EDIT-SELECTED-FEATURES">).
+ </PARA>
+ </SECT2>
+
+&views-popup;
+&views-scrolling;
+&views-scale;
+&views-directedit;
+ </SECT1>
+
+&featurelist;
+
+ <SECT1 ID="GRAPHS">
+ <TITLE>Graphs and Plots</TITLE>
+ <PARA>
+The DNA plots are accessed from the Graph menu (see <XREF
+LINKEND="GRAPHMENU">). When a graph is turned on it is locked to the
+overview window, so it will follow the overview window when the overview is
+scrolled. It will also automatically change the viewing scale to match the
+scale of the overview window. The vertical scrollbar to the right of the
+graph controls the window size of algorithm.
+ </PARA>
+
+ <PARA>
+The feature plots window (see <XREF LINKEND="VIEWMENU-SHOW-FEATURE-PLOTS">)
+has a horizontal scroll bar that sets the position of the plot in the feature.
+As with the DNA plots, the vertical scrollbar to the right of the graph
+controls the window size of algorithm.
+ </PARA>
+
+ <PARA>
+Note that clicking the left mouse button on the plot will show the base or
+residue at that position. Clicking the right mouse button will pop-up a menu.
+The menu contains a toggle which controls whether the graph is scaled and some
+menu items which allow the maximum window size to be set.
+ </PARA>
+ </SECT1>
+
+&mousebuttons;
+</CHAPTER>
diff --git a/docs/manual.sgml b/docs/manual.sgml
new file mode 100644
index 0000000..3ce768f
--- /dev/null
+++ b/docs/manual.sgml
@@ -0,0 +1,91 @@
+<!DOCTYPE Book PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
+<!ENTITY intro SYSTEM "intro_chapter.sgml">
+<!ENTITY start SYSTEM "start_chapter.sgml">
+<!ENTITY mainwindow SYSTEM "main_window_chapter.sgml">
+<!ENTITY ssh SYSTEM "ssh_chapter.sgml">
+<!ENTITY proj SYSTEM "project_manager.sgml">
+<!ENTITY options SYSTEM "options.sgml">
+<!ENTITY filemenu SYSTEM "file_menu.sgml">
+<!ENTITY menus SYSTEM "menus.sgml">
+<!ENTITY runmenu SYSTEM "run_menu.sgml">
+<!ENTITY displaymenu SYSTEM "display_menu.sgml">
+<!ENTITY requirements SYSTEM "requirements.sgml">
+<!ENTITY acknowledgments SYSTEM "acknowledgments.sgml">
+<!ENTITY copyright SYSTEM "copyright.sgml">
+<!ENTITY concepts SYSTEM "concepts.sgml">
+<!ENTITY filetypes SYSTEM "filetypes.sgml">
+<!ENTITY views-popup SYSTEM "views_popup.sgml">
+<!ENTITY views-scrolling SYSTEM "views_scrolling.sgml">
+<!ENTITY views-scale SYSTEM "views_scale.sgml">
+<!ENTITY views-selection SYSTEM "views_selection.sgml">
+<!ENTITY views-directedit SYSTEM "views_directedit.sgml">
+<!ENTITY featurelist SYSTEM "feature_list.sgml">
+<!ENTITY mousebuttons SYSTEM "mousebuttons.sgml">
+<!ENTITY gettingjava SYSTEM "getting_java.sgml">
+<!ENTITY unixargs SYSTEM "unix_args.sgml">
+<!ENTITY jvmopts SYSTEM "jvm_options.sgml">
+<!ENTITY options-menu SYSTEM "options_menu.sgml">
+<!ENTITY nextgen SYSTEM "next_gen.sgml">
+
+<!ENTITY % artemis-only "INCLUDE">
+<!ENTITY % act-only "IGNORE">
+
+<!ENTITY prog "Artemis">
+<!ENTITY art "Artemis">
+
+<!ENTITY command-name "art">
+
+<!ENTITY lt CDATA "<">
+<!ENTITY gt CDATA ">">
+]>
+<!-- see: http://www.pcmedia.co.nz/~michaelh/intro-to-sgml/html/c04.htm-->
+<!-- and: http://www.pcmedia.co.nz/~michaelh/intro-to-sgml/html/template.sgml-->
+<!-- $Header: //tmp/pathsoft/artemis/docs/manual.sgml,v 1.8 2008-01-15 16:36:19 tjc Exp $ -->
+<BOOK ID="index">
+ <TITLE> The &art; Manual </TITLE>
+ <BOOKINFO>
+ <PRODUCTNAME>
+ <EMPHASIS>&art;</EMPHASIS> - a genome annotation tool
+ </PRODUCTNAME>
+ <PUBDATE>22 February 1999</PUBDATE>
+ <COPYRIGHT>
+ <YEAR>1999-2014</YEAR>
+ <HOLDER>Genome Research Limited</HOLDER>
+ </COPYRIGHT>
+ <LEGALNOTICE>
+ <PARA>&prog; 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 2 of the License, or (at your option) any later
+ version.</PARA>
+
+ <PARA>This program 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 <ULINK URL="http://www.gnu.org/copyleft/gpl.html"
+ TYPE="external">GNU General Public License</ULINK> for more
+ details.</PARA>
+
+ <PARA>You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ MA 02111-1307 USA</PARA>
+
+ <PARA>For the full text of the license see <XREF LINKEND="COPYRIGHT">.
+ </PARA>
+ </LEGALNOTICE>
+ <ABSTRACT>
+ <PARA>
+This document describes release 16 of &art; a DNA sequence viewer and
+sequence annotation tool.
+ </PARA>
+ </ABSTRACT>
+ </BOOKINFO>
+ <TOC></TOC>
+ &intro;
+ &start;
+ &mainwindow;
+ &proj;
+ &ssh;
+ &options;
+</BOOK>
diff --git a/docs/menus.sgml b/docs/menus.sgml
new file mode 100644
index 0000000..204540f
--- /dev/null
+++ b/docs/menus.sgml
@@ -0,0 +1,1894 @@
+<SECT1 ID="ENTRIESMENU">
+ <TITLE>The Entries Menu</TITLE>
+ <PARA>
+The items in this menu are used to change which entry is the default entry and
+which entries are active (see <XREF LINKEND="CONCEPTS-ENTRY">). At the
+bottom of the menu there is a toggle button for each entry which controls
+whether the entry is active or not.<![ %artemis-only; [ These toggle buttons
+work in a similar way the the buttons on the entry button line (see <XREF
+LINKEND="ENTRYBUTTONS">).]]>
+ </PARA>
+ <PARA>
+Here is a description of the other menu items:
+ </PARA>
+
+ <SECT2 ID="ENTRIESMENU-SET-NAME-OF-ENTRY">
+ <TITLE>Set Name Of Entry</TITLE>
+ <PARA>
+Set the name of an entry chosen from a sub-menu. The name of the entry is
+used as the name of the file when the entry is saved.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="ENTRIESMENU-SET-DEFAULT-ENTRY">
+ <TITLE>Set Default Entry</TITLE>
+ <PARA>
+Set the default entry by choosing one of the entries from the sub-menu. (See
+<XREF LINKEND="CONCEPTS-DEFAULTENTRY">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="ENTRIESMENU-REMOVE-AN-ENTRY">
+ <TITLE>Remove An Entry</TITLE>
+ <PARA>
+Remove an entry from &prog; by choosing one of the entries from the sub-menu.
+The original file that this entry came from (if any) will not be removed.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="ENTRIESMENU-REMOVE-ACTIVE-ENTRIES">
+ <TITLE>Remove Active Entries</TITLE>
+ <PARA>
+Remove the entries that are currently active. (See <XREF
+LINKEND="CONCEPTS-ACTIVEENTRY">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="ENTRIESMENU-DEACTIVATE-ALL">
+ <TITLE>Deactivate All Entries</TITLE>
+ <PARA>
+Choosing this menu item will deactivate all entries. (See <XREF
+LINKEND="CONCEPTS-ACTIVEENTRY">.)
+ </PARA>
+ </SECT2>
+</SECT1>
+
+<SECT1 ID="SELECTMENU">
+ <TITLE>The Select Menu</TITLE>
+ <PARA>
+The items in this menu are used to modify the current selection (see <XREF
+LINKEND="CONCEPTS-SELECTION">). &art;<![ %artemis-only; [ shows a short summary of
+the current selection at the top of the main window (see <XREF
+LINKEND="MAINWINDOW-BREAKDOWN-SELECTIONSTATUS"> for details).]]>.
+ </PARA>
+
+ <SECT2 ID="SELECTMENU-FEATURE-SELECTOR">
+ <TITLE>Feature Selector ...</TITLE>
+ <PARA>
+Open a new Feature Selector window. This window allows the user to choose
+which features to select or view based on feature keys (see <XREF
+LINKEND="CONCEPTS-KEY">), qualifier values (see <XREF
+LINKEND="CONCEPTS-QUALIFIERS">) and amino acid motifs.
+ </PARA>
+ <PARA>
+The Select button will set the selection to the contain those features that
+match the given key, qualifier and amino acid motif combination.
+ </PARA>
+ <PARA>
+The View button will create a new feature list (see <XREF
+LINKEND="FEATURELIST">) containing only those features that
+match the given key, qualifier and amino acid motif combination.
+ </PARA>
+ <SCREENSHOT>
+ <SCREENINFO>
+The Selector window
+ </SCREENINFO>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="selector.gif">
+ </IMAGEOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="selector.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-ALL">
+ <TITLE>All</TITLE>
+ <PARA>
+Reset the selection so that nothing is selected then select all the features
+in the active entries. [shortcut key: A]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-ALLBASES">
+ <TITLE>All Bases</TITLE>
+ <PARA>
+Reset the selection so that nothing is selected then select all the bases in
+the sequence.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-ALLFEATURES-NONMATCHING-REGIONS">
+ <TITLE>Select All Features in Non-matching Regions</TITLE>
+ <PARA>
+Select all features that have no corresponding match in ACT. This is used to
+higlight regions that are different between sets of sequence. It will only take
+into account matches that have not been filtered out using the score, identity
+or length cut-off.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-NONE">
+ <TITLE>None</TITLE>
+ <PARA>
+Clear the selection so that nothing is selected. [shortcut key: N]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-BY-KEY">
+ <TITLE>By Key</TITLE>
+ <PARA>
+Ask the user for a feature key, reset the selection so that nothing is
+selected, then select all the features with the key given by the user.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-CDS-FEATURES">
+ <TITLE>CDS Features</TITLE>
+ <PARA>
+Reset the selection so that nothing is selected, then select all the CDS
+features that do not have a /pseudo qualifier.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-SAME-KEY">
+ <TITLE>Same Key</TITLE>
+ <PARA>
+Select all the features that have the same key as any of the currently
+selected features.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-ORF">
+ <TITLE>Open Reading Frame</TITLE>
+ <PARA>
+Extend the current selection of bases to cover complete open reading frames.
+Selecting a single base or codon and then choosing this menu item has a
+similar effect to double clicking the middle button on a base or residue (see
+<XREF LINKEND="VIEWS-SELECTION"> for details).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-OVERLAPPING-SELECTION">
+ <TITLE>Features Overlapping Selection</TITLE>
+ <PARA>
+Select those (and only those) features that overlap the currently selected
+range of bases or any of the currently selected features. The current
+selection will be discarded.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-BASE-RANGE">
+ <TITLE>Base Range ...</TITLE>
+ <PARA>
+Ask the user for a range of bases, then select those bases. The range should
+look something like this: <LITERAL>100-200</LITERAL>,
+<LITERAL>complement(100..200)</LITERAL>, <LITERAL>100.200</LITERAL> or
+<LITERAL>100..200</LITERAL>. If the first number is larger than the
+second the bases will be selected on the forward strand, otherwise they will
+be selected on the reverse strand (unless there is a
+<LITERAL>complement</LITERAL> around the range, in which case the sense is
+reversed).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-FEATURE-AA-RANGE">
+ <TITLE>Feature AA Range ...</TITLE>
+ <PARA>
+Ask the user for a range of amino acids in the selected feature and select
+those bases. The range should look something like this:
+<LITERAL>100-200</LITERAL>, or <LITERAL>100..200</LITERAL>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="SELECTMENU-TOGGLE-SELECTION">
+ <TITLE>Toggle Selection</TITLE>
+ <PARA>
+Invert the selection - after choosing this menu item the selection will
+contain only those features that were not in the selection beforehand.
+ </PARA>
+ </SECT2>
+</SECT1>
+
+<SECT1 ID="VIEWMENU">
+ <TITLE>The View Menu</TITLE>
+ <PARA>
+
+ </PARA>
+
+ <SECT2 ID="VIEWMENU-VIEW-SELECTED-FEATURES">
+ <TITLE>Selected Features</TITLE>
+ <PARA>
+Open a view window for each selected feature showing it's feature table entry.
+[shortcut key: V]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-VIEW-SELECTION">
+ <TITLE>Selection</TITLE>
+ <PARA>
+Open a view window that will show the current selection. The window is
+updated as the selection changes, so it can be left open.
+ </PARA>
+ <PARA>
+When one feature is selected the window will show the text (EMBL, GenBank or
+GFF format) of the feature, the base composition, GC percentage, correlation
+score (see <XREF LINKEND="GRAPHMENU-CORRELATION-SCORES">), and the bases and
+translation of the sequence of the feature.
+ </PARA>
+ <PARA>
+When two or more features are selected the window will show the text (EMBL,
+GenBank or GFF format) of the features, the base composition, average GC
+percentage, average correlation score, minimum/maximum GC content and
+minimum/maximum correlation score of the feature sequence.
+ </PARA>
+ <PARA>
+When a range of bases is selected the window will show the base composition,
+GC content percentage and the bases and translation of the sequence of the
+feature.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-SEARCH-RESULTS">
+ <TITLE>Search Results</TITLE>
+ <PARA>
+On this sub-menu allows the user to view the results of feature searches that
+are launched from the run menu in &art;<![ %artemis-only; [ (see <XREF
+LINKEND="RUNMENU">)]]>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-SHOW-CDS-GENES-PRODUCTS">
+ <TITLE>CDS Genes And Products</TITLE>
+ <PARA>
+Pop up a feature list (see <XREF LINKEND="FEATURELIST">) of the CDS showing
+the gene names (from the /gene qualifier) and
+the product (from the /product qualifier). This list includes pseudo genes.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-FEATURE-FILTERS">
+ <TITLE>Feature Filters</TITLE>
+ <PARA>
+Each of the items in this sub-menu each allow the user to view a subset of the
+active features. An example of a subset is all those features with
+<LITERAL>misc_feature</LITERAL> as a key. The features are displayed in a new
+window that contains a menu bar with possible actions to apply to the subset,
+and feature list (see <XREF LINKEND="FEATURELIST">). Most of the possible
+actions will apply only to the features in the list. For example "Show
+Overview" in the View menu (see <XREF LINKEND="VIEWMENU-SHOW-OVERVIEW">) will
+include statistics only on the features in the list.
+ </PARA>
+
+ <SECT3 ID="VIEWMENU-FEATURE-FILTERS-SUSPSTART">
+ <TITLE>Suspicious Start Codons ...</TITLE>
+ <PARA>
+Show those CDS features that have a suspicious start codon. ie. the first
+codon of the feature isn't ATG (in eukaroytic mode) or ATG, GTG and TTG (in
+prokaryotic mode). This function is effected by the setting of the
+"Eukaroytic Mode" option in the main options menu (see <XREF
+LINKEND="LAUNCH-WINDOW-OPTIONS-EUK"> for more).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="VIEWMENU-FEATURE-FILTERS-SUSPSTOP">
+ <TITLE>Suspicious Stop Codons ...</TITLE>
+ <PARA>
+Show those CDS features that have a suspicious stop codon. ie. the last codon
+of the feature isn't one of TAA, TAG or TGA.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="VIEWMENU-FEATURE-FILTERS-NONEMBLKEYS">
+ <TITLE>Non EMBL Keys ...</TITLE>
+ <PARA>
+Show those features that have a key that isn't valid for EMBL/GenBank
+entries.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="VIEWMENU-FEATURE-FILTERS-">
+ <TITLE>Duplicated Features ...</TITLE>
+ <PARA>
+Show those features that are duplicated (ie. features that have the same key
+and location as another feature). These sort of duplicates aren't allowed by
+the EMBL database.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="VIEWMENU-FEATURE-FILTERS-OVERLAPPING">
+ <TITLE>Overlapping CDS Features ...</TITLE>
+ <PARA>
+Show those CDS features that overlap another CDS feature (on either strand).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="VIEWMENU-FEATURE-FILTERS-REQQUAL">
+ <TITLE>Features Missing Required Qualifiers ...</TITLE>
+ <PARA>
+Show those features that are missing a qualifier that is required by the EMBL
+database.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="VIEWMENU-FEATURE-FILTERS-BYKEY">
+ <TITLE>Filter By Key ...</TITLE>
+ <PARA>
+Show those features that have a key chosen by the user.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="VIEWMENU-FEATURE-FILTERS-SELECTED">
+ <TITLE>Selected Features ...</TITLE>
+ <PARA>
+Show the currently selected features in a new feature list. The contents of
+the list will remain the same even if selection subsequently changes. This is
+useful for bookmarking a collection of features for later use.
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-SHOW-OVERVIEW">
+ <TITLE>Overview</TITLE>
+ <PARA>
+Open a new window the will show a summary of the active entries and some
+statistics about the sequence (such as the GC content).
+[shortcut key: O]
+ </PARA>
+
+ <SECT3 ID="VIEWMENU-SHOW-OVERVIEW-SEQSTATS">
+ <TITLE>Sequence Statistics</TITLE>
+ <PARA>
+The overview window show the following statistics about the sequence:
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <PARA>
+Number of bases.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+GC percentage.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+The number of each nucleotide in the sequence.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+GC percentage of non-ambiguous bases - ie. the GC content percentage ignoring
+bases other than A,T,C and G. This should be the same as the "GC percentage"
+above.
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="VIEWMENU-SHOW-OVERVIEW-FEATURESTATS">
+ <TITLE>Feature Statistics</TITLE>
+ <PARA>
+The overview window also shows the following statistics about the features in
+the active entries (if there are any features). Note that the "genes" are the
+non-pseudo CDS features.
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <PARA>
+Number of features in the active entries (see <XREF
+LINKEND="CONCEPTS-ACTIVEENTRY">).
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Gene density - the average number of non-pseudo CDS features per 1000 bases.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Average gene length - the average length of non-pseudo CDS features (not
+including introns).
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Number of non-spliced genes.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Number of spliced genes.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Number of pseudo genes (ie. CDS features with a /pseudo qualifier).
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Protein coding (CDS) features.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Protein coding (CDS) bases.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Protein coding percentage - ie. the number of coding bases excluding introns.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Coding percentage (including introns).
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+A summary of the number of features of each key (type) and their colours.
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-SHOW-FORWARD-OVERVIEW">
+ <TITLE>Forward Strand Overview</TITLE>
+ <PARA>
+Open a new window the will show a summary of the features and bases of the
+forward strand.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-SHOW-REVERSE-OVERVIEW">
+ <TITLE>Reverse Strand Overview</TITLE>
+ <PARA>
+Open a new window the will show a summary of the features and bases of the
+reverse strand.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-VIEW-FEATURE-BASES">
+ <TITLE>Feature Bases</TITLE>
+ <PARA>
+Create a view window for each selected feature, which shows bases of the
+feature.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-VIEW-FEATURE-BASES-FASTA">
+ <TITLE>Feature Bases As FASTA</TITLE>
+ <PARA>
+Create a view window for each selected feature, which shows bases of the
+feature in FASTA format.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-VIEW-FEATURE-AMINO-ACIDS">
+ <TITLE>Feature Amino Acids</TITLE>
+ <PARA>
+Create a view window for each selected feature, which shows amino acids of the
+feature.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-VIEW-FEATURE-AMINO-ACIDS-FASTA">
+ <TITLE>Feature Amino Acids As FASTA</TITLE>
+ <PARA>
+Create a view window for each selected feature, which shows amino acids of the
+feature in FASTA format.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-SHOW-FEATURE-STATISTICS">
+ <TITLE>Feature Statistics</TITLE>
+ <PARA>
+Show some statistics about each selected feature. On the left on the feature
+information window is the amino acid composition of the feature. On the right
+is the codon composition of the feature.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="VIEWMENU-SHOW-FEATURE-PLOTS">
+ <TITLE>Feature Plots</TITLE>
+ <PARA>
+Open a window for each selected feature that shows a plot of the
+Kyte-Doolittle Hydrophobicity [short name: <LITERAL>hydrophobicity</LITERAL>],
+the Hopp-Woods Hydrophilicity [short name: <LITERAL>hydrophilicity</LITERAL>],
+and an approximation of the GCG Coiled Coils algorithm [short name:
+<LITERAL>coiled_coil</LITERAL>]. (For more detail about the coiled coils
+algorithm see "Predicting Coiled Coils from Protein Sequences", Science
+Vol. 252 page 1162.) [shortcut key: W]
+ </PARA>
+ <PARA>
+Some general information about graphs and plots in &prog; can be found in <XREF
+LINKEND="GRAPHS">. Configuration options for graphs are described in <XREF
+LINKEND="OPTIONS-PLOTS">.
+ </PARA>
+ </SECT2>
+</SECT1>
+
+<SECT1 ID="GOTOMENU">
+ <TITLE>The Goto Menu</TITLE>
+ <PARA>
+The items in this menu allow the user to navigate around the sequence and
+features.
+ </PARA>
+
+ <SECT2 ID="GOTOMENU-NAVIGATOR">
+ <TITLE>Navigator ...</TITLE>
+ <PARA>
+Open a new navigation window. [shortcut key: G]
+ </PARA>
+ <PARA>
+This window allows the user to perform five
+different tasks:
+ <ORDEREDLIST>
+ <LISTITEM ID="GOTOMENU-NAVIGATOR-GOTO-BASE">
+ <FORMALPARA>
+ <TITLE>
+Scroll all the views so that a particular base is in the centre of
+the display
+ </TITLE>
+ <PARA>
+To use this function, type a base position into the
+box to the right of the "Goto Base:" label then press the goto button at the
+bottom of the window. The requested base will be selected and then the
+overview display and the DNA display will scroll so that the base is as near
+as possible to the middle of the main window.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ <LISTITEM ID="GOTOMENU-NAVIGATOR-GOTO-GENENAME">
+ <FORMALPARA>
+ <TITLE>
+Find the next feature that has the given gene name
+ </TITLE>
+ <PARA>
+To use this function, type a gene name into the box to the right of the "Goto
+Feature With This Gene Name:" label and then press the goto button.
+&prog; will select the first feature with the given text in any of it's
+qualifiers and will then scroll the display so that feature is in view.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ <LISTITEM ID="GOTOMENU-NAVIGATOR-GOTO-TEXT">
+ <FORMALPARA>
+ <TITLE>
+Find the next feature that has a qualifier containing a particular string
+ </TITLE>
+ <PARA>
+To use this function, type a string into the box to the right of the "Goto
+Feature With This Qualifier Value:" label and then press the goto button.
+&prog; will select the first feature with the given string in any of it's
+qualifier values (see <XREF LINKEND="CONCEPTS-QUALIFIERS">) and will then
+scroll the display so that feature is in view.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ <LISTITEM ID="GOTOMENU-NAVIGATOR-GOTO-KEY">
+ <FORMALPARA>
+ <TITLE>
+Find the next feature that has a particular key
+ </TITLE>
+ <PARA>
+To use this function, type a key into the box to the right of the "Goto
+Feature With This Key:" label and then press the goto button. &prog; will
+select the first feature with the given key and will then scroll the display
+so that feature is in view.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ <LISTITEM ID="GOTOMENU-NAVIGATOR-GOTO-DNA-PATTERN">
+ <FORMALPARA>
+ <TITLE>
+Find the next occurrence of a particular base pattern in the sequence
+ </TITLE>
+ <PARA>
+To use this function, type a base pattern into the box to the right of the
+"Find Base Pattern:" label and then press the goto button. &prog; will select
+the first contiguous group of bases on either strand that match the given base
+pattern and will then scroll the display so that those bases are in view.
+Any IUB base code can be used in the pattern, so for example searching for
+<LITERAL>aanntt</LITERAL> will match any six bases that start with "aa" and
+ends with "tt". See <XREF LINKEND="IUB-BASE-CODES"> for a list of the
+available base codes.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ <LISTITEM ID="GOTOMENU-NAVIGATOR-GOTO-AA-PATTERN">
+ <FORMALPARA>
+ <TITLE>
+Find the next occurrence of a particular residue pattern in the sequence
+ </TITLE>
+ <PARA>
+To use this function, type a amino acid pattern into the
+box to the right of the "Goto Amino Acid String:" label and then press the
+goto button. &prog; will select the first contiguous group of bases on either
+strand that translate to the given amino acids and will then scroll the
+display so that those bases are in view. The letter 'X' can be used as an
+ambiguity code, hence 'AAXXXDD' will match 'AALRTDD' or 'AATTTDD' etc.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ </ORDEREDLIST>
+ </PARA>
+ <PARA>
+Note that for all the functions above except the first ("Goto Base"), if the
+"Start search at beginning" option is set or if there is nothing selected the
+search will start at the beginning of the sequence. Otherwise the search will
+start at the selected base or feature. This means that the user can step
+through the matching bases or features by pressing the goto button repeatedly.
+ </PARA>
+ <PARA>
+If the "Ignore Case" toggle is on (which is the default) Artemis will
+ignore the difference between upper and lower case letters when searching for
+a gene name, a qualifier value or a feature key.
+ </PARA>
+ <PARA>
+The "Allow Substring Matches" toggle affects <XREF
+LINKEND="GOTOMENU-NAVIGATOR-GOTO-GENENAME"> and <XREF
+LINKEND="GOTOMENU-NAVIGATOR-GOTO-TEXT">. If on &prog; will search for
+qualifier values that contain the given characters. For example searching for
+the genename CDC will find CDC1, CDC2, ABCDC etc. If the toggle is off &prog;
+will only find exact matches, so searching for the gene CDC will only find
+features that have <LITERAL>/gene="CDC"</LITERAL> not
+<LITERAL>/gene="CDC11"</LITERAL>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GOTOMENU-GOTO-SELECTION-START">
+ <TITLE>Start of Selection</TITLE>
+ <PARA>
+Scroll all the views so that the first base of the selection is as close to
+the centre as possible. If the a range of bases is selected the views will
+move to the first base of the range. If one or more features are selected,
+then the first base of the first selected feature will be centred. Otherwise,
+if one or more segments (see <XREF LINKEND="CONCEPTS-SEGMENT">) is selected
+then the first base of the first selected segment will be centred.
+[shortcut key: control-left]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GOTOMENU-GOTO-SELECTION-END">
+ <TITLE>End of Selection</TITLE>
+ <PARA>
+This does the same as "Goto Start of Selection", but uses the last base of the
+selected range or the last base of the last selected feature or segment.
+[shortcut key: control-right]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GOTOMENU-GOTO-FEATURE-START">
+ <TITLE>Feature Start</TITLE>
+ <PARA>
+Scroll the views to the start of the first selected feature.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GOTOMENU-GOTO-FEATURE-END">
+ <TITLE>Feature End</TITLE>
+ <PARA>
+Scroll the views to the end of the first selected feature.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GOTOMENU-GOTO-START-OF-SEQ">
+ <TITLE>Start of Sequence</TITLE>
+ <PARA>
+Scroll the views so that the start of the sequence is visible.
+[shortcut key: control-up]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GOTOMENU-GOTO-END-OF-SEQ">
+ <TITLE>End of Sequence</TITLE>
+ <PARA>
+Scroll the views so that the end of the sequence is visible.
+[shortcut key: control-down]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GOTOMENU-GOTO-FEATURE-BASE-POSITION">
+ <TITLE>Feature Base Position ...</TITLE>
+ <PARA>
+Ask the user for a base position within the first selected feature, then
+scroll the views so that that base position is centred.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GOTOMENU-GOTO-FEATURE-AMINO-ACID">
+ <TITLE>Feature Amino Acid ...</TITLE>
+ <PARA>
+Ask the user for an amino acid position within the first selected feature,
+then scroll the views so that that position is centred.
+ </PARA>
+ </SECT2>
+</SECT1>
+
+<SECT1 ID="EDITMENU">
+ <TITLE>The Edit Menu</TITLE>
+ <PARA>
+This menu contains most of the functions that change the entries. Note that
+the changes will not be saved back to the original files until one of the save
+functions in the File menu is used<![ %artemis-only; [
+(see <XREF LINKEND="FILEMENU-SAVE-AN-ENTRY">)]]>.
+ </PARA>
+
+ <SECT2 ID="EDITMENU-UNDO">
+ <TITLE>Undo</TITLE>
+ <PARA>
+This function will undo the last change that was made using the Edit or Create
+menus. Up to 20 changes can be undone. This menu item is only enabled when
+there is something to undo. This limit can be changed in the
+options file (see <XREF LINKEND="OPTIONS-UNDO-LEVELS">). [shortcut key: U]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-REDO">
+ <TITLE>Redo</TITLE>
+ <PARA>
+This function will redo the last undo operation.
+ </PARA>
+ </SECT2>
+
+
+ <SECT2 ID="EDITMENU-EDIT-SELECTED-FEATURES">
+ <TITLE>Selected Features in Editor</TITLE>
+ <PARA>
+Open an edit window for each selected feature. [shortcut key: E]
+ </PARA>
+ <SCREENSHOT>
+ <SCREENINFO>
+The feature edit window
+ </SCREENINFO>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="feature_edit.gif">
+ </IMAGEOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="eps" FILEREF="feature_edit.eps">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ <PARA>
+From the top down the edit window has these parts:
+ <ORDEREDLIST>
+ <LISTITEM>
+ <PARA>
+At the top left is a selector for choosing the key of the feature. This
+only contains a subset of the legal keys. The subset can be changed by
+changing the <LITERAL>common_keys</LITERAL> option in the options file (see
+<XREF LINKEND="OPTIONS-COMMONKEYS">).
+ </PARA>
+ <PARA>
+At the top right of the edit window is a selector for adding a qualifier. For
+example choosing <LITERAL>note</LITERAL> from the menu will insert
+<LITERAL>/note=""</LITERAL> into the qualifier edit area.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Just below the key and qualifier selector is the location entry field. &prog;
+understands most of the EMBL location syntax, including joins, complements,
+ranges with non-exact ends (eg. <LITERAL>(100.200)..>350</LITERAL>) and
+references to other entries
+(eg. <LITERAL>join(100..200,SPB23A1:100..200)</LITERAL>).
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+Below the location is a row of buttons:
+ </PARA>
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <PARA>
+The <LITERAL>Complement</LITERAL> button will complement the contents of the
+location field.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+The <LITERAL>Grab Range</LITERAL> button will grab the currently selected
+range into the location field.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+The <LITERAL>Remove Range</LITERAL> button will remove the selected bases from
+the location string. This is normally used to create an intron in a feature.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+Pressing the <LITERAL>Goto Feature</LITERAL> button has the same effect as the
+"Start of Selection" item in the "Goto" menu. (See <XREF
+LINKEND="GOTOMENU-GOTO-SELECTION-START"> for more).
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+The <LITERAL>Select Feature</LITERAL> button selects this feature (in the same
+way as clicking on the feature in one of the views).
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+The centre of the edit window contains the qualifier entry section. The
+qualifiers should be entered the in same way the appear in the feature table
+part of an EMBL entry, but without the leading <LITERAL>FT</LITERAL> and
+spaces.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+The bottom of the window contains three buttons. The <LITERAL>OK</LITERAL>
+button will update the feature with the changes that have been made by the
+user and will then close the edit window. The <LITERAL>Cancel</LITERAL>
+button will discard the changes and then close the window. The
+<LITERAL>Apply</LITERAL> will make the changes, but will not close the
+window. Before any changes are made the location and the qualifiers are
+checked for formatting errors. Any errors will brought to the attention of
+the user through the use of annoying pop-up boxes. No changes will be
+performed until there all errors have been fixed.
+ </PARA>
+ </LISTITEM>
+ </ORDEREDLIST>
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-SUBSEQUENCE">
+ <TITLE>Subsequence (and Features)</TITLE>
+ <PARA>
+Make a copy (in a new edit window) of the selected bases and the features in
+that range. Any features that overlap the end of the range will be truncated.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-FINDANDREPLACE">
+ <TITLE>Find Or Replace Qualifier Text</TITLE>
+ <PARA>
+ This opens a search window with options to find or replace qualifier
+ text. The search can be restricted to features with a given key and/or
+ it can be restricted to a given qualifier name.
+ </PARA>
+ <PARA>
+ Boolean operators (and/or) can be used in the search. Clicking on the
+ <LITERAL>Show Boolean Search Options</LITERAL> displays 4 options.
+
+ <ORDEREDLIST>
+ <LISTITEM>
+ <PARA>
+ The <LITERAL>Use boolean operators (and, or, & |)</LITERAL> means that
+ it uses any of these operators that are in the <LITERAL>Find</LITERAL> text field.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+ The <LITERAL>Match any string (i.e. x OR y)</LITERAL> means that the words in
+ the <LITERAL>Find</LITERAL> text field will be separated by an OR
+ condition. So that it finds those features with qulaifiers that contain any of the
+ words.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+ The <LITERAL>Match all string (i.e. x AND y)</LITERAL> means that the words in
+ the <LITERAL>Find</LITERAL> text field will be separated by an AND
+ condition. So that it finds those features with qulaifiers that contain all of the
+ words.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+ The <LITERAL>No boolean search</LITERAL> option is the default. This
+ means it searches for those features with qualifiers that contain the complete
+ text from the <LITERAL>Find</LITERAL> text field.
+ </PARA>
+ </LISTITEM>
+ </ORDEREDLIST>
+ </PARA>
+ <PARA>
+ In addition selecting the Duplicate Qualifiers tab provides options to
+ search for or delete duplicate qualifiers.
+ </PARA>
+ </SECT2>
+
+
+ <SECT2 ID="QUALIFIERS">
+ <TITLE>Qualifier(s) of Selected Feature</TITLE>
+ <SECT3 ID="EDITMENU-CHANGE-QUALIFIERS">
+ <TITLE>Change ...</TITLE>
+ <PARA>
+This function allows the user to add or change qualifiers on all the selected
+features in one operation. The main part of the window acts like the
+qualifier editing field of the feature edit window (see <XREF
+LINKEND="EDITMENU-EDIT-SELECTED-FEATURES">).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-REMOVE-QUALIFIERS">
+ <TITLE>Remove ...</TITLE>
+ <PARA>
+This function allows the user to remove all qualifiers with a particular name
+from all the selected features.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-CONVERT-QUALIFIERS">
+ <TITLE>Convert ...</TITLE>
+ <PARA>
+This function allows the user to convert all qualifiers of a particular type
+to another qualifier for all the selected features.
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-SELECTED-FEATURES">
+ <TITLE>Selected Feature(s)</TITLE>
+ <SECT3 ID="EDITMENU-DUPLICATE-SELECTED-FEATURES">
+ <TITLE>Duplicate</TITLE>
+ <PARA>
+Make a copy of each selected feature. Each new feature will be added just
+after the original in the same entry as the original. [shortcut key: D]
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-MERGE-SELECTED-FEATURES">
+ <TITLE>Merge</TITLE>
+ <PARA>
+Create a new feature that contains all the exons and qualifiers of the
+selected features. The selected features must all have the same key.
+[shortcut key: M]
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-UNMERGE-SELECTED-FEATURE">
+ <TITLE>Unmerge</TITLE>
+ <PARA>
+If the selection contains exactly two exons and those exons are adjacent in
+the same feature, split the feature into two pieces between the exons. The
+original feature is truncated and a new feature is created. The qualifiers of
+the old feature are copied to new feature.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-UNMERGE-ALL-SEGMENTS">
+ <TITLE>Unmerge All Segments</TITLE>
+ <PARA>
+All exons in a feature are unmerged.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-DELETE-SELECTED-FEATURES">
+ <TITLE>Delete</TITLE>
+ <PARA>
+Remove each selected feature from it's entry.
+[shortcut key: control-delete]
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="DELETE-SELECTED-EXONS">
+ <TITLE>Delete Exons</TITLE>
+ <PARA>
+Delete the selected exons. The last exon of a feature can't be deleted
+(delete the whole feature instead).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="DELETE-SELECTED-INTRONS">
+ <TITLE>Remove Introns</TITLE>
+ <PARA>
+Delete the selected introns.
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+
+ <SECT2 ID="EDITMENU-MOVE-SELECTED-FEATURES">
+ <TITLE>Move Selected Features To</TITLE>
+ <PARA>
+Move the selected features to another entry. Choose the destination entry
+from the sub-menu.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-COPY-SELECTED-FEATURES">
+ <TITLE>Copy Selected Features To</TITLE>
+ <PARA>
+Copy the selected features to another entry. Choose the destination entry
+from the sub-menu.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-TRIM">
+ <TITLE>Trim Selected Features</TITLE>
+ <SECT3 ID="EDITMENU-TRIM-SELECTED-FEATURES-TO-MET">
+ <TITLE>To Met</TITLE>
+ <PARA>
+For each of the selected features this function will attempt to move the start
+position to the first ATG in the feature if the feature does not already start
+on a ATG codon. If there is no ATG in the first thirty percent of the bases
+of the feature the start position will be unchanged. The search will stop at
+the end of the first segment of a multi-segment feature.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-TRIM-SELECTED-FEATURES-TO-ANY">
+ <TITLE>To Any</TITLE>
+ <PARA>
+This works in the same way as "Trim Selected Features To Met", but will
+attempt to move the start position of the feature to the first TTG, ATG or GTG
+in the feature if it does not already start on one of those codons. As above
+it will only search the first thirty percent of the feature bases and will
+only search the first segment of a multi-segment feature.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-TRIM-SELECTED-FEATURES-TO-NEXT-MET">
+ <TITLE>To Next Met</TITLE>
+ <PARA>
+For each of the selected features this function will attempt to move the start
+position to the next ATG in the feature (the first codon is skipped). If
+there is no ATG in the first thirty percent of the bases of the feature the
+start position will be unchanged. The search will stop at the end of the
+first segment of a multi-segment feature.
+[shortcut key: T]
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-TRIM-SELECTED-FEATURES-TO-NEXT-ANY">
+ <TITLE>To Next Any</TITLE>
+ <PARA>
+This works in the same way as "Trim Selected Features To Next Met", but will
+attempt to move the start position of the feature to the next TTG, ATG or GTG
+in the feature (the first codon is skipped). As above it will only search the
+first thirty percent of the feature bases and will only search the first
+segment of a multi-segment feature.
+[shortcut key: Y]
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-EXTEND">
+ <TITLE>Extend Selected Features</TITLE>
+ <SECT3 ID="EDITMENU-EXTEND-TO-PREVIOUS-STOP-CODON">
+ <TITLE>To Previous Stop Codon</TITLE>
+ <PARA>
+Extend each of the selected features which do not start on a stop codon so
+that each feature starts just after the previous stop codon in this reading
+frame.
+[shortcut key: Q]
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-EXTEND-TO-NEXT-STOP-CODON">
+ <TITLE>To Next Stop Codon</TITLE>
+ <PARA>
+Extend each of the selected features which do not end on a stop codon so that
+each feature ends just before the next stop codon in this reading frame.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-EXTEND-TO-NEXT-STOP-CODON-FIX">
+ <TITLE>To Next Stop Codon and Fix</TITLE>
+ <PARA>
+Same as above but in addition this fixes the stop codons.
+ </PARA>
+ </SECT3>
+
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-FIX-STOP-CODONS">
+ <TITLE>Fix Stop Codons</TITLE>
+ <PARA>
+Check and fix the stop codons to all the selected features. For each feature
+if the last codon is a stop codon, then all is well, nothing further is done
+to the feature. If the last codon is not a stop codon, but the very next
+codon is a stop codon, then the end of the feature is moved forward by three
+bases. If both the last codon and the very next codon after the feature are
+not stop codons, the feature is selected, an error message is displayed and
+processing stops immediately.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-AUTO-GENE-NAMES">
+ <TITLE>Automatically Create Gene Names</TITLE>
+ <PARA>
+Ask for a gene name prefix (using a text requester), and then give a unique
+gene name to each CDS feature in the active entries using that prefix. For
+example if there are four CDS features with locations:
+"<LITERAL>1..500</LITERAL>", "<LITERAL>complement(100..600)</LITERAL>",
+"<LITERAL>200..700</LITERAL>" and "<LITERAL>complement(300..800)</LITERAL>",
+entering <LITERAL>SPBC16A3</LITERAL> will give the four features these names:
+<LITERAL>SPBC16A3.01</LITERAL>, <LITERAL>SPBC16A3.02c</LITERAL>,
+<LITERAL>SPBC16A3.03</LITERAL> and <LITERAL>SPBC16A3.04c</LITERAL>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-FIX-GENE-NAMES">
+ <TITLE>Fix Gene Names</TITLE>
+ <PARA>
+For each selected CDS, add the gene name from the CDS to
+neighbouring/overlapping mRNA, intron, exon, gene, 5'UTR and 3'UTR features.
+Warn about inconsistencies such as overlapping CDSs.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-BASES">
+ <TITLE>Bases</TITLE>
+ <SECT3 ID="EDITMENU-REVERSE-AND-COMPLEMENT">
+ <TITLE>Reverse And Complement</TITLE>
+ <PARA>
+Reverse and complement the sequence and all the features in all the entries
+(active and inactive).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-REVERSE-AND-COMPLEMENT-CONTIG">
+ <TITLE>Reverse And Complement Selected Contig</TITLE>
+ <PARA>
+Reverse and complement the sequence and all the features in a selected
+contig feature. If this option is used in ACT then all the matches within the contig are
+also reversed. Any matches extending past the boundary of the contig are
+deleted. The changes to the comparison file can be saved by right clicking
+in the comparison window and selecting "Save Comparison File...". However,
+ideally the comparison between the two sequences should be recalculated.
+ </PARA>
+ </SECT3>
+
+
+ <SECT3 ID="EDITMENU-DELETE-SELECTED-BASES">
+ <TITLE>Delete Selected Bases</TITLE>
+ <PARA>
+Deletes the selected range of bases (if any) from both strands. The deletion
+will not proceed if the selected range overlaps any features.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-ADD-BASES-AT-SELECTION">
+ <TITLE>Add Bases At Selection</TITLE>
+ <PARA>
+Prompt the user for some bases to insert just before the selected bases. The
+operation will not proceed if there is no selected range, but bases can be
+inserted anywhere in the sequence, including inside a feature. The same
+bases, reversed and complemented, will be inserted at the corresponding place
+on the opposite strand.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-ADD-FROM-FILE">
+ <TITLE>Add Bases From File ...</TITLE>
+ <PARA>
+Prompt the user for the name of a file containing the bases to insert just
+before the selected bases.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="EDITMENU-REPLACE-BASES-AT-SELECTION">
+ <TITLE>Replace Bases At Selection</TITLE>
+ <PARA>
+Prompt the user for some bases to replace the selected bases.
+ </PARA>
+ </SECT3>
+
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-CONTIG-REORDERING">
+ <TITLE>Contig Reordering ...</TITLE>
+ <PARA>
+Opens a 'Contig Tool' displaying contigs, i.e. with feature keys 'fasta_record'
+or 'contig'. The former being created automatically for each sequence when a
+mutiple fasta sequence file is read in. The contigs in this tool can then individually
+be selected and dragged and dropped to another location. In this way the order
+of contigs and features within a contig can be changed.
+ </PARA>
+ <PARA>
+If this is used in ACT then the matches are also reordered with respect to the
+change in the sequence. If a match spans the boundary of a contig that is being
+moved then if possible it is split. In some situations where there is a match
+with 'indels' then this is not possible and the match is deleted. The changes to
+the comparison file can be saved by right clicking in the comparison window and
+selecting "Save Comparison File...". However, ideally the comparison between the
+two sequences should be recalculated.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="EDITMENU-EDIT-HEADER">
+ <TITLE>Header Of Default Entry</TITLE>
+ <PARA>
+Open a edit window containing the header of the default entry. Changes made
+in the edit window will be applied immediately to the entry provided there are
+no errors in the formatting of the header.
+ </PARA>
+ </SECT2>
+
+</SECT1>
+
+<SECT1 ID="CREATEMENU">
+ <TITLE>The Create Menu</TITLE>
+ <PARA>
+This menu contains functions for creating new features (see <XREF
+LINKEND="CONCEPTS-FEATURE">) or entries (see <XREF LINKEND="CONCEPTS-ENTRY">).
+ </PARA>
+
+ <SECT2 ID="CREATEMENU-NEW-FEATURE">
+ <TITLE>New Feature</TITLE>
+ <PARA>
+Create a new feature in the default entry with a key of "misc_feature" (see
+<XREF LINKEND="CONCEPTS-KEY">), a location of that spans the whole sequence
+and which has no qualifiers (see <XREF LINKEND="CONCEPTS-QUALIFIERS">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CREATEMENU-CREATE-FEATURE-FROM-BASE-RANGE">
+ <TITLE>Feature From Base Range</TITLE>
+ <PARA>
+Create a new feature in the default entry with a key of "misc_feature", no
+qualifiers and a location that exactly matches the selected range of bases.
+If no bases are selected an error will be reported.
+[shortcut key: C]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CREATEMENU-CREATE-INTERGENIC-FEATURES">
+ <TITLE>Intergenic Features</TITLE>
+ <PARA>
+Create new features between CDS features in the default entry all with the "misc_feature" key.
+ </PARA>
+ </SECT2>
+
+
+ <SECT2 ID="CREATEMENU-CREATE-FEATURES-FROM-NON-MATCHING-REGIONS">
+ <TITLE>Features From Non-matching Regions</TITLE>
+ <PARA>
+Create features in ACT spanning all the regions where a match is not to be
+found.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CREATEMENU-NEW-ENTRY">
+ <TITLE>New Entry</TITLE>
+ <PARA>
+Create a new entry with no name and no features. The new entry will become
+the default entry (see <XREF LINKEND="CONCEPTS-DEFAULTENTRY">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CREATEMENU-MARK-ORF">
+ <TITLE>Mark Open Reading Frames ...</TITLE>
+ <PARA>
+Create a feature for each "large" open reading frame in the sequence. The
+default minimum size of a "large" open reading frame can be changed by
+changing the <LITERAL>minimum_orf_size</LITERAL> option (see <XREF
+LINKEND="OPTIONS-MIN-ORF-SIZE">). If a codon usage file (see <XREF
+LINKEND="GRAPHMENU-USAGE-PLOTS">) has been read each new ORF will have a codon
+usage score added as a /score qualifier. The new features can then be
+filtered from the display (see "Set Score Cutoffs ..." in <XREF
+LINKEND="VIEWS-POPUPMENU">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CREATEMENU-MARK-EMPTY-ORFS">
+ <TITLE>Mark Empty ORFs ...</TITLE>
+ <PARA>
+Create a feature for each open reading frame that doesn't already contain a
+feature.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CREATEMENU-MARK-ORF-IN-RANGE">
+ <TITLE>Mark Open Reading Frames In Range ...</TITLE>
+ <PARA>
+Create a feature for each "large" open reading frame in a range of bases. A
+range must be selected before using this command.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CREATEMENU-MARK-FROM-PATTERN">
+ <TITLE>Mark From Pattern ...</TITLE>
+ <PARA>
+Open a text requester to ask the user for a base pattern, then create a
+feature for each group of bases that matches that pattern. A new entry will
+be created to hold the features with the name "matches: <pattern>",
+where <pattern> is the pattern that was entered be the user. Any IUB
+base code can be used in the pattern, so for example, aanntt will match any
+six bases that start with "aa" and ends with "tt".
+ </PARA>
+ <PARA>
+ <TABLE COLSEP="1" FRAME="all" ROWSEP="1" ID="IUB-BASE-CODES">
+ <TITLE>IUB Base Codes</TITLE>
+ <TGROUP COLS="3" CHAROFF="50">
+ <COLSPEC COLNUM="1" ALIGN="left">
+ <COLSPEC COLNUM="2" ALIGN="left">
+ <COLSPEC COLNUM="3" ALIGN="left">
+ <TBODY>
+ <ROW>
+ <ENTRY>R = A or G</ENTRY>
+ <ENTRY>S = G or C</ENTRY>
+ <ENTRY>B = C, G or T</ENTRY>
+ </ROW>
+ <ROW>
+ <ENTRY>Y = C or T</ENTRY>
+ <ENTRY>W = A or T</ENTRY>
+ <ENTRY>D = A, G or T</ENTRY>
+ </ROW>
+ <ROW>
+ <ENTRY>K = G or T</ENTRY>
+ <ENTRY>N = A, C, G or T</ENTRY>
+ <ENTRY>H = A, C or T</ENTRY>
+ </ROW>
+ <ROW>
+ <ENTRY>M = A or C</ENTRY>
+ <ENTRY></ENTRY>
+ <ENTRY>V = A, C or G</ENTRY>
+ </ROW>
+ </TBODY>
+ </TGROUP>
+ </TABLE>
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="CREATEMENU-MARK-AMBIGUITIES">
+ <TITLE>Mark Ambiguities</TITLE>
+ <PARA>
+Create a new feature for each block of ambiguous bases. The new features will
+have a key of <LITERAL>misc_feature</LITERAL> and will created in a new entry
+called "ambiguous bases".
+ </PARA>
+ </SECT2>
+</SECT1>
+
+<SECT1 ID="RUNMENU">
+ <TITLE>The Run Menu</TITLE>
+ <PARA>
+This menu is primarily used for running external programs on UNIX.
+Additionally there are menu options to send sequences from selected
+features to the <ULINK URL="http://blast.ncbi.nlm.nih.gov/Blast.cgi"
+TYPE="external">NCBI web BLAST</ULINK>, <ULINK URL="http://pfam.sanger.ac.uk/"
+TYPE="external">Pfam</ULINK> and <ULINK URL="http://rfam.sanger.ac.uk/"
+TYPE="external">Rfam</ULINK>.
+ </PARA>
+ <PARA>
+Once configured correctly, running an external
+program should be as simple as selecting some features of interest, then
+choosing one of the items from the run menu. When the external programs
+finishes the results can viewed using the "Search Results" item in the View
+menu (see <XREF LINKEND="VIEWMENU-SEARCH-RESULTS">).
+ </PARA>
+ <SECT2 ID="RUNMENU-CONFIGURATION">
+ <TITLE>Configuring the Run Menu</TITLE>
+ <PARA>
+To use this feature the <LITERAL>run_blastp</LITERAL>,
+<LITERAL>run_fasta</LITERAL> etc. scripts that are supplied
+with &prog; will need to be changed to reflect the paths and databases that
+are configured at each site. Note that the <LITERAL>run</LITERAL> scripts are
+stored in the <LITERAL>etc/</LITERAL> directory.
+ </PARA>
+ <PARA>
+Each external program that is listed in the options file (see <XREF
+LINKEND="OPTIONS-FEATURE-DNA-PROGRAMS"> and <XREF
+LINKEND="OPTIONS-FEATURE-PROTEIN-PROGRAMS">) gets a "run" menu item and a "set
+options" menu item. For each external program (such as blastp) there must be
+a shell script available that sets any necessary environment variables and
+then launches the search/analysis program (for blastp
+the script is called <LITERAL>run_blastp</LITERAL>).
+ </PARA>
+ <PARA>
+Taking blastp as an example, this is the sequence of events that occurs when
+the user selects the "Run blastp on selected features" menu item:
+ <ORDEREDLIST>
+ <LISTITEM>
+ <PARA>
+&prog; creates a new directory in the current directory called blastp.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+A protein FASTA sequence file is written in the new directory for each
+selected feature. (For a DNA search program such as blastn the file will be a
+DNA FASTA file). The sequence file name will be something like:
+<LITERAL>blastp/features.tab.seq.00001</LITERAL>.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+The name of the expected output file is stored in the feature in a qualifier
+called <LITERAL>/blastp_file</LITERAL>. If the entry is called
+<LITERAL>features.tab</LITERAL> then the qualifier will be set to something
+like: <LITERAL>/blastp_file="blastp/features.tab.seq.00001.out"</LITERAL>.
+Note that because the file name is stored in the entry you will need to save
+the entry to keep the association between the features and the output files.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+A file is then written (called something like
+<LITERAL>blastp/file_of_filenames.1</LITERAL>) that contains the names of all
+the newly created sequence files in the blastp directory.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+&prog then tries to read the <LITERAL>run_blastp</LITERAL> script from the
+&prog; installation directory. The script is executed like this:
+ </PARA>
+ <PARA>
+<LITERAL>run_blastp blastp/file_of_filenames.1 [options]</LITERAL>
+ </PARA>
+ <PARA>
+where <LITERAL>[options]</LITERAL> currently must be a single word (normally a
+database to search). In the case of blastp/blastn/fasta etc. the second
+argument of the script is passed directly to the blast/fasta as the database
+name. For testing purposes it is possible to run <LITERAL>run_blastp</LITERAL>
+on the command line with the same arguments as above.
+ </PARA>
+ <PARA>
+<LITERAL>run_blastp</LITERAL> will run blastp on each of the sequence files
+listed in <LITERAL>file_of_filenames.blastp</LITERAL> and save the output
+in the corresponding <LITERAL>.out</LITERAL> file.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+If the program is successfully started, control will immediately return to the
+user. When <LITERAL>run_blastp</LITERAL> finishes a message will be
+displayed to alert the user.
+ </PARA>
+ <PARA>
+If necessary, it is possible to exit once &prog; indicates that the external
+program has been started and the entry has been saved.
+<LITERAL>run_blastp</LITERAL> will keep running in the background.
+ </PARA>
+ </LISTITEM>
+ </ORDEREDLIST>
+ </PARA>
+ </SECT2>
+</SECT1>
+
+<SECT1 ID="GRAPHMENU">
+ <TITLE>The Graph Menu</TITLE>
+ <PARA>
+Some general information about the graphs can be found in <XREF
+LINKEND="GRAPHS">. Configuration options for graphs are described in <XREF
+LINKEND="OPTIONS-PLOTS">.
+ </PARA>
+ <SECT2 ID="GRAPHMENU-HIDE-ALL-GRAPHS">
+ <TITLE>Hide All Graphs</TITLE>
+ <PARA>
+This item will turn off all the visible graphs.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-USAGE-PLOTS">
+ <TITLE>Add Usage Plots ...</TITLE>
+ <PARA>
+This menu item prompts the user for the name of a file which should contain
+codon usage data in the same format as the data at <ULINK
+URL="http://www.kazusa.or.jp/codon/" TYPE="external">this web
+site</ULINK>. If &prog; successfully loads the codon usage file two new
+plots will be added to the display menu and will be immediately visible. One
+plot shows the codon scores (in a sliding window) for each of the forward
+reading frames and the other shows the same thing for the reverse reading
+frames. [short name: <LITERAL>codon_usage</LITERAL>]
+ </PARA>
+
+ <PARA>
+The graph is calculated using the codon preference statistic
+from <ULINK
+URL="http://www.ncbi.nlm.nih.gov:80/entrez/query.fcgi?cmd=Retrieve&db=PubMed&list_uids=6694906&dopt=Abstract">Gribskov
+et al. (Nucl. Acids Res. 12; 539-549 (1984))</ULINK>.
+ </PARA>
+
+ <PARA>
+Here is an example usage file:
+ <SYNOPSIS>
+UUU 32.2( 48423) UCU 30.5( 45913) UAU 21.8( 32829) UGU 8.9( 13371)
+UUC 13.0( 19519) UCC 12.1( 18149) UAC 11.8( 17721) UGC 5.6( 8372)
+UUA 26.0( 39138) UCA 17.9( 26850) UAA 1.3( 1944) UGA 0.5( 733)
+UUG 24.0( 36134) UCG 8.0( 12055) UAG 0.5( 705) UGG 10.9( 16364)
+
+CUU 25.3( 38015) CCU 21.9( 32964) CAU 16.3( 24577) CGU 16.3( 24495)
+CUC 7.3( 10922) CCC 8.4( 12619) CAC 6.4( 9653) CGC 6.2( 9316)
+CUA 8.6( 12957) CCA 12.7( 19075) CAA 27.3( 41066) CGA 7.9( 11896)
+CUG 6.3( 9503) CCG 4.6( 6910) CAG 10.9( 16457) CGG 3.0( 4487)
+
+AUU 35.0( 52636) ACU 22.9( 34419) AAU 33.9( 51009) AGU 14.7( 22108)
+AUC 12.6( 19000) ACC 10.9( 16378) AAC 17.9( 26895) AGC 9.2( 13905)
+AUA 13.1( 19726) ACA 13.9( 20898) AAA 39.3( 59079) AGA 11.1( 16742)
+AUG 20.9( 31376) ACG 6.5( 9744) AAG 25.2( 37825) AGG 5.1( 7615)
+
+GUU 29.3( 44015) GCU 30.2( 45397) GAU 38.1( 57240) GGU 22.0( 33101)
+GUC 11.0( 16497) GCC 11.6( 17518) GAC 15.8( 23749) GGC 8.5( 12717)
+GUA 12.3( 18451) GCA 15.7( 23649) GAA 44.3( 66550) GGA 15.7( 23623)
+GUG 8.3( 12422) GCG 5.3( 8011) GAG 21.3( 31979) GGG 4.3( 6497)
+ </SYNOPSIS>
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-ADD-USER-PLOT">
+ <TITLE>Add User Plot ...</TITLE>
+ <PARA>
+&prog; is able to display some types of user data in a graph that looks like
+the GC content graph (see <XREF LINKEND="GRAPHMENU-GC-CONTENT">). This menu
+item will prompt the user for the name of a data file which should contain one
+of the following possible graph file formats:
+
+</PARA>
+ <ORDEREDLIST ID="GRAPH-FORMAT">
+ <LISTITEM ID="GRAPH-FORMAT-1">
+ <PARA>
+ one line per base and one or more columns of integer or floating point
+ values per line. The number of lines should match the number of bases in
+ the sequence. &prog; will plot each data point for the corresponding base.
+ Each column represents a data set for a line. Example extract:
+
+<SYNOPSIS>
+21.4 1910.7
+44 1126.1
+1911.7 0
+0 0
+0 1782.0
+1937.5 65.4
+...
+</SYNOPSIS>
+
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="GRAPH-FORMAT-2">
+ <PARA>
+ the first column corresponds to the base position and is an integer.
+ Note to distinguish this format from the previous format the first
+ line of the file must start with a '#'. Line colours can be specified
+ in the header using the keyword colour followed by space separated
+ R:G:B values for each line. The next rows(s) are the data
+ values. Example extract:
+
+<SYNOPSIS>
+# BASE VAL1 VAL2 VAL3 VAL4 VAL5 VAL6
+# colour 5:150:55 255:0:0 0:255:0 0:0:255 100:100:100 50:150:50
+176 2204.8 848.23 0 0 0 536.04
+278 804.99 0 837.2 0 681.63 0
+452 0 699.98 0 0 0 251.18
+553 0 0 0 0 0 52.4
+654 0 0 0 0 334.2 0
+686 0 0 652.78 0 0 0
+831 0 0 0 0 0 67.97
+...
+</SYNOPSIS>
+
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM ID="GRAPH-FORMAT-3">
+ <PARA>
+ indexed tab delimited file. For this tabix is used (see the
+ <ULINK URL="http://samtools.sourceforge.net/tabix.shtml">tabix manual</ULINK>)
+ to create an index. This is especially useful for large data sets as it is memory
+ efficient and only reads the data corresponding to the visible region in &prog;.
+ The first columns contain the sequence name and base position and this is then followed by
+ the values to be plotted.
+ </PARA>
+ <PARA>
+ For example 'file.plot' is a tab delimited file with column 1 containing the sequence
+ name and column 2 the positions, this is sorted and then indexed with tabix:
+
+<SYNOPSIS>
+(grep ^"#" file.plot; grep -v ^"#" file.plot | sort -k1,1 -k2,2n) | bgzip > sorted.plot.gz ;
+tabix -s 1 -b 2 -e 2 sorted.plot.gz
+</SYNOPSIS>
+
+Example extract:
+<SYNOPSIS>
+foo 1 5 5 129 5 5 239
+foo 2 1 10 124 12 10 234
+foo 3 5 16 129 12 15 229
+foo 4 0 23 124 20 20 414
+foo 5 5 22 121 28 25 419
+foo 6 30 36 124 32 30 412
+...
+</SYNOPSIS>
+<![ %artemis-only; [
+Indexed user plots can be used with indexed FASTA sequences and indexed GFF files (see
+<XREF LINKEND="FILETYPES">). The sequence can then be changed using the drop down menu in
+the Entry toolbar and this will change the graph data to the selected sequence.]]>
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM ID="GRAPH-FORMAT-4">
+ <PARA>
+ the next two formats are types of <ULINK
+ URL="https://cgwb.nci.nih.gov/goldenPath/help/wiggle.html" TYPE="external">
+ Wiggle formats</ULINK>. The first is variableStep. Note that &prog; only supports
+ the colour element in the track line.
+ </PARA>
+<SYNOPSIS>
+track type=wiggle_0 color=255,200,0
+variableStep chrom=chr19 span=10
+310 1
+320 12
+330 18
+340 6
+350 5
+430 3
+440 1
+</SYNOPSIS>
+ <PARA>
+ Right clicking on the graph and selecting the
+ 'Configure...' option will display the 'Plot style' option for wiggle plots.
+ The plots can be displayed as histograms or as a heat map.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM ID="GRAPH-FORMAT-5">
+ <PARA>
+ the next format supported by &prog; is fixedStep and is again a Wiggle format.
+ </PARA>
+<SYNOPSIS>
+track type=wiggle_0 name="fixedStep" description="fixedStep format" visibility=full autoScale=off viewLimits=0:1000 color=0,200,100 maxHeightPixels=100:50:20 graphType=points priority=20
+fixedStep chrom=chr19 start=7401 step=300 span=200
+1000
+ 900
+ 800
+ 700
+ 600
+ 500
+ 400
+ 300
+ 200
+ 100
+</SYNOPSIS>
+ </LISTITEM>
+ <LISTITEM ID="GRAPH-FORMAT-6">
+ <PARA>
+ Blast tabular format. The blastall command must be run with the -m 8 flag which
+ generates one line of information per HSP. Alternatively the MSPcrunch file format
+ can be read in as a graph file format. &prog; will prompt the user to determine
+ whether it uses the query or subject coordinates to plot the graph.
+ </PARA>
+<SYNOPSIS>
+</SYNOPSIS>
+ </LISTITEM>
+
+ </ORDEREDLIST>
+
+<PARA>
+When a file is prompted for there is an option which if selected will mean
+the log transform is plotted.
+</PARA>
+
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-GC-CONTENT">
+ <TITLE>GC Content (%)</TITLE>
+ <PARA>
+Controls whether the GC content plot is visible. This is a graph of the
+average GC content of a moving window (default size 120 base), across the
+bases visible in the overview window.
+[Default: off] [short name: <LITERAL>gc_content</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-GCSD-CONTENT">
+ <TITLE>GC Content (%) With 2.5 SD Cutoff</TITLE>
+ <PARA>
+Controls whether the cutoff GC content plot is visible. This is similar to
+the GC content graph, but the plot is clipped so that the GC content of each
+algorithm window is shown only when it is more than 2.5 times the standard
+deviation of the GC content in all the windows.
+[Default: off] [short name: <LITERAL>sd_gc_content</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-AG-CONTENT">
+ <TITLE>AG Content (%)</TITLE>
+ <PARA>
+Controls whether the AG content plot is visible. This is a graph of the
+average AG content of a moving window (default size 120 base), across the
+bases visible in the overview window.
+[Default: off] [short name: <LITERAL>ag_content</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-GC-FRAME-PLOT">
+ <TITLE>GC Frame Plot</TITLE>
+ <PARA>
+Controls whether the GC frame plot is visible. This graph is similar to the
+GC content graph but shows the GC content of the first, second and third
+position independently. For more information on the algorithm and on how to
+interpret the result see <ULINK
+URL="http://www.nih.go.jp/~jun/cgi-bin/frameplot.pl" TYPE="external">this web
+page</ULINK>.
+ </PARA>
+ <PARA>
+See <ULINK
+URL="http://www.ncbi.nlm.nih.gov/htbin-post/Entrez/query?uid=10339816&form=6&db=m&Dopt=b">Ishikawa,
+J. and Hotta, K. FEMS Microbiol. Lett. 174:251-253 (1999)</ULINK> and <ULINK
+URL="http://www.sanger.ac.uk/Software/Artemis/gc_plot/GC_frame.html">GC frame plot</ULINK> for more
+information on the algorithm.
+ </PARA>
+ <PARA>
+[Default: off] [short name: <LITERAL>gc_frame</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-CORRELATION-SCORES">
+ <TITLE>Correlation Scores</TITLE>
+ <PARA>
+Controls whether the (forward) correlation scores plot is visible. The graph
+shows the correlation between the amino acid composition of the globular
+proteins in TREMBL and the composition of the base translation in each of the
+three reading frames. The green line represents forward frame 1, blue
+represents frame 2 and red represents frame 3.
+[Default: off] [short name: <LITERAL>correlation_score</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-REVERSE-CORRELATION-SCORES">
+ <TITLE>Reverse Correlation Scores</TITLE>
+ <PARA>
+This does the same as "Correlation Scores", but does the calculation on the
+reverse strand. The green line represents reverse frame 1 (the bottom frame
+line), blue represents frame 2 and red represents frame 3.
+[Default: off] [short name: <LITERAL>correlation_score</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-GC-DEVIATION">
+ <TITLE>GC Deviation (G-C)/(G+C)</TITLE>
+ <PARA>
+Controls whether the GC deviation plot is visible. This graph shows the
+difference between the "G" content of the forward strand and the reverse
+strand.
+ </PARA>
+ <PARA>
+See "Asymmetric substitution patterns in the two DNA strands of bacteria"
+Lobry JR. - <ULINK
+URL="http://www.ncbi.nlm.nih.gov:80/entrez/query.fcgi?cmd=Retrieve&db=PubMed&list_uids=8676740&dopt=Abstract">Mol
+Biol Evol 1996 May;13(5):660-5</ULINK>.
+ </PARA>
+ <PARA>
+[Default: off] [short name: <LITERAL>gc_deviation</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-AT-DEVIATION">
+ <TITLE>AT Deviation (A-T)/(A+T)</TITLE>
+ <PARA>
+Controls whether the AT deviation plot is visible. This graph shows the
+difference between the "A" content of the forward strand and the reverse
+strand.
+[Default: off] [short name: <LITERAL>at_deviation</LITERAL>]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="GRAPHMENU-KARLINSIG">
+ <TITLE>Karlin Signature Difference</TITLE>
+ <PARA>
+This menu item toggles the display of the graph of the dinucleotide absolute
+relative abundance difference between the whole sequence and a sliding window.
+ </PARA>
+ <PARA>
+For details of the algorithm see "Global dinucleotide signatures and analysis
+of genomic heterogeneity" Samuel Karlin - <ULINK
+URL="http://www.ncbi.nlm.nih.gov:80/entrez/query.fcgi?cmd=Retrieve&db=PubMed&list_uids=10066522&dopt=Abstract">Current
+Opinion in Microbiology 1998, 1:598-610</ULINK>.
+ </PARA>
+ <PARA>
+[Default: off] [short name: <LITERAL>karlin_sig</LITERAL>]
+ </PARA>
+ </SECT2>
+
+
+ <SECT2 ID="DISPLAYMENU-CUMULATIVEAT">
+ <TITLE>Cumulative AT Skew and Cumulative GC Skew</TITLE>
+ <PARA>
+ AT skew is calculated as ([A]-[T])/([A]+[T]), where [A] and [T] are the counts of
+ these bases in the window.
+ Grigoriev A (1999) <ULINK
+URL="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=pubmed&dopt=Abstract&list_uids=10225270">
+Strand-specific compositional asymmetries in double-stranded DNA viruses. Virus Research 60, 1-19</ULINK>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-POSITIONALASYM">
+ <TITLE>Positional Asymmetry</TITLE>
+ <PARA>
+ Shulman MJ, Steinberg CM, Westmoreland N (1981) <ULINK
+URL="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=pubmed&dopt=Abstract&list_uids=6456380">
+The coding function of nucleotide sequences can be discerned by statistical analysis. J Theor Biol
+88:409-20</ULINK>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-ENTROPY">
+ <TITLE>Informational Entropy</TITLE>
+ <PARA>
+ Konopka Andrzej (1984) <ULINK
+URL="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=pubmed&dopt=Abstract&list_uids=6738090">
+Is the information content of DNA evolutionarily significant? J Theor Biol 107:697-704</ULINK>.
+
+Informational entropy is calculated from a table of overlapping DNA
+triplet frequencies, using equation 1 in the above reference.
+The use of overlapping triplets smooths the frame effect.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-SCALEDCHISQ">
+ <TITLE>Scaled Chi Square</TITLE>
+ <PARA>
+ Shields DC, Sharp PM (1987) <ULINK
+URL="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=pubmed&dopt=Abstract&list_uids=3118331">
+Synonymous codon usage in Bacillus subtilis
+reflects both translational selection and mutational biases. Nucleic Acids
+Res 15:8023-40</ULINK>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-MUTRES">
+ <TITLE>Mutational Response Index</TITLE>
+ <PARA>
+ Gatherer D, McEwan NR (1997) <ULINK
+URL="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=pubmed&dopt=Abstract&list_uids=9315288">
+Small regions of preferential codon usage and
+their effect on overall codon bias--the case of the plp gene. Biochem Mol
+Biol Int 43:107-14</ULINK>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-NC">
+ <TITLE>Effective Codon Number</TITLE>
+ <PARA>
+ Wright F (1990) <ULINK
+URL="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=pubmed&dopt=Abstract&list_uids=2110097">
+The 'effective number of codons' used in a gene. Gene 87:23-9</ULINK>, and Fuglsang A (2004) <ULINK
+URL="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=pubmed&dopt=Abstract&list_uids=15081433">
+The 'effective number of codons' revisited. Biochem Biophys Res Commun. May 7;317(3):957-64</ULINK>.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="DISPLAYMENU-INTRINSIC">
+ <TITLE>Intrinsic Codon Deviation Index</TITLE>
+ <PARA>
+ Freire-Picos MA, Gonzalez-Siso MI, Rodriguez-Belmonte E, Rodriguez-Torres
+ AM, Ramil E, Cerdan ME (1994) <ULINK
+URL="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=pubmed&dopt=Abstract&list_uids=8112587">
+Codon usage in Kluyveromyces lactis and in yeast cytochrome c-encoding genes. Gene 139:43-9</ULINK>.
+ </PARA>
+ </SECT2>
+
+</SECT1>
diff --git a/docs/mousebuttons.sgml b/docs/mousebuttons.sgml
new file mode 100644
index 0000000..85d44b9
--- /dev/null
+++ b/docs/mousebuttons.sgml
@@ -0,0 +1,21 @@
+ <SECT1 ID="MOUSEBUTTONS">
+ <TITLE>Notes on Using The Mouse</TITLE>
+ <PARA>
+&prog; makes use of all three buttons of the mouse under X, but other windowing
+systems often have less than three buttons. To make life easier when running
+&prog; on those systems, there are some alternative key bindings.
+ </PARA>
+ <PARA>
+On Windows pressing the <LITERAL>alt</LITERAL> or <LITERAL>meta</LITERAL> key
+and the first mouse button will give the same effect as pressing mouse button
+three (usually a pop-up menu). Pressing the <LITERAL>control</LITERAL> key
+and the first mouse button will do the same thing as mouse button two.
+ </PARA>
+
+ <PARA>
+On Macintosh systems pressing the <LITERAL>control</LITERAL> key and the mouse
+button will give the same effect as pressing mouse button three (usually a
+pop-up menu). Pressing the <LITERAL>alt</LITERAL> key and the mouse button
+will do the same thing as mouse button two.
+ </PARA>
+ </SECT1>
diff --git a/docs/next_gen.sgml b/docs/next_gen.sgml
new file mode 100644
index 0000000..683c6d0
--- /dev/null
+++ b/docs/next_gen.sgml
@@ -0,0 +1,97 @@
+ <SECT2 ID="FILEMENU-READ-BAM">
+ <TITLE>Read BAM / VCF ...</TITLE>
+ <PARA>
+&prog; can read in and visualise BAM, VCF and BCF files. These files need
+to be indexed as described below. They require &prog; to be run with at least
+Java 1.6. Some examples can be found on the
+<ULINK URL="http://www.sanger.ac.uk/resources/software/artemis/ngs/">Artemis homepage</ULINK>.
+ </PARA>
+ <PARA>
+<ULINK URL="http://samtools.sourceforge.net/">BAM</ULINK> files need to be sorted and indexed using <ULINK
+URL="http://samtools.sourceforge.net/">SAMtools</ULINK>. The index file should be in
+the same directory as the BAM file. This provides an integrated
+<ULINK URL="http://bamview.sourceforge.net/">BamView</ULINK> panel in &prog;, displaying
+sequence alignment mappings to a reference sequence. Multiple BAM files can be loaded
+in from here either by selecting each file individually or by selecting a file of
+path names to the BAM files. The BAM files can be read from a local file system or remotely
+from an HTTP server.
+ </PARA>
+ <PARA>
+BamView will look to match the length of the sequence in &prog; with the reference sequence lengths
+in the BAM file header. It will display a warning when it opens if it finds a matching reference sequence
+(from these lengths) and changes to displaying the reads for that. The reference sequence for the mapped
+reads can be changed manually in the drop down list in the toolbar at the top of the BamView.
+ </PARA>
+ <PARA>
+In the case when the reference sequences are concatenated together into one (e.g. in a multiple
+FASTA sequence) and the sequence length matches the sum of sequence lengths given in the
+header of the BAM, &prog; will try to match the names (e.g. locus_tag or label) of the
+features (e.g. contig or chromosome) against the reference sequence names in the BAM. It will
+then adjust the read positions accordingly using the start position of the feature.
+ </PARA>
+ <PARA>
+When open the BamView can be configured via the popup menu which is activated by clicking on the
+BamView panel. The 'View' menu allows the reads to be displayed in a number of views: stack,
+strand-stack, paired-stack, inferred size and coverage.
+ </PARA>
+ <PARA>
+In &prog; the BamView display can be used to calculate the number of reads mapped to the
+regions covered by selected features. In addition the reads per kilobase per million mapped reads (RPKM)
+values for selected features can be calculated on the fly. Note this calculation can take
+a while to complete.
+ </PARA>
+ <PARA>
+Variant Call Format (<ULINK
+URL="http://1000genomes.org/wiki/doku.php?id=1000_genomes:analysis:vcf4.0">VCF</ULINK>)
+files can also be read. The VCF files need to be compressed and indexed using bgzip and
+tabix respectively (see the <ULINK URL="http://samtools.sourceforge.net/tabix.shtml">tabix manual</ULINK> and
+<ULINK URL="http://sourceforge.net/projects/samtools/files/">download page</ULINK>).
+The compressed file gets read in (e.g. file.vcf.gz) and below are the commands for
+generating this from a VCF file:
+ </PARA>
+ <SYNOPSIS>
+ bgzip file.vcf
+ tabix -p vcf file.vcf.gz
+ </SYNOPSIS>
+ <PARA>
+Alternatively a Binary VCF (<ULINK URL="http://samtools.sourceforge.net/mpileup.shtml">BCF</ULINK>) can
+be indexed with BCFtools and read into Artemis or ACT.
+ </PARA>
+ <PARA>
+As with reading in multiple BAM files, it is possible to read a number of (compressed and indexed)
+VCF files by listing their full paths in a single file. They then get displayed in separate rows
+in the VCF panel.
+ </PARA>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="png" FILEREF="vcf.png"></IMAGEOBJECT>
+ </MEDIAOBJECT>
+ <PARA>
+For single base changes the colour represents the base it is being changed to, i.e. T black,
+G blue, A green, C red. There are options available to filter the display by the different
+types of variants. Right clicking on the VCF panel will display a pop-up menu in which
+there is a 'Filter...' menu. This opens a window with check boxes for a number of varaint types and properties that can
+be used to filter on. This can be used to show and hide synonymous, non-synonymous, deletion (grey),
+insertion (magenta), and multiple allele (orange line with a circle at the top)
+variants. In this window there is a check box to hide the variants that do not overlap CDS features.
+There is an option to mark variants that introduce stop codons (into the CDS
+features) with a circle in the middle of the line that represents the variant. There are also options
+to filter the variants by various properties such as their quality score (QUAL) or their depth across the
+samples (DP).
+ </PARA>
+ <PARA>
+Placing the mouse over a vertical line shows an overview of the variation as a
+tooltip. Also right clicking over a line then gives an extra option in the pop-up
+menu to show the details for that variation in a separate window. There are also
+alternative colouring schemes. It is possible to colour the variants by whether they are
+synonymous or non-synonymous or by their quality score (the lower the quality the
+more faded the variant appears).
+ </PARA>
+ <PARA>
+There is an option to provide an overview of the variant types (e.g. synonymous, non-synonymous,
+insertion, deletion) for selected features. Also, filtered data can be exported in VCF format, or
+the reconstructed DNA sequences of variants can be exported in FASTA format for selected features or
+regions for further analyses. These sequences can be used as input for multiple sequence alignment tools.
+ </PARA>
+ </SECT2>
+
diff --git a/docs/options.sgml b/docs/options.sgml
new file mode 100644
index 0000000..fe4444d
--- /dev/null
+++ b/docs/options.sgml
@@ -0,0 +1,510 @@
+<CHAPTER ID="OPTIONS-CHAPT">
+ <TITLE>&prog; Configuration Options</TITLE>
+
+ <SECT1 ID="OPTIONS-INTRO">
+ <TITLE>The Options File</TITLE>
+ <PARA>
+When started on &prog; will potentially look in 5 different places for files
+to examine for configuration information. &prog; reads from the locations in
+order, so the user can override the default options. Note that on Macintosh
+and Windows systems the "current directory" mentioned below is the directory
+where the &prog; application has been installed.
+ </PARA>
+
+ <PARA>
+This is the search order:
+ <ITEMIZEDLIST>
+ <LISTITEM>
+ <PARA>
+The standard &prog; options are read first. On UNIX this file is in the file
+<LITERAL>etc/options</LITERAL> in the &prog; distribution directory. Changing
+that file will change the options settings for all users. On Macintosh and
+Windows system the standard options are stored in the &prog; application
+itself and can't be changed (easily).
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+If the user has specified an options file on the command line with the
+<LITERAL>-options</LITERAL> argument, it will be read next. (UNIX only - see
+<XREF LINKEND="UNIXARGS"> for more).
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+If there is a file called <LITERAL>diani.ini</LITERAL> in the current
+directory it will be read. This is for backwards compatibility.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+If there is a file called <LITERAL>options</LITERAL>,
+<LITERAL>options.txt</LITERAL> or <LITERAL>options.text</LITERAL> in the
+current directory it will be read.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+If there is a file in the user's home directory called
+<LITERAL>.artemis_options</LITERAL> it will be read.
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+ </PARA>
+ </SECT1>
+
+ <SECT1 ID="OPTIONS-DESCRIPTIONS">
+ <TITLE>&prog; Option Descriptions</TITLE>
+ <PARA>
+This section describes the possible options for &prog;.
+ </PARA>
+
+ <SECT2 ID="OPTIONS-FONTSIZE">
+ <TITLE><LITERAL>font_size</LITERAL></TITLE>
+ <PARA>
+This option will set the font size for all the &prog; windows. [default: 14]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-FONTNAME">
+ <TITLE><LITERAL>font_name</LITERAL></TITLE>
+ <PARA>
+This option is used to choose the font for all the &prog; windows. The font
+must be fixed-width. [default: "Monospaced"]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-BASE-PLOT-HEIGHT">
+ <TITLE><LITERAL>base_plot_height</LITERAL></TITLE>
+ <PARA>
+The height (in pixels) of each of the base plots. [default: 150]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-FEATURE-PLOT-HEIGHT">
+ <TITLE><LITERAL>feature_plot_height</LITERAL></TITLE>
+ <PARA>
+The height (in pixels) of each of the feature plots. [default: 160]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-DRAWFEATUREBORDERS">
+ <TITLE><LITERAL>draw_feature_borders</LITERAL></TITLE>
+ <PARA>
+If set to yes, borders will be drawn around each feature and each exon. if
+set to no, borders will only be drawn around the selected features. This can
+also be set in the views popup menu (see <XREF
+LINKEND="VIEWS-POPUPMENU-TOGGLES-FEATURE-BORDERS">). [default: "yes"]
+ <PARA>
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-SHOWFORWARDLINES">
+ <TITLE><LITERAL>show_forward_lines</LITERAL></TITLE>
+ <PARA>
+If set to yes, the forward frame lines are shown by default. This can
+also be set in the views popup menu. [default: "yes"]
+ <PARA>
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-SHOWREVERSELINES">
+ <TITLE><LITERAL>show_reverse_lines</LITERAL></TITLE>
+ <PARA>
+ If set to yes, the reverse frame lines are shown by default. This can
+ also be set in the views popup menu. [default: "yes"]
+ <PARA>
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-DRAWFEATUREARROWS">
+ <TITLE><LITERAL>draw_feature_arrows</LITERAL></TITLE>
+ <PARA>
+If set to yes, a direction arrow will be drawn around at the end of each
+feature. if set to no, no arrows will be drawn. This can also be set in the
+views popup menu (see <XREF LINKEND="VIEWS-POPUPMENU-TOGGLES-FEATURE-ARROWS">).
+[default: "yes"]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-OVERVIEW-FEATURE-LABELS">
+ <TITLE><LITERAL>overview_feature_labels</LITERAL></TITLE>
+ <PARA>
+If this option is no then the feature labels in the overview (see <XREF
+LINKEND="MAINWINDOW-OVERVIEW">) will be off at
+startup. [default: "yes"]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-OVERVIEW-ONE-LINE-PER-ENTRY">
+ <TITLE><LITERAL>overview_one_line_per_entry</LITERAL></TITLE>
+ <PARA>
+If this option is set yes then the overview (see <XREF
+LINKEND="MAINWINDOW-OVERVIEW">) will start in one line per entry mode.
+[default: "no"]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-SHOW-LIST">
+ <TITLE><LITERAL>show_list</LITERAL></TITLE>
+ <PARA>
+If set to yes then the feature list (see <XREF LINKEND="FEATURELIST">) will be
+shown on startup. [default: "yes"]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-SHOW-BASE-VIEW">
+ <TITLE><LITERAL>show_base_view</LITERAL></TITLE>
+ <PARA>
+If set to yes then the DNA base view (see <XREF LINKEND="VIEWS">) will be
+shown on startup. [default: "yes"]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-FEATURES-ON-FRAME-LINES">
+ <TITLE><LITERAL>features_on_frame_lines</LITERAL></TITLE>
+ <PARA>
+If set to yes then the "All Features On Frame Lines" option will be set to
+yes on startup. (See <XREF LINKEND="VIEWS-POPUPMENU-TOGGLES-FRAMEFEATURES">)
+[default: "no"]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-FRAME-LINE-FEATURES">
+ <TITLE><LITERAL>frame_line_features</LITERAL></TITLE>
+ <PARA>
+This is a list of the feature keys that should be shown by default on the
+frame lines. e.g.
+ <SYNOPSIS>
+frame_line_features = \
+ CDS \
+ polypeptide
+ </SYNOPSIS>
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-FEATURE-LABELS">
+ <TITLE><LITERAL>feature_labels</LITERAL></TITLE>
+ <PARA>
+If set to yes then the feature labels will be shown on startup.
+(See <XREF LINKEND="VIEWS-POPUPMENU-TOGGLES-FRAMEFEATURES">)
+[default: "yes"]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-ONE-LINE-PER-ENTRY">
+ <TITLE><LITERAL>one_line_per_entry</LITERAL></TITLE>
+ <PARA>
+If set to yes then the "One Line Per Entry" option will be set to
+yes on startup. (See <XREF LINKEND="VIEWS-POPUPMENU-TOGGLES-FRAMEFEATURES">)
+[default: "no"]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-GENETICCODES">
+ <TITLE><LITERAL>genetic_codes</LITERAL></TITLE>
+ <PARA>
+This option gives the list of names of the available genetic code tables.
+For each name in the list there is a translation_table_NUMBER entry (see
+below) where NUMBER is its location in the genetic_codes list. Similarly
+the start codons are defined as start_codons_NUMBER for each code.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-TRANSLATION-TABLE">
+ <TITLE><LITERAL>translation_table_1</LITERAL></TITLE>
+ <PARA>
+The translation_table option is used to lookup codon translations. The
+table must have exactly 64 entries, and there is one entry for each codon.
+ </PARA>
+ <PARA>
+The entries should appear in this order:
+ <SYNOPSIS>
+ TTT TTC TTA TTG
+ TCT TCC ...
+ ...
+ </SYNOPSIS>
+ </PARA>
+ <PARA>
+This is the default setting for this option is the Standard Code:
+ <SYNOPSIS>
+translation_table_1 = \
+ f f l l \
+ s s s s \
+ y y * * \
+ c c * w \
+ \
+ l l l l \
+ p p p p \
+ h h q q \
+ r r r r \
+ \
+ i i i m \
+ t t t t \
+ n n k k \
+ s s r r \
+ \
+ v v v v \
+ a a a a \
+ d d e e \
+ g g g g
+ </SYNOPSIS>
+ </PARA>
+ <PARA>
+The other translation table entries are defined by giving the differences
+to the Standard Code. These are given as the bases that make up the codon
+immediately followed by the translation (e.g. atam, so that 'ata' codes
+for 'm').
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-EXTRAKEYS">
+ <TITLE><LITERAL>extra_keys</LITERAL></TITLE>
+ <PARA>
+This contains a list of keys (separated by spaces) that are allowed in
+addition to those specified by EMBL. The official EMBL keys are listed in the
+<LITERAL>feature_keys</LITERAL> file in the &prog; code directory.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-EXTRAQUALIFIERS">
+ <TITLE><LITERAL>extra_qualifiers</LITERAL></TITLE>
+ <PARA>
+This contains a list of qualifiers (and their associated type) that are
+allowed in addition to those specified by EMBL. The official EMBL qualifiers
+and qualifier types are listed and described in the
+<LITERAL>qualifier_types</LITERAL> file in the &prog; code directory.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-COMMONKEYS">
+ <TITLE><LITERAL>common_keys</LITERAL></TITLE>
+ <PARA>
+This is a list of the keys that should be shown by default in the feature edit
+window. (see <XREF LINKEND="EDITMENU-EDIT-SELECTED-FEATURES">).
+ </PARA>
+ <PARA>
+The default setting for this option is:
+ <SYNOPSIS>
+common_keys = \
+ allele attenuator CDS conflict exon intron LTR misc_feature misc_RNA mRNA \
+ mutation polyA_signal polyA_site promoter protein_bind RBS repeat_region \
+ repeat_unit rRNA scRNA snRNA source stem_loop STS TATA_signal terminator \
+ tRNA unsure variation -10_signal -35_signal CDS_motif gene \
+ BLASTN_HIT CDS_BEFORE CDS_AFTER BLASTCDS
+ </SYNOPSIS>
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-HYPERLINKS">
+ <TITLE><LITERAL>hyperlinks</LITERAL></TITLE>
+ <PARA>
+This is a list of databases and their URL's for Artemis to provide
+hyperlinks in the Feature Editor (see <XREF LINKEND="EDITMENU-EDIT-SELECTED-FEATURES">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-UNDO-LEVELS">
+ <TITLE><LITERAL>undo_levels</LITERAL></TITLE>
+ <PARA>
+The number of levels of undo to save or 0 to disable undo. More undo
+levels will require more memory. [default: 20]
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-MIN-ORF-SIZE">
+ <TITLE><LITERAL>minimum_orf_size</LITERAL></TITLE>
+ <PARA>
+This option is used to set the minimum size (in amino acid residues) of a
+"large" open reading frame, which controls which ORFS are marked by the "Mark
+Open Reading Frames" menu item (see <XREF LINKEND="CREATEMENU-MARK-ORF">).
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-DIRECTEDIT">
+ <TITLE><LITERAL>direct_edit</LITERAL></TITLE>
+ <PARA>
+Set the default value for the direct edit option. A value of "yes" will turn
+direct edit on by default. See <XREF
+LINKEND="LAUNCH-WINDOW-OPTIONS-DIRECT-EDIT">
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-FEATURE-DNA-PROGRAMS">
+ <TITLE><LITERAL>feature_dna_programs</LITERAL></TITLE>
+ <PARA>
+This is a list of the possible external programs that can be run on the bases
+of a feature. Each pair in the list is a program name and a default database
+to use for that program. For each program name there must be a corresponding
+shell script called "run_something". eg. run_blastn. See <XREF
+LINKEND="RUNMENU-CONFIGURATION"> for more information.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-FEATURE-PROTEIN-PROGRAMS">
+ <TITLE><LITERAL>feature_protein_programs</LITERAL></TITLE>
+ <PARA>
+This is a list of the possible external programs that can be run on the
+translation of a feature. Each pair in the list is a program name and a
+default database to use for that program. For each program name there must be
+a corresponding shell script called "run_something". eg. run_blastp for
+blastp or run_fasta for fasta. See <XREF LINKEND="RUNMENU-CONFIGURATION"> for
+more information.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-NCBI-DNA-PROGRAMS">
+ <TITLE><LITERAL>ncbi_dna_search</LITERAL></TITLE>
+ <PARA>
+This lists the NCBI web-BLAST applications that the bases of a selected feature can
+be sent to. See <XREF LINKEND="RUNMENU">.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-NCBI-PROTEIN-PROGRAMS">
+ <TITLE><LITERAL>ncbi_protein_search</LITERAL></TITLE>
+ <PARA>
+This lists the NCBI web-BLAST applications that the translation of a selected feature can
+be sent to. See <XREF LINKEND="RUNMENU">.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-COLOUR">
+ <TITLE>Setting Colours</TITLE>
+ <PARA>
+The feature colours (see <XREF LINKEND="CONCEPTS-COLOUR">) used by &prog; can
+be changed using the options file. By default there are 18 possible colours
+(numbered 0 to 17), but any number can be used. The option names for the
+colours are <LITERAL>colour_0</LITERAL>, <LITERAL>colour_1</LITERAL>, etc.
+The value of each of these qualifiers should be the three numbers separated by
+spaces. The numbers correspond to red, green and blue respectively and each
+number is an intensity from 0 to 255. As an example, to change colour 2 to
+white put this line in the options file:
+ <SYNOPSIS>
+colour_2 = 255 255 255
+ </SYNOPSIS>
+Here is a list of the default colour numbers:
+ <SYNOPSIS>
+0 white (RGB values: 255 255 255)
+1 dark grey (RGB values: 100 100 100)
+2 red (RGB values: 255 0 0)
+3 green (RGB values: 0 255 0)
+4 blue (RGB values: 0 0 255)
+5 cyan (RGB values: 0 255 255)
+6 magenta (RGB values: 255 0 255)
+7 yellow (RGB values: 255 255 0)
+8 pale green (RGB values: 152 251 152)
+9 light sky blue (RGB values: 135 206 250)
+10 orange (RGB values: 255 165 0)
+11 brown (RGB values: 200 150 100)
+12 pale pink (RGB values: 255 200 200)
+13 light grey (RGB values: 170 170 170)
+14 black (RGB values: 0 0 0)
+15 mid red: (RGB values: 255 63 63)
+16 light red (RGB values: 255 127 127)
+17 pink (RGB values: 255 191 191)
+ </SYNOPSIS>
+ </PARA>
+ </SECT2>
+ <SECT2 ID="OPTIONS-PLOTS">
+ <TITLE>Options For Plots and Graphs</TITLE>
+ <PARA>
+Each graph type has three option settings associated with it: the default
+minimum window size, default maximum window size and the default window size.
+The option names have the following form:
+<LITERAL>[short_name]_default_min_window</LITERAL>,
+<LITERAL>[short_name]_default_max_window</LITERAL> and
+<LITERAL>[short_name]_default_window</LITERAL> (respectively).
+<LITERAL>[short_name]</LITERAL> should be replaced with the short name of the
+graph. (The available short names are documented in <XREF
+LINKEND="GRAPHMENU"> and
+<XREF LINKEND="VIEWMENU-SHOW-FEATURE-PLOTS">).
+ </PARA>
+ <PARA>
+As an example the short name of the GC content graph is gc_content so to set
+the initial minimum window size to 100, the maximum to 1000 and the initial
+window size to 150 use these setting:
+ <SYNOPSIS>
+gc_content_default_min_window = 100
+gc_content_default_max_window = 1000
+gc_content_default_window_size = 150
+ </SYNOPSIS>
+ </PARA>
+ <PARA>
+See also <XREF LINKEND="GRAPHS">.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="OPTIONS-CHADO">
+ <TITLE>Options For A Chado Database</TITLE>
+
+ <SECT3 ID="OPTIONS-CHADO-EXON-MODEL">
+ <TITLE><LITERAL>chado_exon_model</LITERAL></TITLE>
+ <PARA>
+ This is the feature key used when chado exons are joined and displayed in &prog;.
+ For example: <SYNOPSIS>chado_exon_model=CDS</SYNOPSIS>
+ [default: "exon-model"]
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="OPTIONS-CHADO-TRANSCRIPT">
+ <TITLE><LITERAL>chado_transcript</LITERAL></TITLE>
+ <PARA>
+ This is the feature key used when chado transcripts are created in a gene
+ model. [default: "mRNA"]
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="OPTIONS-CHADO-SERVERS">
+ <TITLE><LITERAL>chado_servers</LITERAL></TITLE>
+ <PARA>
+ This can be used to provide a list of available databases. For example:
+ <SYNOPSIS>chado_servers = \
+ test localhost:5432/test?userName \
+ genedb_ro db.genedb.org:5432/snapshot?genedb_ro
+ </SYNOPSIS>
+ </PARA>
+ </SECT3>
+
+ </SECT2>
+ </SECT1>
+
+ <SECT1 ID="OPTIONS-FILE">
+ <TITLE>Options File Format</TITLE>
+ <PARA>
+The option files it should contain settings that look like this:
+
+ <SYNOPSIS>
+option_name = option_value
+ </SYNOPSIS>
+
+If the value of an options is too long to fit on one line it can be split over
+several lines by ending each line with a backslash like this:
+
+ <SYNOPSIS>
+option_name = option_value another_option_value \
+ a_third_option_value a_forth_option_value
+ </SYNOPSIS>
+ </PARA>
+
+ <SECT2 ID="OPTIONS-EXAMPLE">
+ <TITLE>An Example Options File</TITLE>
+ <PARA>
+Here is an example options file:
+ </PARA>
+ <SYNOPSIS>
+# this line is a comment
+
+font_size = 14
+
+minimum_orf_size = 100
+
+common_keys = \
+ allele attenuator CDS conflict exon intron LTR misc_feature misc_RNA mRNA \
+ mutation polyA_signal polyA_site promoter protein_bind RBS repeat_region \
+ repeat_unit rRNA scRNA snRNA source stem_loop STS TATA_signal terminator \
+ tRNA unsure variation -10_signal -35_signal CDS_motif gene
+ </SYNOPSIS>
+ </SECT2>
+ </SECT1>
+</CHAPTER>
diff --git a/docs/options_menu.sgml b/docs/options_menu.sgml
new file mode 100644
index 0000000..9554554
--- /dev/null
+++ b/docs/options_menu.sgml
@@ -0,0 +1,84 @@
+<SECT2 ID="LAUNCH-WINDOW-OPTIONS">
+ <TITLE>The Options Menu</TITLE>
+ <SECT3 ID="LAUNCH-WINDOW-OPTIONS-REREAD">
+ <TITLE>Re-read Options</TITLE>
+ <PARA>
+Choosing this menu item will discard the current options settings and then
+re-read the options file. Note that changing the font size in the file and
+then selecting this menu item will only change the font size for new windows,
+not existing windows. Currently some options are unaffected by this menu
+item. See <XREF LINKEND="OPTIONS-FILE"> for more information about options.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-OPTIONS-DIRECT-EDIT">
+ <TITLE>Enable Direct Editing</TITLE>
+ <PARA>
+This menu item will toggle "direct editing" option. It is off be default
+because it can have surprising results unless the user is expecting it. See
+<XREF LINKEND="VIEWS-DIRECTEDIT"> for more detail about this.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-OPTIONS-EUK">
+ <TITLE>Genetic Code Tables</TITLE>
+ <PARA>
+These options make all the
+<ULINK
+URL="http://www.ncbi.nlm.nih.gov/Taxonomy/taxonomyhome.html/index.cgi?chapter=cgencodes">
+NCBI Genetic Codes</ULINK> available. The default setting is the
+Standard Code. This setting effects the display of start codons (see <XREF
+LINKEND="VIEWS-POPUPMENU-TOGGLES-STARTCODONS">) and the "Suspicious Start
+Codons ..." feature filter (see <XREF
+LINKEND="VIEWMENU-FEATURE-FILTERS-SUSPSTART">).
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-OPTIONS-SSHSEARCHES">
+ <TITLE>Send Searches Via SSH</TITLE>
+ <PARA>
+This is an advanced option for sites that have set up the ability to
+submit searches via SSH to another machine.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="BLACK-BELT-MODE">
+ <TITLE>Black Belt Mode</TITLE>
+ <PARA>
+This is an advanced option that can be used to turn off warning message. This
+options is displayed if the java property (black_belt_mode) is specified on
+opening up &prog;, i.e. art -Dblack_belt_mode=yes.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-OPTIONS-HIGHLIGHTACTIVE">
+ <TITLE>Highlight Active Entry</TITLE>
+ <PARA>
+When this option is on and the "One Line Per Entry" is on (see <XREF
+LINKEND="VIEWS-POPUPMENU-ONE-LINE-PER-ENTRY">) the line that the active entry
+is on will be highlighted in yellow.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-OPTIONS-SHOW-LOG">
+ <TITLE>Show Log Window</TITLE>
+ <PARA>
+Show the log of informational messages from &prog;. Currently the log window
+is only used on UNIX and GNU/Linux systems to show the output of external
+programs. This menu item is only available when running &prog; on UNIX or
+GNU/Linux systems. The logging is controlled by <ULINK
+URL="http://logging.apache.org/log4j/1.2/index.html">log4j</ULINK>. The
+log4j.properties file (etc/log4j.properties in the source distribution) sets
+the level of logging. This can be used to send the logging information to
+other places such as a file.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-OPTIONS-HIDE-LOG">
+ <TITLE>Hide Log Window</TITLE>
+ <PARA>
+Hide the log of informational messages. This menu item is only available when
+running &prog; on UNIX or GNU/Linux systems.
+ </PARA>
+ </SECT3>
+</SECT2>
diff --git a/docs/projMgr.png b/docs/projMgr.png
new file mode 100644
index 0000000..6a49173
Binary files /dev/null and b/docs/projMgr.png differ
diff --git a/docs/project_manager.sgml b/docs/project_manager.sgml
new file mode 100644
index 0000000..9865c35
--- /dev/null
+++ b/docs/project_manager.sgml
@@ -0,0 +1,59 @@
+<CHAPTER ID="PROJ-MANAGER-CHAPTER">
+ <TITLE>Project File Manager</TITLE>
+
+ <SECT1 ID="PROJ-OVERVIEW">
+ <TITLE>Overview</TITLE>
+ <PARA>
+The Project File Manager can be used to facilitate the launching of groups
+of files together. The project details are stored between sessions of &prog;,
+so that it is straightforward to return to a project and open up all the relevant
+files.
+
+ <SCREENSHOT>
+ <SCREENINFO>
+The Project File Manager window
+ </SCREENINFO>
+ <MEDIAOBJECT>
+ <IMAGEOBJECT>
+ <IMAGEDATA FORMAT="png" FILEREF="projMgr.png">
+ </IMAGEOBJECT>
+ </MEDIAOBJECT>
+ </SCREENSHOT>
+ </PARA>
+ </SECT1>
+
+ <SECT1 ID="PROJ-USING">
+ <TITLE>Using the Project File Manager</TITLE>
+ <PARA>
+The Project File Manager can be opened from the <EMPHASIS>File</EMPHASIS> menu in the Artemis launch window (see <XREF LINKEND="LAUNCH-WINDOW">).
+It opens with some example projects in the list on the left hand side of the panel.
+Selecting a project in the list shows the associated files in the right hand side of the
+window.
+ </PARA>
+ <PARA>
+To add a new project click on the green '+' button at the top of the Project File Manager
+window and enter a name for the project. The new project opens with a blank form with a 'sequence'
+field. <EMPHASIS>Note that each project has to have an associated sequence file (e.g. FASTA, EMBL, GenBank or GFF) that
+contains the nucleotide sequence for Artemis to open up.</EMPHASIS> New files can be added to the project
+by selecting the type of file from the drop down list at the bottom and clicking the
+'NEW PROPERTY' button.
+ </PARA>
+ <PARA>
+The project list and details are saved between sessions when Artemis is closed down. This information is
+stored in the home directory in a file called '.artemis.project.properties'. When Artemis is run it looks
+for a 'project.properties' file in the local directory as well as for the '.artemis.project.properties'
+file in the home directory.
+ </PARA>
+ <PARA>
+Before opening a project the files can be toggled on and off in the
+Project File Manager so that each can optionally be deselected and not opened.
+Once a project has been set up it can be selected from the list and clicking 'OPEN' will
+open the main Artemis window.
+ </PARA>
+ <PARA>
+To remove a project from the list, select the project name and click the red '-' button
+at the top of the window.
+ </PARA>
+ </SECT1>
+
+</CHAPTER>
diff --git a/docs/psu.css b/docs/psu.css
new file mode 100644
index 0000000..4ff64b6
--- /dev/null
+++ b/docs/psu.css
@@ -0,0 +1,12 @@
+div.SECT2 {
+ margin-left: 0.5cm
+}
+div.SECT3 {
+ margin-left: 1.0cm
+}
+div.SECT4 {
+ margin-left: 1.5cm
+}
+div.SECT5 {
+ margin-left: 2.0cm
+}
diff --git a/docs/putty1.gif b/docs/putty1.gif
new file mode 100755
index 0000000..65976e6
Binary files /dev/null and b/docs/putty1.gif differ
diff --git a/docs/putty2.gif b/docs/putty2.gif
new file mode 100755
index 0000000..e901912
Binary files /dev/null and b/docs/putty2.gif differ
diff --git a/docs/putty3.gif b/docs/putty3.gif
new file mode 100755
index 0000000..a7fb629
Binary files /dev/null and b/docs/putty3.gif differ
diff --git a/docs/putty4.gif b/docs/putty4.gif
new file mode 100755
index 0000000..bf0f327
Binary files /dev/null and b/docs/putty4.gif differ
diff --git a/docs/putty5.gif b/docs/putty5.gif
new file mode 100755
index 0000000..bd7371d
Binary files /dev/null and b/docs/putty5.gif differ
diff --git a/docs/putty6.gif b/docs/putty6.gif
new file mode 100755
index 0000000..dd4ebde
Binary files /dev/null and b/docs/putty6.gif differ
diff --git a/docs/putty7.gif b/docs/putty7.gif
new file mode 100755
index 0000000..6fc62fe
Binary files /dev/null and b/docs/putty7.gif differ
diff --git a/docs/requirements.sgml b/docs/requirements.sgml
new file mode 100644
index 0000000..16e4b6a
--- /dev/null
+++ b/docs/requirements.sgml
@@ -0,0 +1,10 @@
+<SECT1 ID="REQUIREMENTS">
+ <TITLE>System Requirements</TITLE>
+ <PARA>
+&prog; will run on any machine that has a recent version of Java.
+This version of &prog; requires Java 1.6 at least. Most of the
+development has been done using JDK v1.6 and some of the more
+advanced features (BamView and VCF/BCF View) require Java 1.6 or higher.
+See <XREF LINKEND="INSTALLATION"> for details on how to get Java.
+ </PARA>
+</SECT1>
diff --git a/docs/selector.eps b/docs/selector.eps
new file mode 100644
index 0000000..874c0f2
--- /dev/null
+++ b/docs/selector.eps
@@ -0,0 +1,804 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: /nfs/team81/kmr/powmap/docs/selector.ps
+%%Creator: XV Version 3.10 Rev: 12/16/94 - by John Bradley
+%%BoundingBox: 10 207 603 584
+%%Pages: 1
+%%DocumentFonts:
+%%EndComments
+%%EndProlog
+
+%%Page: 1 1
+
+% remember original state
+/origstate save def
+
+% build a temporary dictionary
+20 dict begin
+
+% define space for color conversions
+/grays 539 string def % space for gray scale line
+/npixls 0 def
+/rgbindx 0 def
+
+% lower left corner
+10 207 translate
+
+% size of image (on paper, in 1/72inch coords)
+592.92000 377.28000 scale
+
+% define 'colorimage' if it isn't defined
+% ('colortogray' and 'mergeprocs' come from xwd2ps
+% via xgrab)
+/colorimage where % do we know about 'colorimage'?
+ { pop } % yes: pop off the 'dict' returned
+ { % no: define one
+ /colortogray { % define an RGB->I function
+ /rgbdata exch store % call input 'rgbdata'
+ rgbdata length 3 idiv
+ /npixls exch store
+ /rgbindx 0 store
+ 0 1 npixls 1 sub {
+ grays exch
+ rgbdata rgbindx get 20 mul % Red
+ rgbdata rgbindx 1 add get 32 mul % Green
+ rgbdata rgbindx 2 add get 12 mul % Blue
+ add add 64 idiv % I = .5G + .31R + .18B
+ put
+ /rgbindx rgbindx 3 add store
+ } for
+ grays 0 npixls getinterval
+ } bind def
+
+ % Utility procedure for colorimage operator.
+ % This procedure takes two procedures off the
+ % stack and merges them into a single procedure.
+
+ /mergeprocs { % def
+ dup length
+ 3 -1 roll
+ dup
+ length
+ dup
+ 5 1 roll
+ 3 -1 roll
+ add
+ array cvx
+ dup
+ 3 -1 roll
+ 0 exch
+ putinterval
+ dup
+ 4 2 roll
+ putinterval
+ } bind def
+
+ /colorimage { % def
+ pop pop % remove 'false 3' operands
+ {colortogray} mergeprocs
+ image
+ } bind def
+ } ifelse % end of 'false' case
+
+
+
+% define the colormap
+/cmap 21 string def
+
+
+% load up the colormap
+currentfile cmap readhexstring
+000000 200000 404040 802000 a0a080 c02000 e0e0c0
+pop pop % lose return values from readhexstring
+
+
+% rlecmapimage expects to have 'w h bits matrix' on stack
+/rlecmapimage {
+ /buffer 1 string def
+ /rgbval 3 string def
+ /block 384 string def
+
+ % proc to read a block from file, and return RGB data
+ { currentfile buffer readhexstring pop
+ /bcount exch 0 get store
+ bcount 128 ge
+ { % it's a non-run block
+ 0 1 bcount 128 sub
+ { currentfile buffer readhexstring pop pop
+
+ % look up value in color map
+ /rgbval cmap buffer 0 get 3 mul 3 getinterval store
+
+ % and put it in position i*3 in block
+ block exch 3 mul rgbval putinterval
+ } for
+ block 0 bcount 127 sub 3 mul getinterval
+ }
+
+ { % else it's a run block
+ currentfile buffer readhexstring pop pop
+
+ % look up value in colormap
+ /rgbval cmap buffer 0 get 3 mul 3 getinterval store
+
+ 0 1 bcount { block exch 3 mul rgbval putinterval } for
+
+ block 0 bcount 1 add 3 mul getinterval
+ } ifelse
+ } % end of proc
+ false 3 colorimage
+} bind def
+
+
+539 343 8 % dimensions of data
+[539 0 0 -343 0 343] % mapping matrix
+rlecmapimage
+
+7f057f057f057f051a05
+7f057f057f057f0519050001
+17057f017f017f017f0182010501
+0105810105090581010509058101057f057f057f05630581010509058101050905820105
+01
+01058101050905810105080501017f057f057f05630501010905010109050101810501
+0105810105810503820305030103820503018101058105030603010101057f0336030106
+07038106030c038106030b0303060b0381060316030106060301060d038106037f033f03
+010101050203010502030101010507030101810501
+010581010588050301030503050301010101050703010101057f03350381060382030603
+05038106031a038106030d0381060315038106038203060305038106030c038106037f03
+3f030101010502038205010301030101010507030101810501
+01058101058105038703010305030103018101058105030603010101057f033503810603
+860306030603060381030601060203010601038306030603810306810603010302060603
+020602030106020384060306030601060103810603860306030603060301030106070381
+060302030106020381060302030106020301060103020602030106010383060306037f03
+3403010101050203820501030103010101050103030501030101810501
+010581010581050301038301030503810301810105820503050405020101057f03350303
+068103068306030603820306030103040684030603060382030603010301060703810603
+020303068303060306810603820306030103810603830306030684060306030602060703
+810603810306020601038106030103030682030603030381060301038106038303060306
+83060306037f033303010101050203820501030103010101050203820105030103010181
+0501
+0105810105810503820305030103820503018101058305030501060101057f0335038106
+038403060306030303850603060306030103850603060306038203060303038106030503
+810603020381060302038206030681060386030603060306038403060306030203810603
+070381060384030603060303038106030103810603020381060303038506030603060384
+03060306037f033603010101050203820501030103010101050203820501030103010181
+0501
+0105810105830503050382030103810301010101050703010101057f0335038106038403
+060306030403810603010301068203060301038206030601060103020606038106030303
+010602038306030603010381060301030206820306030303010607030106020301060103
+0206020301060203010603038106030103010601038106037f0336030101010502038205
+01030103010101050103810501010101030101810501
+010581010581050381030182010301820103018101058105030603010101057f037f037f
+0361030101010502038205010301030101010507030101810501
+010581010581050381030182010301820103018101058105030603010101057f037f037f
+036103010101050203010102030101010507030101810501
+010582010501090181050109018105017f017f017f016301810501090181050109018105
+01
+01058201050109018105017f017f017f017f010701810501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+010581010414040200270401007f047f047f04510482010501
+010581010402040200820400040d0481000416048100040e048100047f047f047f045004
+82010501
+01058101040104810004010401000e0481000416048100040e048100047f047f047f0450
+0482010501
+010581010401048100040204810004030402000604810004050402000504020083040004
+0004000c048200040001000204020001040200030401007f047f047f043c0482010501
+010581010401048100040704810004010481000404048100040404810004010481000402
+048100040104010002048100040e04010002048100040104810004020481000403040100
+7f047f047f043c0482010501
+010581010402040300030481000403048100040304810004030481000403048100048204
+0004030481000401048100040e0481000403048100048204000402048100047f047f047f
+04420482010501
+0105810104060481000401040600040481000403040600010481000407048100040e0481
+000403048100040104810004820400047f047f047f04430482010501
+010581010401048100040204810004010481000409048100040304810004060481000407
+048100040e0481000403048100040104810004820400047f047f047f04430482010501
+010581010401040100020481000402048100040204810004030481000404048100040204
+81000401048100040204810004010481000401048100040a040100020481000403040100
+060401007f047f047f043c0482010501
+0105810104010482000400010005040300030404000404030004040300040402000b0401
+0081040001000604810004050401007f047f047f043c0482010501
+01058101044b048100047f047f047f04450482010501
+01058101044b048100047f047f047f04450482010501
+0105810104490403007f047f047f04450482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f0473047f0618067f04060482010501
+01058101047f0473048106047f0415048102047f04050482010501
+01058101047f0473048106047f0415048102047f04050482010501
+01058101047f0473048106047f0415048102047f04050482010501
+01058101047f0473048106047f0415048102047f04050482010501
+010581010408040a067f045f048106047f0415048102047f04050482010501
+0105810104080409068102040704020081040001007f044f048106043104020083040004
+00040004040200820400044c048102047f04050482010501
+01058101040804010606040102090481000401048100047f044f04810604300481000401
+040100010481000402048100040104810004010401004d0481020403040a027604820105
+01
+010581010408040106060401020904810004820400040504020002040200010402000304
+01007f0436048106042f0481000403048100048204000403048100048204000402048100
+04330410060704810204030409028106040a040200820400043504020081040001002704
+82010501
+010581010408040106060401020904830004000405048100040104810004010481000402
+04810004030401007f0436048106042f0481000406048100040304810004820400043804
+8106040d04810204060481020403040102060001060a0481000401040100370481000401
+04810004270482010501
+010581010408040106060401020904020005048100040304810004820400040204810004
+7f043c048106042f04810004060481000403048100040104030035048106040d04810204
+060481020403040102060001060904810004030481000401040300020401008104008200
+0400820004008200040082000400810004010403000204010081040001000c0481000482
+040004050402000204020001040200020404000c0482010501
+01058101040804010606040102090481000482040004030406000204810004820400047f
+043d048106042f0481000406048100040304810004050481000433048106040d04810204
+060481020403040102060001060904810004060481000402048100040104010081040083
+000400048104008200040083000400048204000402048100040104010002048100040a04
+83000400040504810004010481000401048100040204810004010481000402048100040b
+0482010501
+010581010408040106060401020904810004010481000402048100040704810004820400
+047f043d048106042f048100040604810004030481000482040004020481000433048106
+040d04810204060481020403040102060001060904810004050481000404048100048204
+000482040004820400048204000482040004840400040004040481000482040004020481
+00040a040200050481000403048100048204000402048100040104810004100482010501
+
+010581010408040106060401020904810004010481000403048100040204810004020401
+00060401007f043604810604300481000402048100048204000402048100040104010002
+0481000433048106020e0207048102040304010206000106090481000405048100040404
+810004820400048204000482040004820400048204000484040004000404048100048204
+000402048100040a048100048204000403040600020481000482040004030403000d0482
+010501
+01058101040804010608020804020002040100030403000504810004050401007f043604
+810604310403000104050003048200040001004e04810204030401020600010609048100
+040504810004040481000482040004820400048204000482040004820400048404000400
+0404048100048204000402048100040a0481000401048100040204810004070481000482
+04000407048100040b0482010501
+0105810104080481060208021d048100047f043f048106047f0415048102040304010206
+0001060a0481000402048100048204000402048100040104810004820400048204000482
+0400048204000482040004820400040204810004010481000402048100040a0481000401
+048100040304810004020481000402040100040481000402048100040b0482010501
+010581010431048100047f043f048106047f0415048102040304010208060b0403000304
+030002040200810400820004000300810400820004008100048104000200020402000104
+02000904020002040100030403000504810004030404000d0482010501
+01058101042f0403007f043f048106047f041504810204030481020608065c0481000417
+0482010501
+01058101047f0473048106047f0415048102046b04810004170482010501
+01058101047f0473048106047f04150481020469040300170482010501
+01058101047f0473048106047f0415048102047f04050482010501
+01058101047f0473048106027f0216027f04060482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f046e047f0645065e0482010501
+01058101047f046e048106047f0442048102045d0482010501
+01058101047f046e048106047f0442048102045d0482010501
+01058101047f046e048106047f0442048102045d0482010501
+01058101047f046e048106047f0442048102045d0482010501
+01058101047f046e048106047f0442048102045d0482010501
+01058101047f046e0481060454048100046b048102045d0482010501
+01058101047f046e0481060454048100046b048102045d0482010501
+01058101047f046e04810604400401008104000100040403000204050005040200470410
+0607048102045d0482010501
+01058101047f046e04810604410401000204810004010481000402048100040204810004
+0604810004010481000445048106040d0481020406048102045d0482010501
+010581010408040a062504020007048100040604030004048100047f0415048106044104
+810004020481000482040004040481000401048100040504810004030481000444048106
+040d0481020406048102045d0482010501
+010581010408040906810204090403001804810004060481000405048100040704810004
+7f0415048106044104810004020481000482040004040481000401048100040504060045
+048106040d0481020406048102045d0482010501
+010581010408040106060401020904810004020481000416048100040e048100047f041f
+0481060441048100040204810004820400040404810004010481000405048100044a0481
+06040d0481020406048102045d0482010501
+010581010408040106060401020804810004040482000400810004010401000304030006
+04810004040402000404050003040200060402000304010001040100040401007b048106
+044104810004020481000401048100040204810004020481000401048100040204810004
+020481000444048106020e0207048102045d0482010501
+010581010408040106060401020804810004040481000482040004020481000401048100
+040204810004040481000406048100040504810004070481000404048100040104810004
+0204830004000482040004020401007b0481060440040200010402000204030005040200
+050403005f048102045d0482010501
+010581010408040106060401020804810004040481000482040004020481000406048100
+04040481000406048100040504810004070481000403048100040304810004010401007f
+0405048106047f0442048102045d0482010501
+010581010408040106060401020804810004040481000482040004020481000402040400
+05048100040604810004050481000407048100040304060002048100047f040504810604
+7f0442048102045d0482010501
+010581010408040106060401020804810004040481000482040004020481000401048100
+040204810004040481000406048100040504810004070481000403048100040704810004
+7f0405048106047f0442048102045d0482010501
+010581010408040106060401020904810004020481000401048100040104010002048100
+040204810004040481000406048100040504810004070481000404048100040204810004
+0104810004070401007b048106047f0442048102045d0482010501
+01058101040804010608020a040300040402008104008100040104030082040004010404
+000304040002040400040404000404030002040400050401007b048106047f0442048102
+045d0482010501
+0105810104080481060208020a04010002048100047f0448048106047f0442048102045d
+0482010501
+01058101041d048200040002007f044a048106027f0243025e0482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101044c048100040f04810004260401000a0481000459047f067f061e0603048201
+0501
+01058101041e04020082040004130481000411048100040f048100041f04810004050481
+000409048100041604810004180481000425047f067f061e06030482010501
+01058101041d048100040104010014048100044504810004050481000422048100041804
+81000425047f067f061e06030482010501
+01058101041c048100040304810004010403000204010081040001000204050004040300
+040402000304010081040001000404020003040100810400010004040200810400810004
+08040500030482000400010004040200050404000a040500050402000204020081040001
+0001040500050401001b04020683000600067f067f061706030482010501
+01058101041c048100040604810004020481000401040100020481000402048100040504
+810004020481000404048100040304010002048100040404810004030401000204810004
+0104810004010401000c0481000405040100020481000404048100040304810004020481
+00040b048100040604810004010481000401048100040104810004030481000407040100
+1b0403068100067f067f061806030482010501
+01058101041c048100040504810004040481000482040004020481000402048100040a04
+810004040481000403048100040204810004040481000403048100040204810004820400
+0403048100040b0481000405048100040204810004040481000403048100041004810004
+0504810004030481000401048300040004040481000425047f067f061e06030482010501
+
+01058101041c048100040504810004040481000482040004020481000402048100040604
+040005048100040304810004020481000404048100040304810004020481000482040004
+03048100040b04810004050481000402048100040404810004040403000d048100040504
+060003048100040504810004250403068100067f067f061806030482010501
+01058101041c048100040504810004040481000482040004020481000402048100040504
+810004020481000404048100040304810004020481000404048100040304810004020481
+00048204000403048100040b048100040504810004020481000404048100040804810004
+0b04810004050481000407048300040004040481000425047f067f061e06030482010501
+
+01058101041d048100040204810004820400040204810004010481000402048100040204
+810004010481000401048100040204810004040481000403048100040204810004040481
+0004030481000402048100040104810004010401000c0481000401048100040104810004
+02048100040404810004030481000402048100040b048100040104810004020481000402
+0481000482040004010481000403048100040104810004030401001b0403068100067f06
+7f061806030482010501
+01058101041e040300030403000204020001040200030402000404030082040004010404
+00010402000104020002040400010402000104020002040200820400040c040200020402
+000104020002040400020404000e04020005040300010402008104000100040402000504
+01001b047f067f061e06030482010501
+010581010472048100047b0403068100067f067f061806030482010501
+010581010471048100047c047f067f061e06030482010501
+01058101046d0403007e0403068100067f067f061806030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f04700403068100067f067f061806030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f04700403068100067f067f061806030482010501
+01058101047f047004020683000600067f067f061706030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+010581010408040a027f0463040a02130402000504020046048100040d04020030040100
+630482010501
+010581010408040902810604090404003a040200820400047f0412040902810604080402
+00080481000406048100041e040500150481000408048100040f048100040b0402000204
+02000a048100040e04810004620482010501
+010581010408040102060001060c048100043a04810004010401007f0413040102060001
+060b04810004070481000406048100041f04810004020481000413048100041a04810004
+0c040100020401000b048100040e04810004620482010501
+010581010408040102060001060c04810004040402008104008200040082000400010004
+0403000304010001040100040402000c0481000403048100040104030004040400040402
+007a040102060001060a0483000400040604810004060481000404040300020402000204
+02000a048100040204810004010403000304010001040100010405000404020005040300
+06048100040c048700040004000400040104030002040500050402008204000483040004
+0001005f0482010501
+010581010408040102060001060c04810004030481000401040100020401000204810004
+010481000402048100040204830004000482040004010481000401048100040a04810004
+060481000402048100040104810004020481000402048100040104810004780401020600
+01060a048300040004060481000406048100040304810004020481000401048100040304
+8100040a0481000402048100048204000402048100040204830004000482040004010481
+000408048100040304810004020481000404048100040c04870004000400040004820400
+04020481000402048100040604810004010401000104010002048100045d0482010501
+010581010408040102060001060c04810004020481000403048100040104810004020481
+0004820400040404810004010401000504810004030481000409048100040b0481000401
+048100040604810004030481000477040102060001060904810004010481000405048100
+040604810004020481000404048100048204000482040004820400040a04810004020481
+000405048100040204010006048100040804810004080481000404048100040c04810004
+820400048204000405048100040204810004050481000403048100048204000402048100
+045d0482010501
+010581010408040102060001060c04810004020481000403048100040104810004020481
+00048204000404048100040104810004050406000a048100040704040003040300030406
+007804010206000106090404000604810004060481000402048100040404810004820400
+0482040004820400040a0404000304040003048100040604810004080481000404040400
+05048100040c048100048204000482040004010404000304810004050481000406048100
+0402048100045d0482010501
+010581010408040102060001060c04810004020481000403048100040104810004020481
+0004820400040404810004010481000405048100040f0481000406048100040204810004
+060481000401048100047d04010206000106080481000403048100040404810004060481
+0004020481000404048100040104850004000400040b0481000405048100040204810004
+0204810004060481000408048100040304810004020481000404048100040c0481000403
+0481000482040004020481000402048100040504810004060481000402048100045d0482
+010501
+010581010408040102060001060c04810004030481000401040100020481000402048100
+04010481000402048100040204810004060481000402048100040a048100040204810004
+820400040204810004010481000402048100040204810004020481000477040102060001
+060804810004030481000404048100040604810004030481000402048100040204010081
+04008100040b048100040504810004020481000402048100040604810004010481000404
+048100040304810004020481000404048100040c04810004030481000482040004020481
+000402048100040104810004020481000402048100048204000402048100045d04820105
+01
+01058101040804010208060a040400030402008204000481040001000104020002040300
+03040400050403000d040300030403008204000481040003000504030079040102080607
+0403008104000200020404000304040003040300040481000401048100040a0403000504
+030082040004810400030005040200040404000304030082040004010404000a04020002
+0402000104030082040004020402000504030001040200010402005d0482010501
+01058101040804810206080617048100047f04490481020608067f047f04100482010501
+
+01058101042a048100047f047f047f04660482010501
+0105810104260403007f047f047f04680482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+0105810104170401000a0401007f047f047f046c0482010501
+01058101040104020013048100040a048100047f047f047f046b0482010501
+0105810104030481000412048100040a048100047f047f047f046b0482010501
+01058101040204830004000402040100810400010004040200820400040a048200040001
+000204020001040200030401007f047f047f04570482010501
+0105810104020483000400040304010002048100040104810004010401000b0401000204
+81000401048100040204810004030401007f047f047f04570482010501
+010581010401048100040104810004020481000402048100048204000403048100040a04
+81000403048100048204000402048100047f047f047f045d0482010501
+010581010401040400030481000402048100048204000403048100040a04810004030481
+00040104810004820400047f047f047f045e0482010501
+0105810104820400040304810004010481000402048100048204000403048100040a0481
+000403048100040104810004820400047f047f047f045e0482010501
+0105810104820400040304810004010481000402048100040104810004010401000b0401
+00020481000403040100060401007f047f047f04570482010501
+010582010400020081040002008104000100010402000204020081040081000408040100
+81040001000604810004050401007f047f047f04570482010501
+010581010430048100047f047f047f04600482010501
+010581010430048100047f047f047f04600482010501
+01058101042e0403007f047f047f04600482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f047004020683000600067f067f061706030482010501
+01058101047f04700403068100067f067f061806030482010501
+01058101047f0470047f067f061e06030482010501
+010581010408040a061e0481000433048100040704010029048100040604030044040306
+8100067f067f061806030482010501
+010581010408040906810204080402001104810004330481000408048100041d04810004
+0804810004050481000447047f067f061e06030482010501
+010581010408040106060401020b0481000451048100041d048100041004810004470403
+068100067f067f061806030482010501
+010581010408040106060401020a04830004000402040100810400820004008100040104
+0200030401008104000100040403000d0403000504020082040004010402000504020082
+040004090401008104008200040081000401040300020405000404020004040500040401
+003e047f067f061e06030482010501
+010581010408040106060401020a04830004000403040100810400830004000403048100
+04030401000204810004010481000402048100040a048100040204810004020481000401
+04010004048100040304810004010401000b040100810400830004000482040004020481
+0004020481000408048100040504810004060401003e0403068100067f067f0618060304
+82010501
+010581010408040106060401020904810004010481000402048100048204000482040004
+0304810004030481000402048100048204000404048100040e0481000401048100040304
+8100040304810004020481000403048100040a0481000482040004840400040004040481
+000401048100040804810004050481000447047f067f061e06030482010501
+010581010408040106060401020904040003048100048204000482040004030481000403
+0481000402048100048204000404048100040a0404000204810004090481000402048100
+0403048100040a0481000482040004840400040004040481000401048100040804810004
+0504810004470403068100067f067f061806030482010501
+010581010408040106060401020804810004030481000401048100048204000482040004
+030481000403048100040204810004820400040404810004090481000402048100040104
+8100040904810004020481000403048100040a0481000482040004840400040004040481
+000401048100040804810004050481000447047f067f061e06030482010501
+010581010408040106060401020804810004030481000401048100048204000482040004
+030481000403048100040204810004010481000402048100040a04810004020481000402
+04810004020481000403048100040304810004010401000b048100048204000482040004
+8204000402048100040204810004010481000404048100040504810004060401003e0403
+068100067f067f061806030482010501
+010581010408040106080207040300810400020081040001008104008200040081000481
+040003000104020001040200020403000d04030082040004020403000304040003040200
+810400810004080402008104008200040081000481040002000504020004040400020404
+00050401003e047f067f061e06030482010501
+0105810104080481060208027f045c0403068100067f067f061806030482010501
+01058101047f047004020683000600067f067f061706030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f0470047f067f061e06030482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+010581010408040a02430401003c04010061040a027f04020401007f04080482010501
+0105810104080409028106040804060033048100040b0402008204000402048100042504
+8100040a0406001404810004370409028106040704050043040200820400040204810004
+25048100040a04060014048100045e0482010501
+010581010408040102060001060a04810004020481000432048100040a04810004010401
+00030481000425048100040b048100040204810004130481000437040102060001060904
+8100040204810004400481000401040100030481000425048100040b0481000402048100
+0413048100045e0482010501
+010581010408040102060001060a04810004020481000401040300030401000104010001
+0402000204020001040300030401000104010003040200820400040a0481000402048100
+04810400040003040100010401000304030002040100810400010004040200820400040b
+048100040204810004020402000404030002040500020401000204010002040100010401
+000404020004040400100401020600010609048100040204810004030402000204020001
+04020003040200030401000104010003040400040402000c048100040204810004810400
+040003040100010401000304030002040100810400010004040200820400040b04810004
+020481000402040200040403000204050002040100020401000204010001040100040402
+0004040400370482010501
+010581010408040102060001060a04810004820400040204810004020481000402048300
+040004820400048204000403048100048204000402048100040204830004000482040004
+82040004010401000b048100040704810004060483000400048204000482040004020481
+00040104010002048100040104810004010401000c048100048204000403048100040104
+810004010481000402048100040204810004050481000402048100040204830004000482
+04000401048100040104810004010481000402048100040f040102060001060904810004
+020481000402048100040104810004010481000402048100040204810004010481000402
+04830004000482040004820400040204810004020481000401048100040a048100040704
+810004060483000400048204000482040004020481000401040100020481000401048100
+04010401000c048100048204000403048100040104810004010481000402048100040204
+810004050481000402048100040204830004000482040004010481000401048100040104
+8100040204810004360482010501
+010581010408040102060001060a04030002048100040404810004010401000504810004
+8204000482040004050481000402040100040481000403048100040b0403000404810004
+060401000a04810004010481000402048100048204000403048100040b04030003048100
+040304810004050481000402048100040504810004020481000402040100050481000403
+048100048204000414040102060001060904810004010481000402048100040304810004
+820400040204810004010481000403048100040104010005048100040604810004030481
+00040a0403000404810004060401000a0481000401048100040204810004820400040304
+8100040b0403000304810004030481000405048100040204810004050481000402048100
+040204010005048100040304810004820400043b0482010501
+010581010408040102060001060a04810004820400040104810004040481000401048100
+0405048100048204000482040004010404000304810004040481000403048100040f0481
+00040204810004060481000406040400020481000402048100048204000403048100040b
+048100048204000402040600020404000304810004050481000402048100040204810004
+050406000204030011040102060001060904030004040600020481000482040004020406
+00020481000406040300030406000f048100040204810004060481000406040400020481
+000402048100048204000403048100040b04810004820400040204060002040400030481
+00040504810004020481000402048100040504060002040300380482010501
+010581010408040102060001060a04810004040481000404048100040104810004060485
+000400040004010481000402048100040204810004040481000403048100040a04810004
+020481000402048100040604810004050481000402048100040104810004020481000482
+04000403048100040b048100040504810004060481000402048100040204810004050481
+00040204810004020481000405048100040b048100040f04010206000106090481000401
+048100040204810004070481000482040004020481000407048100040a04810004010481
+00040f048100040204810004020481000406048100040504810004020481000401048100
+0402048100048204000403048100040b0481000405048100040604810004020481000402
+0481000405048100040204810004020481000405048100040b04810004360482010501
+010581010408040102060001060a04810004050481000402048100040204810004060401
+008104008100040104810004020481000402048100040504810004010401000b04010002
+048100040204810004010481000402048100040504810004020481000401048100040204
+8100040104810004010401000c0481000406048100040204810004820400040204810004
+020481000401048100040104810004010401000304810004060481000402048100048204
+000402048100040f04010206000106090481000402048100040204810004020481000402
+040100050481000402048100040104810004050481000402048100040204810004020481
+000409040100020481000402048100040104810004020481000405048100040204810004
+010481000402048100040104810004010401000c04810004060481000402048100048204
+000402048100040204810004010481000401048100040104010003048100040604810004
+0204810004820400040204810004360482010501
+010581010408040102080609040300050403000304040004048100040104810004020403
+008204000481040003000404020081040081000409048200040001000504020003040400
+040403008304000400010001040200020402008104008100040904030006040300030403
+008204000402040200040402008104008100048104000300050403000204040011040102
+08060804020003048100040204030004040100060403000204040003040400050403000b
+048200040001000504020003040400040403008304000400010001040200020402008104
+008100040904030006040300030403008204000402040200040402008104008100048104
+0003000504030002040400380482010501
+0105810104080481020608067f04660481020608067f047f040d0482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f042104410610042f06100438067f04240482010501
+01058101047f0421048106043e048102040f048106042c048102040f0481060435048102
+047f04230482010501
+01058101047f0421048106043e048102040f048106042c048102040f0481060435048102
+047f04230482010501
+01058101047f0421048106043e048102040f048106042c048102040f0481060435048102
+047f04230482010501
+01058101047f0421048106041704020023048102040f0481060410048100041904810204
+0f048106040e04020023048102047f04230482010501
+01058101047f04210481060405040200820400040d04810004160481000409048102040f
+048106040304020002040200030481000419048102040f04810604060402008204000403
+0481000422048102047f04230482010501
+01058101047f0421048106040404810004010401000e0481000416048100040904810204
+0f04810604040481000403048100041f048102040f048106040504810004010401000404
+81000422048102047f04230482010501
+01058101047f042104810604040481000402048100040304020006048100040504020005
+0402008304000400040007048102040f0481060404048100040304810004010402000604
+0200020402000204020004048102040f0481060404048100040304810004030481000404
+040300040404000404020007048102047f04230482010501
+01058101047f042104810604040481000407048100040104810004040481000404048100
+040104810004020481000401040100020481000409048102040f04810604050481000401
+048100040404810004040481000401048100040104810004030481000404048102040f04
+810604040481000409048100040304810004020481000401048100040204810004020481
+0004010481000405048102047f04230482010501
+01058101047f042104810604050403000304810004030481000403048100040304810004
+0304810004820400040304810004010481000409048102040f0481060405048100040104
+81000404048100040304810004030481000482040004820400048204000404048102040f
+048106040404810004090481000402048100040404810004820400040604810004030481
+000404048102047f04230482010501
+01058101047f042104810604090481000401040600040481000403040600010481000407
+0481000409048102040f0481060406048300040004050481000403040600010481000482
+0400048204000404048102040f0481060404048100040904810004020481000404048100
+04010403000304060005048102047f04230482010501
+01058101047f042104810604040481000402048100040104810004090481000403048100
+040604810004070481000409048102040f04810604060483000400040504810004030481
+000407048500040004000405048102040f04810604040481000409048100040204810004
+0404810004050481000401048100040a048102047f04230482010501
+01058101047f042104810604040401000204810004020481000402048100040304810004
+04048100040204810004010481000402048100040104810004010481000405048102040f
+048106040704810004060481000404048100040204810004010401008104008100040504
+8102040f0481060405048100040204810004030481000403048100040204810004010481
+000402048100040204810004020481000404048102047f04230482010501
+01058101047f042104810604040482000400010005040300030404000404030004040300
+0404020007048102040f0481060407048100040404040004040300030481000401048100
+0405048102040f0481060406040300030404000304030003040400050403000604810204
+7f04230482010501
+01058101047f0421048106043e048102040f048106042c048102040f0481060435048102
+047f04230482010501
+01058101047f0421048106043e048102040f048106042c048102040f0481060435048102
+047f04230482010501
+01058101047f0421048106043e048102040f048106042c048102040f0481060435048102
+047f04230482010501
+01058101047f0421048106043e048102040f048106042c048102040f0481060435048102
+047f04230482010501
+01058101047f0421048106043e048102040f048106042c048102040f0481060435048102
+047f04230482010501
+01058101047f0421048106043e048102040f048106042c048102040f0481060435048102
+047f04230482010501
+01058101047f0421048106023f0210048106022d02100481060236027f04240482010501
+
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01058101047f047f047f047f04130482010501
+01057f017f017f017f011601810501
+7f057f057f057f0519050001
+8105017f017f017f017f011801
+
+%
+% Compression made this file 3.21% of the uncompressed size.
+%
+
+
+showpage
+
+% stop using temporary dictionary
+end
+
+% restore original state
+origstate restore
+
+%%Trailer
diff --git a/docs/selector.gif b/docs/selector.gif
new file mode 100644
index 0000000..daaff6e
Binary files /dev/null and b/docs/selector.gif differ
diff --git a/docs/ssh_chapter.sgml b/docs/ssh_chapter.sgml
new file mode 100644
index 0000000..7a91b55
--- /dev/null
+++ b/docs/ssh_chapter.sgml
@@ -0,0 +1,346 @@
+<CHAPTER ID="SSH-CHAPTER">
+ <TITLE>Secure Shell (SSH) Plugin To &prog</TITLE>
+
+ <SECT1 ID="SSH-OVERVIEW">
+ <TITLE>Overview of the SSH Plugin</TITLE>
+ <PARA>
+This is a plug-in to &prog that enables you to view and transfer
+files from a local machine (e.g. PC/MacOSX/UNIX) to another remote
+file system (i.e. any other file system that you have ssh access to).
+The main areas where this is useful are:
+ </PARA>
+ <ITEMIZEDLIST SPACING="compact">
+ <LISTITEM>
+ <PARA>
+file management for both local and remote files. Files can be transferred
+by dragging and droping between file systems. Multiple files can be
+selected and transferred in this way.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+launching Artemis and ACT with sequences and tab files from files
+on the remote machines.
+ </PARA>
+ </LISTITEM>
+
+ <LISTITEM>
+ <PARA>
+running BLAST and Fasta searches can be sent from the local machine that
+is running &prog and run on the remote machine. To do this the software
+(BLAST and Fasta) need to be set up on the remote side (see
+<XREF LINKEND="SSH-CONFIG-SSH-SEARCHES">).
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+
+ </SECT1>
+
+ <SECT1 ID="SSH-LOGIN">
+ <TITLE>Login In</TITLE>
+ <PARA>
+From the &prog launch window go to the 'File' menu and select
+'Open SSH File Manager'. A file manager window will then open with a
+local file manager at the top. Underneath this is a remote file manager
+with a section to enter login details.
+
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="fm_login.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ </PARA>
+
+ <PARA>
+If you can connect directly to the remote machine give the hostname, username
+and password. Alternatively if you are using a SSH tunnel (see <XREF
+LINKEND="SSH-TUNNEL-LOGIN">) then use localhost and the appropriate
+port number.
+ </PARA>
+
+ </SECT1>
+
+ <SECT1 ID="SSH-TUNNEL-LOGIN">
+ <TITLE>Setting up a SSH tunnel</TITLE>
+ <PARA>
+The tunneling capability of SSH Secure Shell allows people to access
+their email, intraweb pages and files securely even when working away
+from the office. This can be used with &prog and this section provides
+some examples of setting up tunnelling. It may be necessary to consult your
+friendly system administrator about the details on how best to do this.
+ </PARA>
+
+ <PARA>
+On <EMPHASIS>UNIX</EMPHASIS> the ssh configuration can be set in the file
+<ComputerOutput>~/.ssh/config</ComputerOutput>. This
+contains the information to set up a tunnel that looks like this:
+ </PARA>
+
+ <PARA>
+ <SCREEN>
+Host *.sanger.ac.uk
+ LocalForward 2222 machine.sanger.ac.uk:22
+ User tjc
+ </SCREEN>
+ </PARA>
+
+ <PARA>
+Once this configuration file is in place you can login from a UNIX
+window to set up a tunnel.
+ </PARA>
+
+ <PARA>
+On <EMPHASIS>Windows</EMPHASIS> you can use Putty, see <XREF LINKEND="PUTTY">.
+ </PARA>
+
+ <PARA>
+When the tunnel has been established you can start artemis and enter
+'localhost' into the Hostname field in the login window and the port
+number (e.g. 2222 in the above example).
+ </PARA>
+
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="fm.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ <PARA>
+The file manager can be used to drag and drop files between each file system.
+If you right click on a selected file this will provide you with a pop-up menu (see below)
+enabling you to delete, rename and open files.
+ </PARA>
+
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="fm_popup.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ <PARA>
+Files on the remote file system can be double clicked on and opened
+in Artemis (in the same way they can be opened from the local file
+manager). Tab files can be read in by dragging them from the remote
+file manager and dropping them into the Artemis window.
+ </PARA>
+
+ <PARA>
+Note for ACT the sequence filename can be dragged from the file manager into
+the file selection box (if the files are not found locally the are then
+located on the server). The tab files in ACT can be dragged from the
+file manager and dropped on the relevant sequence display.
+ </PARA>
+
+ <PARA>
+When files are saved, if the entry orginated from the remote file system
+then this is saved locally in the current working directory and to the
+remote file as well.
+ </PARA>
+ </SECT1>
+
+ <SECT1 ID="PUTTY">
+ <TITLE>Using Putty to Set Up A Tunnel</TITLE>
+ <PARA>
+On <EMPHASIS>Windows</EMPHASIS> you can use
+<ULINK
+URL="http://www.chiark.greenend.org.uk/~sgtatham/putty/" TYPE="external">
+Putty</ULINK> to set up a ssh tunnel. Below are settings that can be used
+in Putty to set up a SSH tunnel.
+ </PARA>
+
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="putty1.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ <PARA>
+ </PARA>
+
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="putty2.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ <PARA>
+SSH 2 only is probably recommended here.
+ </PARA>
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="putty3.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ <PARA>
+X11 forwarding is not necessary.
+ </PARA>
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="putty4.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ <PARA>
+Set the source port to 2222 and set the destination to
+hostname:port (e.g. pcs2.internal.sanger.ac.uk:22).
+ </PARA>
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="putty5.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ <PARA>
+Click 'Add' to add this to the list of forwarded ports.
+ </PARA>
+
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="putty6.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ <PARA>
+Run Artemis and select the 'SSH File Manager' from the 'File'
+menu. At the bottom fill in login details and click on the
+'Connect' button.
+Loging in may take a minute (depending on the connection).
+ </PARA>
+
+<MEDIAOBJECT>
+<IMAGEOBJECT>
+ <IMAGEDATA FORMAT="gif" FILEREF="putty7.gif">
+ </IMAGEOBJECT>
+</MEDIAOBJECT>
+
+ </SECT1>
+
+ <SECT1 ID="SSH-RUNNING-DB-SEARCHES">
+ <TITLE>Using the SSH connection to run Fasta and BLAST</TITLE>
+ <PARA>
+Using the SSH connection you can send Fasta and BLAST searches to be run
+on the remote machine. If you have not already be prompted to login
+(i.e. when opening up the SSH file manager), you will then be asked
+for your login details. The sequence files that are to be searched against the
+database are then sent to the remote machine.
+Once the search has finished the results are transferred back afterwards.
+If &prog detects that you have opened up an entry from the remote file system
+the analysis will be run in the directory you open the entry from. The results
+in this case are stored on both the local and remote machines.
+ </PARA>
+
+ <PARA>
+To keep Fasta and BLAST searches on your local machine and the remote file system
+<EMPHASIS>in-sync</EMPHASIS>:
+ </PARA>
+
+ <ITEMIZEDLIST SPACING="compact">
+ <LISTITEM>
+ <PARA>
+copy any previously run fasta and blast directories from the remote machine to your
+local file system. This can be done by making a tar ball of the directory:
+e.g. creating a tar file of a 'fasta' directory
+ </PARA>
+ <PARA>
+<SCREEN>
+tar cvf tar_ball_name.tar fasta
+</SCREEN>
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+Then transfer this file to the local project directory and extract with:
+ </PARA>
+ <PARA>
+<SCREEN>
+tar xvf tar_ball_name.tar
+</SCREEN>
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+when launching Artemis, make sure you first change directory to the directory above
+the 'fasta' directory that has been unpacked.
+ </PARA>
+ </LISTITEM>
+ <LISTITEM>
+ <PARA>
+open the files from the remote file system in the file manager. It then knows where
+to save it back to.
+ </PARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+ </SECT1>
+
+ <SECT1 ID="SSH-CONFIG-SSH-SEARCHES">
+ <TITLE>Configuring Searches to be run via SSH</TITLE>
+ <PARA>
+This would most likely be done by a systems administrator. The databases and
+fasta and BLAST software need to be set up on the remote machine. Once this is
+done the commands that are sent from the Artemis/ACT client to the remote
+machine are controlled by a 'j2ssh.properties' file, see below for example of
+this file. This needs to be put in the CLASSPATH for Artemis/ACT or the one in
+the distribution (lib/j2ssh/j2ssh.properties) modified.
+ </PARA>
+
+ <PARA>
+The j2ssh.properties file also hold default information. For example default
+parameters can be set for the login prompt, the working directory (for searches
+when sequences are opened on the local side) and any extra directories (other
+than the users home directory) to display in the file manager.
+ </PARA>
+
+ <PARA>
+ <SCREEN>
+#
+#
+# Configuration file for using SSH file management and
+# database searches.
+#
+#
+# default login options
+port=2222
+host=localhost
+#
+#
+# Generic default options
+#
+blastp=blastall -p blastp
+blastx=blastall -p blastx
+tblastx=blastall -p tblastx
+blastn=blastall -p blastn
+fasta=fasta33_t -B -S -q -b 100 -H -l /data/fasta/pubseqgbs
+fastx=fastx33_t -B -S -q -b 40 -H -l /data/fasta/pubseqgbs
+#
+#
+# default working directory
+wdir=/scratch
+#
+#
+# aliases used for fasta
+%uniprot=%U
+%uniprot_archaea=%A
+%uniprot_bacteria=%B
+%uniprot_eukaryota=%E
+%uniprot_viruses=%V
+%uniprot_rest=%R
+%malaria=%M
+%kineto_aa=%K
+default_db=%uniprot_bacteria
+#
+#
+# remote directories to use as roots in the file manager
+remotedir.PD=/nfs/pathdata
+remotedir.YP=/nfs/disk222/yeastpub
+# local directories to use as roots in the file manager
+localdir.PD=/nfs/pathdata
+localdir.YP=/nfs/disk222/yeastpub
+ </SCREEN>
+ </PARA>
+
+ </SECT1>
+
+</CHAPTER>
diff --git a/docs/start_chapter.sgml b/docs/start_chapter.sgml
new file mode 100644
index 0000000..42d645d
--- /dev/null
+++ b/docs/start_chapter.sgml
@@ -0,0 +1,179 @@
+<CHAPTER ID="START">
+ <TITLE>Starting &prog;</TITLE>
+
+ <SECT1 ID="RUNNINGUNIX">
+ <TITLE>Running &prog; on UNIX and GNU/Linux Systems</TITLE>
+ <PARA>
+On Unix and GNU/Linux the easiest way to run the program is to run the script
+called <COMMAND>art</COMMAND> in the &prog; installation directory (see <XREF
+LINKEND="INSTALLATION">), like this:
+ </PARA>
+
+ <PARA>
+ <COMMAND>artemis/art</COMMAND>
+ </PARA>
+
+ <PARA>
+If all goes well you will be presented with a small window with three
+menus. See <XREF LINKEND="LAUNCH-WINDOW"> to find out what to do next.
+ </PARA>
+
+ <PARA>
+Alternatively you start &prog; with the name of a sequence file or embl file
+eg:
+ </PARA>
+
+ <PARA>
+ <COMMAND>artemis/art artemis/etc/c1215.embl</COMMAND>
+ </PARA>
+
+ <PARA>
+Or if you have a sequence file and extra feature table files you can read
+them all with a command like this (the example file c1215.blastn.tab is the
+result of a BLASTN search against EMBL which has been converted to feature
+table format):
+ </PARA>
+
+ <PARA>
+ <COMMAND>
+artemis/art artemis/etc/c1215.embl + artemis/etc/c1215.blastn.tab
+ </COMMAND>
+ </PARA>
+ <PARA>
+Note that any number of feature files can be read by listing them after the
+plus sign. The "+" must be surrounded by spaces.
+ </PARA>
+
+ <PARA>
+See <XREF LINKEND="UNIXARGS"> for a list of the other possible arguments. Also
+to see a summary of the options type:
+ </PARA>
+
+ <PARA>
+ <COMMAND>artemis/art -help</COMMAND>
+ </PARA>
+
+ <PARA>
+Here is an alternative way to run &prog; if the <COMMAND>art</COMMAND> script
+doesn't work for you:
+ </PARA>
+
+ <PARA>
+ <COMMAND>
+cd ~/artemis
+ </COMMAND>
+ </PARA>
+
+ <PARA>
+ <COMMAND>
+java -classpath lib/biojava.jar:lib/jemAlign.jar:lib/j2ssh/j2ssh-core.jar:lib/ibatis/ibatis-2.3.4.726.jar:lib/ibatis/log4j-1.2.14.jar:lib/chado-14-interface.jar:lib/postgresql-8.4-701.jdbc3.jar:lib/picard.jar:lib/picard/sam.jar:lib/batik/batik-awt-util.jar:lib/batik/batik-codec.jar:lib/batik/batik-dom.jar:lib/batik/batik-ext.jar:lib/batik/batik-svggen.jar:lib/batik/batik-util.jar:lib/batik/batik-xml.jar:. -Dartemis.environment=UNIX uk.ac.sanger.artemis.components.ArtemisMain
+ </COMMAND>
+ </PARA>
+ </SECT1>
+
+&unixargs;
+
+ <SECT1 ID="RUNNINGMAC">
+ <TITLE>Running &prog; on Macintosh Systems</TITLE>
+ <PARA>
+On MacOSX machines &prog; can be started by double clicking on the Artemis
+icon. The icon can be added to the dock by dragging it from a 'Finder' window
+and dropping it onto the position in the dock you want it. In addition
+sequence files (with gff, embl, EMBL, genbank, gbk, fasta, seq, art file name
+extensions) can be dragged and dropped on to the Artemis application image to
+open them up.
+ </PARA>
+ <PARA>
+To change the memory allocated to &prog; on MacOSX, set the value in the file Info.plist
+in the directory Artemis.app/Contents. Towards the bottom of the file there are a couple of
+lines that look like this:
+<SYNOPSIS>
+ <key>VMOptions</key>
+ <string>-Xmx800m</string>
+</SYNOPSIS>
+ Changing the value after -Xmx will change the memory used by &prog;.
+ </PARA>
+ </SECT1>
+
+ <SECT1 ID="RUNNINGPC">
+ <TITLE>Running &prog; on Windows Systems</TITLE>
+ <PARA>
+On systems with Java 1.6 (or above) installed &prog; can be
+started by double clicking on the artemis.jar icon.
+ </PARA>
+ </SECT1>
+
+ <SECT1 ID="LAUNCH-WINDOW">
+ <TITLE>The &prog; Launch Window</TITLE>
+ <PARA>
+This is the first window that opens when you start &prog;. The menus at the
+top of this window allow you to read a new sequence or embl entry.
+ </PARA>
+ <SECT2 ID="LAUNCH-WINDOW-FILE-MENU">
+ <TITLE>The File Menu</TITLE>
+
+ <SECT3 ID="LAUNCH-WINDOW-PROJMANAGER">
+ <TITLE>Open Project Manager ...</TITLE>
+ <PARA>
+This opens up the Project File Manager which can be used to facilitate launching
+of groups of files (annotation, userplot, BAM, VCF) with a particular sequence.
+See <XREF LINKEND="PROJ-MANAGER-CHAPTER">.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-FILEMANAGER">
+ <TITLE>Open File Manager ...</TITLE>
+ <PARA>
+Selecting this shows the files and directories that are in the directory &prog;
+is launched from. The user home directory and the current working directories
+are shown and can be navigated.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-SSH-FILEMANAGER">
+ <TITLE>Open SSH File Manager ...</TITLE>
+ <PARA>
+This opens a window displaying a local file manager at the top and login
+options to access a remote file system via Secure Shell (SSH). When the
+login details are typed in and 'Connect' pressed the bottom half of the
+window will display the other (remote) file system. See <XREF LINKEND="SSH-CHAPTER">
+to find out how to use this and how to set up the connection.
+ </PARA>
+ </SECT3>
+
+
+ <SECT3 ID="LAUNCH-WINDOW-FILE-MENU-OPEN">
+ <TITLE>Open ...</TITLE>
+ <PARA>
+If you select this menu item a file requester will be displayed which allows
+you to open a flat file containing an entry. If the file you select is
+successfully read a new window will open, which shows the sequence and
+features for the entry. See <XREF LINKEND="MAINWINDOW-CHAPTER"> to find out
+how to use the main window.
+ </PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-FILE-MENU-READ-FROM-EBI">
+ <TITLE>Open from EBI - Dbfetch ...</TITLE>
+ <PARA>
+The functions will ask the user for an accession number and then will attempt
+to read it directly from the <ULINK URL="http://www.ebi.ac.uk"
+TYPE="external">EBI</ULINK> using
+<ULINK URL="http://www.ebi.ac.uk/cgi-bin/dbfetch" TYPE="external">Dbfetch</ULINK>.
+If all goes well you will be presented with an view/edit window (see <XREF
+LINKEND="MAINWINDOW-CHAPTER">).
+ </PARA>
+ <PARA>
+ </SECT3>
+
+ <SECT3 ID="LAUNCH-WINDOW-FILE-MENU-QUIT">
+ <TITLE>Quit</TITLE>
+ <PARA>
+This menu item will close all windows and then exit the program
+ </PARA>
+ </SECT3>
+ </SECT2>
+
+&options-menu;
+ </SECT1>
+</CHAPTER>
diff --git a/docs/unix_args.sgml b/docs/unix_args.sgml
new file mode 100644
index 0000000..4b996df
--- /dev/null
+++ b/docs/unix_args.sgml
@@ -0,0 +1,31 @@
+ <SECT1 ID="UNIXARGS">
+ <TITLE>UNIX Command Line Arguments for &prog;</TITLE>
+ <PARA>
+As well as the listing file names on the command line, the following switches
+are available to UNIX users:
+ </PARA>
+
+ <SECT2 ID="UNIXARGS-QUIET">
+ <TITLE><LITERAL>-quiet</LITERAL></TITLE>
+ <PARA>
+This option tells &prog; to suppress normal informational messages while
+running.
+ </PARA>
+ </SECT2>
+
+ <SECT2 ID="UNIXARGS-OPTIONS">
+ <TITLE><LITERAL>-options</LITERAL></TITLE>
+ <PARA>
+This option instructs &prog; to read an extra file of options after reading
+the standard options. (See <XREF LINKEND="OPTIONS-INTRO"> for more about the
+&prog; options file.)
+ </PARA>
+ <PARA>
+For example <LITERAL>-options ./new_options</LITERAL> will instruct &prog; to
+read <LITERAL>new_options</LITERAL> in the current directory as an options
+file.
+ </PARA>
+ </SECT2>
+ &jvmopts;
+
+ </SECT1>
diff --git a/docs/user_manual_screen_shot.gif b/docs/user_manual_screen_shot.gif
new file mode 100644
index 0000000..fbbd4b4
Binary files /dev/null and b/docs/user_manual_screen_shot.gif differ
diff --git a/docs/vcf.png b/docs/vcf.png
new file mode 100644
index 0000000..88b7f42
Binary files /dev/null and b/docs/vcf.png differ
diff --git a/docs/views_directedit.sgml b/docs/views_directedit.sgml
new file mode 100644
index 0000000..fcbe613
--- /dev/null
+++ b/docs/views_directedit.sgml
@@ -0,0 +1,12 @@
+<SECT2 ID="VIEWS-DIRECTEDIT">
+ <TITLE>"Direct Editing"</TITLE>
+ <PARA>
+The direct editing option (See <XREF
+LINKEND="LAUNCH-WINDOW-OPTIONS-DIRECT-EDIT">) enables the user to change the
+start or end position of a segment by dragging it around with the mouse. This
+works best in the DNA view window. As an example, to move the start position,
+click the mouse button on the first base of the feature or exon, hold the
+button down, move the mouse pointer to the desired position, then release the
+button.
+ </PARA>
+</SECT2>
diff --git a/docs/views_popup.sgml b/docs/views_popup.sgml
new file mode 100644
index 0000000..a9fbcd8
--- /dev/null
+++ b/docs/views_popup.sgml
@@ -0,0 +1,196 @@
+ <SECT2 ID="VIEWS-POPUPMENU">
+ <TITLE>The Pop-up Menu</TITLE>
+ <PARA>
+The pop-up menu is activated by pressing the third mouse button (see <XREF
+LINKEND="MOUSEBUTTONS">) on a feature view. The menu is split into three
+sections. The top contains functions that will act on the current view. The
+middle contains shortcuts to some of the main window menus. The bottom
+contains four toggle buttons which influence the appearance of the view.
+ </PARA>
+
+ <SECT3 ID="VIEWS-POPUPMENU-FUNCTIONS">
+ <TITLE>Pop-up Menu Functions</TITLE>
+ <PARA>
+Note that not all of these functions are available all the time (the first two
+are only shown when there are some selected features).
+ </PARA>
+
+ <ITEMIZEDLIST SPACING="compact">
+ <LISTITEM ID="VIEWS-POPUPMENU-RAISE-SELECTED-FEATURES">
+ <FORMALPARA>
+ <TITLE>Raise Selected Features</TITLE>
+ <PARA>
+Raise the selected features so that they appear in front of all other
+features.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="VIEWS-POPUPMENU-LOWER-SELECTED-FEATURES">
+ <FORMALPARA>
+ <TITLE>Lower Selected Features</TITLE>
+ <PARA>
+Make the selected features go behind all the other features.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="VIEWS-POPUPMENU-SMALLEST-IN-FRONT">
+ <FORMALPARA>
+ <TITLE>Smallest Features In Front</TITLE>
+ <PARA>
+Sort the visible features so that the smallest features appear in front of the
+larger ones. This is only necessary when the user has manually rearranged the
+features using "Raise Selected Features" or "Lower Selected Features".
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="VIEWS-POPUPMENU-ZOOM-TO-SELECTION">
+ <FORMALPARA>
+ <TITLE>Zoom to Selection</TITLE>
+ <PARA>
+Scroll and scale the display so that the current selection is centred and full
+width.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="VIEWS-POPUPMENU-SELECT-VISIBLE-RANGE">
+ <FORMALPARA>
+ <TITLE>Select Visible Range</TITLE>
+ <PARA>
+Select the currently visible bases on the forward strand.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="VIEWS-POPUPMENU-SELECT-VISIBLE-FEATURES">
+ <FORMALPARA>
+ <TITLE>Select Visible Features</TITLE>
+ <PARA>
+Select those (and only those) features that currently visible in this view.
+Any features that are off screen or have been filtered out with the "Set Score
+Cutoffs ..." control.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+
+ <LISTITEM ID="VIEWS-POPUPMENU-SET-SCORE-CUTOFFS">
+ <FORMALPARA>
+ <TITLE>Set Score Cutoffs ...</TITLE>
+ <PARA>
+The score cutoffs panel allows the user to filter the features of the active
+entries so that features with low or high scores are not shown. The "score"
+of a feature is the value of the <LITERAL>/score</LITERAL> qualifier and
+should be a number from 0 to 100. The cutoffs window has two sliders. Any
+feature that has a score less than the value of the top controller or more
+than the value of the bottom controller will not be shown. Features with no
+<LITERAL>/score</LITERAL> qualifier will always be shown.
+ </PARA>
+ </FORMALPARA>
+ </LISTITEM>
+ </ITEMIZEDLIST>
+ </SECT3>
+
+ <SECT3 ID="VIEWS-POPUPMENU-TOGGLES">
+ <TITLE>Toggle Buttons</TITLE>
+ <SECT4 ID="VIEWS-POPUPMENU-TOGGLES-LABELS">
+ <TITLE>Feature Labels</TITLE>
+ <PARA>
+This toggle button controls whether the feature labels are displayed on their
+own line (when the toggle is on) or on the top of the features (when the
+toggle is off). The default setting for this toggle can be set in the options
+file (see <XREF LINKEND="OPTIONS-FEATURE-LABELS">).
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-ONE-LINE-PER-ENTRY">
+ <TITLE>One Line Per Entry</TITLE>
+ <PARA>
+This toggle controls whether the entries are shown in the context of the three
+frame translation or one entry per line on screen. In the first case the
+entries will be overlaid, in second case they will be shown in parallel.
+The default setting for this toggle can be set in the options file (see <XREF
+LINKEND="OPTIONS-ONE-LINE-PER-ENTRY">).
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-SHOW-FORWARD-FRAMES">
+ <TITLE>Forward Frame Lines</TITLE>
+ <PARA>
+This toggle button controls whether or not to show the 3 frame translation of
+the forward sequence.
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-SHOW-REVERSE-FRAMES">
+ <TITLE>Reverse Frame Lines</TITLE>
+ <PARA>
+This toggle button controls whether or not to show the 3 frame translation of
+the reverse sequence.
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-TOGGLES-STARTCODONS">
+ <TITLE>Start Codons</TITLE>
+ <PARA>
+Toggle the display of start codons in the view. See <XREF
+LINKEND="LAUNCH-WINDOW-OPTIONS-EUK"> to find out how to change which start
+codons to use.
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-TOGGLES-STOPCODONS">
+ <TITLE>Stop Codons</TITLE>
+ <PARA>
+Toggle the display of stop codons in the view.
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-TOGGLES-FEATURE-ARROWS">
+ <TITLE>Feature Arrows</TITLE>
+ <PARA>
+Toggle the display of directional arrows on each feature. The default setting
+for this toggle can be set in the options file (see <XREF
+LINKEND="OPTIONS-DRAWFEATUREARROWS">).
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-TOGGLES-FEATURE-BORDERS">
+ <TITLE>Feature Borders</TITLE>
+ <PARA>
+Toggle the display of black borders around each feature. The default setting
+for this toggle can be set in the options file (see <XREF
+LINKEND="OPTIONS-DRAWFEATUREBORDERS">).
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-TOGGLES-FRAMEFEATURES">
+ <TITLE>All Features On Frame Lines</TITLE>
+ <PARA>
+Normally non-protein features are drawn on the DNA lines. This toggle allows
+the user to force all features to be drawn on the frame lines, which can
+sometimes improve readability. The default setting
+for this toggle can be set in the options file (see <XREF
+LINKEND="OPTIONS-FEATURES-ON-FRAME-LINES">).
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-TOGGLES-FLIPDISPLAY">
+ <TITLE>Flip Display</TITLE>
+ <PARA>
+If selected the sequence and features will be drawn on screen as if they are
+reverse complemented with the first base to the right of the screen.
+ </PARA>
+ </SECT4>
+
+ <SECT4 ID="VIEWS-POPUPMENU-TOGGLES-COLOURISE">
+ <TITLE>Colourise Bases</TITLE>
+ <PARA>
+This toggle turn base colouring on or off. (Note that this feature is
+completely unless, it exists for amusement only).
+ </PARA>
+ </SECT4>
+ </SECT3>
+ </SECT2>
diff --git a/docs/views_scale.sgml b/docs/views_scale.sgml
new file mode 100644
index 0000000..80d45de
--- /dev/null
+++ b/docs/views_scale.sgml
@@ -0,0 +1,16 @@
+ <SECT2 ID="VIEWS-SCALE">
+ <TITLE>Changing The Scale</TITLE>
+ <PARA>
+The vertical scrollbar at the right edge of the view controls the scale -
+moving the scrollbar up will zoom in and moving the scrollbar down will zoom
+out. When the scrollbar is at the top (at it's most "zoomed-in" position),
+the complete six frame translation is shown. Otherwise only the stop codons
+are shown.
+<![ %artemis-only; [
+The main difference between the overview and the DNA view is that
+the DNA view initially shows the full translation, whereas the overview shows
+only the stop codons. The other difference is that the labels are on in the
+overview by default (see <XREF LINKEND="VIEWS-POPUPMENU">).
+]]>
+ </PARA>
+ </SECT2>
diff --git a/docs/views_scrolling.sgml b/docs/views_scrolling.sgml
new file mode 100644
index 0000000..58241ee
--- /dev/null
+++ b/docs/views_scrolling.sgml
@@ -0,0 +1,10 @@
+ <SECT2 ID="VIEWS-SCROLLING">
+ <TITLE>Scrolling The View</TITLE>
+ <PARA>
+Moving the horizontal scrollbar will change the part of the sequence that is
+visible. The position of the view can also be changed by using the Goto menu
+(see <XREF LINKEND="GOTOMENU">), by double clicking the first mouse button on
+a feature in one of the sequence views<![ %artemis-only; [ or in the feature
+list or]]> by double clicking on a base or amino acid.
+ </PARA>
+ </SECT2>
diff --git a/docs/views_selection.sgml b/docs/views_selection.sgml
new file mode 100644
index 0000000..bb4f2ee
--- /dev/null
+++ b/docs/views_selection.sgml
@@ -0,0 +1,28 @@
+ <SECT2 ID="VIEWS-SELECTION">
+ <TITLE>Changing the Selection from a View Window</TITLE>
+ <PARA>
+To select a feature just click on it with the first mouse
+button. This will unselect anything that is currently selected. To add a
+feature to the selection rather than replacing the current selection, hold the
+<LITERAL>shift</LITERAL> key while clicking. A single feature can be removed
+from the selection in the same way, because shift-clicking acts as a toggle.
+The situation is made slightly more complicated by the existence of spliced
+genes. When a feature segment (exon) is added to the selection the feature
+that contains the segment is implicitly added as well. When the last segment
+of a feature is removed from the selection, the feature is implicitly removed.
+ </PARA>
+
+ <PARA>
+A single base or amino acid can be selected simply by clicking on it. A range
+of bases can be added by clicking on the base at one end of the range then
+shift-click on the base at the other end of the range. Alternatively you can
+drag out a range of bases: click on a base then hold the mouse button down and
+move to the other end. To select a complete open reading frame (ORF), double
+click the middle mouse button (see <XREF LINKEND="MOUSEBUTTONS">) anywhere
+inside the ORF (see <XREF LINKEND="SELECTMENU-ORF"> for another way to
+select an ORF).
+ </PARA>
+ <PARA>
+See <XREF LINKEND="CONCEPTS-SELECTION"> for more about the selection.
+ </PARA>
+ </SECT2>
diff --git a/etc/af063097.embl b/etc/af063097.embl
new file mode 100644
index 0000000..9098370
--- /dev/null
+++ b/etc/af063097.embl
@@ -0,0 +1,1903 @@
+ID AF063097 standard; DNA; PHG; 33593 BP.
+XX
+AC AF063097; J02474; L29304; M12772; M13202; M27131; M27836; M34756; M58023;
+AC M59752; M64677; U02597; X02300; X02301; X05655; X61229; X87173; X99627;
+AC X99628; Z11483;
+XX
+SV AF063097.1
+XX
+DT 21-MAY-1998 (Rel. 55, Created)
+DT 07-JAN-1999 (Rel. 58, Last updated, Version 2)
+XX
+DE Bacteriophage P2, complete genome.
+XX
+KW .
+XX
+OS Enterobacteria phage P2
+OC Viruses; dsDNA viruses, no RNA stage; Caudovirales; Myoviridae;
+OC P2-like Viruses.
+XX
+RN [1]
+RP 1-9, 33582-33593
+RX MEDLINE; 73220701.
+RA Murray K., Murray N.E.;
+RT "Terminal nucleotide sequences of DNA from temperate coliphages";
+RL Nature New Biol. 243(126):134-139(1973).
+XX
+RN [2]
+RP 1-26, 33568-33593
+RX MEDLINE; 77209985.
+RA Murray K., Isaksson-Forsen A.G., Challberg M., Englund P.T.;
+RT "Symmetrical nucleotide sequences in the recognition sites for the ter
+RT function of bacteriophages P2, 299 and 186";
+RL J. Mol. Biol. 112(3):471-489(1977).
+XX
+RN [3]
+RP 1-26, 33568-33593
+RX MEDLINE; 82051282.
+RA Lindqvist B.H.;
+RT "Recombination between satellite phage P4 and its helper P2. I. In vivo and
+RT in vitro construction of P4: :P2 hybrid satellite phage";
+RL Gene 14(4):231-241(1981).
+XX
+RN [4]
+RP 2929-3589
+RX MEDLINE; 83268689.
+RA Christie G.E., Calendar R.;
+RT "Bacteriophage P2 late promoters. Transcription initiation sites for two
+RT late mRNAs";
+RL J. Mol. Biol. 167(4):773-790(1983).
+XX
+RN [5]
+RP 25456-25995
+RX MEDLINE; 84248044.
+RA Ljungquist E., Kockum K., Bertani E.L.;
+RT "DNA sequence of the repressor gene and operator region of bacteriophage
+RT P2";
+RL Proc. Natl. Acad. Sci. U.S.A. 81:3988-3992(1984).
+XX
+RN [6]
+RP 10188-10518, 17510-17996
+RX MEDLINE; 85160858.
+RA Christie G.E., Calender R.;
+RT "Bacteriophage P2 late promoters - comparison of the four late promoter
+RT sequences";
+RL J. Mol. Biol. 181:373-382(1985).
+XX
+RN [7]
+RP 23909-24339
+RX MEDLINE; 86281704.
+RA Birkeland N.K., Lindquist B.H.;
+RT "Coliphage P2 late control gene ogr. DNA sequence and product
+RT identification";
+RL J. Mol. Biol. 188(3):487-490(1986).
+XX
+RN [8]
+RP 23889-24333
+RX MEDLINE; 86205872.
+RA Christie G.E., Haggard-Ljungquist E., Feiwell R., Calendar R.;
+RT "Regulation of bacteriophage P2 late-gene expression: the ogr gene";
+RL Proc. Natl. Acad. Sci. U.S.A. 83(10):3238-3242(1986).
+XX
+RN [9]
+RP 25849-27048
+RX MEDLINE; 87286423.
+RA Haggard-Ljungquist E., Kockum K., Bertani L.E.;
+RT "DNA sequences of bacteriophage P2 early genes cox and B and their
+RT regulatory sites";
+RL Mol. Gen. Genet. 208(1-2):52-56(1987).
+XX
+RN [10]
+RP 25849-27048
+RX MEDLINE; 88082694.
+RA Saha S., Haggard-Ljungquist E., Nordstrom K.;
+RT "The cox protein of bacteriophage P2 inhibits the formation of the
+RT repressor protein and autoregulates the early operon";
+RL EMBO J. 6(10):3191-3199(1987).
+XX
+RN [11]
+RP 24252-25595
+RX MEDLINE; 90006769.
+RA Yu A., Bertani L.E., Haggard-Ljungquist E.;
+RT "Control of prophage integration and excision in bacteriophage P2:
+RT nucleotide sequences of the int gene and att sites";
+RL Gene 80(1):1-11(1989).
+XX
+RN [12]
+RP 31590-33593
+RX MEDLINE; 90152373.
+RA Haggard-Ljungquist E., Barreiro V., Calendar R., Kurnit D.M., Cheng H.;
+RT "The P2 phage old gene: sequence, transcription and translational control";
+RL Gene 85(1):25-33(1989).
+XX
+RN [13]
+RP 1-266, 33200-33593
+RX MEDLINE; 91092507.
+RA Ziermann R., Calendar R.;
+RT "Characterization of the cos sites of bacteriophages P2 and P4";
+RL Gene 96(1):9-15(1990).
+XX
+RN [14]
+RP 17627-19373
+RX MEDLINE; 91135001.
+RA Temple L.M., Forsburg S.L., Calendar R., Christie G.E.;
+RT "Nucleotide sequence of the genes encoding the major tail sheath and tail
+RT tube proteins of bacteriophage P2";
+RL Virology 181(1):353-358(1991).
+XX
+RN [15]
+RP 4050-5178
+RX MEDLINE; 91220668.
+RA Six E.W., Sunshine M.G., Williams J., Haggard-Ljungquist E.,
+RA Lindqvist B.H.;
+RT "Morphopoietic switch mutations of bacteriophage P2";
+RL Virology 182(1):34-46(1991).
+XX
+RN [16]
+RP 1-6551
+RX MEDLINE; 92115571.
+RA Linderoth N.A., Ziermann R., Haggard-Ljungquist E., Christie G.E.,
+RA Calendar R.;
+RT "Nucleotide sequence of the DNA packaging and capsid synthesis genes of
+RT bacteriophage P2";
+RL Nucleic Acids Res. 19(25):7207-7214(1991).
+XX
+RN [17]
+RP 12173-15684
+RX MEDLINE; 92165720.
+RA Haggard-Ljungquist E., Halling C., Calendar R.;
+RT "DNA sequence of two tail genes of bacteriophage P2: Further evidence for
+RT horizontal transfer of tail fiber genes among unrelated bacteriophages";
+RL J. Bacteriol. 174:1462-1477(1992).
+XX
+RN [18]
+RP 26923-30721
+RX MEDLINE; 93287108.
+RA Liu Y., Saha S., Haggard-Ljungquist E.;
+RT "Studies of bacteriophage P2 DNA replication. The DNA sequence of the
+RT cis-acting gene A and ori region and construction of a P2 mini-chromosome";
+RL J. Mol. Biol. 231(2):361-374(1993).
+XX
+RN [19]
+RP 6288-8381
+RX MEDLINE; 94327465.
+RA Ziermann R., Bartlett B., Calendar R., Christie G.E.;
+RT "Functions involved in bacteriophage P2-induced host cell lysis and
+RT identification of a new tail gene";
+RL J. Bacteriol. 176:4974-4984(1994).
+XX
+RN [20]
+RP 8376-10381
+RX MEDLINE; 94233699.
+RA Linderoth N.A., Julien B., Flick K.E., Calendar R., Christie G.E.;
+RT "Molecular cloning and characterization of bacteriophage P2 genes R and S
+RT involved in tail completion";
+RL Virology 200:347-359(1994).
+XX
+RN [21]
+RP 10276-12739
+RX MEDLINE; 96036485.
+RA Haggard-Liungquist E., Jacobsen E., Rishovd S., Six W.S., Nilssen O.,
+RA Sunshine M.G., Lindqvist B.H., Kim K.J., Barreiro V., Koonin E.V.,
+RA Calendar R.;
+RT "Bacteriophage P2: genes involved in baseplate assembly";
+RL Virology 213(1):109-121(1995).
+XX
+RN [22]
+RP 15627-17626, 30643-31622
+RA Calendar R., Yu S., Myung H., Barreiro V., Odegrip R., Bertani L.E.,
+RA Carlson K., Davenport L., Mosig G., Christie G.E., Haggard-Ljungquist E.;
+RT "The lysogenic conversion genes of coliphage P2 have unusually high AT
+RT content";
+RL (in) Syvanen M., Kado C. (eds.);
+RL HORIZONTAL GENE TRANSFER:1-1;
+RL Chapman and Hall, London (1998)
+XX
+RN [23]
+RP 19374-23954
+RA Christie G.E., Temple L.M., Bartlett B.A., Goodwin T.S.;
+RT "Bacteriophage P2 tail assembly genes: Gene organization and a programmed
+RT translational frameshift parallel those of bacteriophage lambda";
+RL Unpublished.
+XX
+RN [24]
+RP 1-33593
+RA Christie G.E., Haggard-Ljungquist E., Calendar R.;
+RT "The complete genome of bacteriophage P2";
+RL Unpublished.
+XX
+RN [25]
+RP 24351-25595
+RA Haggard-Ljungquist E.;
+RT ;
+RL Submitted (15-SEP-1989) to the EMBL/GenBank/DDBJ databases.
+RL Department of Microbial Genetics, Karolinska Institutet, Stockholm, Sweden
+XX
+RN [26]
+RP 1-6551
+RA Linderoth N.A.;
+RT ;
+RL Submitted (13-AUG-1991) to the EMBL/GenBank/DDBJ databases.
+RL Dept of Mol and Cell Biology, Div of Biochemistry and Molecular Biology,
+RL University of California, 401 Barker Hall, Berkeley, CA 94720, USA
+XX
+RN [27]
+RP 26923-30721
+RA Haggard-Ljungquist E.;
+RT ;
+RL Submitted (12-NOV-1991) to the EMBL/GenBank/DDBJ databases.
+RL Department of Microbial Genetics, Karolinska Institutet, S-10401,
+RL Stockholm, Sweden
+XX
+RN [28]
+RP 8376-10381
+RA Linderoth N.A.;
+RT ;
+RL Submitted (18-OCT-1993) to the EMBL/GenBank/DDBJ databases.
+RL Nora A. Linderoth, Laboratory of Genetics, The Rockefeller University, 1230
+RL York Avenue, New York, NY 10021, USA
+XX
+RN [29]
+RP 10276-12739
+RA Haggard E.B.;
+RT ;
+RL Submitted (12-MAY-1995) to the EMBL/GenBank/DDBJ databases.
+RL Department of Genetics, Stockholm University, S-10691, Stockholm, Sweden
+XX
+RN [30]
+RP 15627-17626, 30643-31622
+RA Haggard-Ljungquist E.;
+RT ;
+RL Submitted (30-JUL-1996) to the EMBL/GenBank/DDBJ databases.
+RL Department of Genetics, Stockholm University, S-106 91, Stockholm, Sweden
+XX
+RN [31]
+RP 17510-23954
+RA Christie G.E.;
+RT ;
+RL Submitted (21-NOV-1996) to the EMBL/GenBank/DDBJ databases.
+RL Department of Microbiology and Immunology, Virginia Commonwealth
+RL University, Box 980678, Richmond, VA 23298-0678, USA
+XX
+RN [32]
+RP 1-33593
+RA Christie G.E.;
+RT ;
+RL Submitted (05-MAY-1998) to the EMBL/GenBank/DDBJ databases.
+RL Department of Microbiology and Immunology, Virginia Commonwealth
+RL University, Richmond, VA 23298-0678, USA
+XX
+DR SPTREMBL; O64312; O64312.
+DR SPTREMBL; O64313; O64313.
+DR SPTREMBL; O64314; O64314.
+DR SPTREMBL; O64315; O64315.
+DR SPTREMBL; P79669; P79669.
+DR SPTREMBL; P79670; P79670.
+DR SPTREMBL; Q38498; Q38498.
+DR SWISS-PROT; P04132; RPC_BPP2.
+DR SWISS-PROT; P07695; VCOX_BPP2.
+DR SWISS-PROT; P07696; VPB_BPP2.
+DR SWISS-PROT; P08762; VOGR_BPP2.
+DR SWISS-PROT; P10312; VPD_BPP2.
+DR SWISS-PROT; P13520; VOLD_BPP2.
+DR SWISS-PROT; P22501; VPF1_BPP2.
+DR SWISS-PROT; P22502; VPF2_BPP2.
+DR SWISS-PROT; P25475; VPL_BPP2.
+DR SWISS-PROT; P25476; VPM_BPP2.
+DR SWISS-PROT; P25477; VPN_BPP2.
+DR SWISS-PROT; P25478; VPO_BPP2.
+DR SWISS-PROT; P25479; VPP_BPP2.
+DR SWISS-PROT; P25480; VPQ_BPP2.
+DR SWISS-PROT; P26699; TFA_BPP2.
+DR SWISS-PROT; P26700; VPH_BPP2.
+DR SWISS-PROT; P26701; VPI_BPP2.
+DR SWISS-PROT; P31340; VPV_BPP2.
+DR SWISS-PROT; P36932; VINT_BPP2.
+DR SWISS-PROT; P36933; VPR_BPP2.
+DR SWISS-PROT; P36934; VPS_BPP2.
+DR SWISS-PROT; P36935; YSV_BPP2.
+DR SWISS-PROT; P51767; VPJ_BPP2.
+DR SWISS-PROT; P51768; VPW_BPP2.
+DR SWISS-PROT; P51769; LYSA_BPP2.
+DR SWISS-PROT; P51770; LYSB_BPP2.
+DR SWISS-PROT; P51771; LYCV_BPP2.
+DR SWISS-PROT; P51772; VPX_BPP2.
+DR SWISS-PROT; P51773; HOLI_BPP2.
+DR SWISS-PROT; Q06419; VPA_BPP2.
+DR SWISS-PROT; Q06422; YO80_BPP2.
+DR SWISS-PROT; Q06423; YO81_BPP2.
+DR SWISS-PROT; Q06424; YO82_BPP2.
+DR SWISS-PROT; Q06425; YO83_BPP2.
+DR SWISS-PROT; Q06426; YO91_BPP2.
+XX
+CC On May 20, 1998 this sequence version replaced gi:506838 gi:15821
+CC gi:1806573 gi:215666 gi:506836 gi:508561 gi:1061043 gi:215669
+CC gi:1769975 gi:494974 gi:215664 gi:15620 gi:393451 gi:1769977
+CC gi:215681 gi:215675 gi:215676 gi:15141 gi:414530.
+XX
+XX
+FH Key Location/Qualifiers
+FH
+FT source 1..33593
+FT /db_xref="taxon:10679"
+FT /organism="Enterobacteria phage P2"
+FT /lab_host="Escherichia coli"
+FT misc_feature 1..19
+FT /note="cosL; left cohesive end"
+FT terminator complement(160..181)
+FT /note="tQ; putative rho-independent terminator for P
+FT operon"
+FT CDS complement(187..1221)
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P25480"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="Q"
+FT /function="presumed portal protein"
+FT /product="gpQ"
+FT /protein_id="AAD03268.1"
+FT /translation="MSKKKGKTPQPAAKTMTASGPKMEAFTFGEPVPVLDRRDILDYVE
+FT CISNGRWYEPPVSFTGLAKSLRAAVHHSSPIYVKRNILASTFIPHPWLSQQDFSRFVLD
+FT FLVFGNAFLEKRYSTTGKVIRLETSPAKYTRRGVEEDVYWWVPSFNEPTAFAPGSVFHL
+FT LEPDINQELYGLPEYLSALNSAWLNESATLFRRKYYENGAHAGYIMYVTDAVQDRNDIE
+FT MLRENMVKSKGRNNFKNLFLYAPQGKADGIKIIPLSEVATKDDFFNIKKASAADLLDAH
+FT RIPFQLMGGKPENVGSLGDIEKVAKVFVRNELIPLQDRIREINGWLGQEVIRFKNYSLD
+FT TDND"
+FT variation complement(653)
+FT /note="Qam34 mutation"
+FT /replace="t"
+FT /gene="Q"
+FT CDS complement(1221..2993)
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P25479"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="P"
+FT /function="terminase; DNA-dependent ATPase"
+FT /product="gpP"
+FT /protein_id="AAD03269.1"
+FT /translation="MTITTDTTLLHDPRRQAALLYWQGFSVPQIAAMLQMKRPTVQSWK
+FT QRDGWDSVAPISRVEMSLEARLTQLIIKPQKTGGDFKEIDLLGRQIERLARVNRYSQTG
+FT NEADLNPNVANRNKGGRRKPKKNFFSDEAIEKLEQIFFEQSFDYQLHWYRAGLEHRIRD
+FT ILKSRQIGATFYFSREALLRALKTGHNQIFLSASKTQAYVFREYIIAFARLVDVDLTGD
+FT PIVLGNNGAKLIFLGTNSNTAQSHNGDLYVDEIFWIPNFQVLRKVASGMASQSHLRSTY
+FT FSTPSTLAHDAYPFWSGELFNRGRASAAERVEIDVSHNALAGGLLCADGQWRQIVTIED
+FT ALKGGCTLFDIEQLKRENSADDFKNLFMCEFVDDKASVFPFEELQRCMVDTLEEWEDYA
+FT PFAANPFGSRPVWIGYDPSHRGDSAGCVVLAPPVVAGGKFRILERHQWKGMDFATQAES
+FT IRKLTEKYNVEYIGIDATGLGVGVFQLVRSFYPAARDIRYTPEMKTAMVLKAKDVIRRG
+FT CLEYDVSATDITSSFMAIRKTMTSSGRSATYEASRSEEASHADLAWATMHALLNEPLTA
+FT GISTPLTSTILEFY"
+FT variation complement(2210)
+FT /note="Pam24 and Pam137 mutations"
+FT /replace="a"
+FT /gene="P"
+FT variation complement(2948)
+FT /note="Pam253 mutation"
+FT /replace="a"
+FT /gene="P"
+FT misc_feature complement(3016)
+FT /note="transcription start site for P operon; encodes P,Q"
+FT /gene="P"
+FT misc_feature 3057..3087
+FT /evidence=EXPERIMENTAL
+FT /note="activator binding site; pO, pP promoters"
+FT misc_feature 3127
+FT /evidence=EXPERIMENTAL
+FT /note="transcription start site for O operon; encodes O,
+FT N, M, L, X, Y, K, lysA, lysB, R, S"
+FT CDS 3167..4021
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P25478"
+FT /transl_table=11
+FT /gene="O"
+FT /function="presumed capsid scaffolding protein"
+FT /product="gpO"
+FT /protein_id="AAD03270.1"
+FT /translation="MAKKVSKFFRIGVEGDTCDGRVISAQDIQEMAETFDPRVYGCRIN
+FT LEHLRGILPDGIFKRYGDVAELKAEKIDDDSALKGKWALFAKITPTDDLIAMNKAAQKV
+FT YTSMEIQPNFANTGKCYLVGLAVTDDPASLGTEYLEFCRTAKHNPLNRFKLSPENLISV
+FT ATPVELEFEDLPETVFTALTEKVKSIFGRKQASDDARLNDVHEAVTAVAEHVQEKLSAT
+FT EQRLAEMETAFSALKQEVTDRADETSQAFTRLKNSLDHTESLTQQRRSKATGGGGDALM
+FT TNC"
+FT variation 3206
+FT /note="Oam71 mutation"
+FT /replace="t"
+FT /gene="O"
+FT variation 3411
+FT /note="Oam7 mutation"
+FT /replace="a"
+FT /gene="O"
+FT variation 3878
+FT /note="Oam279 mutation"
+FT /replace="t"
+FT /gene="O"
+FT CDS 4080..5153
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P25477"
+FT /transl_table=11
+FT /gene="N"
+FT /function="major capsid precursor"
+FT /product="gpN"
+FT /protein_id="AAD03271.1"
+FT /translation="MRQETRFKFNAYLSRVAELNGIDAGDVSKKFTVEPSVTQTLMNTM
+FT QESSDFLTRINIVPVSEMKGEKIGIGVTGSIASTTDTAGGTERQPKDFSKLASNKYECD
+FT QINFDFYIRYKTLDLWARYQDFQLRIRNAIIKRQSLDFIMAGFNGVKRAETSDRSSNPM
+FT LQDVAVGWLQKYRNEAPARVMSKVTDEEGRTTSEVIRVGKGGDYASLDALVMDATNNLI
+FT EPWYQEDPDLVVIVGRQLLADKYFPIVNKEQDNSEMLAADVIISQKRIGNLPAVRVPYF
+FT PADAMLITKLENLSIYYMDDSHRRVIEENPKLDRVENYESMNIDYVVEDYAAGCLVEKI
+FT KVGDFSTPAKATAEPGA"
+FT variation 4349..4350
+FT /note="Nam209 mutation"
+FT /replace="at"
+FT /gene="N"
+FT variation 4630
+FT /note="sir3 mutation; defective for choice of small
+FT capsids by satellite phage P4"
+FT /replace="c"
+FT /gene="N"
+FT variation 4695
+FT /note="sir5 mutation; defective for choice of small
+FT capsids by satellite phage P4"
+FT /replace="t"
+FT /gene="N"
+FT variation 4695..4697
+FT /note="sir9 mutation; defective for choice of small
+FT capsids by satellite phage P4"
+FT /replace=""
+FT /gene="N"
+FT variation 4699
+FT /note="sir2, sir6 and sir8 mutations; defective for choice
+FT of small capsids by satellite phage P4"
+FT /replace="t"
+FT /gene="N"
+FT variation 4740
+FT /note="sir1 mutation; defective for choice of small
+FT capsids by satellite phage P4"
+FT /replace="g"
+FT /gene="N"
+FT variation 4741
+FT /note="sir4 mutation; defective for choice of small
+FT capsids by satellite phage P4"
+FT /replace="a"
+FT /gene="N"
+FT CDS 5157..5900
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P25476"
+FT /transl_table=11
+FT /gene="M"
+FT /function="terminase"
+FT /product="gpM"
+FT /protein_id="AAD03272.1"
+FT /translation="MTSPAQRHMMRVSAAMTAQREAAPLRHATVYEQMLVKLAADQRTL
+FT KAIYSKELKAAKKRELLPFWLPWVNGVLELGKGAQDDILMTVMLWRLDTGDIAGALEIA
+FT RYALKYGLTMPGKHRRTPPYMFTEEVALAAMRAHAAGESVDTRLLTETLELTATADMPD
+FT EVRAKLHKITGLFLRDGGDAAGALAHLQRATQLDCQAGVKKEIERLERELKPKPEPQPK
+FT AATRAPRKTRSVTPAKRGRPKKKAS"
+FT variation 5361
+FT /note="Mts52 mutation"
+FT /replace="a"
+FT /gene="M"
+FT variation 5751
+FT /note="Mam32 mutation"
+FT /replace="t"
+FT /gene="M"
+FT CDS 6000..6509
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P25475"
+FT /transl_table=11
+FT /gene="L"
+FT /function="capsid completion protein"
+FT /product="gpL"
+FT /protein_id="AAD03273.1"
+FT /translation="MMTLIIPRKEAPVSGEGTVVIPQPAGDEPVIKNTFFFPDIDPKRV
+FT RERMRLEQTVAPARLREAIKSGMAETNAELYEYREQKIAAGFTRLADVPADDIDGESIK
+FT VFYYERAVCAMATASLYERYRGVDASAKGDKKADSIDSTIDELWRDMRWAVARIQGKPR
+FT CIVSQI"
+FT variation 6024
+FT /note="Lam79 mutation"
+FT /replace="t"
+FT /gene="L"
+FT variation 6240
+FT /note="Lam9 mutation"
+FT /replace="t"
+FT /gene="L"
+FT CDS 6509..6712
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P51772"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="X"
+FT /function="essential tail gene"
+FT /product="gpX"
+FT /protein_id="AAD03274.1"
+FT /translation="MKTFALQGDTLDAICVRYYGRTEGVVETVLAANPGLAELGAVLPH
+FT GTAVELPDVQTAPVAETVNLWE"
+FT variation 6562
+FT /note="Xam95 mutation"
+FT /replace="g"
+FT /gene="X"
+FT CDS 6716..6997
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P51773"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="Y"
+FT /function="holin; essential for host cell cell lysis"
+FT /product="gpY"
+FT /protein_id="AAD03275.1"
+FT /translation="MTAEEKSVLSLFMIGVLIVVGKVLAGGEPITPRLFIGRMLLGGFV
+FT SMVAGVVLVQFPDLSLPAVCGIGSMLGIAGYQVIEIAIQRRFKGRGKQ"
+FT variation 6852
+FT /note="Yam94 mutation"
+FT /replace="a"
+FT /gene="Y"
+FT CDS 6997..7494
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P51771"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="K"
+FT /function="endolysin; essential for host cell lysis"
+FT /product="gpK"
+FT /protein_id="AAD03276.1"
+FT /translation="MPVINTHQNIAAFLDMLAVSEGTANHPLTKNRGYDVIVTGLDGKP
+FT EIFTDYSDHPFAHGRPAKVFNRRGEKSTASGRYQQLYLFWPHYRKQLALPDFSPLSQDR
+FT LAIQLIRERGALDDIRAGRIERAISRCRNIWASLPGAGYGQREHSLEKLVTVWRTAGGV
+FT PA"
+FT variation 7114
+FT /note="Kts60 mutation"
+FT /replace="a"
+FT /gene="K"
+FT variation 7126
+FT /note="Kam218 mutation"
+FT /replace="t"
+FT /gene="K"
+FT variation 7318
+FT /note="Kam12 mutation"
+FT /replace="t"
+FT /gene="K"
+FT variation 7322
+FT /note="Kam76 mutation"
+FT /replace="a"
+FT /gene="K"
+FT CDS 7509..7934
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P51769"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="lysA"
+FT /function="nonessential; affects timing of lysis"
+FT /product="LysA"
+FT /protein_id="AAD03277.1"
+FT /translation="MKKLSLSLMLNVSLALMLALSLIYPQSVAVNFVAAWAILATVICV
+FT VAGGVGVYATEYVLERYGRELPPESLAVKIVTSLFLQPVPWRRRAAALVVVVATFISLV
+FT AAGWIFTALIYLVVSLFFRLIRKACRQRLEGREPCQG"
+FT variation 7694
+FT /note="lysAam96 mutation"
+FT /replace="g"
+FT /gene="lysA"
+FT CDS 7922..8347
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P51770"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="lysB"
+FT /function="nonessential; affects timing of lysis"
+FT /product="LysB"
+FT /protein_id="AAD03278.1"
+FT /translation="MSRLMIVLVVLLSLAVAGLFLVKHKNASLRASLDRANNVASGQQT
+FT TITMLKNQLHVALTRADKNELAQVALRQELENAAKREAQREKTITRLLNENEDFRRWYG
+FT ADLPDAVRRLHQRPACTDASDCPQRMPESEPLPDAGQ"
+FT variation 8099..8100
+FT /note="lysBam97 mutation"
+FT /replace="ta"
+FT /gene="lysB"
+FT attenuator 8399..8417
+FT /note="putative transcription attenuator; O operon"
+FT CDS 8455..8922
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P36933"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="R"
+FT /function="essential tail gene; tail completion"
+FT /product="gpR"
+FT /protein_id="AAD03279.1"
+FT /translation="MLKPDSLRRALTDAVTVLKTNPDMLRIFVDNGSIASTLAASLSFE
+FT KRYTLNVIVTDFTGDFDLLIVPVLAWLRENQPDIMTTDEGQKKGFTFYADINNDSSFDI
+FT SISLMLTERTLVSEVDGALHVKNISEPPPPEPVTRPMELYINGELVSKWDE"
+FT variation 8680
+FT /note="Ram3 mutation"
+FT /replace="t"
+FT /gene="R"
+FT variation 8710
+FT /note="Ram42 mutation"
+FT /replace="t"
+FT /gene="R"
+FT variation 8857
+FT /note="Ram23 mutation"
+FT /replace="t"
+FT /gene="R"
+FT CDS 8915..9367
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P36934"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="S"
+FT /function="essential tail gene; tail completion"
+FT /product="gpS"
+FT /protein_id="AAD03280.1"
+FT /translation="MNEFKRFEDRLTGLIESLSPSGRRRLSAELAKRLRQSQQRRVMAQ
+FT KAPDGTPYAPRQQQSVRKKTGRVKRKMFAKLITSRFLHIRASPEQASMEFYGGKSPKIA
+FT SVHQFGLSEENRKDGKKIDYPARPLLGFTGEDVQMIEEIILAHLER"
+FT variation 9209
+FT /note="Sam75 mutation"
+FT /replace="t"
+FT /gene="S"
+FT variation 9213
+FT /note="Sam89 mutation"
+FT /replace="a"
+FT /gene="S"
+FT terminator 9397..9433
+FT /note="putative rho-independent terminator for O operon"
+FT CDS complement(9439..10224)
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P36935"
+FT /transl_table=11
+FT /gene="orf30"
+FT /function="nonessential"
+FT /product="Orf30"
+FT /protein_id="AAD03281.1"
+FT /translation="MVSYNVTNVWGLIVFFLCSFAVLAFFSFGKSNLMRLIAHYFNFGY
+FT SDKKLKRLDREWRDIQLFKIINGINVSGIENVRMIQQGLIDGKLKTSYFFLTRIWGDIT
+FT KPPHIIKTIIVILASIFYILLACYIHNEQSVIVRDATGIPYKNMMYYVYSDKVLLSFKN
+FT KAVEFNKTYSLADCKRLQNVFIKDTLPEIACNKLLQLNEEDSEWLSQEIKDNNSHKKAL
+FT LILSLVYFTSGLVIFLSYTKFFYANKKVLEYKASNKNHS"
+FT misc_feature 10239..10266
+FT /evidence=EXPERIMENTAL
+FT /note="activator binding site; activation of pV promoter"
+FT CDS 10308..10943
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P31340"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="V"
+FT /function="baseplate assembly protein"
+FT /product="gpV"
+FT /protein_id="AAD03282.1"
+FT /translation="MNTLANIQELARALRNMIRTGIIVETDLNAGRCRVQTGGMCTDWL
+FT QWLTHRAGRSRTWWAPSVGEQVLILAVGGELDTAFVLPGIYSGDNPSPSVSADALHIRF
+FT PDGAVIEYEPETSALTVSGIKTASVTASGSVTATVPVVMVKASTRVTLDTPEVVCTNRL
+FT ITGTLEVQKGGTMRGNIEHTGGELSSNGKVLHTHKHPGDSGGTTGSPL"
+FT misc_feature 10308
+FT /evidence=EXPERIMENTAL
+FT /note="transcription start site for V operon; encodes V,
+FT W, J, I, H, G"
+FT /gene="V"
+FT variation 10329
+FT /note="Vam42, Vam203 and Vam205 mutations"
+FT /replace="t"
+FT /gene="V"
+FT variation 10498
+FT /note="Vts199 mutation"
+FT /replace="a"
+FT /gene="V"
+FT CDS 10940..11287
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P51768"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="W"
+FT /function="baseplate assembly protein"
+FT /product="gpW"
+FT /protein_id="AAD03283.1"
+FT /translation="MTARYLGMNRSDGLTVTDLEHISQSIGDILRTPVGSRVMRRDYGS
+FT LLASMIDQPQTPALELQIKVACYMAVLKWEPRVTLSSVTTARSFDGRMTVTLTGQHNDT
+FT GQPLSLTIPVS"
+FT variation 11237
+FT /note="Wam50 mutation"
+FT /replace="t"
+FT /gene="W"
+FT CDS 11292..12200
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P51767"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="J"
+FT /function="baseplate assembly protein"
+FT /product="gpJ"
+FT /protein_id="AAD03284.1"
+FT /translation="MPIIDLNQLPAPDVVEELDFESILAERKATLISLYPEDQQEAVAR
+FT TLTLESEPLVKLLEENAYRELIWRQRVNEAARAVMLACAAGNDLDVIGANYNTTRLTIT
+FT PADDSTIPPTPAVMESDTDYRLRIQQAFEGLSVAGSVGAYQYHGRSADGRVADISVTSP
+FT SPACVTISVLSRENNGVASEDLLAVVRNALNGEDVRPVADRVTVQSAAIVEYQINATLY
+FT LYPGPESEPIRAAAVKKLEAYITAQHRLGRDIRLSAIYAALHVEGVQRVELAAPLADIV
+FT LNSTQASFCTEYRVVTGGSDE"
+FT variation 11937
+FT /note="Jam31 mutation"
+FT /replace="t"
+FT /gene="J"
+FT CDS 12193..12723
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P26701"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="I"
+FT /function="baseplate assembly protein"
+FT /product="gpI"
+FT /protein_id="AAD03285.1"
+FT /translation="MSDSRLLPTGSSPLEVAAAKACAEIEKTPVSIRELWNPDTCPANL
+FT LPWLAWAFSVDRWDEKWPEATKRAVIRDAYFIHCHKGTIGAIRRVVEPLGYLINVTEWW
+FT ENSDPPGTFRLDIGVLESGITEAMYQEMERLIADAKPASRHLIGLNITRDIPGYLFAGG
+FT VAYDGDVITVYPG"
+FT variation 12235
+FT /note="Iam78 mutation"
+FT /replace="t"
+FT /gene="I"
+FT CDS 12734..14743
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P26700"
+FT /transl_table=11
+FT /gene="H"
+FT /function="probable tail fiber protein"
+FT /product="gpH"
+FT /protein_id="AAD03286.1"
+FT /translation="MSIKFRTVITTAGAAKLAAATAPGRRKVGITTMAVGDGGGKLPVP
+FT DAGQTGLIHEVWRHALNKISQDKRNSNYIIAELVIPPEVGGFWMRELGLYDDAGTLIAV
+FT ANMAESYKPALAEGSGRWQTCRMVIIVSSVASVELTIDTTTVMATQDYVDDKIAEHEQS
+FT RRHPDASLTAKGFTQLSSATNSTSETLAATPKAVKAAYDLANGKYTAQDATTARKGLVQ
+FT LSSATNSTSETLAATPKAVKTVMDETNKKAPLNSPALTGTPTTPTARQGTNNTQIANTA
+FT FVMAAIAALVDSSPDALNTLNELAAALGNDPNFATTMTNALAGKQPKDATLTALAGLAT
+FT AADRFPYFTGNDVASLATLTKVGRDILAKSTVAAVIEYLGLQETVNRAGNAVQKNGDTL
+FT SGGLTFENDSILAWIRNTDWAKIGFKNDADGDTDSYMWFETGDNGNEYFKWRSRQSTTT
+FT KDLMTLKWDALNILVNAVINGCFGVGTTNALGGSSIVLGDNDTGFKQNGDGILDVYANS
+FT QRVFRFQNGVAIAFKNIQAGDSKKFSLSSSNTSTKNITFNLWGASTRPVVAELGDEAGW
+FT HFYSQRNTDNSVIFAVNGQMQPSNWGNFDSRYVKDVRLGTRVVQLMARGGRYEKAGHTI
+FT TGLRIIGEVDGDDEAIFRPIQKYINGTWYNVAQV"
+FT variation 13994
+FT /note="Ham72 mutation"
+FT /replace="t"
+FT /gene="H"
+FT variation 14336
+FT /note="Ham59 mutation"
+FT /replace="t"
+FT /gene="H"
+FT CDS 14747..15274
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P26699"
+FT /transl_table=11
+FT /gene="G"
+FT /function="probable tail fiber assembly protein"
+FT /product="gpG"
+FT /protein_id="AAD03287.1"
+FT /translation="MQHLKNIKSGNPKTKEQYQLTKNFDVIWLWSEDGKNWYEEVKNFQ
+FT PDTIKIVYDENNIIVAITRDASTLNPEGFSVVEVPDITSNRRADDSGKWMFKDGAVVKR
+FT IYTADEQQQQAESQKAALLSEAENVIQPLERAVRLNMATDEERARLESWERYSVLVSRV
+FT DPANPEWPEMPQ"
+FT variation 14879
+FT /note="Gam1 mutation"
+FT /replace="t"
+FT /gene="G"
+FT variation 15294..17233
+FT /note="del2 deletion"
+FT /replace=""
+FT terminator 15639..15666
+FT /note="putative rho-independent terminator for V operon"
+FT CDS complement(15675..17261)
+FT /codon_start=1
+FT /db_xref="SPTREMBL:P79669"
+FT /note="cloned gene blocks growth of T5"
+FT /transl_table=11
+FT /gene="fun(Z)"
+FT /function="nonessential; lysogenic conversion; confers
+FT sensitivity to FUdR"
+FT /product="Fun(Z)"
+FT /protein_id="AAD03288.1"
+FT /translation="MIPIKDLTLGYADAENYKRRENKELLNKVFIRDNHLSRLCEPNIS
+FT FLVGEKGTGKTAYSIYLSNNNINNTLATTKYIRETEYQKFITLKSEKHLNLSDFSGIWK
+FT VILYLLISKQVLDKEGKISKLLNYSKFSSLNDAIDEYYLKAFSPEIIQALSFVQESKVA
+FT AELLHKHATLKGEERESLSFSESRFQINLFYIQKKFEEAFSQIRLSQNHILFIDGIDIR
+FT PSSIPYDDYLECIKGLAHAVWEINNDFFPSIKGGKGRMKAVLLIRPDIFESIGLQNQNA
+FT KIRDNSVFLDWRTEYVNHRSSQLFEVLDHLLRTQQDMALKKGQAWDHYFPWDSPNVHDN
+FT YNHPTSFINFLRWSYYRPRDILRMLEIIKSHASGDDGKNQFSLDDFESPSMQRDYSNYL
+FT LGEIKDHLSFYYGSEHYEIFLKFFEFLSGKDTFDYAEYLLAYKQLETHISSISTTKPKF
+FT MTTANDFLQFLFGLNVICYIETTEDNNRFIRWCFRERSYANISPKIKTGMKYQIFYGLA
+FT KSLNVGKALKK"
+FT variation complement(15784)
+FT /note="Z81su3 mutation"
+FT /replace="a"
+FT /gene="fun(Z)"
+FT variation complement(15872)
+FT /note="Z20 mutation"
+FT /replace="t"
+FT /gene="fun(Z)"
+FT variation complement(15944)
+FT /note="Z81 mutation"
+FT /replace="t"
+FT /gene="fun(Z)"
+FT variation complement(15946)
+FT /note="fun1 mutation"
+FT /replace="t"
+FT /gene="fun(Z)"
+FT variation complement(16079)
+FT /note="csZ150 mutation"
+FT /replace="t"
+FT /gene="fun(Z)"
+FT variation complement(16266)
+FT /note="fun99 mutation"
+FT /replace="t"
+FT /gene="fun(Z)"
+FT variation 16267..17299
+FT /note="del5 deletion"
+FT /replace=""
+FT variation complement(16638)
+FT /note="csZ150 mutation"
+FT /replace="a"
+FT /gene="fun(Z)"
+FT misc_feature 17555..17583
+FT /evidence=EXPERIMENTAL
+FT /note="activator binding site; activation of pF promoter"
+FT misc_feature 17626..17628
+FT /evidence=EXPERIMENTAL
+FT /note="transcription start site for F operon; encodes FI,
+FT FII, E, E', T, U, D"
+FT CDS 17652..18842
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P22501"
+FT /transl_table=11
+FT /gene="FI"
+FT /function="essential tail protein; tail sheath"
+FT /product="gpFI"
+FT /protein_id="AAD03289.1"
+FT /translation="MSDYHHGVQVLEINEGTRVISTVSTAIVGMVCTASDADAETFPLN
+FT KPVLITNVQSAISKAGKKGTLAASLQAIADQSKPVTVVMRVEDGTGDDEETKLAQTVSN
+FT IIGTTDENGQYTGLKAMLAAESVTGVKPRILGVPGLDTKEVAVALASVCQKLRAFGYIS
+FT AWGCKTISEVKAYRQNFSQRELMVIWPDFLAWDTVTSTTATAYATARALGLRAKIDQEQ
+FT GWHKTLSNVGVNGVTGISASVFWDLQESGTDADLLNESGVTTLIRRDGFRFWGNRTCSD
+FT DPLFLFENYTRTAQVVADTMAEAHMWAVDKPITATLIRDIVDGINAKFRELKTNGYIVD
+FT ATCWFSEESNDAETLKAGKLYIDYDYTPVPPLENLTLRQRITDKYLANLVTSVNSN"
+FT variation 18183
+FT /note="Fam4 mutation"
+FT /replace="t"
+FT /gene="FI"
+FT CDS 18855..19373
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P22502"
+FT /transl_table=11
+FT /gene="FII"
+FT /function="essential tail protein; tail tube"
+FT /product="gpFII"
+FT /protein_id="AAD03290.1"
+FT /translation="MAMPRKLKLMNVFLNGYSYQGVAKSVTLPKLTRKLENYRGAGMNG
+FT SAPVDLGLDDDALSMEWSLGGFPDSVIWELYAATGVDAVPIRFAGSYQRDDTGETVAVE
+FT VVMRGRQKEIDTGEGKQGEDTESKISVVCTYFRLTMDGKELVEIDTINMIEKVNGVDRL
+FT EQHRRNIGL"
+FT CDS 19430..19705
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O64313"
+FT /transl_table=11
+FT /gene="E"
+FT /function="essential tail protein"
+FT /product="gpE"
+FT /protein_id="AAD03291.1"
+FT /translation="MNKENVITLDNPVKRGEQVIEQVTLMKPSAGTLRGVSLAAVANSE
+FT VDALIKVLPRMTAPMLTEQEVAALELPDLVALAGKVVGFLSPNSVQ"
+FT CDS join(19430..19684,19684..19857)
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O64312"
+FT /note="shares amino terminus with gpE; -1 frameshift"
+FT /transl_table=11
+FT /gene="E+E'"
+FT /function="essential tail protein"
+FT /product="gpE+E'"
+FT /protein_id="AAD03292.1"
+FT /translation="MNKENVITLDNPVKRGEQVIEQVTLMKPSAGTLRGVSLAAVANSE
+FT VDALIKVLPRMTAPMLTEQEVAALELPDLVALAGKVVGFLVAELGAVTFPKNLSVDDLM
+FT ADVAVIFHWPPSELYPMSLTELITWREKALRRSGNTNE"
+FT variation 19473
+FT /note="Ets55 mutation"
+FT /replace="a"
+FT /gene="E"
+FT variation 19619
+FT /note="Eam30 mutation"
+FT /replace="t"
+FT /gene="E"
+FT misc_feature 19685
+FT /evidence=EXPERIMENTAL
+FT /note="site of -1 translational frameshift"
+FT /gene="E+E'"
+FT CDS 19850..22297
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O64314"
+FT /transl_table=11
+FT /gene="T"
+FT /function="essential tail protein; putative tail length
+FT determinator"
+FT /product="gpT"
+FT /protein_id="AAD03293.1"
+FT /translation="MSNNVKLQVLLRAVDQASRPFKSIRTASKSLSGDIRETQKSLREL
+FT NGHASRIEGFRKTSAQLAVTGHALEKARQEAEALATQFKNTERPTRAQAKVLESAKRAA
+FT EDLQAKYNRLTDSVKRQQRELAAVGINTRNLAHDEQGLKNRISETTAQLNRQRDALVRV
+FT SAQQAKLNAVKQRYQAGKELAGNMASVGAAGVGIAAAGTMAGVKLLMPGYEFAQKNSEL
+FT QAVIGVAKDSAEMAALRKQARQLGDNTAASADDAAGAQIIIAKAGGDVDAIQAATPVTL
+FT NMALANRRTMEENAALLMGMKSAFQLSNDKVAHIGDVLSMTMNKTAADFDGMSDALTYA
+FT APVAKNAGVSIEETAAMVGALHDAKITGSMAGTGSRAVLSRLQAPTGKAWDALKELGVK
+FT TSDSKGNTRPIFTILKEMQASFEKNRLGTAQQAEYMKTIFGEEASSAAAVLMTAASTGK
+FT LDKLTAAFKASDGKTAELVNIMQDNLGGDFKAFQSAYEAVGTDLFDQQEGALRKLTQTA
+FT TKYVLKLDGWIQKNKSLASTIGIIAGGALALTGIIGAIGLVAWPVITGINAIIAAAGAM
+FT GAVFTTVGSAVMTAIGAISWPVVAVVAAIVAGALLIRKYWEPVSAFFGGVVEGLKAAFA
+FT PVGELFTPLKPVFDWLGEKLQAAWQWFKNLIAPVKATQDTLNRCRDTGVMFGQALADAL
+FT MLPLNAFNKLRSGIDWVLEKLGVINKESDTLDQTAARTHTATYGTGDYIPATSSYAGYQ
+FT AYQPVTAPAGRSYVDQSKNEYHISLTGGTAPGTQLDRQLQDALEKYERDKRARARASMM
+FT HDG"
+FT variation 20669
+FT /note="Tam215 mutation"
+FT /replace="t"
+FT /gene="T"
+FT variation 21101
+FT /note="Tam5 mutation"
+FT /replace="t"
+FT /gene="T"
+FT variation 21528
+FT /note="Tam64 mutation"
+FT /replace="a"
+FT /gene="T"
+FT CDS 22312..22791
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O64315"
+FT /transl_table=11
+FT /gene="U"
+FT /function="essential tail protein"
+FT /product="gpU"
+FT /protein_id="AAD03294.1"
+FT /translation="MMLALGMFVFMRQTLPHQTMQRESDYRWPSNSRIGKRDAFQFLGV
+FT GEENITLAGVLYPELTGGKLTMTTLRLMAEEGRAWPLLDGTGMIYGMYVISRVSETGSI
+FT FFADGTPRKIDFTLSLTRVDESLAALYGDIGKQAESLIGKAGSMATRFTGMTGAG"
+FT variation 22363
+FT /note="Uam25 mutation"
+FT /replace="t"
+FT /gene="U"
+FT variation 22501
+FT /note="Uam77 and Uam92 mutations"
+FT /replace="t"
+FT /gene="U"
+FT CDS 22791..23954
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P10312"
+FT /transl_table=11
+FT /gene="D"
+FT /function="essential tail protein"
+FT /product="gpD"
+FT /protein_id="AAD03295.1"
+FT /translation="MLDALTFDAGSTLTPDYMLMLDSRDITGNISDRLMSMTLTDNRGF
+FT EADQLDIELNDADGQVGLPVRGAVLTVYIGWKGFALVCKGKFTVDEVEHRGAPDVVTIR
+FT ARSADFRGTLNSRREGSWHDTTLGAIVKAIATRNRLEASVAPSLAGIKIPHIDQSQESD
+FT AKFLTRLAERNGGEVSVKMGKLLFLKAGQGVTASGKKIPQVTITRSDGDRHHFAIADRG
+FT AYTGVTAKWLHTKDPKPQKQKVKLKRKKKEKHLRALEHPKAKPVRQKKAPKVPEAREGE
+FT YMAGEADNVFALTTVYATKAQAMRAAQAKWDKLQRGVAEFSISLATGRADIYTETPVKV
+FT SGFKRVIDEQDWTITKVTHFLNNSGFTTSLELEVRLSDVEYETEDDE"
+FT variation 23592
+FT /note="Dam6 mutation"
+FT /replace="t"
+FT /gene="D"
+FT -35_signal 23973..23975
+FT -10_signal 23996..24002
+FT prim_transcript 24008..24288
+FT /note="putative"
+FT CDS 24036..24254
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P08762"
+FT /transl_table=11
+FT /gene="ogr"
+FT /product="Ogr"
+FT /protein_id="AAD03296.1"
+FT /translation="MFHCPLCQHAAHARTSRYITDTTKERYHQCQNVNCSATFITYESV
+FT QRYIVKPGEVHAVRPHPLPSGQQIMWM"
+FT variation 24148..25274
+FT /note="del15 deletion"
+FT /replace=""
+FT variation 24160
+FT /note="ogr1 and ogr52 mutations"
+FT /replace="g"
+FT /gene="ogr"
+FT terminator 24265..24288
+FT /evidence=EXPERIMENTAL
+FT /note="rho-independent terminator for ogr transcript and F
+FT operon"
+FT protein_bind 24293..24320
+FT /note="P arm"
+FT /bound_moiety="Int"
+FT protein_bind 24324..24357
+FT /bound_moiety="IHF"
+FT misc_feature complement(24356..24382)
+FT /note="attP"
+FT protein_bind 24356..24383
+FT /note="attP core"
+FT /bound_moiety="Int"
+FT variation 24372
+FT /note="saf mutation; altered integration site specificity"
+FT /replace="g"
+FT variation 24376
+FT /note="left end del3 deletion/insertion"
+FT variation 24378..25065
+FT /note="del6 deletion"
+FT /replace=""
+FT variation 24378..25927
+FT /note="vir22 deletion/insertion"
+FT variation 24381..26340
+FT /note="vir94 deletion/insertion"
+FT protein_bind 24399..24472
+FT /bound_moiety="Cox"
+FT CDS complement(24462..25475)
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P36932"
+FT /transl_table=11
+FT /gene="int"
+FT /function="integrase"
+FT /product="Int"
+FT /protein_id="AAD03297.1"
+FT /translation="MAIKKLDDGRYEVDIRPTGRNGKRIRRKFDKKSEAVAFEKYTLYN
+FT HHNKEWLSKPTDKRRLSELTQIWWDLKGKHEEHGKSNLGKIEIFTKITNDPCAFQITKS
+FT LISQYCATRRSQGIKPSSINRDLTCISGMFTALIEAELFFGEHPIRGTKRLKEEKPETG
+FT YLTQEEIALLLAALDGDNKKIAILCLSTGARWGEAARLKAENIIHNRVTFVKTKTNKPR
+FT TVPISEAVAKMIADNKRGFLFPDADYPRFRRTMKAIKPDLPMGQATHALRHSFATHFMI
+FT NGGSIITLQRILGHTRIEQTMVYAHFAPEYLQDAISLNPLRGGTEAESVHTVSTVE"
+FT protein_bind 24470..24496
+FT /note="P' arm"
+FT /gene="int"
+FT /bound_moiety="Int"
+FT variation complement(24539)
+FT /note="intam150 mutation"
+FT /replace="a"
+FT /gene="int"
+FT variation complement(25130)
+FT /note="intam239 mutation"
+FT /replace="a"
+FT /gene="int"
+FT variation complement(25454)
+FT /note="nip1 mutation"
+FT /replace="t"
+FT /gene="int"
+FT variation 25492..25839
+FT /note="vir 79 deletion"
+FT /replace=""
+FT CDS complement(25591..25890)
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P04132"
+FT /transl_table=11
+FT /gene="C"
+FT /function="immunity repressor"
+FT /product="gpC"
+FT /protein_id="AAD03298.1"
+FT /translation="MSNTISEKIVLMRKSEYLSRQQLADLTGVPYGTLSYYESGRSTPP
+FT TDVMMNILQTPQFTKYTLWFMTNQIAPESGQIAPALAHFGQNETTSPHSGQKTG"
+FT variation complement(25637)
+FT /note="sly1 mutation"
+FT /replace="t"
+FT /gene="C"
+FT variation complement(25682)
+FT /note="cts7 mutation"
+FT /replace="t"
+FT /gene="C"
+FT variation complement(25686..25702)
+FT /note="cam11 and cam13 mutations"
+FT /replace="a"
+FT /gene="C"
+FT variation complement(25702)
+FT /note="cts6 mutation"
+FT /replace="a"
+FT /gene="C"
+FT variation complement(25731)
+FT /note="cam10 and cam12 mutations"
+FT /replace="a"
+FT /gene="C"
+FT variation complement(25772)
+FT /note="cts9 mutation"
+FT /replace="t"
+FT /gene="C"
+FT variation complement(25802)
+FT /note="vir1 mutation"
+FT /replace="t"
+FT /gene="C"
+FT variation complement(25829)
+FT /note="cts8 mutation"
+FT /replace="g"
+FT /gene="C"
+FT variation complement(25844)
+FT /note="cts5 mutation"
+FT /replace="a"
+FT /gene="C"
+FT -35_signal 25884..25889
+FT /note="early promoter Pe"
+FT misc_feature 25894..25901
+FT /note="O1 repressor binding site"
+FT variation 25899..25955
+FT /note="vir24 deletion"
+FT /replace=""
+FT variation 25900..25916
+FT /note="vir3 deletion"
+FT /replace=""
+FT variation 25904..25920
+FT /note="vir65 deletion"
+FT /replace=""
+FT -10_signal 25907..25912
+FT /note="early promoter Pe"
+FT variation 25909..25983
+FT /note="vir40 deletion"
+FT /replace=""
+FT variation 25913
+FT /note="c1 mutation"
+FT /replace="c"
+FT misc_feature 25916..25923
+FT /note="O2 repressor binding site"
+FT variation 25922
+FT /note="vir6 mutation"
+FT /replace=""
+FT -10_signal complement(25962..25967)
+FT /note="Pc promoter"
+FT -35_signal complement(25982..25987)
+FT /note="Pc promoter"
+FT CDS 26005..26280
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P07695"
+FT /transl_table=11
+FT /gene="cox"
+FT /function="repressor of Pc; required for prophage
+FT excision; inhibits integration; activates P11 promoter of
+FT satellite phage P4"
+FT /product="Cox"
+FT /protein_id="AAD03299.1"
+FT /translation="MSKQVTLMTDAIPYQEFAKLIGKSTGAVRRMIDKGKLPVIDMTDP
+FT QSASGRAGEYWVYLPAWNNGLKLAYESRPKEIRDGWLMWLGLGEPR"
+FT variation 26069
+FT /note="cox3 mutation"
+FT /replace="a"
+FT /gene="cox"
+FT variation 26129
+FT /note="cox2 mutation"
+FT /replace="c"
+FT /gene="cox"
+FT variation 26164
+FT /note="cox4 mutation"
+FT /replace="a"
+FT /gene="cox"
+FT CDS 26291..26461
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q38498"
+FT /transl_table=11
+FT /gene="orf78"
+FT /product="Orf78"
+FT /protein_id="AAD03300.1"
+FT /translation="MNEPRCIAQLLRNESPRAIDFTITHGKGRKGIIIRTKKQSPLKKA
+FT LTFLKSRRVWK"
+FT CDS 26458..26958
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P07696"
+FT /transl_table=11
+FT /gene="B"
+FT /function="essential protein; DNA replication; required
+FT for lagging strand synthesis"
+FT /product="gpB"
+FT /protein_id="AAD03301.1"
+FT /translation="MTVMTLNLVEKQPAAMRRIIGKHLAVPRWQDTCDYYNQMMERERL
+FT TVCFHAQLKQRHATMCFEEMNDVERERLVCAIDELRGAFSKRRQVGASEYAYISFLTVS
+FT QRRTLFMHAGLTEKEFNQPYWRINEESCYWRDALFRALRELFSLFEYAPTILTSVKPEQ
+FT YLH"
+FT variation 26525
+FT /note="rlb1 mutation"
+FT /replace="g"
+FT /gene="B"
+FT variation 26611
+FT /note="Bam213 mutation"
+FT /replace="t"
+FT /gene="B"
+FT variation 26722
+FT /note="Bam116 mutation"
+FT /replace="t"
+FT /gene="B"
+FT variation 26762
+FT /note="Bts6 mutation"
+FT /replace="t"
+FT /gene="B"
+FT variation 26914
+FT /note="Bts40 mutation"
+FT /replace="t"
+FT /gene="B"
+FT terminator 26980..27007
+FT /note="putative rho-independent terminator"
+FT CDS 27022..27246
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:Q06422"
+FT /transl_table=11
+FT /gene="orf80"
+FT /product="Orf80"
+FT /protein_id="AAD03302.1"
+FT /translation="MHTVSENQCGIYALLLQQARTEAQADAATRFSSHLDAMIRHITKA
+FT ELSRVEIVELLSQESEKFHNIGLSRGEVL"
+FT CDS 27246..27548
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:Q06423"
+FT /transl_table=11
+FT /gene="orf81"
+FT /product="Orf81"
+FT /protein_id="AAD03303.1"
+FT /translation="MFCSRAVVLLNNALKIAVMKNGDLSLIQLGLDKEKREITESVIAI
+FT YQSELNLLSDVVNLLVKRAVFHKQISSVDELTKLTTEIASYCADEFKKLNDKRNW"
+FT CDS 27548..27772
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:Q06424"
+FT /transl_table=11
+FT /gene="orf82"
+FT /product="Orf82"
+FT /protein_id="AAD03304.1"
+FT /translation="MPDNVDFIQEQQAELLERQINAARVKHCGASALVCEECDAPIPAA
+FT RRAAYPSATRCVSCQSVFEAKNKHYRRTA"
+FT CDS 27769..28044
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:Q06425"
+FT /transl_table=11
+FT /gene="orf83"
+FT /product="Orf83"
+FT /protein_id="AAD03305.1"
+FT /translation="MSIRIEIGERYVVTSDSFQFILHEKKRAESGKNAGQEWLAVVGYY
+FT PKLSQLVSGLMHHDILTGSAKSFADLNVQVEQLSKRCSEAFGSYGR"
+FT CDS 28034..30319
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:Q06419"
+FT /transl_table=11
+FT /gene="A"
+FT /function="essential protein; DNA replication; makes
+FT site-specific nick at origin of replication"
+FT /product="gpA"
+FT /protein_id="AAD03306.1"
+FT /translation="MAVKASGRFVPPSAFAAGTGKMFTGAYAWNAPREAVGRERPLTRD
+FT EMRQMQGVLSTINRLPYFLRSLFTSRYDYIRRNKSPVHGFYFLTSTFQRRLWPRIERVN
+FT QRHEMNTDASLLFLAERDHYARLPGMNDKELKKFAARISSQLFMMYEELSDAWVDAHGE
+FT KESLFTDEAQAHLYGHVAGAARAFNISPLYWKKYRKGQMTTRQAYSAIARLFNDEWWTH
+FT QLKGQRMRWHETLLIAVGEVNKDRSPYASKHAIRDVRARRQANLEFLKSCDLENRETGE
+FT RIDLISKVMGSISNPEIRRMELMNTIAGIERYAAAEGDVGMFITLTAPSKYHPTRQVGK
+FT GESKTVQLNHGWNDEAFNPKDAQRYLCHIWSLMRTAFKDNDLQVYGLRVVEPHHDGTPH
+FT WHMMLFCNPRQRNQIIEIMRRYALKEDGDERGAARNRFQAKHLNQGGAAGYIAKYISKN
+FT IDGYALDGQLDNDTGRPLKDTAAAVTAWASTWRIPQFKTVGLPTMGAYRELRKLPRGVS
+FT IADEFDERVEAARAAADSGDFALYISAQGGANVPRDCQTVRVARSPSDEVNEYEEEVER
+FT VVGIYAPHLGARHIHITRTTDWRIVPKVPVVEPLTLKSGIAAPRSPVNNCGKLTGGDTS
+FT LPAPTPSEHAAAVLNLVDDGVIEWNEPEVVRALRGALKYDMRTPNRQQRNGSPLKPHEI
+FT APSARLTRSERLQITRIRVDLAQNGIRPQRWELEALARGATVNYDGKKFTYPVADEWPG
+FT FSTVMEWT"
+FT variation 28649
+FT /note="Aam127 mutation"
+FT /replace="t"
+FT /gene="A"
+FT variation 29528
+FT /note="Ats54 mutation"
+FT /replace="a"
+FT /gene="A"
+FT rep_origin 29892
+FT /evidence=EXPERIMENTAL
+FT /note="nick site at ori"
+FT CDS 30316..30645
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:Q06426"
+FT /transl_table=11
+FT /gene="orf91"
+FT /product="Orf91"
+FT /protein_id="AAD03307.1"
+FT /translation="MMAKIHEVKLHAKYFDLVLEGKKRAEFRKKDRNYERGDTLILHEW
+FT VQGVFTGRKVEARITDVTDLSDWLEDYVLLHRILGEILPLTVKLAVSSTFFINLRRRTV
+FT AIYLL"
+FT CDS complement(30684..31445)
+FT /codon_start=1
+FT /db_xref="SPTREMBL:P79670"
+FT /transl_table=11
+FT /gene="tin"
+FT /function="nonessential protein; lysogenic conversion;
+FT blocks growth of T-even phages"
+FT /product="Tin"
+FT /protein_id="AAD03308.1"
+FT /translation="MNNMDIVKAVKDGMKKSADATYLMDFRSGAKINTEYVATVSIGLS
+FT LLEIKSFRHGDYKVIFEYHTNKFINATVPLSKRSDPQKIFSKKTVRKNTNTTRSGRIDI
+FT AILDSRPFFDIPICAIEVKGNAPCKSLLFSDIRRNLEYFKHTGPTGNSSLGLALNCSFH
+FT SYNDSTKKNYCTTIHHKEDMIRKLKNKYKKYISELNEEIPDDISVTIDVFTAAEHLLSP
+FT DADQYEYESHIDDLHLTLGVMVIFERKSILN"
+FT variation 30992..33334
+FT /note="del4 deletion"
+FT /replace=""
+FT variation 31022..33454
+FT /note="del1 deletion"
+FT /replace=""
+FT CDS complement(31620..33380)
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P13520"
+FT /transl_table=11
+FT /gene="old"
+FT /function="nonessential protein; lysogenic conversion;
+FT exonuclease; blocks growth of phage lambda"
+FT /product="Old"
+FT /protein_id="AAD03309.1"
+FT /translation="MTVRLASVSISNFRSCKSTSAILRPFTALVGYNNAGKSNIILAIK
+FT WLLDGSLISESDVYDPTHPVSVEGVIQGITDDTLSLLTEENQQKIAPFIIDGTLTFARR
+FT QEFNKETGKAKKSLDVYDGTTWKKNPGGIDGAISNIFPEPIHIPAMSDAVEDSTKCKNT
+FT TTIGKILSAIVSEIKQEHEEKFSKNISEIGKYLSHNGENRLESLNKIDSGVNKKVNQFF
+FT PDVSVKLHFPTPTLDEIFKSGTLKVFESREDEPVMRDISRFGHGTQRSIQMALIQYLAE
+FT IKKENSESKKSNTLIFIDEPELYLHPSAINSVRESLVTLSESGYQVIISTHSASMLSAK
+FT HAANAIQVCKDSNGTIARKTISEKIEELYKSSSPQLHSAFTLSNSSYLLFSEEVLLVEG
+FT KTETNVLYALYKKINGHELNPSKICIVAVDGKGSLFKMSQIINAIGIKTRILADCDFLS
+FT NILLTEHKDLLSTECDNLLTALIESINSGELSLNTKVTTFESFKSISSKDFIKICNHEK
+FT TQKHIHEIHQKLKDNGIYIWKSGDIEAVYGFGKKQTEWDSLLDCLCDESKDVRAVIKKY
+FT DEMEDFIKWI"
+FT variation complement(31991)
+FT /note="oldam11 mutation"
+FT /replace="a"
+FT /gene="old"
+FT variation complement(32576)
+FT /note="old1 mutation"
+FT /replace="t"
+FT /gene="old"
+FT misc_feature complement(33424)
+FT /note="transcript start 1; old-tin mRNA"
+FT misc_feature complement(33426)
+FT /note="transcript start 2; old-tin mRNA"
+FT misc_feature 33575..33593
+FT /note="cosR; right cohesive end"
+SQ Sequence 33593 BP; 8398 A; 7983 C; 8870 G; 8342 T; 0 other;
+ ggcgaggcgg ggaaagcact gcgcgctgac ggtggtgctg attgtatttt ttcagcgtct 60
+ cagcgcgtcg tgacggcact tagtctgccc gttgaggcgt tgtgtgtctg cggggtgttt 120
+ tgtgcggtgg tgagcgtgtg aggggggatg acggggtgta aaaaagccgc ccgcaggcgg 180
+ cgatgttcag tcgttgtcag tgtccagtga gtagttttta aagcggatga cctcctgacc 240
+ gagccagccg tttatctcgc ggatcctgtc ctgtaacggg ataagctcat tgcggacaaa 300
+ gacctttgcc actttctcaa tatcacccag cgacccgacg ttctccggct tgccacccat 360
+ caactgaaag gggatgcggt gcgcgtccag caggtcagcg gcgctggctt ttttgatatt 420
+ aaaaaaatcg tccttcgtcg ccacttcact gagggggata attttaatgc cgtcggcttt 480
+ cccctgtggg gcatagagaa acaggttttt aaagttgttg cggcctttcg acttgaccat 540
+ gttttcgcga agcatttcga tatcgttgcg atcctgcacg gcatcggtga catacatgat 600
+ gtatccggca tgtgcgccat tttcgtaata cttgcggcgg aacaacgtgg ccgactcatt 660
+ cagccaggca gagttaaggg cgctgagata ttccggcagg ccgtacagct cctgattaat 720
+ atccggctcc agcaggtgaa acacggagcc gggcgcgaag gctgtcggct cgttgaagga 780
+ cggcacccac cagtaaacat cctcttccac gccacggcgg gtatattttg ccggtgaggt 840
+ ttccagtctg atgaccttac cggtggtgct gtaacgcttt tccagaaacg cattaccgaa 900
+ caccagaaaa tccagcacaa agcggctgaa atcctgctgg gaaagccatg gatgcgggat 960
+ aaatgtcgag gccagaatat tgcgtttgac gtaaatcggc gagctgtgat gcacggcagc 1020
+ ccgcaggctt tttgccagac cggtaaagct gaccggtggc tcataccatc tgccgttact 1080
+ gatgcactcg acgtaatcca gaatgtcacg gcggtcgagt accggcaccg gctcaccaaa 1140
+ ggtgaatgcc tccattttcg ggccgctggc ggtcattgtt tttgccgcag gttgcggtgt 1200
+ tttccctttt ttcttgctca tcagtaaaac tccagaatgg tggatgtcag cggggtgctg 1260
+ ataccggcgg tgagtggctc atttaacagg gcgtgcatgg tcgcccaggc gaggtcggcg 1320
+ tggctggctt cctcgctgcg gctggcctca taggtggcgc tgcgtccgct gctggtcatg 1380
+ gtcttgcgga tagccataaa cgagctggtg atgtcggtgg cgctgacgtc gtattccaga 1440
+ cagccacggc ggataacgtc ttttgccttg agcaccattg cggttttcat ttccggcgtg 1500
+ tagcggatat cacgcgcggc gggatagaac gagcgcacga gctggaacac gccgacaccg 1560
+ aggccggtgg catcaatacc gatgtattcg acgttgtatt tttcggtgag tttgcggatg 1620
+ gattccgcct gggtggcaaa gtccatgcct ttccactggt gacgctcaag tattctgaat 1680
+ ttgccaccgg ccaccaccgg cggtgccagt accacgcatc cggcgctgtc gccacggtgt 1740
+ gacgggtcgt aaccaatcca taccgggcgg gagccgaacg gattggcggc aaacggcgca 1800
+ tagtcttccc attcttccag cgtgtcgacc atgcagcgtt gcagctcctc gaacgggaac 1860
+ accgatgcct tgtcgtcaac aaattcacac atgaacaggt ttttaaaatc gtcggcgctg 1920
+ ttttcgcgtt tgagctgctc aatgtcgaac agcgtgcagc cgcctttcag ggcgtcctca 1980
+ atggtgacaa tctgtcgcca ctggccgtcc gcacagagaa gcccaccggc aagggcgtta 2040
+ tgactgacgt cgatttccac gcgttcggcg gcgctggcgc gtccccggtt aaacagttca 2100
+ cccgaccaga acgggtaggc gtcgtgcgcc agcgtggacg gggtggagaa ataggtcgag 2160
+ cgcaggtgac tctgtgaggc catacctgat gccaccttac gcagtacctg aaaattcggg 2220
+ atccagaaaa tctcatcgac gtacaggtcg ccgttatgac tctgcgcggt gttggagttg 2280
+ gtgccgagaa aaatcagttt tgcgccgtta ttgcccagga caatcgggtc accggtcagg 2340
+ tcaacgtcaa ccagacgggc aaaggcgatg atgtattcgc ggaacacata cgcctgtgtt 2400
+ ttactggccg acagaaaaat ctggttatga ccggttttca gggcacgcag cagcgcctcg 2460
+ cgggaaaaat aaaacgtcgc gccaatctgg cgggatttca ggatatcgcg gatgcggtgc 2520
+ tcaagcccgg cacgatacca gtgcagctga tagtcgaaag actgctcaaa gaaaatctgc 2580
+ tccagctttt cgatggcctc gtcactgaaa aaattctttt tcggtttgcg ccgtccgcct 2640
+ ttgttacggt tagcgacgtt cggattaagg tctgcctcgt tgccggtctg gctgtagcgg 2700
+ ttgacccgtg ccagtcgttc aatctggcgt cccagcaggt caatttcctt gaagtcaccg 2760
+ ccggttttct gtggtttgat gatgagctgg gtcagccgcg cttccagact catttcgaca 2820
+ cggctgatgg gggcaacgct gtcccagccg tcgcgctgtt tccagctctg cactgtcggg 2880
+ cgtttcatct gcaacatggc ggcaatctgc ggcacggaaa acccctgcca gtacagcagc 2940
+ gccgcctgac gacgcgggtc gtgtaaaaga gtggtgtctg tggtgatggt catgaatacc 3000
+ tcgccgtgat gaatacacgg caaggctact gagtcgcgcc ccgcgattcg ctaaggtgct 3060
+ gttgtgtcag tgataagcca tccgggactg atggcggagg atgcgcatcg tcgggaaact 3120
+ gatgccgaca tgtgactcct ctaatcacta ttcaggactc ctgacaatgg caaaaaaagt 3180
+ ctcaaaattc tttcgtatcg gcgttgaggg tgacacctgt gacgggcgtg tcatcagtgc 3240
+ gcaggatatt caggaaatgg ccgaaacctt tgacccgcgt gtctatggtt gccgcattaa 3300
+ cctggaacat ctgcgcggca tcctgcctga cggtattttt aagcgttatg gcgatgtggc 3360
+ cgaactgaag gccgaaaaga ttgacgatga ttcggcgctg aaaggcaaat gggcgctgtt 3420
+ tgcgaaaatc accccgaccg atgaccttat cgcgatgaac aaggccgcgc agaaggtcta 3480
+ cacctcaatg gaaattcagc cgaactttgc caacaccggc aaatgttatc tggtgggtct 3540
+ ggccgtcacc gatgacccgg caagcctcgg cacggaatac ctggaattct gccgcacggc 3600
+ aaaacacaac cccctgaacc gcttcaaatt aagccctgaa aacctgattt cagtggcaac 3660
+ gcctgttgag ctggaatttg aagacctgcc tgaaaccgtg ttcaccgccc tgaccgaaaa 3720
+ ggtgaagtcc atttttggcc gcaaacaggc cagcgatgat gcccgtctga atgacgtgca 3780
+ tgaagcggtg accgctgttg ctgaacatgt gcaggaaaaa ctgagcgcca ctgagcagcg 3840
+ cctcgctgag atggaaaccg ccttttctgc acttaagcag gaggtgactg acagggcgga 3900
+ tgaaaccagc caggcattca cccgcctgaa aaacagtctc gaccacaccg aaagtctgac 3960
+ ccagcagcgc cgcagcaaag ccaccggcgg tggcggtgac gccctgatga cgaactgctg 4020
+ accggcgtca gtcagtccgg gaaaaccttc acgattaacc cttaatttca ggaaaaacta 4080
+ tgcgccagga aacccgcttt aaatttaatg cctacctgtc ccgtgttgcc gaactgaacg 4140
+ gcatcgacgc cggtgatgtg tcgaaaaaat tcaccgttga accgtcggtc acccagaccc 4200
+ tgatgaacac catgcaggag tcctctgact ttctgacccg catcaatatt gtgccggtca 4260
+ gcgaaatgaa aggggaaaaa attggtatcg gtgtcaccgg ctccatcgcc agcactaccg 4320
+ acactgccgg tggtaccgag cgtcagccga aggacttctc gaagctggcg tcaaacaagt 4380
+ acgaatgcga ccagattaac ttcgattttt atatccgcta caaaacgctg gacctgtggg 4440
+ cgcgttatca ggatttccag ctccgtatcc gtaacgccat tatcaaacgc cagtcccttg 4500
+ atttcatcat ggccggtttt aacggcgtga agcgtgccga aacctctgac cgcagcagca 4560
+ atccgatgtt gcaggatgtg gcggtcggct ggctgcagaa ataccgcaat gaagcaccgg 4620
+ cgcgcgtgat gagcaaggtc actgacgagg aaggccgcac cacctctgag gttatccgcg 4680
+ tgggtaaggg cggtgattat gccagccttg atgcactggt gatggatgcg accaacaacc 4740
+ tgattgaacc gtggtatcag gaagaccctg accttgtggt gattgtgggg cgtcagctac 4800
+ tggcggacaa gtatttcccc atcgtcaaca aggagcagga caacagcgaa atgctggccg 4860
+ ctgacgtcat catcagccag aaacgcatcg gtaacctacc agcggtacgc gtcccgtact 4920
+ tcccggcgga tgcgatgctc atcacgaagc tggaaaacct gtccatctac tacatggatg 4980
+ acagccatcg ccgcgtgatt gaggaaaacc cgaaactcga ccgcgtggag aactacgagt 5040
+ caatgaacat tgattacgtg gtggaagact acgccgccgg ttgtctggtg gaaaaaatca 5100
+ aggtcggtga cttctccaca ccggctaagg cgaccgcaga gccgggagcg taaccgatga 5160
+ cgagtcccgc acagcgccac atgatgcggg tctcggcagc gatgaccgcg cagcgggaag 5220
+ ccgccccgct gcgacatgca actgtctatg agcagatgct ggttaagctc gccgcagacc 5280
+ agcgcacact gaaagcgatt tactcaaaag agctgaaggc cgcaaaaaaa cgcgaactgc 5340
+ tgccgttctg gttgccgtgg gtgaacggcg tgctggagct gggcaaaggt gcacaggatg 5400
+ acattctgat gacggtcatg ctgtggcgtc tggataccgg cgatattgcc ggtgcgctgg 5460
+ agattgcccg ttatgccctg aagtacggtc tgaccatgcc gggtaaacac cgccgtaccc 5520
+ cgccgtacat gttcaccgag gaggtagcgc ttgcggccat gcgcgctcac gctgccggtg 5580
+ agtctgtgga tacccgcctg ctgacggaga cccttgaact gaccgccacg gctgacatgc 5640
+ ctgatgaagt gcgcgcaaag ctgcacaaaa tcaccggtct gtttctgcgt gacggtggtg 5700
+ atgccgccgg tgcgctggcg cacctgcaac gtgcgacaca gctcgactgt caggcaggcg 5760
+ tcaaaaaaga gattgaacga ctggagcggg agctgaaacc gaagccggag ccgcagccca 5820
+ aagcggccac ccgcgccccg cgtaagaccc ggagcgtgac accggcaaaa cgtggacgcc 5880
+ cgaaaaagaa agccagttaa caaccgaatg cgccccgcgc cagggcggca cgccggtcag 5940
+ tgacggtgaa tcacctgaca ctgcaccggc gtccaccgcc cgacttttca gaggtagtca 6000
+ tgatgacgct gattattccg cgaaaggagg ctcccgtgtc cggtgagggt acggtggtca 6060
+ tcccgcaacc ggcaggcgac gagccggtga ttaaaaacac gttctttttt cccgatatcg 6120
+ acccgaagcg cgtccgggaa cgtatgcgcc ttgagcagac cgtcgccccc gcccgtctgc 6180
+ gtgaggccat caagtcaggc atggctgaaa cgaatgcgga gctgtacgag taccgcgaac 6240
+ agaaaattgc cgccggtttt acgcgtctgg ctgacgtccc ggcggacgat atcgacggtg 6300
+ aaagcatcaa ggttttttac tacgagcgcg ccgtgtgtgc gatggcgacc gcgtcgcttt 6360
+ atgagcgtta tcgcggtgtg gatgccagtg cgaaaggcga caagaaggct gacagcattg 6420
+ acagcaccat tgatgagctg tggcgggata tgcgctgggc ggtggcgcgc atccagggca 6480
+ agccgcgctg catcgtgagt caaatctgat gaagaccttt gcgctacagg gcgacacgct 6540
+ cgacgccatt tgtgtccgct attacgggcg cactgagggc gtggttgaga ccgtgctcgc 6600
+ cgcaaatccg ggactggctg aactgggggc ggtgctgcca cacggcaccg ccgtcgaact 6660
+ gcccgacgtt cagaccgcgc ccgtggctga aactgtcaat ctgtgggagt aacgcatgac 6720
+ agcagaagaa aaaagcgtcc tgtcgctttt catgattggg gtgctgattg ttgtcggcaa 6780
+ ggtgcttgcc ggtggtgaac ctatcacccc gcgtctgttt atcgggcgca tgttgctcgg 6840
+ tggttttgtc tcgatggttg ccggtgttgt tctggtgcag tttcctgacc tgtcactgcc 6900
+ agcggtgtgc ggcatcggct ccatgctggg tatcgccggt tatcaggtga ttgagattgc 6960
+ cattcagcgc cgctttaagg gcagggggaa acagtaatgc cggtaattaa cacgcatcag 7020
+ aatatcgccg cctttctcga catgctggcc gtgtccgaag ggacggcgaa tcatccactg 7080
+ acgaaaaacc ggggctatga cgtgatagtc accggactgg acgggaagcc ggaaattttc 7140
+ accgactaca gtgaccaccc gttcgcacat ggccgaccgg cgaaggtgtt taaccgtcgc 7200
+ ggtgaaaaat ccacggcctc cggtcgctat cagcagcttt acctgttctg gccgcattac 7260
+ cgcaaacagc ttgccctgcc ggatttcagt ccgttgtcac aggacagact cgccattcag 7320
+ ttgatccgcg aacgcggagc actggatgac atccgggcgg gacgcattga gcgcgccatt 7380
+ tcacgctgtc gcaatatctg ggcgtccctg ccgggtgccg gttacggtca gcgtgagcat 7440
+ tcactggaaa aactggtcac cgtctggcgt accgctggcg gcgtaccggc ttaaacggag 7500
+ taaataccat gaagaaatta tccctttcac tgatgctgaa cgtgtcgctg gcgctgatgc 7560
+ tggcactgtc cctgatttac ccgcagagcg tggccgtcaa ttttgtcgct gcctgggcga 7620
+ ttctggcgac ggttatctgt gtggttgccg gtggtgtggg cgtgtatgcc actgagtatg 7680
+ tgctggaacg ctacgggcgg gagctgccgc cggaatcgct ggccgtgaag attgtcacgt 7740
+ cgctgttttt gcagccggtg ccgtggcgca gacgggcggc ggctctggta gtggtggtgg 7800
+ cgacgtttat ctcgctggtc gctgccgggt ggatttttac cgcgctgatt tatcttgtgg 7860
+ tgtcgctgtt tttccggctg atacgtaaag cctgtcgtca gcgtcttgag gggcgggaac 7920
+ catgtcaagg ctgatgattg tgctggtcgt gttgttatcg ctggcggtgg ccggtctgtt 7980
+ tctggtgaaa cacaaaaatg ccagcctgcg cgcctcgctg gacagggcga acaacgtcgc 8040
+ cagcggtcag cagacgacca tcaccatgct gaaaaatcag cttcatgttg cgctcaccag 8100
+ ggcagataaa aacgagctgg cgcaggtggc actgcgtcag gaactggaga acgccgcgaa 8160
+ acgtgaagca cagcgcgaga aaaccatcac gaggttactt aatgagaacg aagattttcg 8220
+ ccgctggtac ggtgctgacc tgcctgatgc tgtgcgccgg ttgcaccagc gccccgcctg 8280
+ caccgacgcc agtgattgtc cccaacgcat gcccgaaagt gagcctttgc ccgatgccgg 8340
+ gcagtgaccc gcagacgaac ggcgatttaa gtgccgatat ccggcagctt gagaacgcgc 8400
+ tggcacgctg tgccagccag gtaaaaatga ttaaacactg tcaggacgaa aacgatgctc 8460
+ aaacccgaca gcctgcgcag ggcgctgact gatgccgtca cggtgctgaa aactaacccc 8520
+ gatatgctgc ggatattcgt ggataacggg agtattgcct ccacactggc ggcgtcgctg 8580
+ tcattcgaaa agcgttacac gctcaatgtg attgtgaccg actttaccgg tgattttgac 8640
+ ctgctcattg tgccggtgct ggcgtggctg cgggaaaatc agcccgacat catgaccacc 8700
+ gacgaaggcc agaaaaaggg cttcacgttt tatgcagaca tcaacaatga cagcagcttt 8760
+ gatatcagta tcagcctgat gctgaccgag cgcacgctgg tcagtgaggt ggacggcgca 8820
+ ctgcatgtga agaatatctc ggaacccccg ccgccggagc cggtcacccg cccgatggag 8880
+ ctgtatatca atggcgaact ggtgagtaag tgggatgaat gagtttaagc gttttgaaga 8940
+ ccggctgacc ggactgattg aatcgctgtc accgtcaggg cgtcggcgac tgagtgccga 9000
+ actggcgaaa cgtctgcggc agagtcagca gcgtcgggtg atggcacaga aagccccgga 9060
+ cggcacaccc tacgcgccac gccagcagca gagcgtcaga aaaaagaccg gtcgcgttaa 9120
+ gcgaaaaatg tttgcgaaac ttattaccag tcgttttttg catatccgtg ccagcccgga 9180
+ gcaggcatca atggaatttt acggcgggaa gtcgccgaaa atcgccagtg tgcatcagtt 9240
+ tggtctgtcg gaagaaaacc ggaaagacgg taagaaaatt gattatccgg cgcgtcccct 9300
+ gctcggcttt accggtgagg atgtgcagat gattgaagag attatcctgg ctcaccttga 9360
+ gcgttagttt tatccaggca gaggctgatg cgcaattaaa cattgagcgg ccatgctggt 9420
+ cgctcaatgt ttagaggttt atgagtgatt tttatttgat gctttgtatt ctaaaacctt 9480
+ cttattggcg taaaagaatt ttgtatatga caggaatata accagacctg aagtgaaata 9540
+ gacgagggat agtattaata atgctttttt gtgactgtta ttatctttaa tctcctggct 9600
+ taaccattcg gagtcctcct cgtttagctg taagagctta ttgcaggcga tctcaggaag 9660
+ tgtgtctttt ataaatacgt tttgcagtct cttgcaatcg gcaaggctat aagttttatt 9720
+ aaattcaact gctttatttt tgaaggataa aagaactttg tcactataaa catagtacat 9780
+ catgttttta tatggtatgc ctgtggcatc ccttactata acggattgtt cgttgtgtat 9840
+ gtaacatgcg aggagaatgt aaaaaatact ggccagaatt acaattattg ttttaattat 9900
+ gtgtggtggt tttgttatgt caccccagat acgagtaagg aaaaaatacg atgtttttag 9960
+ ttttccatca atcagtccct gctgtatcat tctcacattt tcaatgcctg atacattgat 10020
+ tccgttaatt attttaaata gttgaatgtc gcgccactcg cggtcaagtc tttttaattt 10080
+ tttgtctgaa tatccaaaat tgaaataatg tgcaataagc ctcataaggt tacttttacc 10140
+ aaagctaaaa aatgctaata ctgcaaagct acaaaggaaa aaaacgatta gcccccacac 10200
+ attagtcaca ttatagctga ccattacgct ctccttgaat gttgtctggt agttctacaa 10260
+ atgaatccag atagcataac ttttatatat tgtgcaatct cacatgcatg aacactctcg 10320
+ caaatattca ggaactcgcg cgcgcactgc gcaacatgat tcgcactggc attatcgtcg 10380
+ aaaccgacct taacgccggt cgctgccgcg tgcagaccgg cggcatgtgc accgactggc 10440
+ ttcagtggct gacccatcgc gcaggacgtt cgcgcacatg gtgggcacct tccgtggggg 10500
+ aacaggtgct gattctggcc gtgggtggtg aactcgacac ggcgttcgtt ctgccgggga 10560
+ tttattccgg cgataacccc tcgccgtctg tgtcggcgga tgccctgcat atccgtttcc 10620
+ ctgacggggc ggtgattgaa tatgaacccg aaaccagtgc actcacggta agcggaatta 10680
+ aaacggccag cgtgacggct tccggttctg ttactgccac ggtgccggtg gtcatggtga 10740
+ aagcatcaac ccgcgtcacc ctggacaccc cggaggtggt ctgcaccaac aggctgatta 10800
+ ccggcacgct ggaagtgcag aaaggcggga cgatgcgcgg caacattgaa cacaccggcg 10860
+ gtgaactctc atcaaacggt aaggtactgc atacccataa acaccccggc gacagcggcg 10920
+ gcacaaccgg gagtccttta tgacagcgcg ttatctcgga atgaatcgca gtgatggcct 10980
+ gactgtcact gaccttgagc atatcagcca gagtatcggc gatatcctgc gcacaccggt 11040
+ cggctcacgg gtgatgcgtc gtgattacgg ctcgttgctg gcgtcaatga ttgaccagcc 11100
+ gcagaccccg gcgcttgagt tgcagattaa agtcgcctgt tacatggcag tgctgaaatg 11160
+ ggaaccccgc gtcaccctgt catccgtcac cacggcgcgc agttttgacg ggcgaatgac 11220
+ ggtcacgtta accggccagc acaacgacac cggccagcca ctttcattaa ccatccctgt 11280
+ gagttgaaac catgccgatt atcgacctga accagctacc cgcaccggat gtggtcgagg 11340
+ agctggactt tgaaagcatt ctcgctgaac gcaaggcgac actgatttcc ctttacccgg 11400
+ aagatcagca ggaggcggtc gcccgtaccc tgacactgga atctgagcct ctcgtcaaac 11460
+ tgctggaaga aaatgcttat cgtgagctta tctggcgtca gcgtgtgaat gaggccgcac 11520
+ gggcggtgat gctggcctgt gccgccggta atgaccttga tgtgattggt gccaattaca 11580
+ acaccacgcg cctgactatc accccggcag atgattcgac catcccgccg acaccggcag 11640
+ tgatggaatc tgacaccgat tatcgtctgc gtattcagca ggcttttgag ggcttaagcg 11700
+ tcgccgggtc agtgggagcc tatcagtatc atggtcgcag tgctgacggg cgtgtcgcgg 11760
+ atatttctgt caccagtccg tctccggcct gtgtcaccat ctctgtgctg tcacgtgaaa 11820
+ ataacggcgt cgcatccgaa gacctgctgg ctgtggtgcg taacgccctt aatggcgagg 11880
+ acgtcaggcc ggtggccgac cgcgtgaccg tgcagtctgc cgccatcgtt gaataccaga 11940
+ taaacgccac gctttacctt taccctggtc ccgaaagcga acccatccgc gctgccgctg 12000
+ tgaaaaagct ggaagcgtat atcacggcac agcaccggct ggggcgcgac atccgtctgt 12060
+ ctgccattta tgccgctttg catgtggaag gtgtgcagcg tgtcgaactg gctgcaccac 12120
+ tggccgacat cgtgctcaac agtacgcagg cgtctttctg taccgaatac cgcgtcgtga 12180
+ ccggaggctc ggatgagtga ttcgcgactg ctgccgaccg gctcatcacc gcttgaggtc 12240
+ gccgccgcaa aagcctgtgc ggaaattgaa aaaacgccgg tcagtattcg tgaactgtgg 12300
+ aacccggaca cctgtccggc aaatctgctg ccgtggctgg cgtgggcgtt ttcggtcgac 12360
+ aggtgggatg aaaagtggcc ggaagcgaca aaacgcgccg ttatccgcga tgcctatttc 12420
+ atccactgtc ataagggcac gataggtgca atccggcgtg tggtggagcc gctcggctat 12480
+ ctcatcaacg tgacggagtg gtgggaaaac agtgacccgc ccggcacctt ccggcttgat 12540
+ attggtgtac tggaaagcgg tatcacagag gcaatgtatc aggaaatgga acggctgatt 12600
+ gctgatgcca aacctgcaag ccgtcacctt attggcctga acattacccg ggacattccc 12660
+ ggctatctgt tcgccggtgg tgtggcttac gacggcgatg taattacggt ttaccccgga 12720
+ taagtgagga ataatgagca taaaattcag aaccgttatc accactgccg gtgcagcaaa 12780
+ gctggcagcg gcaaccgcgc cgggaaggcg gaaggtcggc attaccacga tggccgtcgg 12840
+ ggatggcggt ggtaaattgc ctgtcccgga tgccggacag accgggctta tccatgaagt 12900
+ ctggcgacat gcgctgaaca aaatcagcca ggacaaacga aacagtaatt atattatcgc 12960
+ cgagctggtt attccgccgg aggtgggcgg tttctggatg cgtgagcttg gcctgtacga 13020
+ tgatgcggga acgttaattg ccgtggcgaa catggccgaa agctataagc cagcccttgc 13080
+ cgaaggctca ggacgttggc agacctgtcg catggtcatc atcgtcagca gtgtggcctc 13140
+ agtggagctg accattgaca ccacaacggt gatggcgacg caggattacg ttgatgacaa 13200
+ aattgcagag cacgaacagt cacgacgtca cccggacgcc tcgctgacag caaaaggttt 13260
+ tactcagtta agcagtgcga ccaacagcac gtctgaaaca ctggccgcaa cgccgaaagc 13320
+ ggtaaaggcc gcgtatgacc tggctaacgg gaaatatacc gcacaggacg ccaccacagc 13380
+ gcgaaaaggc cttgtccagc ttagtagcgc caccaacagc acgtctgaaa cgctcgccgc 13440
+ aacaccaaaa gccgttaaga cggtaatgga tgaaacgaac aaaaaagcgc cattaaacag 13500
+ ccctgcactg accggaacgc caacgacgcc aactgcgcga cagggaacga ataatactca 13560
+ gatcgcaaac acggctttcg ttatggccgc gattgccgcc cttgtagact cgtcgcctga 13620
+ cgcactgaat acgctgaacg agctggcggc ggcgctgggc aatgacccga attttgctac 13680
+ caccatgact aatgcgcttg cgggtaagca accgaaagat gctaccctga cggcgctggc 13740
+ ggggcttgct actgcggcag acaggtttcc gtattttacg gggaatgatg ttgccagcct 13800
+ ggcgaccctg acaaaagtcg ggcgggatat tctggctaaa tcgaccgttg ccgccgttat 13860
+ cgaatatctc ggtttacagg aaacggtaaa ccgagccggg aacgccgtgc aaaaaaatgg 13920
+ cgataccttg tccggtggac ttacttttga aaacgactca atccttgcct ggattcgaaa 13980
+ tactgactgg gcgaagattg gatttaaaaa tgatgccgat ggtgacactg attcatacat 14040
+ gtggtttgaa acgggggata acggcaatga atatttcaaa tggagaagcc gccagagtac 14100
+ cacaacaaaa gacctgatga cgttgaaatg ggatgcacta aatattcttg ttaatgccgt 14160
+ cattaatggc tgttttggag ttggtacgac gaatgcacta ggtggtagct ctattgttct 14220
+ tggtgataat gataccggat ttaaacagaa tggagacggt attcttgatg tttatgctaa 14280
+ cagtcagcgt gtattccgtt ttcagaatgg agtggctatt gcttttaaaa atattcaggc 14340
+ aggtgatagt aaaaagttct cgctatccag ctctaataca tccacgaaga atattacctt 14400
+ taatttatgg ggtgcttcca cccgtccagt ggttgcagag ttaggcgatg aggccggatg 14460
+ gcatttctat agccagcgaa atacagataa ctcggtaata tttgctgtta acggtcagat 14520
+ gcaacccagc aactggggaa attttgattc ccgctatgtg aaagatgttc gcctgggtac 14580
+ gcgagttgtt caattgatgg cgcgaggtgg tcgttatgaa aaagccggac acacgattac 14640
+ cggattaaga atcattggtg aagtagatgg cgatgatgaa gccatcttca ggccgataca 14700
+ aaaatacatc aatggcacat ggtataacgt tgcgcaggtg taagttatgc agcatttaaa 14760
+ gaacattaag tcaggtaatc caaaaacaaa agagcaatat cagctaacaa agaattttga 14820
+ tgttatctgg ttatggtccg aagacggaaa aaactggtat gaggaagtga agaactttca 14880
+ gccagacaca ataaagattg tttacgatga aaataatatt attgtcgcta tcaccagaga 14940
+ tgcttcaacg cttaatcctg aaggttttag cgttgttgag gttcctgata ttacctccaa 15000
+ ccgacgtgct gacgactcag gtaaatggat gtttaaggat ggtgctgtgg ttaaacggat 15060
+ ttatacggca gatgaacagc aacaacaggc agaatcacaa aaggccgcgt tactttccga 15120
+ agcggaaaac gttattcagc cactggaacg cgctgtcagg ctgaatatgg cgacggatga 15180
+ ggaacgtgca cgactggagt catgggaacg ttacagcgtt ctggtcagcc gtgtggatcc 15240
+ tgcaaatcct gaatggccgg aaatgccgca ataagttgta tgacctctgt tgtgaactta 15300
+ catatctatg gcacagagta aagcctaatc tgaagtccac tctgtgccaa aagcggacct 15360
+ tataaacaag aacaatggtc atatcagtgt gttttgattg catagctaac gtgcgtcttc 15420
+ ctgtacagaa tcataagatg atagggcata ggagatgatt attatcgcgt gttttaaaaa 15480
+ tagctttctg catcaagtgt cacttgcgaa gaggtattgg cgctatgatt ggcatcactc 15540
+ agttcagata atattttaaa tatttggtaa atgttcagat tacacaattc ggtctgcgtc 15600
+ ccgttaaaat tatctactat tagttattac tatgaggtga atggcaagtg ttttccactc 15660
+ gccatttttg ttttttattt ttttaaagcc tttccgacgt ttaaggattt tgccaagcca 15720
+ taaaatattt gatatttcat tcctgttttt atttttggtg agatatttgc gtaacttctt 15780
+ tctctaaaac accaacgtat aaacctatta ttatcttcgg ttgtttctat atagcatatt 15840
+ acattgagac caaaaagaaa ctgaaggaag tcatttgcag ttgtcatgaa ttttggtttt 15900
+ gttgtggaaa tggaactaat atgagtttca agttgtttat atgctaagag gtattcagcg 15960
+ taatcaaaag tatcttttcc agagaggaat tcaaagaact ttaaaaaaat ctcataatgt 16020
+ tcacttccat aataaaatga taaatgatct tttatttctc caagtaaata gttagagtaa 16080
+ tctctttgca tgctaggact ttcaaagtca tctagactaa attgattttt cccatcatca 16140
+ ccgctggcgt gtgatttgat aatttcgagc atccgtaata tatctcttgg gcgataatat 16200
+ gaccagcgaa ggaaattaat aaatgatgtg gggtggttat aattatcatg gacgttgggt 16260
+ gaatcccaag ggaaataatg gtcccatgcc tgtccctttt taagggccat gtcttgctga 16320
+ gtcctaagca gatgatctaa tacctcaaat aattgagagc tacggtggtt aacatattct 16380
+ gtccgccagt ccaagaatac tgaattatct ctgatttttg cgttttgatt ttgtaatcct 16440
+ atggattcaa atatatctgg cctgatcaag agaacggcct tcattctccc ttttcctccc 16500
+ tttatacttg ggaagaaatc gttgttaatc tcccagactg cgtgagctaa tccttttata 16560
+ cattctagat aatcatcgta tggaattgag gaagggcgga tatcaatacc atcgataaat 16620
+ aaaatatgat tttgagataa acgtatttgg ctaaaggcct cttcaaattt tttttgtata 16680
+ taaaataaat ttatctgaaa tctgctctca gaaaaagata aagactctct ttcttcacct 16740
+ tttaaggttg catgcttatg aagtagttcg gctgcaactt ttgactcttg aacaaaactg 16800
+ agggcttgaa ttatttctgg tgaaaatgcc ttaagataat actcatctat tgcatcatta 16860
+ agtgatgaga attttgaata atttagtaac tttgaaattt tcccttcttt atctaatact 16920
+ tgttttgata ttaataaata tagaataact ttccatatgc cagagaagtc agacaaattt 16980
+ aaatgctttt cagatttaag agtgataaat ttttgatatt ctgtctctct tatatattta 17040
+ gttgtagcta gcgtattgtt aatattgtta tttgagagat agatagagta tgcggtttta 17100
+ ccagtacctt tctctcctac aagaaacgat atatttggtt cacataatct agataggtga 17160
+ ttatcacgta tgaacacttt gttaagtaat tctttattct cccttctctt ataattttca 17220
+ gcatcagcat agccaagagt tagatctttg attggtatca ttattacctc aactaaagca 17280
+ attgaaaaac atatggttat aaaaacatag cgctgctctg tcgcatattg tggatatcaa 17340
+ catatgaaat gtaaaggaac tatttgtgga tgaatgaacc gaatcgctgt agcttcggta 17400
+ gtcatctttc agacttgtat aaatgaacaa cttccgcttc tcgctcaaag gaaactgtca 17460
+ gatttgatag cttttgggct atgtaaactg tcagtcggaa aatgagtgag tacaaatcag 17520
+ ggcaggtgag cgaattgccc gccttttctt taccggtggt tgtgctgtcg attagccaac 17580
+ cgggacaaat agcctgacat ctccggcgca actgaaaata ccactcaccc attaaccacg 17640
+ gagttaaacg gatgagtgac tatcatcacg gcgtgcaggt gctggagatt aacgagggca 17700
+ cccgcgtcat ttccaccgta tccacggcca ttgtcggcat ggtctgcacg gccagcgatg 17760
+ cagatgcgga aaccttcccc ctcaataaac ctgtgctgat taccaatgtg cagagcgcaa 17820
+ tttcaaaggc cggtaaaaaa ggcacgctgg cggcatcgtt gcaggccatc gctgaccagt 17880
+ caaaaccggt caccgttgtc atgcgcgtgg aagacggcac cggtgatgac gaggaaacga 17940
+ aactcgcgca gaccgtttcc aatatcatcg gcaccaccga tgaaaacggt cagtacaccg 18000
+ gactaaaagc catgctggcg gcggagtcgg taaccggtgt taaaccgcgt attctcggcg 18060
+ tgccgggact ggataccaaa gaggtggctg ttgcactggc atcagtctgt cagaagctgc 18120
+ gtgctttcgg gtatatcagc gcatggggct gtaaaaccat ttccgaggtg aaagcctatc 18180
+ gtcagaattt cagccagcgt gagctgatgg tcatctggcc ggatttcctc gcatgggata 18240
+ cggtcaccag taccaccgcc accgcgtatg ccaccgcccg tgcgctgggg ctgcgcgcta 18300
+ aaatcgacca ggagcagggc tggcataaaa cgctgtccaa tgtcggggtg aacggtgtta 18360
+ ccggcatcag cgcatctgta ttctgggatt tgcaggagtc cggcaccgat gctgacctgc 18420
+ ttaacgagtc aggcgtcact acgctgattc gccgcgacgg tttccgcttc tggggtaacc 18480
+ gtacctgctc tgatgacccg ctgttcctct ttgaaaacta cacccgcacc gcgcaggtcg 18540
+ tggccgacac gatggctgag gcgcacatgt gggcggtgga caagcccatc actgcaacgc 18600
+ tgattcgcga catcgttgac ggcatcaatg ccaaattccg tgagctgaaa acaaacggct 18660
+ atatcgtgga tgcgacctgc tggttcagcg aagaatccaa cgatgcggaa accctcaagg 18720
+ ccggaaaact gtatatcgac tacgactata caccggtgcc tcctctcgaa aacctgaccc 18780
+ tgcgccagcg tattaccgat aaatacctgg caaatctggt cacctcggtt aacagcaatt 18840
+ aaggagcctg accgatggca atgccgcgca aactcaagtt aatgaacgtc tttctgaacg 18900
+ gctacagcta tcagggcgtt gcaaagtccg tcacgctgcc aaaactgacc cgtaagctcg 18960
+ aaaactatcg cggtgcgggg atgaacggca gcgcaccggt agacctcggc cttgatgacg 19020
+ atgcgctgtc aatggagtgg tcgctcggtg gcttcccgga ttcggttatc tgggagcttt 19080
+ acgccgcaac cggtgtggat gccgtgccga ttcgttttgc aggctcttac cagcgcgacg 19140
+ ataccggcga aacggtggcc gtcgaagtgg tcatgcgtgg acgtcagaaa gaaatcgaca 19200
+ ccggcgaggg taaacaggga gaagacactg agtcgaaaat ctccgtggtc tgcacctatt 19260
+ tccggctgac gatggacggt aaggagctgg tcgaaattga caccatcaac atgattgaga 19320
+ aggtgaacgg cgtcgatcgg ctggagcaac accgccgcaa tatcggcctg tgattttcat 19380
+ ccggtcagcc tggctggccg gttaaccctg attcagaagt gagaaaacca tgaacaaaga 19440
+ aaatgtcatt accctggaca atccggtcaa acgtggtgag caggttatcg aacaggtcac 19500
+ gctgatgaaa cccagtgccg ggacgctacg cggtgtcagt ctggctgcgg ttgcaaactc 19560
+ cgaagtcgat gcactgatta aggtgctgcc gcgcatgacg gcaccgatgc tgaccgagca 19620
+ ggaagtcgcc gcgctggaac tgcctgacct tgtggcgctg gccggtaagg tggtcggttt 19680
+ tttgtcgccg aactcggtgc agtgacgttt ccgaaaaatc tctcggtcga tgacctgatg 19740
+ gcggatgtgg cagtgatatt tcactggccg ccatcagaac tgtatcccat gagcctgacc 19800
+ gaactcatca catggcgcga aaaggcgctc cggcgaagcg gaaacacgaa tgagtaacaa 19860
+ tgtaaaatta caggtattgc tcagggctgt tgaccaggca tcccgcccgt ttaaatccat 19920
+ ccgcacagcg agcaagtcgc tgtcggggga tatccgggaa acacaaaaat cactgcgcga 19980
+ gctgaacggt cacgcatccc gtattgaggg attccgcaag accagtgcac agctcgccgt 20040
+ gactggtcat gcacttgaaa aggcacggca ggaggccgaa gcccttgcca cacagtttaa 20100
+ aaacaccgaa cgtccgaccc gtgctcaggc gaaagtcctg gaatccgcaa agcgtgcggc 20160
+ ggaggactta caggcgaaat ataaccgcct gacagattcc gttaaacgcc agcagcggga 20220
+ actggccgct gtgggaatta atacccgcaa tcttgcacat gatgagcagg gactgaaaaa 20280
+ ccgtatcagt gaaaccaccg cacagcttaa ccgtcagcgt gatgcgctgg tgcgtgtcag 20340
+ tgcgcaacag gcaaaactta acgcagtaaa acagcgttat caggccggaa aggaactggc 20400
+ cggaaatatg gcctcagtgg gcgctgccgg tgtggggatt gcggcggcgg gaacgatggc 20460
+ cggtgttaag ctactgatgc ccggttatga gtttgcgcag aaaaactcag aattacaggc 20520
+ tgtgatcgga gtggcaaaag actccgccga aatggccgca ctccgcaagc aggcgcgcca 20580
+ gctcggcgac aataccgccg cctcggcaga tgatgcagcc ggtgcgcaga ttattattgc 20640
+ gaaagccggt ggggatgttg atgccattca ggcggcaacg ccggtcacgc tgaacatggc 20700
+ gctggcgaac cgtcgcacaa tggaagaaaa cgccgccctg ctgatgggga tgaaatccgc 20760
+ ctttcagctt tcaaacgata aggtcgctca tatcggggat gttctctcca tgacgatgaa 20820
+ caaaaccgcc gccgattttg acggcatgag cgatgcgctg acctatgccg cacctgtggc 20880
+ aaaaaatgcc ggtgtcagca ttgaagaaac cgccgcaatg gtcggggcgc tgcatgatgc 20940
+ aaaaatcaca ggctcaatgg cggggacggg aagccgtgcc gtgttaagcc gcctgcaggc 21000
+ accgacggga aaagcatggg atgcactcaa agagcttgga gtgaaaacct cagacagcaa 21060
+ aggaaacacc cggccaatat ttaccattct gaaagaaatg caggccagtt ttgagaaaaa 21120
+ ccggctcggt actgcccagc aggctgaata catgaaaact attttcgggg aggaggccag 21180
+ ctcagccgct gccgtgctga tgactgccgc ctcaaccgga aagctggaca aactgaccgc 21240
+ tgcgtttaaa gcctcagacg ggaagaccgc cgagctggta aatatcatgc aggacaacct 21300
+ aggcggtgac tttaaagcgt ttcagtccgc ttatgaggcg gtggggactg acctgtttga 21360
+ ccagcaggaa ggcgcgctgc gtaagctcac gcagacggcc acaaagtatg tgttaaaact 21420
+ cgacggctgg atacagaaaa acaaatcact ggcgtcaacc atcggcatca ttgccggcgg 21480
+ tgcactggcg cttactggca tcatcggtgc cattggcctc gtagcctggc cggttatcac 21540
+ cggcatcaat gccatcatcg cggcagcagg cgcaatgggg gcagtcttca cgacggttgg 21600
+ cagtgctgtt atgaccgcca tcggggctat tagctggccg gttgtggccg tggtggctgc 21660
+ cattgtcgcc ggtgcgttgc ttatccgtaa atactgggag cctgtcagcg cattctttgg 21720
+ tggtgtggtt gaagggctga aagcggcatt tgcgccggtg ggggaactgt tcacgccact 21780
+ taaaccggtt tttgactggc tgggcgaaaa gttacaggcc gcgtggcagt ggtttaaaaa 21840
+ cctgattgcc ccggtcaaag ccacccagga caccctgaac cgttgccgtg acacgggcgt 21900
+ catgttcggg caggcactgg ctgacgcgtt gatgctgccg cttaatgcgt tcaacaaact 21960
+ gcgcagtggt attgactggg tactggaaaa actcggtgtt atcaacaaag agtcagacac 22020
+ acttgaccag accgccgcca gaactcatac cgccacgtat ggtaccggtg actatattcc 22080
+ ggcgaccagc tcttatgcag gctatcaggc ttatcagccg gtcacggcac cggctggccg 22140
+ ctcttatgta gaccagagta aaaacgaata tcacatcagc ctgacggggg ggactgcgcc 22200
+ ggggacacag cttgaccgcc agttacagga tgcgctcgaa aaatacgagc gggataaacg 22260
+ tgcgcgcgcc cgtgccagca tgatgcatga cggttaagga ggtgacgaaa aatgatgctc 22320
+ gcgttaggta tgtttgtttt tatgcgccag acgctgccac accagaccat gcagcgtgaa 22380
+ tcagattatc gctggccgtc aaattcccgt atcggtaaac gggatgcctt tcagtttctc 22440
+ ggtgtgggtg aggaaaacat cacgctggcc ggtgtgcttt atcccgaact gaccggcggc 22500
+ aagctgacga tgaccacgct caggctgatg gcagaggagg ggcgggcgtg gccgttgctg 22560
+ gatggcaccg gcatgattta cggcatgtat gtcatcagca gggtgagtga aacagggagt 22620
+ attttctttg cagacggcac accccggaaa attgatttta cgctgtcact cacccgcgtt 22680
+ gatgaatcac tggccgcgct ttatggcgat atcggtaaac aggcggaatc gctcatcggt 22740
+ aaggccggca gtatggcgac cagattcaca ggtatgacgg gggcgggata atgctggatg 22800
+ cgctgacatt tgatgcaggc agtacgctga cgccggatta catgctgatg ctcgacagca 22860
+ gggatattac cggcaatatc agcgaccgtc tgatgagcat gaccctgacg gataaccggg 22920
+ gctttgaggc tgaccagctt gatattgaac tgaacgatgc cgacgggcag gtcgggctgc 22980
+ cggttcgtgg cgctgtcctg acggtgtata tcggctggaa aggttttgcc ctggtatgca 23040
+ aagggaaatt taccgttgat gaggttgaac accggggcgc accggatgta gtcaccatcc 23100
+ gcgcccggag tgcagatttt cgcgggacgc tcaattcccg ccgggaaggc tcctggcatg 23160
+ acaccacgct cggtgcgatt gttaaggcga tagccacccg taacaggctg gaagccagtg 23220
+ tcgctccgtc actggccgga ataaaaattc cacacatcga ccagtcgcag gagtctgatg 23280
+ cgaaattcct gacccgtctt gcagaacgca acggcggtga ggtgtcggta aaaatgggaa 23340
+ aactgttgtt tctcaaagcg gggcagggag tgacggccag cggtaaaaaa atcccgcagg 23400
+ tcaccataac ccgcagcgac ggcgaccgcc atcattttgc gattgctgac cgtggagcct 23460
+ acaccggtgt aacggcaaaa tggctacaca ctaaagaccc gaagccgcaa aagcagaagg 23520
+ taaaactgaa acgcaaaaag aaagagaaac acctgcgcgc actggagcac ccgaaagcga 23580
+ aaccggtcag gcagaagaaa gcgcctaaag taccggaagc gcgtgaaggt gaatacatgg 23640
+ ccggtgaggc tgacaacgtt tttgccctga ccacggtata tgccacgaaa gcgcaggcca 23700
+ tgcgcgccgc tcaggcgaag tgggataaac tgcaacgggg cgttgcggag ttctctatca 23760
+ gcctggctac cggtcgggca gatatttaca cggaaacacc ggtcaaagtg tctggcttta 23820
+ agcgcgtcat agacgagcag gactggacaa tcactaaggt gacacatttt ctgaataata 23880
+ gcggcttcac gacgtcctta gagcttgagg tcaggctttc tgatgtggag tacgaaacag 23940
+ aagatgatga gtgatgtttt tgttttatct gtttgttttg taaggataaa ttaactaaaa 24000
+ tggcaccatc aacaaaaccg gaagaggtgc tcgcgatgtt tcattgtcct ttatgccagc 24060
+ atgccgcaca tgcgcgtaca agtcgctata tcactgacac gacaaaagag cgttatcatc 24120
+ agtgccagaa cgtgaattgc agcgccacgt tcatcactta tgagtcggta cagcgataca 24180
+ tcgtgaagcc gggagaagtc cacgccgtaa ggccgcaccc gttgccatca gggcagcaaa 24240
+ ttatgtggat gtaattacaa acagaaagcc cctcagtcga ggggcttttt tgtcgatgtg 24300
+ gtcaatgtgt ggacgtgacc agaaataaat ccttttattt caatttattg tacgtaaaaa 24360
+ ataagcccgt gtaagggaga tttagggtgt caccagtagg ggctttcaac ggtacaatgc 24420
+ gggtttgagc ggcataaatt accactgaaa gcccttaaac gttactctac tgtggacact 24480
+ gtgtggacac tctcggcctc agtaccacct cttagcggat taagagaaat ggcgtcctga 24540
+ aggtactctg gcgcaaaatg agcgtaaacc atagtttgct caatccgcgt gtgacctagt 24600
+ atccgttgta gcgtgataat acttcctcca ttaatcatga aatgagtggc aaagctgtgc 24660
+ cttagtgcat gtgtggcttg ccccattggc aaatccggtt ttattgcttt cattgttcgt 24720
+ ctgaagcgag ggtaatcagc atcagggaat aaaaaacctc gtttgttatc cgcgatcatt 24780
+ ttggcaacag cctctgagat cgggacggtg cgtggtttgt ttgttttcgt tttaacaaac 24840
+ gtgacgcggt tatggatgat attttctgct ttcaaacgag ctgcttctcc ccaacgtgct 24900
+ cctgtactca ggcaaagaat cgcaatcttt ttattgtcgc cgtcaagtgc tgcaagcagt 24960
+ aaggcaattt cttcctgtgt tagatagcct gtttctggtt tttcctcctt aagccttttt 25020
+ gtccctctga tagggtgctc accaaagaat aactccgctt caatcagggc tgtaaacatg 25080
+ ccgctaatac atgttaaatc acgattgata ctcgaaggtt taataccctg acttcttcgg 25140
+ gtggcgcagt actggctgat aagcgatttc gtaatttgaa atgcgcatgg gtcattcgtt 25200
+ atttttgtga agatttcaat ttttccaaga ttagatttcc catgctcttc gtgtttaccc 25260
+ tttaaatccc accagatctg tgtcagctcc gacagacgtc gcttgtctgt tggttttgat 25320
+ agccattctt tattgtggtg gttgtacaac gtgtatttct cgaaagcgac agcttcgctt 25380
+ ttcttatcaa acttcctacg gatgcgtttt ccattacgtc cagtagggcg gatgtccact 25440
+ tcatatcgac catcatcgag ttttttgatt gccatcagaa aaccctccga gtggtgtgtt 25500
+ tttttgcgac tactaatcgc ttttttcgtg gtggctgaaa tttagccacc aatagtaggc 25560
+ acttgtgatg aatatattca cgatgaattg ttaaccagtc ttttgaccgg agtggggcga 25620
+ cgttgtttcg ttttgcccaa agtgtgcgag agcgggcgca atttgcccgg actcaggagc 25680
+ gatctgattg gtcatgaacc ataaagtgta tttggtgaat tgtggggtct gcaggatgtt 25740
+ catcatgaca tctgttggag gtgttgaacg accactttca tagtaactca gcgtgccata 25800
+ cggaacccct gttaaatcag caagttgttg tctgctcaaa tactctgatt ttcgcattaa 25860
+ gactatcttc tcgcttatcg tgtttgacat ggtgtttaga tctcaatagt atttagttta 25920
+ gatgtagatt gtttagtgct tggatgtggg cactaaaagg cattataaga cattaaacgc 25980
+ aattcatgag ggctagagga cgacatgagc aagcaagtaa cactcatgac tgatgcgatt 26040
+ ccttatcagg agttcgcaaa actaatagga aaatcgacag gagcggttcg tcggatgatc 26100
+ gataaaggaa agctgcctgt aattgatatg accgatccac aatcagcttc aggtcgtgca 26160
+ ggtgaatatt gggtatacct tccggcatgg aataacggac taaaactggc ttatgaaagc 26220
+ cgccctaaag agattcgtga cggctggttg atgtggttag gtctcggtga accacgttaa 26280
+ ggagaaccgt atgaatgagc ctcgttgtat tgctcagtta ctgcgtaacg aaagccccag 26340
+ ggcgattgac ttcaccatca cccacggtaa ggggcgtaag ggaatcatta tccgcaccaa 26400
+ aaaacagagt ccgttaaaaa aggctctgac ctttctgaaa agccggaggg tatggaaatg 26460
+ acagtgatga cgctcaatct cgttgaaaaa cagccagcag ctatgcgccg gataattggt 26520
+ aagcatcttg ccgttcctcg ctggcaggat acatgtgatt attataatca gatgatggag 26580
+ cgcgaacggt taacggtttg cttccatgct cagttaaaac agcgtcacgc aacgatgtgt 26640
+ tttgaagaaa tgaacgacgt cgaacgtgaa cgactggtat gtgcaattga tgaattgcgt 26700
+ ggtgcattct caaaacgccg tcaggttggc gcaagtgagt atgcatatat tagtttttta 26760
+ acagtcagtc agcgtcgtac tttatttatg catgccggat tgactgaaaa agaattcaac 26820
+ cagccatact ggcgaattaa tgaagagtca tgttactggc gtgatgcttt attccgtgca 26880
+ ttacgtgaat tattcagtct gtttgagtat gcaccgacaa ttctgacgtc ggtaaaaccg 26940
+ gagcaatatc tgcattaagt aattaaccag agtttttaac gcacttaatt gtgcggggct 27000
+ tctttttgcc tggagaaagt tatgcataca gtttctgaaa atcagtgcgg tatatacgca 27060
+ ttactgctgc aacaggccag aaccgaagca caggccgacg ctgcgacgcg cttttcttct 27120
+ catcttgatg ccatgattcg ccacatcaca aaggcggagt tatcccgcgt ggagatagtc 27180
+ gagctgctca gtcaggagtc ggaaaaattt cacaatatcg gattgtctcg cggggaggtg 27240
+ ctttgatgtt ctgttctcgt gcagttgtat tactgaataa cgccttaaaa atcgccgtta 27300
+ tgaaaaatgg cgatttgtct cttattcaac ttggtcttga taaagaaaaa cgcgaaataa 27360
+ ctgaatctgt tatcgcgatt tatcagagtg aattaaacct cctgtctgat gtggtcaatt 27420
+ tacttgttaa acgcgctgta tttcacaagc aaatttcctc cgtggatgaa ctgacgaaat 27480
+ taacgacaga aatcgccagc tattgcgctg atgaatttaa aaaactgaac gacaaaagga 27540
+ actggtaatg ccggacaacg tagattttat tcaggaacaa caggctgaat tactggagcg 27600
+ ccagattaac gcggcaaggg taaaacattg cggtgcttct gcgctggttt gcgaagagtg 27660
+ tgacgcgcca atacctgctg cccgtcgtgc ggcttacccg tcagccacgc gttgtgtttc 27720
+ ctgtcagtca gtctttgaag caaaaaacaa acattaccgg agaacggcat gagtattcgt 27780
+ attgaaattg gcgaacgtta tgtcgttacc agtgacagct ttcagtttat tctccacgag 27840
+ aaaaagagag cggaaagcgg taaaaacgcc ggtcaggaat ggctggcggt ggttggttat 27900
+ tacccgaaat taagccagct cgtttcaggc ctgatgcatc acgatattct gaccggaagc 27960
+ gcaaagtctt ttgctgattt aaacgtgcag gttgagcaac tcagcaagcg ttgttcagag 28020
+ gcttttggct catatggccg ttaaagcctc cgggcgtttt gtccctccgt cagcatttgc 28080
+ cgcaggcacc ggtaagatgt ttaccggtgc ttatgcatgg aacgcgccac gcgaggccgt 28140
+ cgggcgcgaa agacccctta cacgtgacga gatgcgtcag atgcaaggtg ttttatccac 28200
+ gattaaccgc ctgccttact ttttgcgctc gctgtttact tcacgctatg actacatccg 28260
+ gcgcaataaa agcccggtgc acgggtttta tttcctcaca tccacttttc agcgtcgttt 28320
+ atggccgcgc attgagcgtg tgaatcagcg ccatgaaatg aacaccgacg cgtcgttgct 28380
+ gtttctggca gagcgtgacc actatgcgcg cctgccggga atgaatgaca aggagctgaa 28440
+ aaagtttgcc gcccgtatct catcgcagct tttcatgatg tatgaggaac tcagcgatgc 28500
+ ctgggtggat gcacatggcg aaaaagaatc gctgtttacg gatgaggcgc aggctcacct 28560
+ ctatggtcat gttgctggcg ctgcacgtgc tttcaatatt tccccgcttt actggaaaaa 28620
+ ataccgtaaa ggacagatga ccacgaggca ggcatattct gccattgccc gtctgtttaa 28680
+ cgatgagtgg tggactcatc agctcaaagg ccagcgtatg cgctggcatg agacgttact 28740
+ gattgctgtc ggggaggtga ataaagaccg ttctccttat gccagtaaac atgccattcg 28800
+ tgatgtgcgt gcacgccgcc aagcaaatct ggaatttctt aaatcgtgtg accttgaaaa 28860
+ cagggaaacc ggcgagcgca tcgaccttat cagtaaggtg atgggcagta tttctaatcc 28920
+ tgaaattcgc cggatggagc tgatgaacac cattgccggt attgagcgtt acgccgccgc 28980
+ agagggtgat gtggggatgt ttatcacgct taccgcgcct tcaaagtatc acccgacacg 29040
+ tcaggtcgga aaaggcgaaa gtaaaaccgt ccagctaaat cacggctgga acgatgaggc 29100
+ atttaatcca aaggatgcgc agcgttatct ctgccatatc tggagcctga tgcgcacggc 29160
+ attcaaagat aatgatttac aggtctacgg tttgcgtgtc gtcgagccac accacgacgg 29220
+ aacgccgcac tggcatatga tgcttttttg taatccacgc cagcgtaacc agattatcga 29280
+ aatcatgcgt cgctatgcgc tcaaagagga tggcgacgaa agaggagccg cgcgaaaccg 29340
+ ttttcaggca aaacacctta accagggcgg tgctgcgggg tatatcgcga aatacatctc 29400
+ aaaaaacatc gatggctatg cactggatgg tcagctcgat aacgataccg gcagaccgct 29460
+ gaaagacact gctgcggctg ttaccgcatg ggcgtcaacg tggcgcatcc cacaatttaa 29520
+ aacggttggt ctgccgacaa tgggggctta ccgtgaacta cgcaaattgc ctcgcggcgt 29580
+ cagcattgct gatgagtttg acgagcgcgt cgaggctgca cgcgccgccg cagacagtgg 29640
+ tgattttgcg ttgtatatca gcgcgcaggg tggggcaaat gtcccgcgcg attgtcagac 29700
+ tgtcagggtc gcccgtagtc cgtcggatga ggttaacgag tacgaggaag aagtcgagag 29760
+ agtggtcggc atttacgcgc cgcatctcgg cgcgcgtcat attcatatca ccagaacgac 29820
+ ggactggcgc attgtgccga aagttccggt cgttgagcct ctgactttaa aaagcggcat 29880
+ cgccgcgcct cggagtcctg tcaataactg tggaaagctc accggtggtg atacttcgtt 29940
+ accggctccc acaccttctg agcacgccgc agcagtgctt aatctggttg atgacggtgt 30000
+ tattgaatgg aatgaaccgg aggtcgtgag ggcgctcagg ggcgcattaa aatacgacat 30060
+ gagaacgcca aaccgtcagc aaagaaacgg aagcccgtta aaaccgcatg aaattgcacc 30120
+ atctgccaga ctgaccaggt ctgaacgatt gcagatcacc cgtatccgcg ttgaccttgc 30180
+ tcagaacggt atcaggcctc agcgatggga acttgaggcg ctggcgcgtg gagcaaccgt 30240
+ aaattatgac gggaaaaaat tcacgtatcc ggtcgctgat gagtggccgg gattctcaac 30300
+ agtaatggag tggacatgat ggcaaaaatt cacgaggtaa agctgcacgc aaaatatttc 30360
+ gaccttgtac tggaaggaaa gaaacgcgca gagtttcgga aaaaagatcg taattatgag 30420
+ cgcggggaca cgttgatttt gcatgaatgg gtgcagggtg tgtttacggg gcgaaaggtt 30480
+ gaagcccgga taacagatgt tactgacctg tcagactggc tggaagatta tgtcttgctg 30540
+ cacagaatac taggtgagat actgccgctg actgtcaagc tggcagtatc ctccacattt 30600
+ tttataaatc taaggcgaag aacggtggca atatatttgc tttaaattgt aaataagcaa 30660
+ attattctaa gccacaaatg atatcaattg agtatcgatt ttcgttcgaa tataaccata 30720
+ acgccaagcg tcaaatgtaa gtcatctata tgtgattcgt attcatattg gtcagcatca 30780
+ ggagatagta aatgctctgc tgctgtaaaa acatcaattg taacagatat atcgtctgga 30840
+ atttcttcat ttaattcgga tatgtatttt ttatatttgt tttttaattt ccttatcatg 30900
+ tcttccttat ggtggattgt agtgcagtaa ttttttttag ttgaatcatt gtatgaatgg 30960
+ aatgaacagt ttaatgctag gccaagactt gaatttcctg tagggcctgt gtgtttaaag 31020
+ tattcaagat ttcttcttat gtcagaaaat aaaagacttt tacagggggc atttccttta 31080
+ acttctattg cacatattgg aatgtcgaaa aatggtctgc tgtccaatat ggcaatatca 31140
+ attctgcctg atcttgttgt gtttgtgttt tttctgacag ttttttttga gaatattttt 31200
+ tgggggtcag accgtttcga taaaggaact gttgcattaa tgaatttatt tgtatggtac 31260
+ tcaaatataa ctttgtaatc accatggcga aaagatttta tttctaacaa cgatagacct 31320
+ atagatacgg tagctacata ttcagtgttt atttttgcac cagacctgaa atccattaaa 31380
+ taagttgcgt cggctgactt tttcattcca tcttttacag ctttcacaat atccatgtta 31440
+ ttcatatcaa caccttttat attccagaat tataaatctt aatgattgat ttgtttgtgt 31500
+ gtttaggtta ctagattgac gtacttatag cacattctga ttttgaaatc accatccttg 31560
+ ggtaaagcac agcctatcac tattggttta gtactgtgct gtaatagtat cagattaagt 31620
+ taaatccatt ttatgaaatc ttccatttca tcatattttt ttattactgc tctaacatct 31680
+ ttgctttcat cacataaaca atctaaaaga ctatcccatt cagtttgttt tttaccaaat 31740
+ ccataaacag cctctatatc accggatttc catatataga ttccgttatc tttcaatttt 31800
+ tgatgtattt catgtatatg cttttgtgtt ttttcatggt tacatatttt gatgaagtct 31860
+ ttgcttgaaa tgcttttgaa tgactcaaaa gtagtaactt ttgtatttaa actaagctct 31920
+ cctgagttga ttgattcaat caaagcagtc aatagattat cacattcagt actaagtaag 31980
+ tctttatgct ctgttaatag aatatttgat aagaaatcac aatcagctag aatccttgtt 32040
+ tttatgccga tggcattgat gatttgtgac atcttaaata aactaccctt accgtcaacg 32100
+ gcaacaatgc agattttact cgggttgagt tcatgtccgt taattttttt ataaagtgca 32160
+ tatagaacgt ttgtctctgt tttcccttca acaagcaaaa cttcttcaga aaataaaagg 32220
+ tatgatgaat tagaaagcgt gaatgctgag tgcaattgcg gtgatgaaga tttatataat 32280
+ tcttcgattt tttcagatat agtcttcctt gctatggttc cattagaatc cttacaaacc 32340
+ tgaatcgcat ttgctgcatg ctttgcagaa agcatactgg ctgagtgagt tgatattata 32400
+ acctgatacc ctgattcact taatgtgaca agtgattctc tgacagaatt aatggctgaa 32460
+ gggtgtaaat ataactcagg ttcatcaatg aaaatcaaag tgtttgattt ttttgattcg 32520
+ ctgttttctt tttttatttc ggccaggtat tgaattaatg ccatttgaat ggaacgttgt 32580
+ gttccgtgac caaatcggct gatatctctc attaccggtt catcttcccg agactcaaaa 32640
+ actttcagag tgccggattt aaatatctca tctaatgtcg gtgtgggaaa gtgtaacttt 32700
+ acacttacgt caggaaaaaa ttggtttact tttttattta cacctgagtc tattttatta 32760
+ aggctttcta atctgttctc accgttgtga gaaagatatt tacctatttc tgatatgttt 32820
+ tttgaaaatt tttcttcgtg ttcttgtttt atttcagaaa caattgcgga aagtatcttt 32880
+ cctattgtgg tcgtgttttt gcattttgtt gagtcttcga cagcgtcaga cattgcaggg 32940
+ atatgaattg gttccggaaa tatattggag attgcaccat ctatgccgcc agggtttttc 33000
+ ttccacgtgg taccgtcata tacatccaga ctttttttgg cttttcctgt ttccttatta 33060
+ aattcctgtc ttcttgcaaa ggtaagagtc ccgtcaatta taaacggagc tattttttgt 33120
+ tgattctctt ctgttaacaa agacagagta tcatctgtta taccttgaat aacgccttca 33180
+ acggatacgg ggtgtgtagg atcgtacaca tctgattctg aaatcaaaga gccatctaac 33240
+ agccacttaa ttgctaagat aatatttgat tttcctgcgt tattataacc aactaaagca 33300
+ gtgaaggggc gcaaaatagc cgatgttgac ttacaagaac gaaagttgct aattgaaact 33360
+ gaagcaagac gtacagtcat tatttttcct taaatgtgct atttgtatgc aatgagttca 33420
+ tacgaaacgc ttttttacat tttatagtcg ttgcattcaa gggtgcatga gattgcatta 33480
+ agggaaactg tgatatggct tggcttttga ctggaaatac tgatggctca ttagttttat 33540
+ taaggtgcat taaaaccgcc ccgtgaagcg ggcgggcgag gcggggaaag cac 33593
+//
diff --git a/etc/af063097.fasta b/etc/af063097.fasta
new file mode 100644
index 0000000..8ed3378
--- /dev/null
+++ b/etc/af063097.fasta
@@ -0,0 +1,561 @@
+>AF063097 AF063097 Bacteriophage P2, complete genome.
+ggcgaggcggggaaagcactgcgcgctgacggtggtgctgattgtattttttcagcgtct
+cagcgcgtcgtgacggcacttagtctgcccgttgaggcgttgtgtgtctgcggggtgttt
+tgtgcggtggtgagcgtgtgaggggggatgacggggtgtaaaaaagccgcccgcaggcgg
+cgatgttcagtcgttgtcagtgtccagtgagtagtttttaaagcggatgacctcctgacc
+gagccagccgtttatctcgcggatcctgtcctgtaacgggataagctcattgcggacaaa
+gacctttgccactttctcaatatcacccagcgacccgacgttctccggcttgccacccat
+caactgaaaggggatgcggtgcgcgtccagcaggtcagcggcgctggcttttttgatatt
+aaaaaaatcgtccttcgtcgccacttcactgagggggataattttaatgccgtcggcttt
+cccctgtggggcatagagaaacaggtttttaaagttgttgcggcctttcgacttgaccat
+gttttcgcgaagcatttcgatatcgttgcgatcctgcacggcatcggtgacatacatgat
+gtatccggcatgtgcgccattttcgtaatacttgcggcggaacaacgtggccgactcatt
+cagccaggcagagttaagggcgctgagatattccggcaggccgtacagctcctgattaat
+atccggctccagcaggtgaaacacggagccgggcgcgaaggctgtcggctcgttgaagga
+cggcacccaccagtaaacatcctcttccacgccacggcgggtatattttgccggtgaggt
+ttccagtctgatgaccttaccggtggtgctgtaacgcttttccagaaacgcattaccgaa
+caccagaaaatccagcacaaagcggctgaaatcctgctgggaaagccatggatgcgggat
+aaatgtcgaggccagaatattgcgtttgacgtaaatcggcgagctgtgatgcacggcagc
+ccgcaggctttttgccagaccggtaaagctgaccggtggctcataccatctgccgttact
+gatgcactcgacgtaatccagaatgtcacggcggtcgagtaccggcaccggctcaccaaa
+ggtgaatgcctccattttcgggccgctggcggtcattgtttttgccgcaggttgcggtgt
+tttcccttttttcttgctcatcagtaaaactccagaatggtggatgtcagcggggtgctg
+ataccggcggtgagtggctcatttaacagggcgtgcatggtcgcccaggcgaggtcggcg
+tggctggcttcctcgctgcggctggcctcataggtggcgctgcgtccgctgctggtcatg
+gtcttgcggatagccataaacgagctggtgatgtcggtggcgctgacgtcgtattccaga
+cagccacggcggataacgtcttttgccttgagcaccattgcggttttcatttccggcgtg
+tagcggatatcacgcgcggcgggatagaacgagcgcacgagctggaacacgccgacaccg
+aggccggtggcatcaataccgatgtattcgacgttgtatttttcggtgagtttgcggatg
+gattccgcctgggtggcaaagtccatgcctttccactggtgacgctcaagtattctgaat
+ttgccaccggccaccaccggcggtgccagtaccacgcatccggcgctgtcgccacggtgt
+gacgggtcgtaaccaatccataccgggcgggagccgaacggattggcggcaaacggcgca
+tagtcttcccattcttccagcgtgtcgaccatgcagcgttgcagctcctcgaacgggaac
+accgatgccttgtcgtcaacaaattcacacatgaacaggtttttaaaatcgtcggcgctg
+ttttcgcgtttgagctgctcaatgtcgaacagcgtgcagccgcctttcagggcgtcctca
+atggtgacaatctgtcgccactggccgtccgcacagagaagcccaccggcaagggcgtta
+tgactgacgtcgatttccacgcgttcggcggcgctggcgcgtccccggttaaacagttca
+cccgaccagaacgggtaggcgtcgtgcgccagcgtggacggggtggagaaataggtcgag
+cgcaggtgactctgtgaggccatacctgatgccaccttacgcagtacctgaaaattcggg
+atccagaaaatctcatcgacgtacaggtcgccgttatgactctgcgcggtgttggagttg
+gtgccgagaaaaatcagttttgcgccgttattgcccaggacaatcgggtcaccggtcagg
+tcaacgtcaaccagacgggcaaaggcgatgatgtattcgcggaacacatacgcctgtgtt
+ttactggccgacagaaaaatctggttatgaccggttttcagggcacgcagcagcgcctcg
+cgggaaaaataaaacgtcgcgccaatctggcgggatttcaggatatcgcggatgcggtgc
+tcaagcccggcacgataccagtgcagctgatagtcgaaagactgctcaaagaaaatctgc
+tccagcttttcgatggcctcgtcactgaaaaaattctttttcggtttgcgccgtccgcct
+ttgttacggttagcgacgttcggattaaggtctgcctcgttgccggtctggctgtagcgg
+ttgacccgtgccagtcgttcaatctggcgtcccagcaggtcaatttccttgaagtcaccg
+ccggttttctgtggtttgatgatgagctgggtcagccgcgcttccagactcatttcgaca
+cggctgatgggggcaacgctgtcccagccgtcgcgctgtttccagctctgcactgtcggg
+cgtttcatctgcaacatggcggcaatctgcggcacggaaaacccctgccagtacagcagc
+gccgcctgacgacgcgggtcgtgtaaaagagtggtgtctgtggtgatggtcatgaatacc
+tcgccgtgatgaatacacggcaaggctactgagtcgcgccccgcgattcgctaaggtgct
+gttgtgtcagtgataagccatccgggactgatggcggaggatgcgcatcgtcgggaaact
+gatgccgacatgtgactcctctaatcactattcaggactcctgacaatggcaaaaaaagt
+ctcaaaattctttcgtatcggcgttgagggtgacacctgtgacgggcgtgtcatcagtgc
+gcaggatattcaggaaatggccgaaacctttgacccgcgtgtctatggttgccgcattaa
+cctggaacatctgcgcggcatcctgcctgacggtatttttaagcgttatggcgatgtggc
+cgaactgaaggccgaaaagattgacgatgattcggcgctgaaaggcaaatgggcgctgtt
+tgcgaaaatcaccccgaccgatgaccttatcgcgatgaacaaggccgcgcagaaggtcta
+cacctcaatggaaattcagccgaactttgccaacaccggcaaatgttatctggtgggtct
+ggccgtcaccgatgacccggcaagcctcggcacggaatacctggaattctgccgcacggc
+aaaacacaaccccctgaaccgcttcaaattaagccctgaaaacctgatttcagtggcaac
+gcctgttgagctggaatttgaagacctgcctgaaaccgtgttcaccgccctgaccgaaaa
+ggtgaagtccatttttggccgcaaacaggccagcgatgatgcccgtctgaatgacgtgca
+tgaagcggtgaccgctgttgctgaacatgtgcaggaaaaactgagcgccactgagcagcg
+cctcgctgagatggaaaccgccttttctgcacttaagcaggaggtgactgacagggcgga
+tgaaaccagccaggcattcacccgcctgaaaaacagtctcgaccacaccgaaagtctgac
+ccagcagcgccgcagcaaagccaccggcggtggcggtgacgccctgatgacgaactgctg
+accggcgtcagtcagtccgggaaaaccttcacgattaacccttaatttcaggaaaaacta
+tgcgccaggaaacccgctttaaatttaatgcctacctgtcccgtgttgccgaactgaacg
+gcatcgacgccggtgatgtgtcgaaaaaattcaccgttgaaccgtcggtcacccagaccc
+tgatgaacaccatgcaggagtcctctgactttctgacccgcatcaatattgtgccggtca
+gcgaaatgaaaggggaaaaaattggtatcggtgtcaccggctccatcgccagcactaccg
+acactgccggtggtaccgagcgtcagccgaaggacttctcgaagctggcgtcaaacaagt
+acgaatgcgaccagattaacttcgatttttatatccgctacaaaacgctggacctgtggg
+cgcgttatcaggatttccagctccgtatccgtaacgccattatcaaacgccagtcccttg
+atttcatcatggccggttttaacggcgtgaagcgtgccgaaacctctgaccgcagcagca
+atccgatgttgcaggatgtggcggtcggctggctgcagaaataccgcaatgaagcaccgg
+cgcgcgtgatgagcaaggtcactgacgaggaaggccgcaccacctctgaggttatccgcg
+tgggtaagggcggtgattatgccagccttgatgcactggtgatggatgcgaccaacaacc
+tgattgaaccgtggtatcaggaagaccctgaccttgtggtgattgtggggcgtcagctac
+tggcggacaagtatttccccatcgtcaacaaggagcaggacaacagcgaaatgctggccg
+ctgacgtcatcatcagccagaaacgcatcggtaacctaccagcggtacgcgtcccgtact
+tcccggcggatgcgatgctcatcacgaagctggaaaacctgtccatctactacatggatg
+acagccatcgccgcgtgattgaggaaaacccgaaactcgaccgcgtggagaactacgagt
+caatgaacattgattacgtggtggaagactacgccgccggttgtctggtggaaaaaatca
+aggtcggtgacttctccacaccggctaaggcgaccgcagagccgggagcgtaaccgatga
+cgagtcccgcacagcgccacatgatgcgggtctcggcagcgatgaccgcgcagcgggaag
+ccgccccgctgcgacatgcaactgtctatgagcagatgctggttaagctcgccgcagacc
+agcgcacactgaaagcgatttactcaaaagagctgaaggccgcaaaaaaacgcgaactgc
+tgccgttctggttgccgtgggtgaacggcgtgctggagctgggcaaaggtgcacaggatg
+acattctgatgacggtcatgctgtggcgtctggataccggcgatattgccggtgcgctgg
+agattgcccgttatgccctgaagtacggtctgaccatgccgggtaaacaccgccgtaccc
+cgccgtacatgttcaccgaggaggtagcgcttgcggccatgcgcgctcacgctgccggtg
+agtctgtggatacccgcctgctgacggagacccttgaactgaccgccacggctgacatgc
+ctgatgaagtgcgcgcaaagctgcacaaaatcaccggtctgtttctgcgtgacggtggtg
+atgccgccggtgcgctggcgcacctgcaacgtgcgacacagctcgactgtcaggcaggcg
+tcaaaaaagagattgaacgactggagcgggagctgaaaccgaagccggagccgcagccca
+aagcggccacccgcgccccgcgtaagacccggagcgtgacaccggcaaaacgtggacgcc
+cgaaaaagaaagccagttaacaaccgaatgcgccccgcgccagggcggcacgccggtcag
+tgacggtgaatcacctgacactgcaccggcgtccaccgcccgacttttcagaggtagtca
+tgatgacgctgattattccgcgaaaggaggctcccgtgtccggtgagggtacggtggtca
+tcccgcaaccggcaggcgacgagccggtgattaaaaacacgttcttttttcccgatatcg
+acccgaagcgcgtccgggaacgtatgcgccttgagcagaccgtcgcccccgcccgtctgc
+gtgaggccatcaagtcaggcatggctgaaacgaatgcggagctgtacgagtaccgcgaac
+agaaaattgccgccggttttacgcgtctggctgacgtcccggcggacgatatcgacggtg
+aaagcatcaaggttttttactacgagcgcgccgtgtgtgcgatggcgaccgcgtcgcttt
+atgagcgttatcgcggtgtggatgccagtgcgaaaggcgacaagaaggctgacagcattg
+acagcaccattgatgagctgtggcgggatatgcgctgggcggtggcgcgcatccagggca
+agccgcgctgcatcgtgagtcaaatctgatgaagacctttgcgctacagggcgacacgct
+cgacgccatttgtgtccgctattacgggcgcactgagggcgtggttgagaccgtgctcgc
+cgcaaatccgggactggctgaactgggggcggtgctgccacacggcaccgccgtcgaact
+gcccgacgttcagaccgcgcccgtggctgaaactgtcaatctgtgggagtaacgcatgac
+agcagaagaaaaaagcgtcctgtcgcttttcatgattggggtgctgattgttgtcggcaa
+ggtgcttgccggtggtgaacctatcaccccgcgtctgtttatcgggcgcatgttgctcgg
+tggttttgtctcgatggttgccggtgttgttctggtgcagtttcctgacctgtcactgcc
+agcggtgtgcggcatcggctccatgctgggtatcgccggttatcaggtgattgagattgc
+cattcagcgccgctttaagggcagggggaaacagtaatgccggtaattaacacgcatcag
+aatatcgccgcctttctcgacatgctggccgtgtccgaagggacggcgaatcatccactg
+acgaaaaaccggggctatgacgtgatagtcaccggactggacgggaagccggaaattttc
+accgactacagtgaccacccgttcgcacatggccgaccggcgaaggtgtttaaccgtcgc
+ggtgaaaaatccacggcctccggtcgctatcagcagctttacctgttctggccgcattac
+cgcaaacagcttgccctgccggatttcagtccgttgtcacaggacagactcgccattcag
+ttgatccgcgaacgcggagcactggatgacatccgggcgggacgcattgagcgcgccatt
+tcacgctgtcgcaatatctgggcgtccctgccgggtgccggttacggtcagcgtgagcat
+tcactggaaaaactggtcaccgtctggcgtaccgctggcggcgtaccggcttaaacggag
+taaataccatgaagaaattatccctttcactgatgctgaacgtgtcgctggcgctgatgc
+tggcactgtccctgatttacccgcagagcgtggccgtcaattttgtcgctgcctgggcga
+ttctggcgacggttatctgtgtggttgccggtggtgtgggcgtgtatgccactgagtatg
+tgctggaacgctacgggcgggagctgccgccggaatcgctggccgtgaagattgtcacgt
+cgctgtttttgcagccggtgccgtggcgcagacgggcggcggctctggtagtggtggtgg
+cgacgtttatctcgctggtcgctgccgggtggatttttaccgcgctgatttatcttgtgg
+tgtcgctgtttttccggctgatacgtaaagcctgtcgtcagcgtcttgaggggcgggaac
+catgtcaaggctgatgattgtgctggtcgtgttgttatcgctggcggtggccggtctgtt
+tctggtgaaacacaaaaatgccagcctgcgcgcctcgctggacagggcgaacaacgtcgc
+cagcggtcagcagacgaccatcaccatgctgaaaaatcagcttcatgttgcgctcaccag
+ggcagataaaaacgagctggcgcaggtggcactgcgtcaggaactggagaacgccgcgaa
+acgtgaagcacagcgcgagaaaaccatcacgaggttacttaatgagaacgaagattttcg
+ccgctggtacggtgctgacctgcctgatgctgtgcgccggttgcaccagcgccccgcctg
+caccgacgccagtgattgtccccaacgcatgcccgaaagtgagcctttgcccgatgccgg
+gcagtgacccgcagacgaacggcgatttaagtgccgatatccggcagcttgagaacgcgc
+tggcacgctgtgccagccaggtaaaaatgattaaacactgtcaggacgaaaacgatgctc
+aaacccgacagcctgcgcagggcgctgactgatgccgtcacggtgctgaaaactaacccc
+gatatgctgcggatattcgtggataacgggagtattgcctccacactggcggcgtcgctg
+tcattcgaaaagcgttacacgctcaatgtgattgtgaccgactttaccggtgattttgac
+ctgctcattgtgccggtgctggcgtggctgcgggaaaatcagcccgacatcatgaccacc
+gacgaaggccagaaaaagggcttcacgttttatgcagacatcaacaatgacagcagcttt
+gatatcagtatcagcctgatgctgaccgagcgcacgctggtcagtgaggtggacggcgca
+ctgcatgtgaagaatatctcggaacccccgccgccggagccggtcacccgcccgatggag
+ctgtatatcaatggcgaactggtgagtaagtgggatgaatgagtttaagcgttttgaaga
+ccggctgaccggactgattgaatcgctgtcaccgtcagggcgtcggcgactgagtgccga
+actggcgaaacgtctgcggcagagtcagcagcgtcgggtgatggcacagaaagccccgga
+cggcacaccctacgcgccacgccagcagcagagcgtcagaaaaaagaccggtcgcgttaa
+gcgaaaaatgtttgcgaaacttattaccagtcgttttttgcatatccgtgccagcccgga
+gcaggcatcaatggaattttacggcgggaagtcgccgaaaatcgccagtgtgcatcagtt
+tggtctgtcggaagaaaaccggaaagacggtaagaaaattgattatccggcgcgtcccct
+gctcggctttaccggtgaggatgtgcagatgattgaagagattatcctggctcaccttga
+gcgttagttttatccaggcagaggctgatgcgcaattaaacattgagcggccatgctggt
+cgctcaatgtttagaggtttatgagtgatttttatttgatgctttgtattctaaaacctt
+cttattggcgtaaaagaattttgtatatgacaggaatataaccagacctgaagtgaaata
+gacgagggatagtattaataatgcttttttgtgactgttattatctttaatctcctggct
+taaccattcggagtcctcctcgtttagctgtaagagcttattgcaggcgatctcaggaag
+tgtgtcttttataaatacgttttgcagtctcttgcaatcggcaaggctataagttttatt
+aaattcaactgctttatttttgaaggataaaagaactttgtcactataaacatagtacat
+catgtttttatatggtatgcctgtggcatcccttactataacggattgttcgttgtgtat
+gtaacatgcgaggagaatgtaaaaaatactggccagaattacaattattgttttaattat
+gtgtggtggttttgttatgtcaccccagatacgagtaaggaaaaaatacgatgtttttag
+ttttccatcaatcagtccctgctgtatcattctcacattttcaatgcctgatacattgat
+tccgttaattattttaaatagttgaatgtcgcgccactcgcggtcaagtctttttaattt
+tttgtctgaatatccaaaattgaaataatgtgcaataagcctcataaggttacttttacc
+aaagctaaaaaatgctaatactgcaaagctacaaaggaaaaaaacgattagcccccacac
+attagtcacattatagctgaccattacgctctccttgaatgttgtctggtagttctacaa
+atgaatccagatagcataacttttatatattgtgcaatctcacatgcatgaacactctcg
+caaatattcaggaactcgcgcgcgcactgcgcaacatgattcgcactggcattatcgtcg
+aaaccgaccttaacgccggtcgctgccgcgtgcagaccggcggcatgtgcaccgactggc
+ttcagtggctgacccatcgcgcaggacgttcgcgcacatggtgggcaccttccgtggggg
+aacaggtgctgattctggccgtgggtggtgaactcgacacggcgttcgttctgccgggga
+tttattccggcgataacccctcgccgtctgtgtcggcggatgccctgcatatccgtttcc
+ctgacggggcggtgattgaatatgaacccgaaaccagtgcactcacggtaagcggaatta
+aaacggccagcgtgacggcttccggttctgttactgccacggtgccggtggtcatggtga
+aagcatcaacccgcgtcaccctggacaccccggaggtggtctgcaccaacaggctgatta
+ccggcacgctggaagtgcagaaaggcgggacgatgcgcggcaacattgaacacaccggcg
+gtgaactctcatcaaacggtaaggtactgcatacccataaacaccccggcgacagcggcg
+gcacaaccgggagtcctttatgacagcgcgttatctcggaatgaatcgcagtgatggcct
+gactgtcactgaccttgagcatatcagccagagtatcggcgatatcctgcgcacaccggt
+cggctcacgggtgatgcgtcgtgattacggctcgttgctggcgtcaatgattgaccagcc
+gcagaccccggcgcttgagttgcagattaaagtcgcctgttacatggcagtgctgaaatg
+ggaaccccgcgtcaccctgtcatccgtcaccacggcgcgcagttttgacgggcgaatgac
+ggtcacgttaaccggccagcacaacgacaccggccagccactttcattaaccatccctgt
+gagttgaaaccatgccgattatcgacctgaaccagctacccgcaccggatgtggtcgagg
+agctggactttgaaagcattctcgctgaacgcaaggcgacactgatttccctttacccgg
+aagatcagcaggaggcggtcgcccgtaccctgacactggaatctgagcctctcgtcaaac
+tgctggaagaaaatgcttatcgtgagcttatctggcgtcagcgtgtgaatgaggccgcac
+gggcggtgatgctggcctgtgccgccggtaatgaccttgatgtgattggtgccaattaca
+acaccacgcgcctgactatcaccccggcagatgattcgaccatcccgccgacaccggcag
+tgatggaatctgacaccgattatcgtctgcgtattcagcaggcttttgagggcttaagcg
+tcgccgggtcagtgggagcctatcagtatcatggtcgcagtgctgacgggcgtgtcgcgg
+atatttctgtcaccagtccgtctccggcctgtgtcaccatctctgtgctgtcacgtgaaa
+ataacggcgtcgcatccgaagacctgctggctgtggtgcgtaacgcccttaatggcgagg
+acgtcaggccggtggccgaccgcgtgaccgtgcagtctgccgccatcgttgaataccaga
+taaacgccacgctttacctttaccctggtcccgaaagcgaacccatccgcgctgccgctg
+tgaaaaagctggaagcgtatatcacggcacagcaccggctggggcgcgacatccgtctgt
+ctgccatttatgccgctttgcatgtggaaggtgtgcagcgtgtcgaactggctgcaccac
+tggccgacatcgtgctcaacagtacgcaggcgtctttctgtaccgaataccgcgtcgtga
+ccggaggctcggatgagtgattcgcgactgctgccgaccggctcatcaccgcttgaggtc
+gccgccgcaaaagcctgtgcggaaattgaaaaaacgccggtcagtattcgtgaactgtgg
+aacccggacacctgtccggcaaatctgctgccgtggctggcgtgggcgttttcggtcgac
+aggtgggatgaaaagtggccggaagcgacaaaacgcgccgttatccgcgatgcctatttc
+atccactgtcataagggcacgataggtgcaatccggcgtgtggtggagccgctcggctat
+ctcatcaacgtgacggagtggtgggaaaacagtgacccgcccggcaccttccggcttgat
+attggtgtactggaaagcggtatcacagaggcaatgtatcaggaaatggaacggctgatt
+gctgatgccaaacctgcaagccgtcaccttattggcctgaacattacccgggacattccc
+ggctatctgttcgccggtggtgtggcttacgacggcgatgtaattacggtttaccccgga
+taagtgaggaataatgagcataaaattcagaaccgttatcaccactgccggtgcagcaaa
+gctggcagcggcaaccgcgccgggaaggcggaaggtcggcattaccacgatggccgtcgg
+ggatggcggtggtaaattgcctgtcccggatgccggacagaccgggcttatccatgaagt
+ctggcgacatgcgctgaacaaaatcagccaggacaaacgaaacagtaattatattatcgc
+cgagctggttattccgccggaggtgggcggtttctggatgcgtgagcttggcctgtacga
+tgatgcgggaacgttaattgccgtggcgaacatggccgaaagctataagccagcccttgc
+cgaaggctcaggacgttggcagacctgtcgcatggtcatcatcgtcagcagtgtggcctc
+agtggagctgaccattgacaccacaacggtgatggcgacgcaggattacgttgatgacaa
+aattgcagagcacgaacagtcacgacgtcacccggacgcctcgctgacagcaaaaggttt
+tactcagttaagcagtgcgaccaacagcacgtctgaaacactggccgcaacgccgaaagc
+ggtaaaggccgcgtatgacctggctaacgggaaatataccgcacaggacgccaccacagc
+gcgaaaaggccttgtccagcttagtagcgccaccaacagcacgtctgaaacgctcgccgc
+aacaccaaaagccgttaagacggtaatggatgaaacgaacaaaaaagcgccattaaacag
+ccctgcactgaccggaacgccaacgacgccaactgcgcgacagggaacgaataatactca
+gatcgcaaacacggctttcgttatggccgcgattgccgcccttgtagactcgtcgcctga
+cgcactgaatacgctgaacgagctggcggcggcgctgggcaatgacccgaattttgctac
+caccatgactaatgcgcttgcgggtaagcaaccgaaagatgctaccctgacggcgctggc
+ggggcttgctactgcggcagacaggtttccgtattttacggggaatgatgttgccagcct
+ggcgaccctgacaaaagtcgggcgggatattctggctaaatcgaccgttgccgccgttat
+cgaatatctcggtttacaggaaacggtaaaccgagccgggaacgccgtgcaaaaaaatgg
+cgataccttgtccggtggacttacttttgaaaacgactcaatccttgcctggattcgaaa
+tactgactgggcgaagattggatttaaaaatgatgccgatggtgacactgattcatacat
+gtggtttgaaacgggggataacggcaatgaatatttcaaatggagaagccgccagagtac
+cacaacaaaagacctgatgacgttgaaatgggatgcactaaatattcttgttaatgccgt
+cattaatggctgttttggagttggtacgacgaatgcactaggtggtagctctattgttct
+tggtgataatgataccggatttaaacagaatggagacggtattcttgatgtttatgctaa
+cagtcagcgtgtattccgttttcagaatggagtggctattgcttttaaaaatattcaggc
+aggtgatagtaaaaagttctcgctatccagctctaatacatccacgaagaatattacctt
+taatttatggggtgcttccacccgtccagtggttgcagagttaggcgatgaggccggatg
+gcatttctatagccagcgaaatacagataactcggtaatatttgctgttaacggtcagat
+gcaacccagcaactggggaaattttgattcccgctatgtgaaagatgttcgcctgggtac
+gcgagttgttcaattgatggcgcgaggtggtcgttatgaaaaagccggacacacgattac
+cggattaagaatcattggtgaagtagatggcgatgatgaagccatcttcaggccgataca
+aaaatacatcaatggcacatggtataacgttgcgcaggtgtaagttatgcagcatttaaa
+gaacattaagtcaggtaatccaaaaacaaaagagcaatatcagctaacaaagaattttga
+tgttatctggttatggtccgaagacggaaaaaactggtatgaggaagtgaagaactttca
+gccagacacaataaagattgtttacgatgaaaataatattattgtcgctatcaccagaga
+tgcttcaacgcttaatcctgaaggttttagcgttgttgaggttcctgatattacctccaa
+ccgacgtgctgacgactcaggtaaatggatgtttaaggatggtgctgtggttaaacggat
+ttatacggcagatgaacagcaacaacaggcagaatcacaaaaggccgcgttactttccga
+agcggaaaacgttattcagccactggaacgcgctgtcaggctgaatatggcgacggatga
+ggaacgtgcacgactggagtcatgggaacgttacagcgttctggtcagccgtgtggatcc
+tgcaaatcctgaatggccggaaatgccgcaataagttgtatgacctctgttgtgaactta
+catatctatggcacagagtaaagcctaatctgaagtccactctgtgccaaaagcggacct
+tataaacaagaacaatggtcatatcagtgtgttttgattgcatagctaacgtgcgtcttc
+ctgtacagaatcataagatgatagggcataggagatgattattatcgcgtgttttaaaaa
+tagctttctgcatcaagtgtcacttgcgaagaggtattggcgctatgattggcatcactc
+agttcagataatattttaaatatttggtaaatgttcagattacacaattcggtctgcgtc
+ccgttaaaattatctactattagttattactatgaggtgaatggcaagtgttttccactc
+gccatttttgttttttatttttttaaagcctttccgacgtttaaggattttgccaagcca
+taaaatatttgatatttcattcctgtttttatttttggtgagatatttgcgtaacttctt
+tctctaaaacaccaacgtataaacctattattatcttcggttgtttctatatagcatatt
+acattgagaccaaaaagaaactgaaggaagtcatttgcagttgtcatgaattttggtttt
+gttgtggaaatggaactaatatgagtttcaagttgtttatatgctaagaggtattcagcg
+taatcaaaagtatcttttccagagaggaattcaaagaactttaaaaaaatctcataatgt
+tcacttccataataaaatgataaatgatcttttatttctccaagtaaatagttagagtaa
+tctctttgcatgctaggactttcaaagtcatctagactaaattgatttttcccatcatca
+ccgctggcgtgtgatttgataatttcgagcatccgtaatatatctcttgggcgataatat
+gaccagcgaaggaaattaataaatgatgtggggtggttataattatcatggacgttgggt
+gaatcccaagggaaataatggtcccatgcctgtccctttttaagggccatgtcttgctga
+gtcctaagcagatgatctaatacctcaaataattgagagctacggtggttaacatattct
+gtccgccagtccaagaatactgaattatctctgatttttgcgttttgattttgtaatcct
+atggattcaaatatatctggcctgatcaagagaacggccttcattctcccttttcctccc
+tttatacttgggaagaaatcgttgttaatctcccagactgcgtgagctaatccttttata
+cattctagataatcatcgtatggaattgaggaagggcggatatcaataccatcgataaat
+aaaatatgattttgagataaacgtatttggctaaaggcctcttcaaattttttttgtata
+taaaataaatttatctgaaatctgctctcagaaaaagataaagactctctttcttcacct
+tttaaggttgcatgcttatgaagtagttcggctgcaacttttgactcttgaacaaaactg
+agggcttgaattatttctggtgaaaatgccttaagataatactcatctattgcatcatta
+agtgatgagaattttgaataatttagtaactttgaaattttcccttctttatctaatact
+tgttttgatattaataaatatagaataactttccatatgccagagaagtcagacaaattt
+aaatgcttttcagatttaagagtgataaatttttgatattctgtctctcttatatattta
+gttgtagctagcgtattgttaatattgttatttgagagatagatagagtatgcggtttta
+ccagtacctttctctcctacaagaaacgatatatttggttcacataatctagataggtga
+ttatcacgtatgaacactttgttaagtaattctttattctcccttctcttataattttca
+gcatcagcatagccaagagttagatctttgattggtatcattattacctcaactaaagca
+attgaaaaacatatggttataaaaacatagcgctgctctgtcgcatattgtggatatcaa
+catatgaaatgtaaaggaactatttgtggatgaatgaaccgaatcgctgtagcttcggta
+gtcatctttcagacttgtataaatgaacaacttccgcttctcgctcaaaggaaactgtca
+gatttgatagcttttgggctatgtaaactgtcagtcggaaaatgagtgagtacaaatcag
+ggcaggtgagcgaattgcccgccttttctttaccggtggttgtgctgtcgattagccaac
+cgggacaaatagcctgacatctccggcgcaactgaaaataccactcacccattaaccacg
+gagttaaacggatgagtgactatcatcacggcgtgcaggtgctggagattaacgagggca
+cccgcgtcatttccaccgtatccacggccattgtcggcatggtctgcacggccagcgatg
+cagatgcggaaaccttccccctcaataaacctgtgctgattaccaatgtgcagagcgcaa
+tttcaaaggccggtaaaaaaggcacgctggcggcatcgttgcaggccatcgctgaccagt
+caaaaccggtcaccgttgtcatgcgcgtggaagacggcaccggtgatgacgaggaaacga
+aactcgcgcagaccgtttccaatatcatcggcaccaccgatgaaaacggtcagtacaccg
+gactaaaagccatgctggcggcggagtcggtaaccggtgttaaaccgcgtattctcggcg
+tgccgggactggataccaaagaggtggctgttgcactggcatcagtctgtcagaagctgc
+gtgctttcgggtatatcagcgcatggggctgtaaaaccatttccgaggtgaaagcctatc
+gtcagaatttcagccagcgtgagctgatggtcatctggccggatttcctcgcatgggata
+cggtcaccagtaccaccgccaccgcgtatgccaccgcccgtgcgctggggctgcgcgcta
+aaatcgaccaggagcagggctggcataaaacgctgtccaatgtcggggtgaacggtgtta
+ccggcatcagcgcatctgtattctgggatttgcaggagtccggcaccgatgctgacctgc
+ttaacgagtcaggcgtcactacgctgattcgccgcgacggtttccgcttctggggtaacc
+gtacctgctctgatgacccgctgttcctctttgaaaactacacccgcaccgcgcaggtcg
+tggccgacacgatggctgaggcgcacatgtgggcggtggacaagcccatcactgcaacgc
+tgattcgcgacatcgttgacggcatcaatgccaaattccgtgagctgaaaacaaacggct
+atatcgtggatgcgacctgctggttcagcgaagaatccaacgatgcggaaaccctcaagg
+ccggaaaactgtatatcgactacgactatacaccggtgcctcctctcgaaaacctgaccc
+tgcgccagcgtattaccgataaatacctggcaaatctggtcacctcggttaacagcaatt
+aaggagcctgaccgatggcaatgccgcgcaaactcaagttaatgaacgtctttctgaacg
+gctacagctatcagggcgttgcaaagtccgtcacgctgccaaaactgacccgtaagctcg
+aaaactatcgcggtgcggggatgaacggcagcgcaccggtagacctcggccttgatgacg
+atgcgctgtcaatggagtggtcgctcggtggcttcccggattcggttatctgggagcttt
+acgccgcaaccggtgtggatgccgtgccgattcgttttgcaggctcttaccagcgcgacg
+ataccggcgaaacggtggccgtcgaagtggtcatgcgtggacgtcagaaagaaatcgaca
+ccggcgagggtaaacagggagaagacactgagtcgaaaatctccgtggtctgcacctatt
+tccggctgacgatggacggtaaggagctggtcgaaattgacaccatcaacatgattgaga
+aggtgaacggcgtcgatcggctggagcaacaccgccgcaatatcggcctgtgattttcat
+ccggtcagcctggctggccggttaaccctgattcagaagtgagaaaaccatgaacaaaga
+aaatgtcattaccctggacaatccggtcaaacgtggtgagcaggttatcgaacaggtcac
+gctgatgaaacccagtgccgggacgctacgcggtgtcagtctggctgcggttgcaaactc
+cgaagtcgatgcactgattaaggtgctgccgcgcatgacggcaccgatgctgaccgagca
+ggaagtcgccgcgctggaactgcctgaccttgtggcgctggccggtaaggtggtcggttt
+tttgtcgccgaactcggtgcagtgacgtttccgaaaaatctctcggtcgatgacctgatg
+gcggatgtggcagtgatatttcactggccgccatcagaactgtatcccatgagcctgacc
+gaactcatcacatggcgcgaaaaggcgctccggcgaagcggaaacacgaatgagtaacaa
+tgtaaaattacaggtattgctcagggctgttgaccaggcatcccgcccgtttaaatccat
+ccgcacagcgagcaagtcgctgtcgggggatatccgggaaacacaaaaatcactgcgcga
+gctgaacggtcacgcatcccgtattgagggattccgcaagaccagtgcacagctcgccgt
+gactggtcatgcacttgaaaaggcacggcaggaggccgaagcccttgccacacagtttaa
+aaacaccgaacgtccgacccgtgctcaggcgaaagtcctggaatccgcaaagcgtgcggc
+ggaggacttacaggcgaaatataaccgcctgacagattccgttaaacgccagcagcggga
+actggccgctgtgggaattaatacccgcaatcttgcacatgatgagcagggactgaaaaa
+ccgtatcagtgaaaccaccgcacagcttaaccgtcagcgtgatgcgctggtgcgtgtcag
+tgcgcaacaggcaaaacttaacgcagtaaaacagcgttatcaggccggaaaggaactggc
+cggaaatatggcctcagtgggcgctgccggtgtggggattgcggcggcgggaacgatggc
+cggtgttaagctactgatgcccggttatgagtttgcgcagaaaaactcagaattacaggc
+tgtgatcggagtggcaaaagactccgccgaaatggccgcactccgcaagcaggcgcgcca
+gctcggcgacaataccgccgcctcggcagatgatgcagccggtgcgcagattattattgc
+gaaagccggtggggatgttgatgccattcaggcggcaacgccggtcacgctgaacatggc
+gctggcgaaccgtcgcacaatggaagaaaacgccgccctgctgatggggatgaaatccgc
+ctttcagctttcaaacgataaggtcgctcatatcggggatgttctctccatgacgatgaa
+caaaaccgccgccgattttgacggcatgagcgatgcgctgacctatgccgcacctgtggc
+aaaaaatgccggtgtcagcattgaagaaaccgccgcaatggtcggggcgctgcatgatgc
+aaaaatcacaggctcaatggcggggacgggaagccgtgccgtgttaagccgcctgcaggc
+accgacgggaaaagcatgggatgcactcaaagagcttggagtgaaaacctcagacagcaa
+aggaaacacccggccaatatttaccattctgaaagaaatgcaggccagttttgagaaaaa
+ccggctcggtactgcccagcaggctgaatacatgaaaactattttcggggaggaggccag
+ctcagccgctgccgtgctgatgactgccgcctcaaccggaaagctggacaaactgaccgc
+tgcgtttaaagcctcagacgggaagaccgccgagctggtaaatatcatgcaggacaacct
+aggcggtgactttaaagcgtttcagtccgcttatgaggcggtggggactgacctgtttga
+ccagcaggaaggcgcgctgcgtaagctcacgcagacggccacaaagtatgtgttaaaact
+cgacggctggatacagaaaaacaaatcactggcgtcaaccatcggcatcattgccggcgg
+tgcactggcgcttactggcatcatcggtgccattggcctcgtagcctggccggttatcac
+cggcatcaatgccatcatcgcggcagcaggcgcaatgggggcagtcttcacgacggttgg
+cagtgctgttatgaccgccatcggggctattagctggccggttgtggccgtggtggctgc
+cattgtcgccggtgcgttgcttatccgtaaatactgggagcctgtcagcgcattctttgg
+tggtgtggttgaagggctgaaagcggcatttgcgccggtgggggaactgttcacgccact
+taaaccggtttttgactggctgggcgaaaagttacaggccgcgtggcagtggtttaaaaa
+cctgattgccccggtcaaagccacccaggacaccctgaaccgttgccgtgacacgggcgt
+catgttcgggcaggcactggctgacgcgttgatgctgccgcttaatgcgttcaacaaact
+gcgcagtggtattgactgggtactggaaaaactcggtgttatcaacaaagagtcagacac
+acttgaccagaccgccgccagaactcataccgccacgtatggtaccggtgactatattcc
+ggcgaccagctcttatgcaggctatcaggcttatcagccggtcacggcaccggctggccg
+ctcttatgtagaccagagtaaaaacgaatatcacatcagcctgacgggggggactgcgcc
+ggggacacagcttgaccgccagttacaggatgcgctcgaaaaatacgagcgggataaacg
+tgcgcgcgcccgtgccagcatgatgcatgacggttaaggaggtgacgaaaaatgatgctc
+gcgttaggtatgtttgtttttatgcgccagacgctgccacaccagaccatgcagcgtgaa
+tcagattatcgctggccgtcaaattcccgtatcggtaaacgggatgcctttcagtttctc
+ggtgtgggtgaggaaaacatcacgctggccggtgtgctttatcccgaactgaccggcggc
+aagctgacgatgaccacgctcaggctgatggcagaggaggggcgggcgtggccgttgctg
+gatggcaccggcatgatttacggcatgtatgtcatcagcagggtgagtgaaacagggagt
+attttctttgcagacggcacaccccggaaaattgattttacgctgtcactcacccgcgtt
+gatgaatcactggccgcgctttatggcgatatcggtaaacaggcggaatcgctcatcggt
+aaggccggcagtatggcgaccagattcacaggtatgacgggggcgggataatgctggatg
+cgctgacatttgatgcaggcagtacgctgacgccggattacatgctgatgctcgacagca
+gggatattaccggcaatatcagcgaccgtctgatgagcatgaccctgacggataaccggg
+gctttgaggctgaccagcttgatattgaactgaacgatgccgacgggcaggtcgggctgc
+cggttcgtggcgctgtcctgacggtgtatatcggctggaaaggttttgccctggtatgca
+aagggaaatttaccgttgatgaggttgaacaccggggcgcaccggatgtagtcaccatcc
+gcgcccggagtgcagattttcgcgggacgctcaattcccgccgggaaggctcctggcatg
+acaccacgctcggtgcgattgttaaggcgatagccacccgtaacaggctggaagccagtg
+tcgctccgtcactggccggaataaaaattccacacatcgaccagtcgcaggagtctgatg
+cgaaattcctgacccgtcttgcagaacgcaacggcggtgaggtgtcggtaaaaatgggaa
+aactgttgtttctcaaagcggggcagggagtgacggccagcggtaaaaaaatcccgcagg
+tcaccataacccgcagcgacggcgaccgccatcattttgcgattgctgaccgtggagcct
+acaccggtgtaacggcaaaatggctacacactaaagacccgaagccgcaaaagcagaagg
+taaaactgaaacgcaaaaagaaagagaaacacctgcgcgcactggagcacccgaaagcga
+aaccggtcaggcagaagaaagcgcctaaagtaccggaagcgcgtgaaggtgaatacatgg
+ccggtgaggctgacaacgtttttgccctgaccacggtatatgccacgaaagcgcaggcca
+tgcgcgccgctcaggcgaagtgggataaactgcaacggggcgttgcggagttctctatca
+gcctggctaccggtcgggcagatatttacacggaaacaccggtcaaagtgtctggcttta
+agcgcgtcatagacgagcaggactggacaatcactaaggtgacacattttctgaataata
+gcggcttcacgacgtccttagagcttgaggtcaggctttctgatgtggagtacgaaacag
+aagatgatgagtgatgtttttgttttatctgtttgttttgtaaggataaattaactaaaa
+tggcaccatcaacaaaaccggaagaggtgctcgcgatgtttcattgtcctttatgccagc
+atgccgcacatgcgcgtacaagtcgctatatcactgacacgacaaaagagcgttatcatc
+agtgccagaacgtgaattgcagcgccacgttcatcacttatgagtcggtacagcgataca
+tcgtgaagccgggagaagtccacgccgtaaggccgcacccgttgccatcagggcagcaaa
+ttatgtggatgtaattacaaacagaaagcccctcagtcgaggggcttttttgtcgatgtg
+gtcaatgtgtggacgtgaccagaaataaatccttttatttcaatttattgtacgtaaaaa
+ataagcccgtgtaagggagatttagggtgtcaccagtaggggctttcaacggtacaatgc
+gggtttgagcggcataaattaccactgaaagcccttaaacgttactctactgtggacact
+gtgtggacactctcggcctcagtaccacctcttagcggattaagagaaatggcgtcctga
+aggtactctggcgcaaaatgagcgtaaaccatagtttgctcaatccgcgtgtgacctagt
+atccgttgtagcgtgataatacttcctccattaatcatgaaatgagtggcaaagctgtgc
+cttagtgcatgtgtggcttgccccattggcaaatccggttttattgctttcattgttcgt
+ctgaagcgagggtaatcagcatcagggaataaaaaacctcgtttgttatccgcgatcatt
+ttggcaacagcctctgagatcgggacggtgcgtggtttgtttgttttcgttttaacaaac
+gtgacgcggttatggatgatattttctgctttcaaacgagctgcttctccccaacgtgct
+cctgtactcaggcaaagaatcgcaatctttttattgtcgccgtcaagtgctgcaagcagt
+aaggcaatttcttcctgtgttagatagcctgtttctggtttttcctccttaagccttttt
+gtccctctgatagggtgctcaccaaagaataactccgcttcaatcagggctgtaaacatg
+ccgctaatacatgttaaatcacgattgatactcgaaggtttaataccctgacttcttcgg
+gtggcgcagtactggctgataagcgatttcgtaatttgaaatgcgcatgggtcattcgtt
+atttttgtgaagatttcaatttttccaagattagatttcccatgctcttcgtgtttaccc
+tttaaatcccaccagatctgtgtcagctccgacagacgtcgcttgtctgttggttttgat
+agccattctttattgtggtggttgtacaacgtgtatttctcgaaagcgacagcttcgctt
+ttcttatcaaacttcctacggatgcgttttccattacgtccagtagggcggatgtccact
+tcatatcgaccatcatcgagttttttgattgccatcagaaaaccctccgagtggtgtgtt
+tttttgcgactactaatcgcttttttcgtggtggctgaaatttagccaccaatagtaggc
+acttgtgatgaatatattcacgatgaattgttaaccagtcttttgaccggagtggggcga
+cgttgtttcgttttgcccaaagtgtgcgagagcgggcgcaatttgcccggactcaggagc
+gatctgattggtcatgaaccataaagtgtatttggtgaattgtggggtctgcaggatgtt
+catcatgacatctgttggaggtgttgaacgaccactttcatagtaactcagcgtgccata
+cggaacccctgttaaatcagcaagttgttgtctgctcaaatactctgattttcgcattaa
+gactatcttctcgcttatcgtgtttgacatggtgtttagatctcaatagtatttagttta
+gatgtagattgtttagtgcttggatgtgggcactaaaaggcattataagacattaaacgc
+aattcatgagggctagaggacgacatgagcaagcaagtaacactcatgactgatgcgatt
+ccttatcaggagttcgcaaaactaataggaaaatcgacaggagcggttcgtcggatgatc
+gataaaggaaagctgcctgtaattgatatgaccgatccacaatcagcttcaggtcgtgca
+ggtgaatattgggtataccttccggcatggaataacggactaaaactggcttatgaaagc
+cgccctaaagagattcgtgacggctggttgatgtggttaggtctcggtgaaccacgttaa
+ggagaaccgtatgaatgagcctcgttgtattgctcagttactgcgtaacgaaagccccag
+ggcgattgacttcaccatcacccacggtaaggggcgtaagggaatcattatccgcaccaa
+aaaacagagtccgttaaaaaaggctctgacctttctgaaaagccggagggtatggaaatg
+acagtgatgacgctcaatctcgttgaaaaacagccagcagctatgcgccggataattggt
+aagcatcttgccgttcctcgctggcaggatacatgtgattattataatcagatgatggag
+cgcgaacggttaacggtttgcttccatgctcagttaaaacagcgtcacgcaacgatgtgt
+tttgaagaaatgaacgacgtcgaacgtgaacgactggtatgtgcaattgatgaattgcgt
+ggtgcattctcaaaacgccgtcaggttggcgcaagtgagtatgcatatattagtttttta
+acagtcagtcagcgtcgtactttatttatgcatgccggattgactgaaaaagaattcaac
+cagccatactggcgaattaatgaagagtcatgttactggcgtgatgctttattccgtgca
+ttacgtgaattattcagtctgtttgagtatgcaccgacaattctgacgtcggtaaaaccg
+gagcaatatctgcattaagtaattaaccagagtttttaacgcacttaattgtgcggggct
+tctttttgcctggagaaagttatgcatacagtttctgaaaatcagtgcggtatatacgca
+ttactgctgcaacaggccagaaccgaagcacaggccgacgctgcgacgcgcttttcttct
+catcttgatgccatgattcgccacatcacaaaggcggagttatcccgcgtggagatagtc
+gagctgctcagtcaggagtcggaaaaatttcacaatatcggattgtctcgcggggaggtg
+ctttgatgttctgttctcgtgcagttgtattactgaataacgccttaaaaatcgccgtta
+tgaaaaatggcgatttgtctcttattcaacttggtcttgataaagaaaaacgcgaaataa
+ctgaatctgttatcgcgatttatcagagtgaattaaacctcctgtctgatgtggtcaatt
+tacttgttaaacgcgctgtatttcacaagcaaatttcctccgtggatgaactgacgaaat
+taacgacagaaatcgccagctattgcgctgatgaatttaaaaaactgaacgacaaaagga
+actggtaatgccggacaacgtagattttattcaggaacaacaggctgaattactggagcg
+ccagattaacgcggcaagggtaaaacattgcggtgcttctgcgctggtttgcgaagagtg
+tgacgcgccaatacctgctgcccgtcgtgcggcttacccgtcagccacgcgttgtgtttc
+ctgtcagtcagtctttgaagcaaaaaacaaacattaccggagaacggcatgagtattcgt
+attgaaattggcgaacgttatgtcgttaccagtgacagctttcagtttattctccacgag
+aaaaagagagcggaaagcggtaaaaacgccggtcaggaatggctggcggtggttggttat
+tacccgaaattaagccagctcgtttcaggcctgatgcatcacgatattctgaccggaagc
+gcaaagtcttttgctgatttaaacgtgcaggttgagcaactcagcaagcgttgttcagag
+gcttttggctcatatggccgttaaagcctccgggcgttttgtccctccgtcagcatttgc
+cgcaggcaccggtaagatgtttaccggtgcttatgcatggaacgcgccacgcgaggccgt
+cgggcgcgaaagaccccttacacgtgacgagatgcgtcagatgcaaggtgttttatccac
+gattaaccgcctgccttactttttgcgctcgctgtttacttcacgctatgactacatccg
+gcgcaataaaagcccggtgcacgggttttatttcctcacatccacttttcagcgtcgttt
+atggccgcgcattgagcgtgtgaatcagcgccatgaaatgaacaccgacgcgtcgttgct
+gtttctggcagagcgtgaccactatgcgcgcctgccgggaatgaatgacaaggagctgaa
+aaagtttgccgcccgtatctcatcgcagcttttcatgatgtatgaggaactcagcgatgc
+ctgggtggatgcacatggcgaaaaagaatcgctgtttacggatgaggcgcaggctcacct
+ctatggtcatgttgctggcgctgcacgtgctttcaatatttccccgctttactggaaaaa
+ataccgtaaaggacagatgaccacgaggcaggcatattctgccattgcccgtctgtttaa
+cgatgagtggtggactcatcagctcaaaggccagcgtatgcgctggcatgagacgttact
+gattgctgtcggggaggtgaataaagaccgttctccttatgccagtaaacatgccattcg
+tgatgtgcgtgcacgccgccaagcaaatctggaatttcttaaatcgtgtgaccttgaaaa
+cagggaaaccggcgagcgcatcgaccttatcagtaaggtgatgggcagtatttctaatcc
+tgaaattcgccggatggagctgatgaacaccattgccggtattgagcgttacgccgccgc
+agagggtgatgtggggatgtttatcacgcttaccgcgccttcaaagtatcacccgacacg
+tcaggtcggaaaaggcgaaagtaaaaccgtccagctaaatcacggctggaacgatgaggc
+atttaatccaaaggatgcgcagcgttatctctgccatatctggagcctgatgcgcacggc
+attcaaagataatgatttacaggtctacggtttgcgtgtcgtcgagccacaccacgacgg
+aacgccgcactggcatatgatgcttttttgtaatccacgccagcgtaaccagattatcga
+aatcatgcgtcgctatgcgctcaaagaggatggcgacgaaagaggagccgcgcgaaaccg
+ttttcaggcaaaacaccttaaccagggcggtgctgcggggtatatcgcgaaatacatctc
+aaaaaacatcgatggctatgcactggatggtcagctcgataacgataccggcagaccgct
+gaaagacactgctgcggctgttaccgcatgggcgtcaacgtggcgcatcccacaatttaa
+aacggttggtctgccgacaatgggggcttaccgtgaactacgcaaattgcctcgcggcgt
+cagcattgctgatgagtttgacgagcgcgtcgaggctgcacgcgccgccgcagacagtgg
+tgattttgcgttgtatatcagcgcgcagggtggggcaaatgtcccgcgcgattgtcagac
+tgtcagggtcgcccgtagtccgtcggatgaggttaacgagtacgaggaagaagtcgagag
+agtggtcggcatttacgcgccgcatctcggcgcgcgtcatattcatatcaccagaacgac
+ggactggcgcattgtgccgaaagttccggtcgttgagcctctgactttaaaaagcggcat
+cgccgcgcctcggagtcctgtcaataactgtggaaagctcaccggtggtgatacttcgtt
+accggctcccacaccttctgagcacgccgcagcagtgcttaatctggttgatgacggtgt
+tattgaatggaatgaaccggaggtcgtgagggcgctcaggggcgcattaaaatacgacat
+gagaacgccaaaccgtcagcaaagaaacggaagcccgttaaaaccgcatgaaattgcacc
+atctgccagactgaccaggtctgaacgattgcagatcacccgtatccgcgttgaccttgc
+tcagaacggtatcaggcctcagcgatgggaacttgaggcgctggcgcgtggagcaaccgt
+aaattatgacgggaaaaaattcacgtatccggtcgctgatgagtggccgggattctcaac
+agtaatggagtggacatgatggcaaaaattcacgaggtaaagctgcacgcaaaatatttc
+gaccttgtactggaaggaaagaaacgcgcagagtttcggaaaaaagatcgtaattatgag
+cgcggggacacgttgattttgcatgaatgggtgcagggtgtgtttacggggcgaaaggtt
+gaagcccggataacagatgttactgacctgtcagactggctggaagattatgtcttgctg
+cacagaatactaggtgagatactgccgctgactgtcaagctggcagtatcctccacattt
+tttataaatctaaggcgaagaacggtggcaatatatttgctttaaattgtaaataagcaa
+attattctaagccacaaatgatatcaattgagtatcgattttcgttcgaatataaccata
+acgccaagcgtcaaatgtaagtcatctatatgtgattcgtattcatattggtcagcatca
+ggagatagtaaatgctctgctgctgtaaaaacatcaattgtaacagatatatcgtctgga
+atttcttcatttaattcggatatgtattttttatatttgttttttaatttccttatcatg
+tcttccttatggtggattgtagtgcagtaattttttttagttgaatcattgtatgaatgg
+aatgaacagtttaatgctaggccaagacttgaatttcctgtagggcctgtgtgtttaaag
+tattcaagatttcttcttatgtcagaaaataaaagacttttacagggggcatttccttta
+acttctattgcacatattggaatgtcgaaaaatggtctgctgtccaatatggcaatatca
+attctgcctgatcttgttgtgtttgtgttttttctgacagttttttttgagaatattttt
+tgggggtcagaccgtttcgataaaggaactgttgcattaatgaatttatttgtatggtac
+tcaaatataactttgtaatcaccatggcgaaaagattttatttctaacaacgatagacct
+atagatacggtagctacatattcagtgtttatttttgcaccagacctgaaatccattaaa
+taagttgcgtcggctgactttttcattccatcttttacagctttcacaatatccatgtta
+ttcatatcaacaccttttatattccagaattataaatcttaatgattgatttgtttgtgt
+gtttaggttactagattgacgtacttatagcacattctgattttgaaatcaccatccttg
+ggtaaagcacagcctatcactattggtttagtactgtgctgtaatagtatcagattaagt
+taaatccattttatgaaatcttccatttcatcatatttttttattactgctctaacatct
+ttgctttcatcacataaacaatctaaaagactatcccattcagtttgttttttaccaaat
+ccataaacagcctctatatcaccggatttccatatatagattccgttatctttcaatttt
+tgatgtatttcatgtatatgcttttgtgttttttcatggttacatattttgatgaagtct
+ttgcttgaaatgcttttgaatgactcaaaagtagtaacttttgtatttaaactaagctct
+cctgagttgattgattcaatcaaagcagtcaatagattatcacattcagtactaagtaag
+tctttatgctctgttaatagaatatttgataagaaatcacaatcagctagaatccttgtt
+tttatgccgatggcattgatgatttgtgacatcttaaataaactacccttaccgtcaacg
+gcaacaatgcagattttactcgggttgagttcatgtccgttaatttttttataaagtgca
+tatagaacgtttgtctctgttttcccttcaacaagcaaaacttcttcagaaaataaaagg
+tatgatgaattagaaagcgtgaatgctgagtgcaattgcggtgatgaagatttatataat
+tcttcgattttttcagatatagtcttccttgctatggttccattagaatccttacaaacc
+tgaatcgcatttgctgcatgctttgcagaaagcatactggctgagtgagttgatattata
+acctgataccctgattcacttaatgtgacaagtgattctctgacagaattaatggctgaa
+gggtgtaaatataactcaggttcatcaatgaaaatcaaagtgtttgatttttttgattcg
+ctgttttctttttttatttcggccaggtattgaattaatgccatttgaatggaacgttgt
+gttccgtgaccaaatcggctgatatctctcattaccggttcatcttcccgagactcaaaa
+actttcagagtgccggatttaaatatctcatctaatgtcggtgtgggaaagtgtaacttt
+acacttacgtcaggaaaaaattggtttacttttttatttacacctgagtctattttatta
+aggctttctaatctgttctcaccgttgtgagaaagatatttacctatttctgatatgttt
+tttgaaaatttttcttcgtgttcttgttttatttcagaaacaattgcggaaagtatcttt
+cctattgtggtcgtgtttttgcattttgttgagtcttcgacagcgtcagacattgcaggg
+atatgaattggttccggaaatatattggagattgcaccatctatgccgccagggtttttc
+ttccacgtggtaccgtcatatacatccagactttttttggcttttcctgtttccttatta
+aattcctgtcttcttgcaaaggtaagagtcccgtcaattataaacggagctattttttgt
+tgattctcttctgttaacaaagacagagtatcatctgttataccttgaataacgccttca
+acggatacggggtgtgtaggatcgtacacatctgattctgaaatcaaagagccatctaac
+agccacttaattgctaagataatatttgattttcctgcgttattataaccaactaaagca
+gtgaaggggcgcaaaatagccgatgttgacttacaagaacgaaagttgctaattgaaact
+gaagcaagacgtacagtcattatttttccttaaatgtgctatttgtatgcaatgagttca
+tacgaaacgcttttttacattttatagtcgttgcattcaagggtgcatgagattgcatta
+agggaaactgtgatatggcttggcttttgactggaaatactgatggctcattagttttat
+taaggtgcattaaaaccgccccgtgaagcgggcgggcgaggcggggaaagcac
diff --git a/etc/af063097_v_b132222.crunch b/etc/af063097_v_b132222.crunch
new file mode 100644
index 0000000..b89fbf8
--- /dev/null
+++ b/etc/af063097_v_b132222.crunch
@@ -0,0 +1,199 @@
+76 50.00 207 440 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 201 434 AF063097 AF063097 Bacteriophage P2, complete genome.
+373 52.00 208 1179 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 202 1173 AF063097 AF063097 Bacteriophage P2, complete genome.
+423 65.00 224 1165 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 218 1159 AF063097 AF063097 Bacteriophage P2, complete genome.
+90 71.00 999 1163 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 993 1157 AF063097 AF063097 Bacteriophage P2, complete genome.
+46 29.00 1665 1958 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 2328 2621 AF063097 AF063097 Bacteriophage P2, complete genome.
+104 70.00 1978 2127 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1241 1390 AF063097 AF063097 Bacteriophage P2, complete genome.
+666 77.00 2103 3284 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1366 2547 AF063097 AF063097 Bacteriophage P2, complete genome.
+204 66.00 3360 3794 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 2623 3057 AF063097 AF063097 Bacteriophage P2, complete genome.
+571 81.00 3881 4750 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3152 4021 AF063097 AF063097 Bacteriophage P2, complete genome.
+651 76.00 4814 5872 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4068 5126 AF063097 AF063097 Bacteriophage P2, complete genome.
+214 82.00 5895 6236 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5154 5495 AF063097 AF063097 Bacteriophage P2, complete genome.
+148 67.00 6243 6545 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5508 5810 AF063097 AF063097 Bacteriophage P2, complete genome.
+525 58.00 2135 3361 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1398 2624 AF063097 AF063097 Bacteriophage P2, complete genome.
+143 57.00 3446 3793 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 2709 3056 AF063097 AF063097 Bacteriophage P2, complete genome.
+210 69.00 3883 4326 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3154 3597 AF063097 AF063097 Bacteriophage P2, complete genome.
+44 47.00 4378 4542 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3649 3813 AF063097 AF063097 Bacteriophage P2, complete genome.
+54 53.00 4582 4722 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3853 3993 AF063097 AF063097 Bacteriophage P2, complete genome.
+49 61.00 2173 2286 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1436 1549 AF063097 AF063097 Bacteriophage P2, complete genome.
+271 53.00 2476 3234 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1739 2497 AF063097 AF063097 Bacteriophage P2, complete genome.
+67 49.00 3640 3867 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 2903 3130 AF063097 AF063097 Bacteriophage P2, complete genome.
+226 54.00 3915 4544 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3186 3815 AF063097 AF063097 Bacteriophage P2, complete genome.
+53 48.00 4581 4763 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3852 4034 AF063097 AF063097 Bacteriophage P2, complete genome.
+94 61.00 5178 5393 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4432 4647 AF063097 AF063097 Bacteriophage P2, complete genome.
+91 35.00 5424 5879 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4678 5133 AF063097 AF063097 Bacteriophage P2, complete genome.
+140 52.00 5860 6246 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5119 5505 AF063097 AF063097 Bacteriophage P2, complete genome.
+45 36.00 6355 6555 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5620 5820 AF063097 AF063097 Bacteriophage P2, complete genome.
+73 57.00 5182 5388 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4436 4642 AF063097 AF063097 Bacteriophage P2, complete genome.
+68 52.00 5542 5727 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4796 4981 AF063097 AF063097 Bacteriophage P2, complete genome.
+129 58.00 5885 6229 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5144 5488 AF063097 AF063097 Bacteriophage P2, complete genome.
+290 65.00 6734 7306 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5997 6569 AF063097 AF063097 Bacteriophage P2, complete genome.
+137 44.00 6792 7301 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6055 6564 AF063097 AF063097 Bacteriophage P2, complete genome.
+59 54.00 6826 6993 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6089 6256 AF063097 AF063097 Bacteriophage P2, complete genome.
+167 61.00 7078 7449 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6341 6712 AF063097 AF063097 Bacteriophage P2, complete genome.
+188 90.00 7452 7721 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6716 6985 AF063097 AF063097 Bacteriophage P2, complete genome.
+390 90.00 7699 8232 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6961 7494 AF063097 AF063097 Bacteriophage P2, complete genome.
+130 63.00 7442 7714 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6706 6978 AF063097 AF063097 Bacteriophage P2, complete genome.
+151 47.00 7725 8231 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6987 7493 AF063097 AF063097 Bacteriophage P2, complete genome.
+131 66.00 7453 7713 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6717 6977 AF063097 AF063097 Bacteriophage P2, complete genome.
+108 50.00 7721 8035 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6983 7297 AF063097 AF063097 Bacteriophage P2, complete genome.
+58 60.00 8090 8224 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 7352 7486 AF063097 AF063097 Bacteriophage P2, complete genome.
+413 50.00 8286 9500 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 7991 9205 AF063097 AF063097 Bacteriophage P2, complete genome.
+177 72.00 8485 8775 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 8190 8480 AF063097 AF063097 Bacteriophage P2, complete genome.
+259 57.00 8573 9220 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 8278 8925 AF063097 AF063097 Bacteriophage P2, complete genome.
+71 43.00 9471 9659 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 9179 9367 AF063097 AF063097 Bacteriophage P2, complete genome.
+335 68.00 9720 10343 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10299 10922 AF063097 AF063097 Bacteriophage P2, complete genome.
+161 63.00 10367 10681 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10940 11254 AF063097 AF063097 Bacteriophage P2, complete genome.
+844 62.00 10778 12718 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 11349 13289 AF063097 AF063097 Bacteriophage P2, complete genome.
+66 44.00 9826 10089 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10405 10668 AF063097 AF063097 Bacteriophage P2, complete genome.
+78 49.00 10138 10353 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10717 10932 AF063097 AF063097 Bacteriophage P2, complete genome.
+54 45.00 10369 10590 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10942 11163 AF063097 AF063097 Bacteriophage P2, complete genome.
+55 52.00 9968 10117 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10547 10696 AF063097 AF063097 Bacteriophage P2, complete genome.
+53 42.00 10317 10553 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10890 11126 AF063097 AF063097 Bacteriophage P2, complete genome.
+74 44.00 10725 10985 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 11296 11556 AF063097 AF063097 Bacteriophage P2, complete genome.
+64 49.00 11040 11255 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 11611 11826 AF063097 AF063097 Bacteriophage P2, complete genome.
+140 45.00 10735 11256 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 11306 11827 AF063097 AF063097 Bacteriophage P2, complete genome.
+506 68.00 11340 12266 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 11911 12837 AF063097 AF063097 Bacteriophage P2, complete genome.
+149 49.00 12378 12908 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 12949 13479 AF063097 AF063097 Bacteriophage P2, complete genome.
+650 66.00 11575 12915 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 12146 13486 AF063097 AF063097 Bacteriophage P2, complete genome.
+57 65.00 12664 12783 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 13367 13486 AF063097 AF063097 Bacteriophage P2, complete genome.
+50 49.00 12964 13080 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 13859 13975 AF063097 AF063097 Bacteriophage P2, complete genome.
+99 60.00 13794 14018 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 15011 15235 AF063097 AF063097 Bacteriophage P2, complete genome.
+89 41.00 14145 14480 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 17614 17949 AF063097 AF063097 Bacteriophage P2, complete genome.
+89 49.00 14149 14415 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 17618 17884 AF063097 AF063097 Bacteriophage P2, complete genome.
+77 45.00 14442 14687 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 17920 18165 AF063097 AF063097 Bacteriophage P2, complete genome.
+210 79.00 14153 14488 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 17622 17957 AF063097 AF063097 Bacteriophage P2, complete genome.
+596 82.00 14468 15358 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 17946 18836 AF063097 AF063097 Bacteriophage P2, complete genome.
+290 68.00 15377 15892 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 18855 19370 AF063097 AF063097 Bacteriophage P2, complete genome.
+77 67.00 14617 14763 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 18095 18241 AF063097 AF063097 Bacteriophage P2, complete genome.
+306 52.00 14814 15746 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 18292 19224 AF063097 AF063097 Bacteriophage P2, complete genome.
+46 57.00 15774 15893 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19252 19371 AF063097 AF063097 Bacteriophage P2, complete genome.
+88 63.00 14881 15051 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 18359 18529 AF063097 AF063097 Bacteriophage P2, complete genome.
+137 58.00 15154 15495 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 18632 18973 AF063097 AF063097 Bacteriophage P2, complete genome.
+43 42.00 15637 15795 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19115 19273 AF063097 AF063097 Bacteriophage P2, complete genome.
+197 53.00 16019 16588 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19429 19998 AF063097 AF063097 Bacteriophage P2, complete genome.
+58 36.00 16685 17026 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 20095 20436 AF063097 AF063097 Bacteriophage P2, complete genome.
+262 55.00 17029 17754 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 20442 21167 AF063097 AF063097 Bacteriophage P2, complete genome.
+87 46.00 17794 18045 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 21207 21458 AF063097 AF063097 Bacteriophage P2, complete genome.
+176 69.00 16032 16367 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19442 19777 AF063097 AF063097 Bacteriophage P2, complete genome.
+276 63.00 16401 16988 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19811 20398 AF063097 AF063097 Bacteriophage P2, complete genome.
+602 78.00 17025 18032 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 20438 21445 AF063097 AF063097 Bacteriophage P2, complete genome.
+310 67.00 18108 18650 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 21521 22063 AF063097 AF063097 Bacteriophage P2, complete genome.
+141 78.00 18657 18875 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22073 22291 AF063097 AF063097 Bacteriophage P2, complete genome.
+231 60.00 18891 19343 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22312 22764 AF063097 AF063097 Bacteriophage P2, complete genome.
+477 75.00 19373 20152 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22791 23570 AF063097 AF063097 Bacteriophage P2, complete genome.
+186 72.00 20213 20536 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23628 23951 AF063097 AF063097 Bacteriophage P2, complete genome.
+133 63.00 20609 20827 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 24036 24254 AF063097 AF063097 Bacteriophage P2, complete genome.
+172 47.00 16231 16806 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19641 20216 AF063097 AF063097 Bacteriophage P2, complete genome.
+47 43.00 16828 17016 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 20238 20426 AF063097 AF063097 Bacteriophage P2, complete genome.
+243 49.00 17153 17881 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 20566 21294 AF063097 AF063097 Bacteriophage P2, complete genome.
+186 44.00 17924 18643 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 21337 22056 AF063097 AF063097 Bacteriophage P2, complete genome.
+68 54.00 18664 18876 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22080 22292 AF063097 AF063097 Bacteriophage P2, complete genome.
+108 45.00 19794 20204 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23212 23622 AF063097 AF063097 Bacteriophage P2, complete genome.
+146 53.00 19798 20181 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23216 23599 AF063097 AF063097 Bacteriophage P2, complete genome.
+49 54.00 20304 20447 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23719 23862 AF063097 AF063097 Bacteriophage P2, complete genome.
+56 48.00 21451 21630 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 24517 24696 AF063097 AF063097 Bacteriophage P2, complete genome.
+85 49.00 24663 24875 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 27545 27757 AF063097 AF063097 Bacteriophage P2, complete genome.
+120 55.00 25471 25728 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 27769 28026 AF063097 AF063097 Bacteriophage P2, complete genome.
+88 39.00 27293 27568 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 28592 28867 AF063097 AF063097 Bacteriophage P2, complete genome.
+78 55.00 27563 27763 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 28865 29065 AF063097 AF063097 Bacteriophage P2, complete genome.
+89 43.00 27776 27982 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 29105 29311 AF063097 AF063097 Bacteriophage P2, complete genome.
+106 52.00 28001 28255 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 29315 29569 AF063097 AF063097 Bacteriophage P2, complete genome.
+84 42.00 28298 28594 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 29615 29911 AF063097 AF063097 Bacteriophage P2, complete genome.
+42 34.00 27999 28259 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 29313 29573 AF063097 AF063097 Bacteriophage P2, complete genome.
+64 40.00 28249 27989 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 29563 29303 AF063097 AF063097 Bacteriophage P2, complete genome.
+46 53.00 27909 27775 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 29238 29104 AF063097 AF063097 Bacteriophage P2, complete genome.
+42 30.00 27997 27785 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 29326 29114 AF063097 AF063097 Bacteriophage P2, complete genome.
+75 51.00 25724 25470 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 28022 27768 AF063097 AF063097 Bacteriophage P2, complete genome.
+51 38.00 25650 25468 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 27948 27766 AF063097 AF063097 Bacteriophage P2, complete genome.
+58 56.00 24874 24719 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 27756 27601 AF063097 AF063097 Bacteriophage P2, complete genome.
+56 45.00 22442 22284 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 25484 25326 AF063097 AF063097 Bacteriophage P2, complete genome.
+69 41.00 21869 21672 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 24938 24741 AF063097 AF063097 Bacteriophage P2, complete genome.
+116 59.00 21668 21447 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 24734 24513 AF063097 AF063097 Bacteriophage P2, complete genome.
+51 38.00 20805 20563 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 24232 23990 AF063097 AF063097 Bacteriophage P2, complete genome.
+123 52.00 20536 20192 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23951 23607 AF063097 AF063097 Bacteriophage P2, complete genome.
+375 66.00 20190 19387 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23608 22805 AF063097 AF063097 Bacteriophage P2, complete genome.
+142 53.00 19327 18890 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22748 22311 AF063097 AF063097 Bacteriophage P2, complete genome.
+92 56.00 18881 18657 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22297 22073 AF063097 AF063097 Bacteriophage P2, complete genome.
+225 61.00 18616 18107 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22029 21520 AF063097 AF063097 Bacteriophage P2, complete genome.
+523 71.00 18046 17036 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 21459 20449 AF063097 AF063097 Bacteriophage P2, complete genome.
+408 63.00 16966 16007 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 20376 19417 AF063097 AF063097 Bacteriophage P2, complete genome.
+580 54.00 15892 14468 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19370 17946 AF063097 AF063097 Bacteriophage P2, complete genome.
+153 78.00 14418 14149 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 17887 17618 AF063097 AF063097 Bacteriophage P2, complete genome.
+74 57.00 20516 20295 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23931 23710 AF063097 AF063097 Bacteriophage P2, complete genome.
+95 75.00 20484 20305 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23899 23720 AF063097 AF063097 Bacteriophage P2, complete genome.
+236 43.00 20194 19361 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23612 22779 AF063097 AF063097 Bacteriophage P2, complete genome.
+82 47.00 20072 19797 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 23490 23215 AF063097 AF063097 Bacteriophage P2, complete genome.
+68 52.00 18874 18656 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22290 22072 AF063097 AF063097 Bacteriophage P2, complete genome.
+599 52.00 18650 17028 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22063 20441 AF063097 AF063097 Bacteriophage P2, complete genome.
+149 42.00 17015 16431 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 20425 19841 AF063097 AF063097 Bacteriophage P2, complete genome.
+126 50.00 16388 16032 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19798 19442 AF063097 AF063097 Bacteriophage P2, complete genome.
+66 50.00 18873 18664 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22289 22080 AF063097 AF063097 Bacteriophage P2, complete genome.
+121 47.00 18651 18238 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 22064 21651 AF063097 AF063097 Bacteriophage P2, complete genome.
+88 52.00 18141 17884 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 21554 21297 AF063097 AF063097 Bacteriophage P2, complete genome.
+98 51.00 17751 17458 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 21164 20871 AF063097 AF063097 Bacteriophage P2, complete genome.
+121 56.00 17361 17038 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 20774 20451 AF063097 AF063097 Bacteriophage P2, complete genome.
+190 44.00 16947 16207 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 20357 19617 AF063097 AF063097 Bacteriophage P2, complete genome.
+173 72.00 15888 15556 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19366 19034 AF063097 AF063097 Bacteriophage P2, complete genome.
+251 56.00 15572 14904 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 19050 18382 AF063097 AF063097 Bacteriophage P2, complete genome.
+302 78.00 15357 14824 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 18835 18302 AF063097 AF063097 Bacteriophage P2, complete genome.
+143 73.00 14748 14479 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 18226 17957 AF063097 AF063097 Bacteriophage P2, complete genome.
+125 52.00 14479 14144 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 17948 17613 AF063097 AF063097 Bacteriophage P2, complete genome.
+67 59.00 14750 14574 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 18228 18052 AF063097 AF063097 Bacteriophage P2, complete genome.
+50 45.00 12909 12763 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 13480 13334 AF063097 AF063097 Bacteriophage P2, complete genome.
+682 53.00 12694 10721 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 13265 11292 AF063097 AF063097 Bacteriophage P2, complete genome.
+123 55.00 10710 10366 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 11283 10939 AF063097 AF063097 Bacteriophage P2, complete genome.
+246 60.00 10297 9716 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10876 10295 AF063097 AF063097 Bacteriophage P2, complete genome.
+127 77.00 12746 12513 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 13317 13084 AF063097 AF063097 Bacteriophage P2, complete genome.
+59 82.00 12413 12315 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 12984 12886 AF063097 AF063097 Bacteriophage P2, complete genome.
+437 45.00 12260 10731 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 12831 11302 AF063097 AF063097 Bacteriophage P2, complete genome.
+83 40.00 10651 10367 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 11224 10940 AF063097 AF063097 Bacteriophage P2, complete genome.
+102 50.00 10280 10029 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10859 10608 AF063097 AF063097 Bacteriophage P2, complete genome.
+61 36.00 9983 9714 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10562 10293 AF063097 AF063097 Bacteriophage P2, complete genome.
+611 55.00 12696 11092 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 13267 11663 AF063097 AF063097 Bacteriophage P2, complete genome.
+69 78.00 10935 10801 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 11506 11372 AF063097 AF063097 Bacteriophage P2, complete genome.
+57 54.00 10116 9967 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 10695 10546 AF063097 AF063097 Bacteriophage P2, complete genome.
+46 32.00 9500 9156 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 9205 8861 AF063097 AF063097 Bacteriophage P2, complete genome.
+358 46.00 9499 8228 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 9204 7933 AF063097 AF063097 Bacteriophage P2, complete genome.
+199 58.00 9021 8485 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 8726 8190 AF063097 AF063097 Bacteriophage P2, complete genome.
+43 40.00 9011 8841 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 8716 8546 AF063097 AF063097 Bacteriophage P2, complete genome.
+155 46.00 8774 8229 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 8479 7934 AF063097 AF063097 Bacteriophage P2, complete genome.
+156 49.00 8245 7724 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 7507 6986 AF063097 AF063097 Bacteriophage P2, complete genome.
+100 66.00 7713 7519 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6977 6783 AF063097 AF063097 Bacteriophage P2, complete genome.
+264 74.00 8228 7731 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 7490 6993 AF063097 AF063097 Bacteriophage P2, complete genome.
+135 69.00 7714 7451 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6978 6715 AF063097 AF063097 Bacteriophage P2, complete genome.
+243 53.00 7449 6727 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6712 5990 AF063097 AF063097 Bacteriophage P2, complete genome.
+189 51.00 8223 7723 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 7485 6985 AF063097 AF063097 Bacteriophage P2, complete genome.
+125 61.00 7715 7452 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6979 6716 AF063097 AF063097 Bacteriophage P2, complete genome.
+144 52.00 7448 7050 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6711 6313 AF063097 AF063097 Bacteriophage P2, complete genome.
+66 52.00 7007 6825 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6270 6088 AF063097 AF063097 Bacteriophage P2, complete genome.
+170 47.00 7309 6791 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 6572 6054 AF063097 AF063097 Bacteriophage P2, complete genome.
+66 40.00 6554 6258 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5819 5523 AF063097 AF063097 Bacteriophage P2, complete genome.
+132 51.00 6236 5880 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5495 5139 AF063097 AF063097 Bacteriophage P2, complete genome.
+351 45.00 5878 4817 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5132 4071 AF063097 AF063097 Bacteriophage P2, complete genome.
+127 46.00 4751 4368 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4022 3639 AF063097 AF063097 Bacteriophage P2, complete genome.
+241 61.00 4372 3881 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3643 3152 AF063097 AF063097 Bacteriophage P2, complete genome.
+611 51.00 3876 2134 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3139 1397 AF063097 AF063097 Bacteriophage P2, complete genome.
+98 52.00 6544 6245 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5809 5510 AF063097 AF063097 Bacteriophage P2, complete genome.
+133 58.00 6235 5894 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5494 5153 AF063097 AF063097 Bacteriophage P2, complete genome.
+301 68.00 5871 5245 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5125 4499 AF063097 AF063097 Bacteriophage P2, complete genome.
+161 68.00 5154 4816 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4408 4070 AF063097 AF063097 Bacteriophage P2, complete genome.
+401 66.00 4749 3880 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4020 3151 AF063097 AF063097 Bacteriophage P2, complete genome.
+1286 85.00 3871 2000 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3134 1263 AF063097 AF063097 Bacteriophage P2, complete genome.
+48 58.00 6228 6130 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5487 5389 AF063097 AF063097 Bacteriophage P2, complete genome.
+72 58.00 6069 5884 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5328 5143 AF063097 AF063097 Bacteriophage P2, complete genome.
+125 50.00 5840 5460 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 5094 4714 AF063097 AF063097 Bacteriophage P2, complete genome.
+95 53.00 5393 5109 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4647 4363 AF063097 AF063097 Bacteriophage P2, complete genome.
+44 59.00 4916 4815 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4170 4069 AF063097 AF063097 Bacteriophage P2, complete genome.
+122 47.00 4750 4427 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 4021 3698 AF063097 AF063097 Bacteriophage P2, complete genome.
+168 66.00 4319 3975 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3590 3246 AF063097 AF063097 Bacteriophage P2, complete genome.
+128 52.00 3857 3486 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 3120 2749 AF063097 AF063097 Bacteriophage P2, complete genome.
+199 58.00 3254 2778 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 2517 2041 AF063097 AF063097 Bacteriophage P2, complete genome.
+89 65.00 2729 2544 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1992 1807 AF063097 AF063097 Bacteriophage P2, complete genome.
+63 52.00 2495 2346 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1758 1609 AF063097 AF063097 Bacteriophage P2, complete genome.
+126 54.00 2285 1977 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1548 1240 AF063097 AF063097 Bacteriophage P2, complete genome.
+84 44.00 1958 1665 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 2621 2328 AF063097 AF063097 Bacteriophage P2, complete genome.
+58 45.00 1391 1251 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 2021 1881 AF063097 AF063097 Bacteriophage P2, complete genome.
+264 47.00 1222 362 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1216 356 AF063097 AF063097 Bacteriophage P2, complete genome.
+645 78.00 1209 208 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1203 202 AF063097 AF063097 Bacteriophage P2, complete genome.
+267 49.00 1145 303 big_blast_x_t_2_50000_af063097.fasta_b132222.fasta.00000001 1139 297 AF063097 AF063097 Bacteriophage P2, complete genome.
diff --git a/etc/b132222.embl b/etc/b132222.embl
new file mode 100644
index 0000000..8dc1ddd
--- /dev/null
+++ b/etc/b132222.embl
@@ -0,0 +1,1512 @@
+ID B132222 standard; DNA; PHG; 30624 BP.
+XX
+AC U32222; U51471; X04449; X53318;
+XX
+SV U32222.1
+XX
+DT 27-OCT-1995 (Rel. 45, Created)
+DT 09-SEP-1998 (Rel. 56, Last updated, Version 6)
+XX
+DE Bacteriophage 186, complete sequence.
+XX
+KW .
+XX
+OS Enterobacteria phage 186
+OC Viruses; dsDNA viruses, no RNA stage; Caudovirales; Myoviridae;
+OC P2-like Viruses.
+XX
+RN [1]
+RP 20315-23876
+RX MEDLINE; 87112711.
+RA Kalionis B., Dodd I.B., Egan J.B.;
+RT "Control of gene expression in the P2-related template coliphages. III. DNA
+RT sequence of the major control region of phage 186";
+RL J. Mol. Biol. 191(2):199-209(1986).
+XX
+RN [2]
+RP 23435-24666
+RX MEDLINE; 89199647.
+RA Richardson H., Puspurs A., Egan J.B.;
+RT "Control of gene expression in the P2-related temperate coliphage 186. VI.
+RT Sequence analysis of the early lytic region";
+RL J. Mol. Biol. 206(1):251-255(1989).
+XX
+RN [3]
+RP 22430-23430
+RX MEDLINE; 90317829.
+RA Dodd I.B., Kalionis B., Egan J.B.;
+RT "Control of gene expression in the temperate coliphage 186. VIII. Control
+RT of lysis and lysogeny by a transcriptional switch involving face-to-face
+RT promoters";
+RL J. Mol. Biol. 214(1):27-37(1990).
+XX
+RN [4]
+RP 12142-14054
+RX MEDLINE; 95407087.
+RA Xue Q., Egan J.B.;
+RT "DNA sequence of tail fiber genes of coliphage 186 and evidence for a
+RT common ancestor shared by dsDNA phage fiber genes";
+RL Virology 212(1):128-133(1995).
+XX
+RN [5]
+RP 14042-15941
+RX MEDLINE; 95407099.
+RA Xue Q., Egan J.B.;
+RT "Tail sheath and tail tube genes of the temperate coliphage 186";
+RL Virology 212(1):218-221(1995).
+XX
+RN [6]
+RP 11442-18696
+RA Xue Q.;
+RT "Studies on the tail region of the temperate coliphage 186 genome";
+RL Thesis (1993), University of Adelaide
+XX
+RN [7]
+RP 1-20866
+RX MEDLINE; 98371265.
+RA Portelli R., Dodd I.B., Xue Q., Egan J.B.;
+RT "The late-expressed region of the temperate coliphage 186 genome";
+RL Virology 248(1):117-130(1998).
+XX
+RN [8]
+RP 1-30624
+RA Dodd I.B., Egan J.B.;
+RT "Bacteriophage 186 complete genome";
+RL Unpublished.
+XX
+RN [9]
+RP 11442-18696
+RA Xue Q., Egan J.B.;
+RT ;
+RL Submitted (25-JUL-1995) to the EMBL/GenBank/DDBJ databases.
+RL Department of Biochemistry, University of Adelaide, Adelaide, SA 5005,
+RL Australia
+XX
+RN [10]
+RP 1-30624
+RA Xue Q., Egan J.B.;
+RT ;
+RL Submitted (15-MAY-1998) to the EMBL/GenBank/DDBJ databases.
+RL Department of Biochemistry, University of Adelaide, Adelaide, SA 5005,
+RL Australia
+XX
+DR SPTREMBL; O80303; O80303.
+DR SPTREMBL; O80304; O80304.
+DR SPTREMBL; O80305; O80305.
+DR SPTREMBL; O80306; O80306.
+DR SPTREMBL; O80307; O80307.
+DR SPTREMBL; O80308; O80308.
+DR SPTREMBL; O80309; O80309.
+DR SPTREMBL; O80310; O80310.
+DR SPTREMBL; O80311; O80311.
+DR SPTREMBL; O80312; O80312.
+DR SPTREMBL; O80313; O80313.
+DR SPTREMBL; O80314; O80314.
+DR SPTREMBL; O80315; O80315.
+DR SPTREMBL; O80316; O80316.
+DR SPTREMBL; O80317; O80317.
+DR SPTREMBL; Q01088; Q01088.
+DR SPTREMBL; Q37840; Q37840.
+DR SPTREMBL; Q37841; Q37841.
+DR SPTREMBL; Q37842; Q37842.
+DR SPTREMBL; Q37843; Q37843.
+DR SPTREMBL; Q37844; Q37844.
+DR SPTREMBL; Q37845; Q37845.
+DR SPTREMBL; Q37846; Q37846.
+DR SPTREMBL; Q37848; Q37848.
+DR SPTREMBL; Q37850; Q37850.
+DR SPTREMBL; Q37851; Q37851.
+DR SPTREMBL; Q9T0R3; Q9T0R3.
+DR SWISS-PROT; P06723; VINT_BP186.
+DR SWISS-PROT; P08685; CP69_BP186.
+DR SWISS-PROT; P08707; RPC1_BP186.
+DR SWISS-PROT; P08711; VPB_BP186.
+DR SWISS-PROT; P15236; VFIL_BP186.
+DR SWISS-PROT; P21678; RPC2_BP186.
+DR SWISS-PROT; P21679; VPD_BP186.
+DR SWISS-PROT; P21680; VDHR_BP186.
+DR SWISS-PROT; P21681; VAPL_BP186.
+DR SWISS-PROT; P21682; CP79_BP186.
+DR SWISS-PROT; P41059; CP80_BP186.
+DR SWISS-PROT; P41060; CP81_BP186.
+DR SWISS-PROT; P41061; CP83_BP186.
+DR SWISS-PROT; P41062; CP84_BP186.
+DR SWISS-PROT; P41063; TUM_BP186.
+DR SWISS-PROT; P41064; VPA_BP186.
+XX
+CC On Jul 23, 1998 this sequence version replaced gi:14895 gi:15549
+CC gi:1262241 gi:974436.
+XX
+XX
+FH Key Location/Qualifiers
+FH
+FT source 1..30624
+FT /db_xref="taxon:29252"
+FT /organism="Enterobacteria phage 186"
+FT misc_recomb 1..19
+FT /evidence=EXPERIMENTAL
+FT /note="cos; cohesive end; top strand cut at 5' end and
+FT bottom strand cut at 3' end"
+FT /organism="Enterobacteria phage 186"
+FT terminator complement(164..187)
+FT /evidence=NOT_EXPERIMENTAL
+FT /note="tW"
+FT /gene="W"
+FT CDS complement(196..1218)
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37851"
+FT /note="Orf2; P2 Q homolog"
+FT /transl_table=11
+FT /gene="W"
+FT /product="capsid portal protein"
+FT /protein_id="AAC34146.1"
+FT /translation="MSKRKPRKQVAMTASAPQKMEAFTFGEPVPVLDKRDILDYVECIS
+FT NGKWYEPPVSFSGLAKSLRSAVHHSSPIYVKRNVLASTYIPHPLLSRQDFSRFALDYLV
+FT FGNAFLEQRHSVTGQLIKLLASPAKYTRRGVDDSVFWFVENFTLPHEFAPDTVFHLLEP
+FT DINQEIYGLPEYLSALNSAWLNESATLFRRKYYQNGAHAGYIMYVTDPAQSATDVESLR
+FT EAMRNSKGLGNFKNLFFYAPNGKPDGIKIVPLSEVATKDDFFNIKKASAADLMDAHRVP
+FT FQLMGGKPENIGSLGDVEKVAKVFVRNDLSPLQDRFREVNDWLGMEVIRFKEYTLDNPE
+FT "
+FT CDS complement(1215..1961)
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80303"
+FT /note="similar to Orf12"
+FT /transl_table=11
+FT /gene="W"
+FT /product="W protein"
+FT /protein_id="AAC34147.1"
+FT /translation="MKNNVFSQSQIQAMADILHNDSFDYQATWLRVGKLNIDRSITKSR
+FT QIGATQLFSREALLDALTTGDNHVWFAHTIEHARVALMYMSNLSARVGVSLTSNGHSLQ
+FT LDDGAVISFVGEESHCAALAGNVYLDEFGWFNNPLRAAKVAAGIACHKRHSLTMFTSPS
+FT DNYDAFRVWNGTTRRHRPSPLINTGDSVFCTDGVWRQSVTLDAACQRGCNLFAPDEIKH
+FT EYSDDDYRLLFGCDWSFAVAAGEVAA"
+FT CDS complement(1961..3730)
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q9T0R3"
+FT /note="Orf12; P2 P homolog; contains putative
+FT helix-turn-helix DNA-binding motif"
+FT /transl_table=11
+FT /product="terminase subunit"
+FT /protein_id="AAC34148.1"
+FT /translation="MTISTDTTLLHDPRRQASLLYWQGFSVPQIAEMLQVKRPTVQSWK
+FT QRDGWDGIAPISRVESSLEARLIQLIAKPQKSGGDFKEIDLLGRQIERLARVNRYSQTG
+FT NEADLNPNVANRNKGERKKPKKNFFSDEAIEKLEELFFDQSFEYQLQWYRAGLAHRIRD
+FT ILKSRQIGATFYFSREALLRALKTGHNQIFLSASKTQAYVFREYIIQFARLVDVDLTGD
+FT PIVIGNNGAKLIFLGTNSNTAQSHNGDLYVDEIFWIPNFQKLRKVASGMASQKHLRSTY
+FT FSTPSTLAHGAYPFWSGELFNKGRSRIADRIEIDISHRALAGGQLCDDGQWRQIVTIED
+FT ALAGGCTLFDLDQLKRENSDEDFKNLFMCEFVDDKASVFPFEELQRCMVDVMEEWEDFA
+FT PFADHPFGSRPVWIGYDPSHTGDSAGCVVLAPPVVSGGKFRMLERHQWKGMDFAAQADG
+FT IRKLTEKYSVEYIGIDATGLGLGVFQLVRSFYPAARGIRYTPEMKTAMVLKAKDTIRRG
+FT CLEYDAGATDVTQSFMSIRKTMTSSGRSATYEASRTEEASHADIAWATMHALLNEPLSA
+FT GSGIQPKSILEFN"
+FT promoter complement(3753..3790)
+FT /evidence=EXPERIMENTAL
+FT /note="p12"
+FT /function="B-dependent transcription of orf12 operon"
+FT protein_bind 3798..3817
+FT /evidence=EXPERIMENTAL
+FT /bound_moiety="B protein"
+FT /function="activation of pV and p12 promoters"
+FT promoter 3828..3865
+FT /evidence=EXPERIMENTAL
+FT /note="pV"
+FT /gene="V"
+FT /function="B-dependent transcription of V operon"
+FT CDS 3896..4750
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q01088"
+FT /note="presumed capsid scaffold; P2 O homolog"
+FT /transl_table=11
+FT /gene="V"
+FT /product="V protein"
+FT /protein_id="AAC34149.1"
+FT /translation="MAKKVSKFFRIGVEGDTCDGRIISASDIQEMAETYDPRVYGCRIN
+FT LEHLRGLLPDGIFKRYGDVVELKAEKIDDDSALNGKWALFAKITPTDDLIAMNKAAQKV
+FT YTSMEIQPNFANTGKCYLVGLAVTDDPASLGTEYLEFCRTAKHNPLQRFKAHPENVFSA
+FT ATLAELEFEDVPDTVLNSLADKVKAIFSRKQVSDDARLNDVHEAVTAVSEHVQTNLTAQ
+FT DKRLSDMETAFATFKQELTGKVEETSQAFSVLKTTLDKTESFSQPRRTKASGGGGDELL
+FT TDC"
+FT variation 3980
+FT /note="Vam38; glutamine to amber"
+FT /replace="t"
+FT /gene="V"
+FT CDS 4826..5893
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80304"
+FT /note="T protein; P2 N homolog"
+FT /transl_table=11
+FT /gene="T"
+FT /product="major capsid protein"
+FT /protein_id="AAC34150.1"
+FT /translation="MRQETRFKFNAYLTQLAKLNGISVDDVSKKFTVEPSVTQTLMNTV
+FT QASSAFLQMINILPVAEMKGEKIGVGVTGTIASTTDTSGDKERQTADFTALESNKYECN
+FT QINFDFHLTYKRLDLWARFQDFQRRIRDAIVQRQALDFIMAGFNGTTRADTSDRVKNPM
+FT LQDVAVGWLQKYRNEAPARVMSNITDADGKVVSAVIRVGKNGDYENLDALVMDGTNTLI
+FT DEIYQDDPKLVAIVGRKLLADKYFPLVNKQQENTESLAADIIISQKRIGNLPAVRVPYF
+FT PANAVFVTTLENLSIYFMDESHRRSIDENPKKDRVENYESMNIDYVVEAYAAGCLLENI
+FT TLGDFTAPAAPEGGE"
+FT CDS 5898..6647
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80305"
+FT /note="terminase subunit; P2 M homolog"
+FT /transl_table=11
+FT /gene="R"
+FT /product="R protein"
+FT /protein_id="AAC34151.1"
+FT /translation="MTSPAQRHMMRVSASQAAQREQAPLRHATAYEQMLVKLADDRRTL
+FT KNIRSNERKAEKKRELLPFYAPWVAGVLTDGRGAQDDIVMTVMLWRLDAGDIAGALEIA
+FT PYALKYGLTSDHRRTTPYMLVEEVALAAQRLRDAGESVDLSWLQATIDLTDGADVPDMV
+FT RARLHKVTGLTLRDAGMNAEALAQFQRAMQLDRNAGVRKEIERLERALKPKTEAAPRKT
+FT TKPRTRKPATKPAAKRGRPPKAAKTAG"
+FT CDS 6740..7246
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80306"
+FT /note="capsid completion protein; P2 L homolog"
+FT /transl_table=11
+FT /gene="Q"
+FT /product="Q protein"
+FT /protein_id="AAC34152.1"
+FT /translation="MTTVILNQPDEPQDVPGVVIPAPETGGAVIKNTFFFPDVDPKRVR
+FT ELMRLEQTVSDARLRNAIKTGMAETNAELYDYRLRQTAAGFKQLADVPAEEIDGENVRV
+FT FHYLSAVTAMATATLYERYRGVEATGKGDKKADSVETTIDDLWRDMRWSVARLQDKPRC
+FT IVGQL"
+FT CDS 7246..7449
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80307"
+FT /note="Orf23; P2 X homolog; tail protein"
+FT /transl_table=11
+FT /protein_id="AAC34153.1"
+FT /translation="MKVRSMQGDTLDTICARYYGRTEGVVETVLQANPGLSELGVILPN
+FT GTEIDLPDVASSPVTKTINLWE"
+FT CDS 7452..7748
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80308"
+FT /note="Orf24; P2 Y homolog; holin"
+FT /transl_table=11
+FT /protein_id="AAC34154.1"
+FT /translation="MTEGEKGVLSLFVIGVMIVVGKVLAGGEPITPRLFIGRMLLGGFV
+FT SMVAGVVLVQFPDMSLPAVCGIGSMLGIAGYQVVEIAIQRRFKSQKGESDAGH"
+FT CDS 7735..8232
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80309"
+FT /note="endolysin; P2 K and lambda S homolog"
+FT /transl_table=11
+FT /gene="P"
+FT /product="P protein"
+FT /protein_id="AAC34155.1"
+FT /translation="MPVINTHQNIAAFLDMLAYSEGTANHPLTKNRGYDVIVTGLDGRP
+FT EIFTDYSDHPFAHGRPPKVFNRRGEKSTASGRYQQLYLFWPHYQKQLALPDFSPLSQDK
+FT LAIQLIRERGALDDIRAGRIERAVSRCRNIWASLPGAGYGQREHGLEKLVTIWRTAGGV
+FT VA"
+FT variation 7999
+FT /note="Pam65"
+FT /replace="t"
+FT /gene="P"
+FT CDS 8229..8642
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80310"
+FT /note="Orf27; P2 LysB homolog; control of lysis"
+FT /transl_table=11
+FT /protein_id="AAC34156.1"
+FT /translation="MKVLITLLVMAVLGLLWLHHENGNLSRSFETANRVASEQKTTIGM
+FT LKNQLSVAGQLARRNESAQVTLREQLAKASAEASRREQTITRLLNENEAFRRWYNAALP
+FT DAVRRLHTRTACASAGDCGQRMPEGEPLPYAGK"
+FT CDS 8497..8787
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80311"
+FT /note="Orf28; homologs present in other phages, e.g. P2,
+FT HP1"
+FT /transl_table=11
+FT /product="unknown"
+FT /protein_id="AAC34157.1"
+FT /translation="MKMKPFAAGITLLCLMLCVGCTPEQPAPVPVIVVNGCPRVSLCPM
+FT PGSDPKTNGDLSADIRRLEGALTACALQVKTVKHCQDELDAEAQKPAQSAD"
+FT CDS 8750..9217
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80312"
+FT /note="P2 R homolog; tail completion protein"
+FT /transl_table=11
+FT /gene="N"
+FT /product="N protein"
+FT /protein_id="AAC34158.1"
+FT /translation="MQKHKSLRKALINAVPQLRNNPDMLRLFADNGHTDSRLASSLSFE
+FT KVYVLNVVVTDFTGDLDLIFVPVQAWLREHQPDIMTTDDGREKGFTWIIDINNDDSLDI
+FT SISLRLTERTLVKEVGGALHVSYAPEPPLPELVKRPVAMYANGELVSQWDE"
+FT variation 8877
+FT /note="Nam47"
+FT /replace="a"
+FT /gene="N"
+FT CDS 9210..9659
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80313"
+FT /note="P2 S homolog; tail completion protein"
+FT /transl_table=11
+FT /gene="O"
+FT /product="O protein"
+FT /protein_id="AAC34159.1"
+FT /translation="MSELTALQERLAGLIASLSPAARRKMAAEIAKKLRTSQQQRIKRQ
+FT QAPDGTPYAARKRQPVRSKKGRIKREMFAKLRTSRFMKAKGSDSAAVVEFTGKVQRMAR
+FT VHQYGLKDRPNRNSRDVQYEARPLLGFTRDDEQMIEDVIISHLGK"
+FT variation 9324
+FT /note="Oam61"
+FT /replace="t"
+FT /gene="O"
+FT variation 9345
+FT /note="Oam62"
+FT /replace="t"
+FT /gene="O"
+FT CDS 9729..10370
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80314"
+FT /note="Orf32; P2 V homolog; baseplate protein"
+FT /transl_table=11
+FT /protein_id="AAC34160.1"
+FT /translation="MNTLSTIQELARAIRNLIRSGVVTEVDTVQGLCRVQSGGIQTTWL
+FT NWLTTRAGRSRTWWAPSLGEQVLLLAIGGELDTAFVLPGIFSDDNPAPSASADAWHVVF
+FT PDGAVIEYEPETSALTVRGIKTADVTASESITATVPVVLVKAETRITLDTPEVVCTNKL
+FT TTATLEVQKGGEMRGDIVHNGGTFKSNGVQLDDHGHGGVQRGGAWTEDTK"
+FT CDS 10367..10714
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80315"
+FT /note="P2 W homolog; putative baseplate protein"
+FT /transl_table=11
+FT /gene="M"
+FT /product="M protein"
+FT /protein_id="AAC34161.1"
+FT /translation="MTARYMGMNRNTGLGISDTEHISQSMRDILLTPVGSRVMRREYGS
+FT LLSALIDMPQNPALRLQIMVACYSAIQKWEPRIRLTSISFETGDAGEMYVDITGMRTDT
+FT GASVSTTVSLS"
+FT CDS 10721..11629
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37840"
+FT /note="P2 J homolog; baseplate or base of tail fibre"
+FT /transl_table=11
+FT /gene="L"
+FT /protein_id="AAC34162.1"
+FT /translation="MATVDLSLLPVPDVVEELDFETILAERIATLISLYPEDQQEAVAR
+FT TLALESEPIVKLLQENAYREVIWRQRVNEAARAGMLAYARDSDLDNLGANFNVERLVVR
+FT PADDTTIPPTPAEMELDADFRLRIQQAFEGMSVAGSTGAYEFHGRSADGRVADISVISP
+FT SPACVTISVLSRENNGAASDELQSIVRNALNAEDVRPVADRVTVQSAQIIDYQIRATLF
+FT VYPGPENEPIRAAAEAKLKAYISAQHRLGRDIRLSAIYAALHVEGVQRVELAAPVADIV
+FT LDKTQASFCTDYQIVIGGSDE"
+FT variation 10818
+FT /note="Lam21"
+FT /replace="a"
+FT /gene="L"
+FT CDS 11622..12152
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37841"
+FT /note="Orf38; P2 I homolog; tail protein"
+FT /transl_table=11
+FT /protein_id="AAC34163.1"
+FT /translation="MSDVRLLPVGSSPLEVAAARACADIENTPVPLRRLWSPDTCPANL
+FT LPWLAWAFSVDRWDENWPEKTKRDVIRSAYFTHCHKGTIGAVRRVIEPLGYIINVTEWW
+FT ETGDPAGTFRLDIGVLESGITEEMYLEMERLIADAKPASRHLIGLNIIQDIPGYMYTGG
+FT VSCDGDIITVYPG"
+FT CDS 12163..13551
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37842"
+FT /note="similar to P2 tail fiber protein H, PIR Accession
+FT Number B42291"
+FT /transl_table=11
+FT /gene="K"
+FT /protein_id="AAC34164.1"
+FT /translation="MSTKFKTIITTAGAEKLAAATVPGGKKVNITVMAVGDGGGKLPVP
+FT DAGQVQLVNEVWRHALNKISQDNRNSNYIVAELVIPPEVGGFWMRELGLYDDEGTLIAV
+FT ANMAESYKPELAEGSGRAQTCRMVIIVSSIASVELSIDSTMVMATQEYVDDRIAEHEKS
+FT RRHPDATLKEKGFTQLSSATDSASEVLAATPKAVKAAYDLANAKYTAQDASTAQKGIVR
+FT LSSAADSTSEAEAATPKAVKIAMDNANARLAKDRNGADIPNPPLFVQNIGLKPTVDKAA
+FT NAVDKNGDTMNGNLTLKGDYRLSFIIQNEDGSIRAYIFKDKGGDGIRISNGDDGGGDFV
+FT FGKNGQFYCPDIMHVGNTIVWGDGNIEGARWGGLLSDWLTAQLVARDNNINLRAPYEWV
+FT NQNFVNRAQRGAQASMTMDGGLVEAPWGCFLTGGNGNEGNQVGVALYRPLQILRNNTWV
+FT TIEN"
+FT misc_recomb 12962..12987
+FT /note="Mu-like inversion crossover site"
+FT /organism="Enterobacteria phage 186"
+FT /gene="K"
+FT CDS 13554..14054
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37843"
+FT /note="unnamed protein product"
+FT /transl_table=11
+FT /protein_id="AAC34165.1"
+FT /translation="MKLINLQRYILMNYFLGDGIQYFIDATGKDWYKSLPKFTKKYSLA
+FT IENDTGVIRSISEDASRLYPGGLTVVDVDSIPAGCDIFGGWVFDGEKVIPREYTLAEQQ
+FT RQSDEKKKTLLAEAEATISTLERAVRLGMATGEEIRRLEAWERYSVLVSRVKQSDDWPQ
+FT KPE"
+FT terminator 14058..14081
+FT /evidence=NOT_EXPERIMENTAL
+FT /note="t45"
+FT protein_bind 14091..14110
+FT /evidence=NOT_EXPERIMENTAL
+FT /bound_moiety="B protein"
+FT /function="activation of transcription from pJ"
+FT promoter 14122..14159
+FT /evidence=EXPERIMENTAL
+FT /note="pJ"
+FT /function="B-dependent transcription of J operon"
+FT promoter 14159
+FT /note="initiation point of late promoter pJ"
+FT CDS 14183..15361
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37844"
+FT /note="similar to P2 tail sheath protein FI, Swiss-Prot
+FT Accession Number P22501"
+FT /transl_table=11
+FT /gene="J"
+FT /protein_id="AAC34166.1"
+FT /translation="MSDFHHGTKVIEINDGTRVISTVATAIVGMVWTASDADAETFPLN
+FT EPVLITNVQSAIAKAGKKGTLSASLQAIADQSKPVTVVVRVAEGTGDDAEAQTTSNIIG
+FT GTDENGKYTGIKALLTAEAVTGVKPRILGVPGLDTQEVATALASVCISLRAFGYVSAWG
+FT CKTISEAMAYRENFSQRELMVIWPDFLAWDTTANATATAYATARALGLRAYIDQTIGWH
+FT KTLSNVGVQGVTGISASVFWDLQASGTDADLLNEAGVTTLVRKDGFRFWGNRTCSDDPL
+FT FLFENYTRTAQVLADTMAEAHMWAVDKPITASLIRDIVDGINAKFRELKSNGYIVDGEC
+FT WFDEESNDKETLKAGKLYIDYDYTPVPPLESLTLRQRITDKYLVNLAESVNS"
+FT CDS 15377..15898
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37845"
+FT /note="similar to P2 tail tube protein FII, Swiss-Prot
+FT Accession Number P22502"
+FT /transl_table=11
+FT /gene="I"
+FT /protein_id="AAC34167.1"
+FT /translation="MALPRKLKYLNMFNDGLSYMGVVESVTLPKLTRKLENYRGGGMNG
+FT AAAIDLGLDDDALTVEWSVGGLPDVALWAQYAAPGADAVPLRFAGSYQRDDTGEIVAVE
+FT VVMRGRHKEIDGGENKQGENTSTKLSTVCTYYRLTIDGSDIIEIDTVNMVEKVNGVDRL
+FT EQHRRAIGLL"
+FT terminator 15906..15932
+FT /note="tI; partial terminator of pJ transcript"
+FT CDS 15960..16295
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37846"
+FT /note="Orf51; P2 E homolog; tail protein"
+FT /transl_table=11
+FT /gene="H"
+FT /protein_id="AAC34168.1"
+FT /translation="MAKAPRKTPEFIDTAGNEIDTVNPNVVTLDKPIKRAGQTIEKVTL
+FT IEPNAGTLRGVSLAAVAQSEVDALIKVLPRMTYPALTTQELTAMNLPDMLSLAAKVIGF
+FT LSPASAE"
+FT CDS <16271..16447
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80316"
+FT /note="Orf52; tail protein; probably produced by a
+FT translational frameshift from orf51 into orf52"
+FT /transl_table=11
+FT /gene="H"
+FT /protein_id="AAC34169.1"
+FT /translation="FVTGFGGIDFPPGLSTDDLMADIAVIFHWPPSELYSLSLTELITW
+FT REKALQRSGNHNE"
+FT variation 16314
+FT /note="Ham50"
+FT /replace="a"
+FT /gene="H"
+FT variation 16421
+FT /note="Ham56"
+FT /replace="t"
+FT /gene="H"
+FT CDS 16440..18878
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37848"
+FT /note="P2 T homolog; tail protein"
+FT /transl_table=11
+FT /gene="G"
+FT /product="G protein"
+FT /protein_id="AAC34170.1"
+FT /translation="MSNNLRLEVLLKAVDQATRPLKSIQTASKSLSGDIRDTQKGLRDL
+FT NGQASKIDGFRKASAQLAVTSQALDKAKREAGELAVQFKNTTNPTRAQAQALEAARRAA
+FT SELQTKYNSLRTSVQRQRSELMQAGINTRTLSADERRLKTSISETTAHVNRQREALARV
+FT SAQQAKLSRVKERYKSGKELAGNMAAAGAAGSVSRHGTMAGVKLLMPGYDFTQKNSELQ
+FT AVLGVDKQSPEMQALRKQARHVGDNTAASADDAASAQIIIAKSGGDAAAIQARRRVTLN
+FT MALSNRRTMEENAALLTGMKSAFQLSNDKIAHIGDVLSMTMNKTAADFDGLSDALTYAA
+FT PVAKNAGVSIEQTAAMVGALHDAKITGSMAGTGSRAILSRLQLPPEKAFEAIKELGVKT
+FT SDSKGNTRPIFSILKEMQRSFEKNNLGTSQRGEYMKTIFGEEASSAAAVLMEAASSGKL
+FT DRLTAAFKASDGKTEELVKVMQDNLGGDFKEFQSAYEAVGTDLFDQQEGSLRKLTQTAT
+FT QYVLKLDGWIQKNKGLATTIGIIAGGALALIGIIGGIGLVAWPVVMGINAIIAAAGVLG
+FT TVFTVTGSAIVTALGAITWPIVAVGAAIVAGALLIRKYWEPISAFFSGVIEGIMSAFAP
+FT VAEMFAPLAPIFDGLGEKLRGVWQWFKDLIAPVKATQETLDSCKNVGVIFGQALASALM
+FT RPLNVFNKLRSGVDWLLEKLGIINKESDNLDQTAAKTNAATQGNSYIPATSTYGGYQAY
+FT QPVTAPAGRSYIDQSKSEYKITLPGGAAPGHQLDRQLRDTLEQIEREKRARQRASMSHD
+FT "
+FT CDS 18891..19376
+FT /codon_start=1
+FT /db_xref="SPTREMBL:O80317"
+FT /note="P2 U homolog; tail protein"
+FT /transl_table=11
+FT /gene="F"
+FT /product="F protein"
+FT /protein_id="AAC34171.1"
+FT /translation="MMLALGMFVFERRTLPYQSMQHSKDYRWASNDRVGKPPAYQFLGE
+FT GETSIQLAGTLYPAITGGWISLKAVEVMANEGRAWPLIEGTGNILGMYIVDKVSTTHAE
+FT FFSDGAARKIDFTLSLKRVDESLTAMFGDLNKQASELLGSAGNLTDKLQSALGGLTA"
+FT CDS 19373..20542
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P21679"
+FT /note="P2 D homolog; tail protein"
+FT /transl_table=11
+FT /gene="D"
+FT /product="D protein"
+FT /protein_id="AAC34172.1"
+FT /translation="MITGMTIDAGTSLAPAFMLTLNSQDITSNFSDRLISLTMTDNRGF
+FT EADQLDIELDDTDGKVELPLRGAVLTLWLGWQGSALLNKGDFTVDEIEHRGAPDILTIR
+FT ARSADFRGTLNSRREESWHDTTIGELVSTIAKRNKLTASVADSLKKIPVPHIDQSQESD
+FT AVFLTRLADRNGATVSVKAGKLLFLKAGSAMTASGKPVPQMTLTRNDGDRHQFAIADRG
+FT AYTGVTAKWLHTKDPKPQKQKVTLKRKPKEKHLRALEHPKAKPVSKKTKAKKEPEAREG
+FT EYMAGEADNVLALTTVYASKAQAMRAAQAKWDKLQRGVAEFSITLALGRADLFPETPVR
+FT VSGFKRVIDEQAWLISKVTHNLNNSGFTTGLELEVKLSDVEYNAESDDE"
+FT variation 20378
+FT /note="Dam23"
+FT /replace="t"
+FT /gene="D"
+FT variation 20492
+FT /note="Dam14"
+FT /replace="t"
+FT /gene="D"
+FT protein_bind 20548..20596
+FT /evidence=EXPERIMENTAL
+FT /note="two CI binding sites for repression of pB"
+FT /gene="B"
+FT /bound_moiety="CI"
+FT -35_signal 20549..20554
+FT /gene="B"
+FT promoter 20549..20584
+FT /evidence=EXPERIMENTAL
+FT /note="pB; repressed by CI"
+FT /gene="B"
+FT /function="transcription of B gene"
+FT -10_signal 20572..20577
+FT /gene="B"
+FT CDS 20609..20827
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P08711"
+FT /note="C4 Zn finger protein; P2 Ogr and P4 Delta homolog"
+FT /transl_table=11
+FT /gene="B"
+FT /function="activate late gene promoters"
+FT /product="B protein"
+FT /protein_id="AAC34173.1"
+FT /translation="MFHCPKCHHAAHARTSRYLTENTKERYHQCQNINCSCTFMTMETI
+FT ERFIVTPGAIDPAPPHPTVGGQRPLWL"
+FT variation 20690
+FT /note="Bam57"
+FT /replace="t"
+FT /gene="B"
+FT variation 20744
+FT /note="Bam17"
+FT /replace="t"
+FT /gene="B"
+FT terminator 20845..20866
+FT /note="tB; termination of transcription from pB and late
+FT promoter"
+FT protein_bind 20889..21147
+FT /note="predicted Int arm-type recognition sequences;
+FT consensus = YAYCGCCAYTY; 3 sites to left of attP and 2
+FT sites to right"
+FT /bound_moiety="Int"
+FT terminator complement(20930..20957)
+FT /note="predicted terminator of lysogenic operon"
+FT protein_bind 20959..20985
+FT /evidence=EXPERIMENTAL
+FT /note="IHF DNase I footprint location"
+FT /bound_moiety="IHF"
+FT tRNA 20985..21030
+FT /note="truncated"
+FT /product="tRNA-Ile"
+FT misc_feature 20995..21030
+FT /note="in common with attB in Escherichia coli, which is
+FT in the 3' end of ileY"
+FT misc_feature 21023..21029
+FT /note="attP; predicted crossover region with attB"
+FT variation 21030..22865
+FT /note="del1 deletion"
+FT protein_bind 21057..21086
+FT /evidence=EXPERIMENTAL
+FT /note="IHF DNase I footprint location"
+FT /bound_moiety="IHF"
+FT protein_bind 21075..21121
+FT /note="five binding sites for Apl excisionase at attP"
+FT /bound_moiety="Apl"
+FT CDS complement(21157..21408)
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P08685"
+FT /note="Orf69"
+FT /transl_table=11
+FT /protein_id="AAC34174.1"
+FT /translation="MSVRLAPDGTYVYGQPMLAPNGTYVGGGAPRLAPDGTYVGGVPRL
+FT APNGSYVAGRPTLAPDGTYVGGRYHLAPDGTYVGDGSE"
+FT CDS complement(21423..22433)
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P06723"
+FT /note="integrase"
+FT /transl_table=11
+FT /gene="int"
+FT /product="Int"
+FT /protein_id="AAC34175.1"
+FT /translation="MTVRKNPAGGWICELYPNGAKGKRIRKKFATKGEALAFEQYTVQN
+FT PWQEEKEDRRTLKELVDSWYSAHGITLKDGLKRQLAMHHAFECMGEPLARDFDAQMFSR
+FT YREKRLKGEYARSNRVKEVSPRTLNLELAYFRAVFNELNRLGEWKGENPLKNMRPFRTE
+FT EMEMTWLTHDQISQLLGECNRHDHPDLETVVRICLATGARWSEAESLRKSQLAKYKITY
+FT TNTKGRKNRTVPISKELYESLPDDKKGRLFSDCYGAFRSALERTGIELPAGQLTHVLRH
+FT TFASHFMMNGGNILVLQRVLGHTDIKMTMRYAHFAPDHLEDAVKLNPLVHITNSK"
+FT CDS complement(22433..23011)
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P08707"
+FT /note="immunity repressor; DNA-binding protein; has
+FT positive and negative autoregulation by control of pL"
+FT /transl_table=11
+FT /gene="cI"
+FT /function="maintenance of lysogeny; repress pB and pR"
+FT /product="CI repressor"
+FT /protein_id="AAC34176.1"
+FT /translation="MRIDSLGWSNVDVLDRICEAYGFSQKIQLANHFDIASSSLSNRYT
+FT RGAISYDFAAHCALETGANLQWLLTGEGEAFVNNRESSDAKRIEGFTLSEEILKSDKQL
+FT SVDAQFFTKPLTDGMAIRSEGKIYFVDKQASLSDGLWLVDIEGAISIRELTKLPGRKLH
+FT VAGGKVPFECGIDDIKTLGRVVGVYSEVN"
+FT variation 22684..22685
+FT /note="cI10"
+FT /replace="g"
+FT /gene="cI"
+FT protein_bind 22726..22742
+FT /note="FL; unknown function"
+FT /gene="cI"
+FT /bound_moiety="CI"
+FT variation 22989
+FT /note="cIam461"
+FT /replace="t"
+FT /gene="cI"
+FT protein_bind 23011..23067
+FT /note="locus of vir mutations; 3 binding sites for CI
+FT repressor"
+FT /bound_moiety="CI"
+FT /function="repression of pR"
+FT -35_signal 23026..23031
+FT /note="pR"
+FT promoter 23026..23063
+FT /evidence=EXPERIMENTAL
+FT /note="pR; repressed by CI and Apl"
+FT /function="transcription of early lytic operon"
+FT -10_signal 23049..23054
+FT /note="pR"
+FT protein_bind 23056..23123
+FT /note="7 binding sites for Apl; locus of goa mutations"
+FT /bound_moiety="Apl"
+FT /function="repression of pR and pL"
+FT promoter complement(23125..23159)
+FT /evidence=EXPERIMENTAL
+FT /note="pL; repressed by Apl; inhibited by convergent pR
+FT promoter; activated by CI repression of pR"
+FT /function="transcription of lysogenic operon"
+FT -10_signal complement(23131..23136)
+FT /note="pL"
+FT CDS 23141..23404
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P21681"
+FT /note="contains putative helix-turn-helix DNA binding
+FT motif"
+FT /transl_table=11
+FT /gene="apl"
+FT /function="excisionase; binds to attL"
+FT /function="repressor: binds between and represses pR and
+FT pL"
+FT /function="appears to assist derepression during prophage
+FT induction"
+FT /product="Apl"
+FT /protein_id="AAC34177.1"
+FT /translation="MASEIAIIKVPAPIVTLQQFAELEGVSERTAYRWTTGDNPCVPIE
+FT PRTIRKGCKKAGGPIRIYYARWKEEQLRKALGHSRFQLVIGA"
+FT -35_signal complement(23154..23159)
+FT /note="pL"
+FT /gene="apl"
+FT variation 23205..23219
+FT /note="apl11 deletion"
+FT /gene="apl"
+FT variation 23236
+FT /note="aplam"
+FT /replace="g"
+FT /gene="apl"
+FT promoter complement(23400..23462)
+FT /evidence=EXPERIMENTAL
+FT /note="pE; activated by CII, allowing transcription of
+FT lysogenic operon"
+FT /function="transcript for establishment of lysogeny"
+FT protein_bind 23404..23420
+FT /note="FR"
+FT /bound_moiety="CI"
+FT /function="probably responsible for CI repression of pE
+FT promoter"
+FT CDS 23435..23944
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P21678"
+FT /note="DNA binding protein; has putative helix-turn-helix
+FT motif"
+FT /transl_table=11
+FT /gene="cII"
+FT /function="required for establishment of lysogeny"
+FT /function="activates pE, alternative lysogenic operon
+FT promoter"
+FT /product="CII"
+FT /protein_id="AAC34178.1"
+FT /translation="MFDFQVSKHPHYDEACRAFAQRHNMAKLAERAGMNVQTLRNKLNP
+FT EQPHQFTPPELWLLTDLTEDSTLVDGFLAQIHCLPCVPVNELAKDKLQSYVMRAMSELG
+FT ELASGAVSDERLTTARKHNMIESVNSGIRMLSLSALALHARLQTNPAMSSVVDTMSGIG
+FT ASFGLI"
+FT protein_bind 23435..23462
+FT /gene="cII"
+FT /bound_moiety="CII"
+FT /function="activate pE"
+FT variation 23440
+FT /note="prevents CII binding to pE; cIV476"
+FT /replace="a"
+FT /gene="cII"
+FT variation 23657
+FT /note="cIIam475"
+FT /replace="t"
+FT /gene="cII"
+FT CDS 23952..24179
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P15236"
+FT /evidence=EXPERIMENTAL
+FT /note="overexpression blocks cell division"
+FT /transl_table=11
+FT /gene="fil"
+FT /product="Fil"
+FT /protein_id="AAC34179.1"
+FT /translation="MLKSEPSFASLLVKQSPGMHYGHGWIAGKDGKRWHPCRSQSELLK
+FT GLKTKSPKSSGFLIIRIVHFVIKGVKHVTR"
+FT variation 24112
+FT /note="CP77am; filam"
+FT /replace="a"
+FT /gene="fil"
+FT CDS 24166..24366
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P21680"
+FT /transl_table=11
+FT /gene="dhr"
+FT /function="inhibits host DNA replication"
+FT /product="Dhr"
+FT /protein_id="AAC34180.1"
+FT /translation="MSRDELRIVLGAMIPNMEEGFEIKTRDGAILRVDPEWECCKEFKD
+FT GLKAEIIKQLKSKPAVVFGYS"
+FT variation 24194
+FT /note="CP78am; dhram"
+FT /replace="a"
+FT /gene="dhr"
+FT terminator 24384..24412
+FT /note="tR1; one of a number of transcription terminators
+FT in this region that are anti-terminated during early lytic
+FT development by an unknown mechanism"
+FT CDS 24433..24666
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P21682"
+FT /note="unknown function; not essential"
+FT /transl_table=11
+FT /product="Orf79"
+FT /protein_id="AAC34181.1"
+FT /translation="MSRTIYLSTPSGAGDHLLESLFKEAKKEERKDRRLAVSIRLEDLA
+FT VHITNSDMTGKEAAELLRREATRFENESQELH"
+FT CDS 24666..24893
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P41059"
+FT /note="unknown function; not essential"
+FT /transl_table=11
+FT /product="Orf80"
+FT /protein_id="AAC34182.1"
+FT /translation="MADAMDLAQLREQEDRERHISNARSRRHEVSAFICEECDAPIPEA
+FT RRRAIPGVQCCVTCQEILELKSKHYNGGAL"
+FT CDS 24890..25474
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P41060"
+FT /note="unknown function; not essential"
+FT /transl_table=11
+FT /product="Orf81"
+FT /protein_id="AAC34183.1"
+FT /translation="MSITNATISQRAKKWLEDDRIFIDTETTGLGDDAEIVEICLIDSA
+FT GFIMLNTLVKPTKPIPAEATAIHGITDEMVMYAPTWKDIHGAVASLFFEYGFVIYNADY
+FT DTRLIYQTAKLYGLENDGFCYFLNERSACAMMLYAEYRGEPGRFKGYKWHKLVDAAAHE
+FT GVSVEGKAHRALADCRMTLGIIDALAKGGAA"
+FT CDS 25471..25746
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P41061"
+FT /note="unknown function; not essential"
+FT /transl_table=11
+FT /product="Orf83"
+FT /protein_id="AAC34184.1"
+FT /translation="MSIRIEIGDKWVITSDQYQFILNEKKVVKTGNKAGEEWLDTIGYY
+FT PKINQLISGLVHHHIHTAMIISLSAMAEEIEKLSFICEEAFKAVKK"
+FT CDS 25743..26747
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P41062"
+FT /note="unknown function; not essential. translation to end
+FT of orf84 appears necessary for translation of A protein"
+FT /transl_table=11
+FT /product="Orf84"
+FT /protein_id="AAC34185.1"
+FT /translation="MIDSRCFAESTINIVSVSGGKDSLAQWILAVENDVPRTTVFADTG
+FT HEHSQTMEYLDYLESRLGPVIRVKADFTRRIEGKRKFIAEKWPVSLVEECGMSHEQAAE
+FT RIAKALEILKPTGNPFLDLCMWKGRFPSTKARFCSLELKHDSVRDKIVLPALEKYDEVI
+FT LWQGVRAQESPARAALPMWEEDADNTPGLHVYRPILNWTHEDVFALAKRHGIKPNPLYQ
+FT QGCSRVGCMPCIHARKSELAEIFARWPEEIARVAEWERLVAACSRRGNSTFFPSTHDPR
+FT RAEKRIEVVTVEEYGIASYRDWAMTTRGGSQYDLLAATNDKTVCSSVYAGVCE"
+FT variation 25803
+FT /note="am43"
+FT /replace="t"
+FT /phenotype="lethal due to polarity on A"
+FT variation 25845..26288
+FT /note="del444"
+FT /replace=""
+FT variation 26460
+FT /note="am11"
+FT /replace="t"
+FT /phenotype="lethal due to polarity on A"
+FT CDS 26744..28828
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P41064"
+FT /note="rolling circle replicase; putative endonuclease;
+FT similar to P2 A protein"
+FT /transl_table=11
+FT /gene="A"
+FT /product="A protein"
+FT /protein_id="AAC34186.1"
+FT /translation="MTGVVYAFPWNAPRSAIASSYLTYDQQHRRDRMFAALLHARKVLF
+FT LQPECVRFDVYRTAAVLEQNQGSQRANAFLISFCKKALPRLELVAKKYECSGINSNVSA
+FT AVFDGHFDTQLMQYLASRMVNMVARFNRLPDMSRADIDLLAADIANFIRAELADIDDTG
+FT FSELKTLYTWYMRAGFISLQFNVTPPKWERVTKKYFCEDEIAPAVMRMFNEVWWRGRLR
+FT RIAAAWREHLQIAVGNVSKKRHAYASKNCVTDWREQKRRTREFLKGLDLEDEEGNRISL
+FT IEKYDGSVANPAIRRCELMARIRGFENICNELGYVGEFYTLTAPSKYHATTKAGYRNSK
+FT WNGASPSDTQSYLTGLWARIRAKLHREEIRIFGIRVAEPHHDGTPHWHMLMFMLPEDVE
+FT RVRLIIRDYAWEEDHYELRSDKAKKARFHAEAIDPEKGSATGYVAKYISKNIDGYALDG
+FT ETDDESGELLKETAPAVSAWAARWHIRQFQFIGGAPVTVYRELRRMADPETARALSVEF
+FT AAVHDAAHYGRWADYVNAQGGPFVRRDDLQVRTLYEPRTEFNQYGEETVCIKGVYDASI
+FT GAGSPILTRLTQWKIVPKRAVDLAVDVKGASAPSRSSVNNCTGSESDPPILDLTKPLSR
+FT RERRELTNRLRKKKPTTRRKFIHGTDKQNVAITKTIDEIHSDNRHHNQPGRSPAPDGRW
+FT "
+FT terminator 26829..26856
+FT /note="tM1; expected terminator for lytic transcripts in
+FT absence of translation of A gene"
+FT /gene="A"
+FT variation 27542
+FT /note="Aam24"
+FT /replace="t"
+FT /gene="A"
+FT rep_origin 28576
+FT /note="located by similarity with P2; consistent with
+FT electron microscopy mapping"
+FT /gene="A"
+FT -35_signal 29020..29025
+FT /note="p95"
+FT promoter 29020..29056
+FT /evidence=EXPERIMENTAL
+FT /note="p95"
+FT /function="SOS-dependent transcription of SOS operon"
+FT -10_signal 29044..29049
+FT /note="p95"
+FT protein_bind 29044..29063
+FT /note="putative"
+FT /bound_moiety="LexA"
+FT variation 29053..29055
+FT /note="cIII"
+FT /replace="tt"
+FT variation 29074
+FT /note="tum9"
+FT /replace="a"
+FT CDS 29082..29522
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P41063"
+FT /note="expression repressed by LexA"
+FT /transl_table=11
+FT /gene="tum"
+FT /function="inhibits CI DNA binding"
+FT /product="Tum"
+FT /protein_id="AAC34187.1"
+FT /translation="MDRELNEHVMIERVEMIARLTAEGTCQERDREIALNLIAEIARGN
+FT LMKNNNFSVVFSAPPVGETFAKEGKVKVNITLDKDQKIGQPVIDAFQCELTKRIQSVFP
+FT STRVTVKKGSMTGVELMGFDKDSDREALDSILQEVWEDESWR"
+FT CDS 29109..29522
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P41063"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="tum"
+FT /function="modulates SOS induction"
+FT /product="truncated Tum95.2"
+FT /protein_id="AAC34188.1"
+FT /translation="MIERVEMIARLTAEGTCQERDREIALNLIAEIARGNLMKNNNFSV
+FT VFSAPPVGETFAKEGKVKVNITLDKDQKIGQPVIDAFQCELTKRIQSVFPSTRVTVKKG
+FT SMTGVELMGFDKDSDREALDSILQEVWEDESWR"
+FT variation 29115
+FT /note="tum16"
+FT /replace="a"
+FT /gene="tum"
+FT variation 29182
+FT /note="tum13"
+FT /replace="t"
+FT /gene="tum"
+FT variation 29199
+FT /note="tum17"
+FT /replace="a"
+FT /gene="tum"
+FT CDS 29220..29522
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P41063"
+FT /evidence=EXPERIMENTAL
+FT /transl_table=11
+FT /gene="tum"
+FT /function="modulates SOS induction"
+FT /product="truncated Tum95.4"
+FT /protein_id="AAC34189.1"
+FT /translation="MKNNNFSVVFSAPPVGETFAKEGKVKVNITLDKDQKIGQPVIDAF
+FT QCELTKRIQSVFPSTRVTVKKGSMTGVELMGFDKDSDREALDSILQEVWEDESWR"
+FT variation 29245..29251
+FT /note="tum14"
+FT /replace="ttttt"
+FT /gene="tum"
+FT CDS 29292..29522
+FT /codon_start=1
+FT /db_xref="SWISS-PROT:P41063"
+FT /evidence=EXPERIMENTAL
+FT /note="truncated Tum - modulates SOS induction of 186
+FT prophage. GTG start codon."
+FT /transl_table=11
+FT /gene="tum"
+FT /product="bacteriophage 186 Tum95.5"
+FT /protein_id="AAC34190.1"
+FT /translation="MKVNITLDKDQKIGQPVIDAFQCELTKRIQSVFPSTRVTVKKGSM
+FT TGVELMGFDKDSDREALDSILQEVWEDESWR"
+FT variation 29391
+FT /note="tum2"
+FT /replace="t"
+FT /gene="tum"
+FT terminator 29545..29576
+FT /evidence=EXPERIMENTAL
+FT /note="t95a; partial termination of p95 transcript"
+FT CDS 29604..30335
+FT /codon_start=1
+FT /db_xref="SPTREMBL:Q37850"
+FT /note="expression from plasmid blocks 186 infection"
+FT /transl_table=11
+FT /gene="orf97"
+FT /product="Orf97"
+FT /protein_id="AAC34191.1"
+FT /translation="MDTVIAFLSLALFIAFIVGLIKPSLVRMPSRKRSSAVYLGGCLAL
+FT GVIGSILWPTEKTQPVAKTDVPAVKAEPATPTFEYADKTLKEYRNEPKETRHEIVKDYV
+FT GFKGVQASATDVFYACMSEYTFTKDDALKLSDVLGWCFNDFEKDPQSLNNKINLDEFQG
+FT NFSGWDGSYRPLEKLIKASMNDDSSYKHVSTVYHLILNKDPHAVVKTTFRGTNAYGGVV
+FT KQTVAARVNVRTGEVDSILDN"
+FT variation 29691..30304
+FT /note="del97"
+FT /replace=""
+FT /gene="orf97"
+FT terminator 30436..30463
+FT /note="t95b; p95 transcript terminator"
+SQ Sequence 30624 BP; 7461 A; 7801 C; 8456 G; 6906 T; 0 other;
+ ggcgtggcgg ggaaagcatt gcgcgccaga ggtggcgcgt gaatgataaa aattatcgtc 60
+ tgagcgcctc gtaatggcgc tatcgtggtg ctgttggttc gttggtggtc gtgtgtgttt 120
+ gtgcgcgtgt ggcgcgtctg agacgtgatg gtggcggggt atgaaaaagc cgccatgctg 180
+ gcggcttgag agggattatt ccgggttgtc gagggtgtac tctttgaacc tgatgacctc 240
+ catgccgagc cagtcgttta cctccctgaa cctgtcctgc agcggcgaca ggtcgttacg 300
+ cacaaatacc tttgccactt tctcaacgtc accgagcgag ccgatattct cgggcttgcc 360
+ gcccatgagc tggaacggta cgcggtgcgc gtccatcagg tcggcggcgc tggctttctt 420
+ gatgttgaaa aagtcatcct ttgtggcgac ctcgctcagt ggcacgattt taatgccgtc 480
+ cggttttcca ttgggagcgt agaaaaacag gtttttaaaa ttgccgagcc ctttcgagtt 540
+ gcgcatcgcc tcgcgcagcg attcgacgtc ggtcgcgctc tgcgccgggt cggtcacata 600
+ catgatgtaa cccgcatgcg cgccgttctg gtaatacttg cggcggaaca gcgttgcgga 660
+ ttcattcagc caggcggaat taagcgcgct gagatattcg ggcaggccgt aaatctcctg 720
+ attaatgtcg ggctccagca ggtgaaacac ggtatcaggc gcgaattcat gcggcagagt 780
+ gaagttttcc acaaaccaga aaaccgaatc gtcgaccccg cggcgggtgt atttggccgg 840
+ tgaggccagt agcttgatta gctggccggt aacgctgtgg cgctgctcaa gaaaggcgtt 900
+ gccgaatacc agatagtcga gcgcaaagcg gctgaaatcc tgacgggaca gcagcgggtg 960
+ cgggatgtag gtgctcgcga gcacgttgcg cttaacgtaa atcggtgagc tgtgatgtac 1020
+ agcagagcgc aggctctttg ccagcccgga gaagctgacc ggcggctcgt accatttgcc 1080
+ gttactgatg cactcgacat aatccagaat gtcgcgctta tcgagtaccg gcactggctc 1140
+ gccaaaggtg aacgcctcca ttttttgcgg ggcgctggcg gtcatggcta cctgtttgcg 1200
+ tggcttgcgc ttgctcatgc tgccacctca cccgctgcaa cagcgaaaga ccagtcgcag 1260
+ ccaaacagca ggcgataatc atcgtcgctg tattcgtgtt taatttcatc gggcgcaaag 1320
+ agattgcacc cgcgctggca tgctgcatcc agagtgaccg actgacgcca gacaccatct 1380
+ gtacaaaata cgctgtcgcc ggtattgatg agcggtgacg gtcggtgcct gcgggttgtg 1440
+ ccgttccata ccctgaaagc gtcataatta tcagagggcg aggtaaacat cgtcaggctg 1500
+ tggcgtttat ggcaggcgat acccgccgcg actttcgcag ctcttagcgg gttattgaac 1560
+ catccgaact catcaagata gacattaccc gccagtgcgg cgcaatggga ttcctcgccg 1620
+ acaaagctga taaccgcacc gtcgtcgagc tgcaggctgt ggccgttact cgtaagactg 1680
+ acgccgacgc gcgccgaaag gttactcatg tacatcagcg ccacgcgcgc atgctcaata 1740
+ gtgtgagcaa accagacatg attatcgcct gttgtcagcg catcgagcag cgcctcacgg 1800
+ ctaaagagct gcgttgcgcc aatctggcgc gatttggtga tgctgcggtc gatattgagt 1860
+ ttcccgacac gcaaccatgt tgcctgatag tcaaagctgt cattgtgcag aatatcggcc 1920
+ attgcctgaa tctggctttg tgagaaaacg ttatttttca ttaattaaac tccagaatgg 1980
+ atttaggctg tatgccgcta ccggcggaaa gcggttcgtt taacagggcg tgcatggtcg 2040
+ cccatgcaat atcggcgtga ctggcttcct cggtgcggct ggcctcgtag gtggcgctgc 2100
+ gcccgctgct ggtcatggtt ttgcggatgg acataaacga ctgcgtgacg tcggttgctc 2160
+ cggcgtcgta ctccagacag ccgcggcgaa tggtgtcttt tgccttgagt accattgcgg 2220
+ ttttcatttc aggcgtgtaa cggatgccgc gtgcggccgg gtagaatgag cgcaccaact 2280
+ ggaacacgcc gaggccgagg ccggttgcgt caatgccgat gtattcgacg ctgtatttct 2340
+ cggtcagttt gcggatgcca tctgcctgcg cggcaaagtc catgcccttc cactggtggc 2400
+ gctccagcat gcggaacttg ccaccagaaa ccaccggcgg cgcgagcacg acgcacccgg 2460
+ cgctgtcgcc agtgtgtgac gggtcgtagc caatccagac ggggcgagag ccgaacggat 2520
+ ggtcggcgaa cggggcgaag tcctcccatt cttccatcac gtcgaccatg cagcgctgca 2580
+ gctcctcgaa cgggaatacc gacgctttat cgtcgacaaa ctcgcacata aacaggtttt 2640
+ taaagtcctc atcactgttt tcgcgtttga gttggtcgag gtcgaacagg gtgcagccac 2700
+ cggcaagggc gtcctcaatg gtgacaatct gccgccactg gccatcgtcg cagagctggc 2760
+ caccggcgag cgcgcggtga ctgatgtcaa tttcgatgcg gtcggcaata cggctgcgcc 2820
+ ccttgttgaa cagctcgcca gaccagaagg gataagcgcc gtgcgccagc gttgagggtg 2880
+ tcgaaaagta ggttgagcgc agatgcttct gcgaggccat gcccgaggcg actttgcgca 2940
+ gcttctgaaa attcgggatc cagaatattt catcgacata caggtcgccg ttatggctct 3000
+ gagccgtgtt ggagttggta ccgagaaaaa tcagctttgc gccgttgttg ccaatgacaa 3060
+ tcgggtcgcc ggtcaggtcg acgtcaacca gtcgcgcaaa ctgtatgatg tattcgcgga 3120
+ acacgtaagc ctgcgtttta ctggccgaca gaaaaatctg gttatggccg gtcttgagtg 3180
+ ctcgcagcag tgcctcacgg gaaaaataga acgtcgcgcc aatctggcgg gatttgagaa 3240
+ tgtcgcgaat acggtgtgcc agtcctgcgc ggtaccactg caactggtac tcgaaagact 3300
+ ggtcgaaaaa taattcctcc agcttttcga tagcctcgtc gctgaaaaaa ttctttttcg 3360
+ gcttcttgcg ctcacccttg ttgcggttgg cgacgttggg gttaaggtcg gcctcgttgc 3420
+ cggtctggct gtagcggtta acgcgtgcca gtcgttcaat ctgccgcccg agcaggtcaa 3480
+ tctctttgaa gtcaccgcct gacttttgcg gcttggcgat gagctgaatc aggcgcgcct 3540
+ caaggctgct ttcaacgcgg gaaatcggtg cgatgccatc ccagccgtcg cgctgtttcc 3600
+ agctctgcac ggtcgggcgc ttgacctgca gcatttcggc aatctgcggc acggaaaaac 3660
+ cctgccagta aagcagcgat gcctgtcgtc gcgggtcatg caacaaggtt gtatcggtgg 3720
+ aaatggtcat tgatgcctcg ccgtaatgga ttcagggcaa ggctacttaa tggccgtcag 3780
+ tgattcgcta aggtgctgtt gtgtgggcgg ttgtccagtc gtcattggtg gtctggcgtg 3840
+ tcctgagtct ggaaactggc ggtgaccagt aaccccaacc tcaggactcc tgacaatggc 3900
+ aaaaaaagtc tcaaaattct ttcgaatcgg cgtcgagggt gatacctgcg acgggcgcat 3960
+ tatcagcgcc agtgatattc aggaaatggc cgaaacctat gacccgcgtg tttacggttg 4020
+ ccgtatcaac cttgaacacc tgcgcggcct gttgcccgat ggcatattca agcgttacgg 4080
+ cgatgtggtc gagctgaaag ccgagaagat tgacgacgat tctgcgctta acggcaagtg 4140
+ ggcgttgttc gctaaaatca ccccgaccga tgaccttatc gcgatgaata aagccgcgca 4200
+ gaaggtctac acctcaatgg aaattcagcc gaattttgcc aataccggca aatgctacct 4260
+ cgtcggcctt gctgtcaccg atgacccggc gagcctcggc actgaatatc tcgaattctg 4320
+ ccgcactgcg aagcacaacc cgctgcagcg ctttaaagcc caccctgaaa acgtcttttc 4380
+ cgccgccacg ctggccgaac tggaatttga agacgttccc gacacggtgc tcaacagcct 4440
+ ggccgataag gtgaaagcca ttttcagccg taagcaggtc agcgacgatg cgcgcctgaa 4500
+ tgatgtacat gaagcggtga ccgccgtcag cgagcatgtg cagaccaacc tcactgcaca 4560
+ ggataagcgt ctttccgata tggaaaccgc gtttgccacc ttcaaacagg aactgaccgg 4620
+ caaggttgaa gaaaccagcc aggcattttc cgtcctgaaa accactctcg acaaaaccga 4680
+ aagtttcagc cagccgcgac gcacgaaagc cagcggcggt ggtggcgatg agctgctgac 4740
+ tgactgctga taaaccgcag accgaaaccg ggcggaaccc ccgcccgatg ctgtgactaa 4800
+ ccgattaaat caaacaggaa atactatgcg tcaggaaacc cgttttaagt tcaatgccta 4860
+ tctgacccag ctcgccaaac tgaacggcat cagcgttgat gacgtcagca aaaaattcac 4920
+ cgtcgagccg tccgtcacgc aaacgctgat gaacaccgtg caggcgtcat ccgcgtttct 4980
+ gcagatgatt aacattctgc cggtcgcaga aatgaagggt gagaaaatcg gtgtcggtgt 5040
+ gaccggcacc atcgccagca cgaccgacac ctcgggcgac aaagagcgcc agaccgcaga 5100
+ ttttaccgcg cttgagtcca acaagtacga gtgcaaccag attaactttg acttccacct 5160
+ gacctataaa cgcctcgacc tgtgggcgcg ttttcaggac tttcagcgtc gtattcgcga 5220
+ cgccattgtc cagcgtcagg cactggattt catcatggcc gggtttaacg gtaccacccg 5280
+ cgctgacacc tctgaccgtg ttaaaaaccc gatgctgcag gatgtggccg ttggctggct 5340
+ gcagaaatac cgcaatgaag ccccggcgcg tgtgatgagc aacatcaccg acgctgacgg 5400
+ taaagtcgtt tcggcagtga ttcgcgtcgg taaaaacggc gactatgaga acctcgacgc 5460
+ actggtgatg gatggtacca ataccctgat tgacgagatt tatcaggatg acccgaaact 5520
+ cgttgccatc gttggccgta agctgctggc cgacaaatat tttccgctgg tcaacaaaca 5580
+ gcaggaaaac accgagtcgc tcgcggcgga tatcatcatc agccagaagc gcatcggcaa 5640
+ cctgccagcc gtacgtgtgc cgtacttccc ggcgaacgcg gtgttcgtga ccacgctgga 5700
+ aaacctctct atctatttca tggatgagag ccaccgccgc agcattgatg aaaacccgaa 5760
+ aaaagaccgc gtggaaaact acgagtcgat gaacatcgac tatgtggtcg aggcgtatgc 5820
+ cgccgggtgc ctgctggaaa acatcaccct gggcgatttc accgcacctg cagcaccgga 5880
+ aggcggagag taaacccatg acgagccccg cacagcgtca catgatgcgg gtctcggcct 5940
+ ctcaagccgc gcagcgggaa caagccccgc tgcgccatgc aaccgcctat gagcagatgc 6000
+ tggttaagct ggccgatgac cgccgcacgt taaaaaacat ccgttcaaac gaacgtaaag 6060
+ ccgagaaaaa gcgcgagctg ctgccgttct atgcgccgtg ggtcgccggt gtgctgactg 6120
+ atggtcgtgg tgcgcaggat gacattgtta tgaccgtcat gctgtggcgt ctcgatgccg 6180
+ gtgatatcgc tggcgcgctg gaaattgccc cctacgcgct gaagtacggc ctcacctctg 6240
+ accatcgccg cacgacacct tacatgctgg tcgaggaagt ggcacttgcc gcgcagcgcc 6300
+ tgcgcgatgc cggtgagtct gtcgaccttt cctggctgca ggccactatc gacctgaccg 6360
+ acggcgctga cgttcccgat atggtgcgcg cccgtctgca taaggtgaca ggcctgaccc 6420
+ tgcgtgatgc cggtatgaat gcagaggcgc tggcgcagtt tcagcgcgcg atgcagctcg 6480
+ accgcaatgc cggtgtgcgc aaggagattg agcgactgga acgggcattg aagccaaaga 6540
+ cagaggccgc gccccgtaaa acgactaaac cgcgcacgcg taaacctgcc accaaaccgg 6600
+ cagcaaagcg cgggcgtcca ccaaaggcgg caaaaaccgc cggttaactg aacgctcccc 6660
+ gagccgggcg gcacgccggt caaagcaggc aaagacctga cggcgaccgg cgtccaccgc 6720
+ ccaacctgat gaggttgtca tgacgacagt gatactgaac cagcccgacg aaccgcagga 6780
+ cgtaccgggc gtggtgattc ccgcaccgga gacgggcggt gcagtgatta aaaacacgtt 6840
+ ctttttccct gatgtggatc cgaagcgcgt gcgcgaactg atgcgccttg agcagacggt 6900
+ ttccgatgcg cgcctgcgca atgccatcaa gaccggcatg gccgagacca atgcggagct 6960
+ ttacgactac cggctgcgcc agactgccgc cgggtttaag caactggccg acgtgcctgc 7020
+ cgaggaaatc gacggcgaga atgtacgtgt tttccactat ctgagtgctg tgacggcaat 7080
+ ggcaaccgcc accctgtatg agcgctatcg cggcgttgag gccaccggca aaggtgacaa 7140
+ aaaagccgac agcgtggaaa ccaccattga tgacctgtgg cgggatatgc gctggtcagt 7200
+ tgcgcgtctg caggacaagc cgcgctgcat tgtggggcag ctctgatgaa agtcaggtcg 7260
+ atgcagggcg ataccctcga cacgatttgc gccaggtatt acgggcgcac tgagggcgtg 7320
+ gttgagacgg tgctgcaggc taatccgggt ctgtctgagc tgggtgtcat tctgccgaat 7380
+ ggtacagaga ttgacctgcc cgatgtggca tcgtcacccg taactaaaac tatcaacctt 7440
+ tgggagtaac catgacagaa ggtgaaaaag gcgtcctgtc actgtttgtg attggcgtga 7500
+ tgattgttgt tgggaaagtg ctggcaggcg gtgaacccat caccccgcgt ctgtttattg 7560
+ gccgcatgct gctcggtggt tttgtttcaa tggtcgccgg tgttgttctg gtgcagtttc 7620
+ ctgacatgtc actgcccgcc gtgtgcggga ttggatccat gctcggtatt gccggttatc 7680
+ aggtggttga aatcgccata cagcgccgct ttaagtcaca aaagggggaa agtgatgccg 7740
+ gtcattaaca ctcaccagaa tatcgctgcg tttctggaca tgctggcgta ttccgaagga 7800
+ acagcgaacc atccgctgac gaaaaatcgt ggctacgacg tcattgtcac tggccttgat 7860
+ ggcagaccag agattttcac cgattacagc gaccaccctt tcgcgcatgg ccgaccaccg 7920
+ aaagtgttta atcgccgtgg cgagaaatcc acagcctcgg gacgttacca gcagctttat 7980
+ ctgttctggc cgcactatca gaaacagctc gcattgcctg atttcagccc actgtcgcag 8040
+ gataagctcg cgatccagtt aatccgggag cgcggtgctc ttgacgatat ccgggcgggg 8100
+ cgtattgagc gtgctgtttc acgttgtcgc aatatctggg cgtcattacc gggggccggt 8160
+ tacggccagc gcgagcacgg tctcgaaaag ctggtcacca tctggcgcac ggctggcggg 8220
+ gtggtggcat gaaagtcctg ataacgctgc ttgtgatggc cgtgctcggg ttgctgtggc 8280
+ tgcaccatga gaacggcaat ttatcccgct catttgagac ggcaaaccgc gtcgcgagcg 8340
+ agcaaaagac gacgattggg atgctgaaaa atcagctcag tgttgccggt cagctcgccc 8400
+ gacgtaatga atccgcgcag gtgacactgc gcgaacagct cgcaaaggca agtgcagaag 8460
+ ccagccgccg tgagcagacg ataacgaggt tacttaatga aaatgaagcc tttcgccgct 8520
+ ggtataacgc tgctctgcct gatgctgtgc gtcggctgca cacccgaaca gcctgcgcca 8580
+ gtgccggtga ttgtggtcaa cggatgcccg agggtgagcc tttgccctat gccgggaagt 8640
+ gacccgaaaa ccaatggtga cctgagcgca gatatccgcc gtcttgaggg ggcgctgact 8700
+ gcctgcgcgc tgcaggtcaa aaccgtcaaa cactgtcagg atgaactcga tgcagaagca 8760
+ caaaagcctg cgcaaagcgc tgattaacgc cgtgccgcag ctccgaaata accccgatat 8820
+ gctgcgcctt tttgccgaca acggccacac cgattcccga ctggcgagct cgctgtcgtt 8880
+ tgaaaaggtg tatgtgctta acgtggtggt gaccgacttt actggcgacc tcgatttgat 8940
+ attcgtgccg gtgcaggcat ggctgcgtga acatcagccg gacattatga ccaccgacga 9000
+ cgggcgggaa aaaggattca cctggattat tgatatcaat aacgacgatt cgctcgatat 9060
+ cagtatcagc ctgaggctca ccgagcgcac gctcgtcaaa gaggtcgggg gcgcgctgca 9120
+ tgtcagttat gcaccagaac cgccactgcc tgagctggtg aagcgcccgg ttgctatgta 9180
+ tgcaaacggt gaattagtga gccagtggga tgagtgaatt aaccgcgctg caggagcgcc 9240
+ tcgccggtct gattgccagc ctgtcaccgg cggcacgtcg caaaatggcg gctgagattg 9300
+ cgaaaaagct gcgtaccagt cagcagcagc gcatcaagcg ccagcaggca cccgacggca 9360
+ ccccgtatgc cgcacgaaag cgccagccgg tacggagcaa gaaaggccgc attaagcgcg 9420
+ aaatgttcgc caaactgcgc accagtcgct ttatgaaagc caaaggcagc gatagtgcgg 9480
+ cggtggtgga gtttaccggc aaggtgcagc gcatggcgcg ggtgcatcag tacggcctca 9540
+ aagaccgccc aaaccgcaac agccgggatg tgcagtacga ggcgcgcccg ttgctcggtt 9600
+ tcacccgcga cgatgagcaa atgattgaag acgtcattat cagccacctc ggcaaataaa 9660
+ tattgtgtga accaccaccg gaggcgcgtg atttggcgcg gctaaagacc agaggcattc 9720
+ tttgcactat gaatacgtta tccacgatac aggagctcgc gcgtgcgatt cgtaacctca 9780
+ tccgctcagg tgtggtgacg gaggtcgata ccgtgcaggg gctgtgccgc gtacaaagcg 9840
+ gcgggatcca gacgacatgg ctgaactggc tgaccacccg cgccggtcgt tcgcgcacat 9900
+ ggtgggctcc ctcgctcggt gagcaggtgc tgctgctggc aatcggtggc gagcttgata 9960
+ ccgctttcgt gctgccgggg attttctccg acgataaccc cgctccgtct gcctcggcgg 10020
+ atgcgtggca tgtggttttt cctgatggcg cggtcattga gtacgagccg gagaccagtg 10080
+ cactgacggt cagaggcatc aaaactgctg acgtgaccgc gtcggaatcc atcacggcca 10140
+ ctgtgcctgt ggtgctggtg aaagcagaaa cccgcatcac cctcgacacc ccggaggtgg 10200
+ tatgcaccaa caaactgacg acggcaacgc ttgaggtgca gaaaggcggg gagatgagag 10260
+ gcgacattgt ccacaacggc ggcacattta aatcaaacgg tgtgcagctc gacgaccacg 10320
+ gtcacggtgg tgtgcaaaga ggcggtgcct ggacggagga caccaaatga cggcgcgcta 10380
+ tatggggatg aaccgcaaca ccggtctcgg tatcagtgac actgaacaca tcagccagag 10440
+ catgcgcgac attctgctga cgccggtcgg ctcgcgggtg atgcgtcgtg aatatggctc 10500
+ gctcctgtct gcgctgattg atatgccgca aaacccggcg ctcaggctgc aaatcatggt 10560
+ ggcgtgctat tcggccatcc agaagtggga gccgcgtatc aggcttacat ccatcagctt 10620
+ tgagactggt gatgctggcg agatgtatgt cgatattacc gggatgcgta ccgatacggg 10680
+ tgcgtcagtt tcaaccactg tttcactgag ttaaatcact atggcaactg ttgacctgag 10740
+ cctgttaccc gttcccgatg tggttgagga actggatttt gaaaccatcc ttgcggaacg 10800
+ cattgcgacg ctaatttcgc tataccccga agaccagcag gaagctgtag cccggacgct 10860
+ cgcacttgag tctgaaccaa ttgttaaatt gctgcaggaa aacgcctacc gtgaggttat 10920
+ ctggcgtcag cgtgttaatg aagctgctcg cgcaggcatg ctggcttatg ccagagatag 10980
+ cgacctcgat aatctcgggg cgaatttcaa tgttgagcgc ctggtcgtca ggcctgctga 11040
+ tgacaccacc atccccccga cccccgctga aatggagctt gatgccgatt ttcgtctgcg 11100
+ tatacagcag gcatttgaag ggatgagcgt tgcgggatcc accggagctt atgaatttca 11160
+ tggccgcagt gctgacgggc gtgtcgcaga tatttctgtt atcagccctt ccccagcatg 11220
+ tgtcacgata tctgtgcttt cgcgtgagaa taacggtgcg gcgtctgatg agcttcagag 11280
+ catcgtgcgc aatgcgctga atgctgagga cgtgaggccg gttgctgacc gggttacagt 11340
+ gcagtcagct cagattattg actaccagat acgcgcaaca cttttcgttt atccggggcc 11400
+ ggaaaatgag ccgattcgtg cggcggctga ggcgaagctc aaagcttata tcagtgcaca 11460
+ gcacaggttg gggcgggata ttcgcctgtc agcaatttat gccgcgttgc atgttgaggg 11520
+ ggtgcaacgt gtcgaactgg ctgcacctgt ggccgacatt gtgcttgata aaacgcaggc 11580
+ atctttttgc acagactatc agatagtgat tggtggctcc gatgagtgat gtccgcctgt 11640
+ tacctgtagg gtcatcacct cttgaggtgg ctgctgccag agcctgcgca gatattgaaa 11700
+ atacacctgt tccgttacgc cgcctgtgga gtcctgacac ctgtcctgca aatcttttgc 11760
+ cgtggctggc gtgggcgttt tccgttgacc gctgggatga gaactggccg gaaaaaacaa 11820
+ agcgcgatgt tattcgcagc gcgtatttca ctcactgcca caaaggcacg ataggcgctg 11880
+ tcaggcgagt tattgagccg ctcggataca taatcaacgt tacggaatgg tgggagacag 11940
+ gtgacccggc gggaacgttt cgtcttgata tcggtgtgct ggaaagcggt attaccgaag 12000
+ aaatgtattt agaaatggag cggttgattg ctgatgcgaa acctgcgagc cgtcatttga 12060
+ ttggcctgaa tattatccag gatattcccg gttatatgta tacaggtggt gtgagctgtg 12120
+ acggcgacat tattacagtt taccccggat aagtgaggaa taatgagcac gaaatttaaa 12180
+ actattatca ccactgccgg agccgaaaaa ctggcagcgg ctactgtgcc gggtggtaaa 12240
+ aaagtgaata tcaccgtgat ggccgttggt gacggaggcg gaaaactgcc agtgccggac 12300
+ gccggtcagg tgcagctcgt gaatgaggtc tggcgccatg ccctgaataa aatcagccag 12360
+ gacaaccgga acagcaatta cattgtagcc gagctggtta ttccgcccga ggtgggagga 12420
+ ttctggatgc gcgagttggg tctttatgat gacgagggca ctctcattgc tgttgccaat 12480
+ atggcagaaa gctacaagcc agaacttgcc gagggctcag gccgtgcgca gacatgtcgc 12540
+ atggtcatta ttgtcagcag tattgcctca gttgaattat ccattgattc gacaatggta 12600
+ atggcgacgc aggaatatgt tgatgacagg attgcggaac atgagaagtc acgtcgacat 12660
+ cctgacgcca cactgaaaga aaaaggcttt actcaactca gtagcgcaac agacagcgcg 12720
+ tctgaggtgc ttgcggcgac gccgaaagcg gttaaagcgg cgtatgacct tgctaatgct 12780
+ aaatataccg ctcaggacgc gagcacagcg caaaaaggca ttgtgaggct gagtagtgcg 12840
+ gcagacagta ccagtgaagc tgaggccgca acaccgaaag ctgtcaaaat tgcgatggat 12900
+ aatgcgaatg cgaggctggc aaaagaccgg aacggcgctg atattccgaa tccgccgttg 12960
+ tttgtccaga atatcggttt aaaacccacg gtcgataaag ctgctaatgc cgttgataaa 13020
+ aatggcgaca cgatgaacgg aaacctgaca ctcaagggtg attaccggct gagttttatt 13080
+ atccagaatg aagacggttc tattcgtgct tatatcttca aggacaaagg tggcgacggt 13140
+ attcgcatca gtaatggcga tgacggcggc ggtgattttg tttttggtaa aaatggacag 13200
+ ttttactgcc cggatattat gcatgttggt aatacgattg tatggggtga cggaaatatc 13260
+ gagggggcgc gatggggcgg tttgttgagc gactggttaa ctgctcaact ggttgcccgt 13320
+ gacaataata ttaacttgcg agcaccctat gaatgggtta accaaaactt tgtcaaccgt 13380
+ gcacagcgcg gcgcacaggc cagcatgact atggacggcg gacttgtaga ggctccatgg 13440
+ ggatgttttc ttacgggggg gaacggtaac gagggtaatc aggttggagt ggctttatac 13500
+ cgtccattgc agattcttcg caataatact tgggtaacga tagagaactg aaaatgaaac 13560
+ tgattaattt gcaacgctac attctgatga attatttttt aggtgatggc attcagtatt 13620
+ ttatagatgc cacaggtaag gactggtata aatcactgcc caaattcaca aagaaataca 13680
+ gccttgctat cgaaaatgat acgggcgtta ttcgtagtat cagcgaagat gcatcccggc 13740
+ tttatcccgg tggtttgacc gttgttgatg ttgacagtat tccggcaggt tgtgacattt 13800
+ tcggcggatg ggtatttgac ggagagaaag tcattcctcg tgagtacaca ttggccgaac 13860
+ agcaacgcca gtcagatgaa aagaaaaaaa cacttcttgc tgaggcggag gcaacaattt 13920
+ ccacgcttga gcgtgcggta aggttaggca tggctaccgg ggaagaaata aggcgactcg 13980
+ aagcatggga gcgttacagc gtattggtga gcagggttaa gcagtcagat gactggccgc 14040
+ aaaaacctga ataacccggc ggcatttgcc cgcttctttc tcatccatta gttgtgccag 14100
+ acatcatcca gccctgacaa atagcccctc acaagaccag ccaggacaat aacactcgcc 14160
+ cactaactac ggagttaacc ggatgagtga ttttcaccac ggcacgaagg tcatcgaaat 14220
+ aaatgacggt acgcgtgtta tttccacggt cgcaaccgca atcgtcggca tggtctggac 14280
+ agccagcgat gcggatgccg agacatttcc cctcaacgag ccggtactga ttaccaatgt 14340
+ gcagagcgcc attgcgaaag ccggtaaaaa aggcactctg tcagcttccc tgcaggccat 14400
+ cgccgaccag tcaaagcctg tcaccgttgt cgtgcgcgtt gccgaaggta ccggagacga 14460
+ tgcagaagcg cagaccacgt ccaacattat cggcggcacg gatgagaacg gtaaatacac 14520
+ cggtatcaag gcgctgctaa ctgccgaagc ggtcaccggc gttaagccgc gcattctcgg 14580
+ tgtgccgggt ctcgatacgc aggaagtcgc aaccgcactt gcatcggtct gtatcagcct 14640
+ gcgtgcgttt ggttacgtca gcgcatgggg ctgtaagacc atttccgagg cgatggcata 14700
+ tcgcgagaat ttcagccagc gcgagctgat ggtcatctgg cctgatttcc tcgcatggga 14760
+ taccaccgcg aacgccaccg ccacggcata cgccaccgcc cgcgcactcg gtctgcgtgc 14820
+ ctacatcgac cagactatcg gctggcacaa aacgctgtct aacgttggcg tgcagggcgt 14880
+ taccggcatc agcgcctcag tcttttggga tttgcaggca tccggcacag atgctgacct 14940
+ gctcaacgag gccggagtca ccacgctggt gcgcaaggat ggtttccgtt tttgggggaa 15000
+ ccgcacatgc tctgatgacc cgcttttcct gtttgagaac tacacccgaa ccgcgcaggt 15060
+ gctggccgac acaatggctg aggcgcacat gtgggcggtc gataagccca tcaccgcatc 15120
+ gctcatccgt gacattgtcg acggcattaa cgccaaattc cgcgagctga aatcaaatgg 15180
+ ctacatcgtg gacggtgaat gctggttcga cgaggaatcg aacgataagg aaaccctcaa 15240
+ ggccgggaaa ctgtatatcg actacgacta tacaccggtt cccccactgg aaagcctgac 15300
+ cctgcgccag cgtatcaccg ataaatatct ggtgaatctg gctgaatcgg tcaacagcta 15360
+ aggagcctga aacaacatgg cactaccccg caaacttaaa tatctgaaca tgttcaatga 15420
+ cggccttagc tacatgggcg ttgttgaatc cgtgacgctg ccgaagctga cccgcaagct 15480
+ cgaaaactat cgcggcggcg gtatgaatgg cgctgcagcg attgacctcg gcctcgacga 15540
+ tgatgcgctt actgtcgaat ggtctgtcgg tggcctgcct gatgtggcgc tgtgggcgca 15600
+ gtatgccgcg ccgggggctg atgccgtgcc gctgcgtttt gctggctctt accagcgtga 15660
+ cgacactggc gaaatcgttg cggtcgaggt ggtcatgcgt ggccgtcata aagaaatcga 15720
+ cggcggcgaa aataagcagg gtgaaaatac ctcgaccaaa ctgtcgaccg tttgcaccta 15780
+ ttaccgcctc acgattgatg gtagcgacat tatcgaaatc gacaccgtca acatggtcga 15840
+ gaaggtgaac ggcgtcgacc gtctggaaca gcaccgccgc gcaatcgggc tgctgtaatt 15900
+ ccctgaccgg tcagcactgc tggccggtta ttacccccat tcagagcaga gaaaaaatca 15960
+ tggcaaaagc accacgcaaa acccctgaat ttattgatac ggctggcaat gaaattgaca 16020
+ ccgtaaaccc gaatgtcgtg accctcgaca agccgattaa acgcgccggt cagacaattg 16080
+ aaaaggtcac gctgattgaa ccgaacgcag gcaccctgcg cggcgtcagt ctggcggcgg 16140
+ tggcgcagtc cgaggtcgat gcgctgatta aggtgctgcc ccgcatgacc tacccggcac 16200
+ tcaccacgca ggagctcacc gcaatgaacc tgcccgatat gctgtcgctg gccgctaagg 16260
+ tgattggttt tttgtcaccg gcttcggcgg aatagacttc ccgcccggcc tgtcgaccga 16320
+ tgacctgatg gcggatatcg cagtgatatt ccactggccg ccatcagagc tctattccct 16380
+ gagcctgacc gagctcatca catggcgcga aaaggcgctg cagcgtagcg gaaaccacaa 16440
+ tgagtaataa cctgaggctt gaggtattgc tgaaagcggt cgaccaggcg acccgaccgc 16500
+ ttaaatccat ccagaccgcg agtaaaagcc tgtcgggcga tattcgcgac acacaaaaag 16560
+ ggctgcgtga cctgaatggt caggcgtcga aaatcgacgg ctttcgtaag gcaagcgcgc 16620
+ aactggccgt aaccagtcag gcgcttgata aggcgaaacg cgaggccggt gagctggccg 16680
+ tgcagtttaa aaacaccacc aatccgaccc gcgcgcaggc gcaggcactt gaagcggcaa 16740
+ ggcgtgccgc ctctgagttg cagacgaaat acaacagcct gagaacgtcg gtacagcgcc 16800
+ agcgctccga gctgatgcag gccggtatta atacccgcac tctctctgca gatgagcgtc 16860
+ ggctcaaaac ctccatcagc gaaacaactg cgcacgttaa tcgacaacgt gaggcactgg 16920
+ cgcgcgtcag tgcgcagcag gcgaaattaa gccgggtgaa agagcgatat aaatcaggca 16980
+ aggagcttgc cggtaacatg gccgcagcag gcgctgcggg gtcggtatcg cgacacggaa 17040
+ cgatggccgg ggttaaatta ctgatgcccg gttatgactt tacgcagaaa aattccgagc 17100
+ tgcaggctgt gctcggggtc gataagcagt cgccagaaat gcaggcgtta cgcaaacagg 17160
+ cgcgccacgt cggcgacaat actgcagcct ctgcagatga tgcagcaagc gcgcaaatca 17220
+ tcattgcgaa aagcggcggt gatgcagctg ccattcaggc gcgacgccga gtcacgctga 17280
+ atatggcgct gtcaaaccgg cgcacgatgg aggaaaacgc agcgctgctg accgggatga 17340
+ aatcagcgtt tcagctttca aacgacaaga ttgcgcacat tggcgacgtt ctctcgatga 17400
+ cgatgaacaa aaccgccgcc gattttgacg gactgagcga cgcgctgacc tatgccgcac 17460
+ cggtggcaaa aaatgccggg gtgagcatcg agcaaaccgc cgcaatggtc ggtgcactgc 17520
+ acgatgccaa aatcaccggg tcaatggcgg gcacgggtag ccgcgccatt ctcagccgcc 17580
+ tgcagctccc accggaaaaa gcgtttgagg ccattaagga gctcggcgtc aaaacgtcag 17640
+ acagcaaggg gaacacgcgc ccgatattct ccatcctgaa agaaatgcag cgcagctttg 17700
+ agaaaaacaa cctcgggaca agccagcgcg gcgagtatat gaaaaccatt ttcggcgagg 17760
+ aggccagctc ggcggcggcg gtactgatgg aagccgcctc aagcggcaaa cttgaccggc 17820
+ tcaccgctgc gtttaaagcc tcggacggta aaaccgagga actggttaag gttatgcagg 17880
+ ataacctcgg cggcgacttt aaagagttcc agtctgctta tgaggcagtc ggtactgacc 17940
+ tttttgacca gcaagagggc tcgctgcgta aactcaccca aaccgccacg caatacgtgt 18000
+ taaagctcga cggctggatc cagaagaaca aagggctggc gacaaccatt ggcatcatcg 18060
+ ccggtggcgc tctagctctg attggtatca tcggcggtat tggcctcgtt gcgtggccgg 18120
+ ttgttatggg gattaacgcc attatcgctg ctgctggcgt gctgggtacg gtctttactg 18180
+ ttaccggtag tgccattgtg accgcacttg gcgcgattac ctggccgatt gtcgcagtgg 18240
+ gggcggctat tgtggctggt gcgttactca tccgtaaata ttgggagccc atcagcgcat 18300
+ ttttctcggg ggtgattgag ggcatcatga gtgcctttgc accggtcgca gaaatgttcg 18360
+ ctccactggc acccattttt gacggtctcg gtgagaaact gcgcggcgtc tggcagtggt 18420
+ ttaaagacct gatagcaccg gtcaaagcca cgcaggaaac gctcgatagc tgcaaaaatg 18480
+ tcggcgtcat atttggtcag gcgctggcct ctgctttgat gcgtccgctc aatgtattta 18540
+ acaagttgcg cagcggtgtc gactggcttc tcgaaaagct cggtatcatc aacaaagagt 18600
+ cagacaacct cgaccagact gccgccaaaa ccaacgccgc cacgcagggt aattcctaca 18660
+ tcccggcaac cagcacatat ggcggctatc aagcttacca gccagttacc gcaccggcgg 18720
+ gacgctctta cattgaccag agtaaaagcg aatacaaaat cactctgccg ggaggtgctg 18780
+ cgccggggca tcagcttgac agacagctac gcgacacgct cgaacagatt gagcgcgaaa 18840
+ agcgtgcgcg tcagcgtgcc agtatgagcc atgactgaga ggaataaacg atgatgcttg 18900
+ cgcttggaat gtttgtgttt gaacgtcgca ccctgcctta tcagtcgatg cagcactcga 18960
+ aggattaccg ctgggcgtct aatgaccggg tgggtaagcc gcctgcttat cagtttctcg 19020
+ gcgaggggga aacgtcgata cagcttgccg gtacactgta cccagccatc accggcggct 19080
+ ggatctcact aaaggctgta gaggtaatgg ctaacgaggg cagagcgtgg ccgttgattg 19140
+ agggaactgg aaacatttta gggatgtata tcgtcgataa ggtgtcgacc acgcacgccg 19200
+ agtttttcag cgacggcgct gccagaaaga ttgatttcac cctttcgctt aaacgggtcg 19260
+ acgaatcact gacggcaatg tttggcgacc tgaataagca ggccagcgag cttctaggct 19320
+ ctgccggaaa tctgactgat aagctgcaga gtgcgctcgg aggactgacc gcatgattac 19380
+ gggcatgact attgacgccg gaactagcct tgcaccggca tttatgttaa cgctgaacag 19440
+ ccaggacatt accagcaatt ttagtgaccg gctgatttct ctcaccatga cagacaaccg 19500
+ gggctttgag gctgaccagc tcgacattga gctcgacgac accgacggca aagtcgaatt 19560
+ accactgcgc ggggcggtgc tgacgttgtg gcttggctgg cagggttcgg cgcttctgaa 19620
+ taagggcgat ttcacggtcg atgagattga gcaccggggc gcgcctgata tcctgaccat 19680
+ ccgcgcgcgt agtgcagact ttcgcggaac gctcaattca cggcgtgaag aatcatggca 19740
+ cgatactacc atcggtgagt tggtcagcac cattgcaaag cgcaataaac tgacggccag 19800
+ tgtcgcggat tcactgaaaa aaattccggt accgcatatc gaccagtcgc aggagtccga 19860
+ cgccgtattt ctgacccgac tggctgaccg caatggggcg acggtgtcag tgaaagcggg 19920
+ gaaactcctg tttctgaaag ccggtagtgc gatgacggcc agcggcaagc ccgtcccgca 19980
+ aatgacacta actcgcaacg atggtgaccg tcatcagttt gctattgccg accgtggggc 20040
+ ttacaccggc gtaaccgcaa aatggttgca caccaaagac ccgaagccgc aaaagcagaa 20100
+ agtaacactg aaacgcaagc caaaagagaa gcacctgcgc gcactggagc acccgaaagc 20160
+ aaagccagtc agcaaaaaga caaaggccaa aaaagagccg gaagcgcgcg agggtgagta 20220
+ tatggccggt gaggccgata acgtgctggc gctgacgacg gtctacgctt ctaaggcgca 20280
+ ggcgatgcgt gccgctcagg ctaagtggga taagctgcag cgaggcgttg cggagttttc 20340
+ aattacgctg gcgcttggta gggctgattt attccctgag acaccggtgc gcgtatcagg 20400
+ ctttaagcgc gtcatagatg agcaggcatg gttaatcagt aaggtaactc acaatctgaa 20460
+ taatagcggc ttcacgacgg gcttagagct tgaggttaaa ctctctgatg tggagtacaa 20520
+ cgcggaatcg gatgatgaat aaaatgtatt cacaaaaagt gaatttatga ttatcattta 20580
+ ttcacgaatt gagaataaag ggtgggttat gtttcattgt ccgaagtgcc atcatgccgc 20640
+ acatgcgcga acaagccgct atctaaccga aaacacgaaa gaacgctacc accagtgcca 20700
+ gaacatcaac tgtagttgta cgtttatgac aatggaaacg atagagcgct ttattgttac 20760
+ tccgggagcc attgacccgg caccgcctca cccgactgtc ggtggtcagc ggccattgtg 20820
+ gctctgataa atttccgcta aatgcccgcc gcgtgcgggt ttttttatgc actcaggaaa 20880
+ gtggcggtaa aaaatccacc gccattctat cgccactcga aaacgaggca acaaaaaggc 20940
+ cactcgagag agtggcctaa ctgtatgtat ttactactta aatttggtgg cccctgctgg 21000
+ acttgaacca gcgaccaagc gattatgagt tcctacaaga acaaccgaaa atcaatagta 21060
+ tgcgttattt atcattgaca tagattgcca ttgtttgcca ataattacct attattcgcc 21120
+ atttctaccg ccactttatc gccattctat tatctgttac tcagaaccat cgccgacata 21180
+ ggttccatca ggtgcgagat gatatcttcc accaacataa gtcccatctg gggcaagtgt 21240
+ tgggcggcca gcaacataac ttccatttgg cgctaagcgt ggaacgccac cgacataagt 21300
+ tccatcgggt gcaaggcgtg gtgctcctcc tccaacataa gtcccattag gggcaagcat 21360
+ aggttgtccg taaacatagg taccatcagg tgctaatcga actgacatac aacctccata 21420
+ acttatttgc tgttagtaat gtgaactaaa ggatttagtt taactgcatc ctctaaatgg 21480
+ tcgggtgcaa agtgcgcata tcgcatggtc atttttatat ctgtatggcc gagtacgcgc 21540
+ tgcaacacca aaatattacc gccattcatc ataaagtgac tagcgaaggt gtgacgtaaa 21600
+ acgtgggtaa gttgtcctgc cggtagttcg atacctgttc tttccaaagc tgaccggaac 21660
+ gcgccataac aatcactgaa caaccggccc tttttatcat caggcagaga ctcatagagc 21720
+ tctttgctga ttgggacggt gcgatttttt ctacctttcg tgttggtgta tgtgattttg 21780
+ tatttcgcga gttggctttt tctcagactc tcggcctcag accaccgtgc gccagttgcg 21840
+ agacagattc ttaccacggt ttctaaatca gggtggtcat gtcggttaca ctctccgagc 21900
+ agttgcgaaa tttggtcgtg agttagccaa gtcatttcca tttcttctgt gcggaatggg 21960
+ cgcatatttt ttagtgggtt ttcacccttc cattctccga ggcggtttag ctcattgaac 22020
+ accgcccgga agtaggccag ctcaagatta agcgtgcgag gcgatacctc tttcactctg 22080
+ tttgaacggg catactcacc ttttaaccgt ttttctcggt agcgggaaaa catctgcgca 22140
+ tcgaaatcgc gtgcgagtgg ttcgcccata cactcaaaag catggtgcat ggctaactgg 22200
+ cgtttcaaac catctttcag tgtaatgcca tgagcgctat accatgaatc aaccagctct 22260
+ tttaacgtgc gcctgtcttc cttttcttcc tgccacgggt tttgaacggt gtactgctca 22320
+ aacgccagag cctcgccttt agtagcgaat ttctttctga tacgtttgcc ttttgcaccg 22380
+ tttgggtaga gttcacaaat ccaaccgcca gccggatttt tacggacggt cattagttaa 22440
+ cctcgctgta tacacccacc acacgcccca acgttttaat atcatcaatg ccgcattcga 22500
+ aaggaacctt cccgcctgca acatgtagtt ttctacccgg tagttttgtt aactctcgaa 22560
+ tgcttattgc tccctcaatg tcgactagcc ataagccgtc agacaatgat gcttgcttgt 22620
+ ccacaaaata aattttcccc tcggaacgga tagccatccc atctgtgagc ggttttgtga 22680
+ aaaattgggc atcaacactc aattgtttat cggatttgag gatttcttca cttaatgtga 22740
+ atccctcaat cctttttgcg tcgctcgatt ctctgttatt tacaaatgct tctccttctc 22800
+ cggtaagtaa ccactggaga ttagcacctg tttcaagggc acagtgagcc gcaaagtcat 22860
+ aggagatagc gcctcgggtg tacctgtttg acaatgagct ggacgcgata tcgaagtggt 22920
+ tagctaattg aattttctgc gaaaatccgt acgcctcgca gatgcggtca agtacatcca 22980
+ cgttgctcca tcctaaagaa tctattctca tttcgataaa acctatttac tatctctcaa 23040
+ ttgggagata tattttggct aaacccacgc aattgatggc aagtgttggc aaacagagtc 23100
+ aaatcaattg caaactttgg ctaataggga atcatgcaat atggcttctg aaatcgcaat 23160
+ catcaaagtg cctgcaccta tcgttactct gcaacaattc gcagagcttg agggtgtttc 23220
+ tgaacgcacc gcctaccgct ggacaaccgg cgacaaccct tgtgtaccaa tcgaaccccg 23280
+ cacaatccgt aaaggctgca agaaagcagg tggcccgatt cgcatttatt acgcacgctg 23340
+ gaaagaagag cagttgcgta aggcgttggg acattcccgt tttcaactcg tcatcggtgc 23400
+ ttaattcact ttatgtgaat tgtaaggatg caacatgttt gattttcagg tttccaaaca 23460
+ tccccactat gacgaagcgt gccgggcttt tgcgcagcgt cacaacatgg cgaagctggc 23520
+ cgagcgtgcg ggtatgaatg ttcaaacgtt acgtaacaag ctcaacccag aacagcctca 23580
+ ccagttcacg ccgcctgaat tgtggctgct gactgacctg accgaagact caaccctcgt 23640
+ tgatggtttt ctggcgcaga ttcattgtct gccatgcgtg ccggttaatg agctggctaa 23700
+ agataaattg cagtcttacg tcatgcgcgc aatgagtgaa ctcggtgaac tggcgagcgg 23760
+ tgcggtatct gatgagcgtc tgaccactgc ccgtaagcac aacatgattg aaagcgttaa 23820
+ ctccggcatt cgcatgttgt cattgtcggc tctggcgctg catgcacgtc tgcagactaa 23880
+ tcccgctatg tcgagcgtgg tcgataccat gagcggtatt ggcgcatcgt ttggtctgat 23940
+ ttgaggtgcg tatgctgaaa agtgaaccgt catttgcgtc tctgctcgtt aagcaaagcc 24000
+ ccggtatgca ttacggccac ggctggatcg caggtaagga cggcaagcgc tggcacccgt 24060
+ gccgctcaca gtccgaatta ttaaaagggc tgaaaacaaa gtcgccgaaa tcgtcaggtt 24120
+ ttttaattat tcgtattgtc cactttgtaa ttaaaggagt gaaacatgtc acgcgatgaa 24180
+ ttaagaattg ttttgggtgc catgattcca aatatggagg aaggttttga aattaaaacc 24240
+ cgcgacggcg caatacttcg cgttgaccct gagtgggagt gctgcaaaga atttaaggat 24300
+ ggattaaaag ccgaaatcat caagcagtta aaaagcaaac ctgctgttgt atttggatat 24360
+ agttaattaa ttaaacgtaa ttacttggcg taaacccgcc gggcattctt ttgccaaaaa 24420
+ acaggaggat atatgagtcg aactatttat ttatcaacgc cgagtggtgc tggcgaccac 24480
+ ttgctggagt ctttgtttaa agaagccaaa aaagaagagc gcaaagaccg ccgtctcgcc 24540
+ gtttcaatcc gtctcgaaga tctggccgtt cacattacca attcagatat gacaggcaaa 24600
+ gaagcggccg agctactgcg ccgcgaagcc actcgctttg agaacgaatc acaggagctt 24660
+ cactaatggc cgacgcaatg gatttagcac aactgcgcga gcaggaagac cgcgaacgcc 24720
+ acataagcaa cgcgcgcagc cgtcgccatg aggtttctgc atttatctgt gaggaatgcg 24780
+ atgcacctat cccggaagcg cgccgccgag ccataccggg cgtgcagtgc tgcgttacct 24840
+ gtcaggaaat cttagagctg aaaagtaaac attataacgg aggtgcttta tgagcattac 24900
+ caatgcaact attagccagc gtgcaaaaaa atggcttgaa gatgaccgta tatttattga 24960
+ caccgaaact acgggtttgg gtgatgatgc ggaaatagta gaaatctgtt taatagatag 25020
+ cgctggtttt atcatgctaa atacattggt taaaccaact aaaccaattc cagcagaggc 25080
+ tacggccatt catggaataa ctgatgaaat ggttatgtat gcgccaacgt ggaaagatat 25140
+ tcacggcgca gtagcttctt tattttttga gtatggcttt gttatttata acgccgatta 25200
+ cgacacaaga cttatatatc aaactgcgaa attatatggg cttgagaatg acggcttttg 25260
+ ttatttttta aatgagcgtt cggcctgcgc catgatgcta tatgcagagt atcgcggcga 25320
+ gccagggcga tttaaaggtt ataaatggca caaattagtt gatgccgctg cacatgaagg 25380
+ ggttagcgtt gaaggaaagg cacaccgtgc attagcagat tgccggatga ctcttggcat 25440
+ tatcgacgct ttggcaaaag gcggtgcagc atgagtatcc gtatcgaaat aggtgataaa 25500
+ tgggtaatca ccagcgacca atatcaattc atcctgaatg aaaaaaaagt cgttaagacc 25560
+ ggcaataaag ctggcgagga atggctcgac accatcggtt attacccgaa gattaatcag 25620
+ ctcatttctg gtctggtaca tcaccacatt catacggcaa tgattatttc ccttagtgca 25680
+ atggcagagg aaatagagaa gttatctttt atctgtgaag aagcatttaa ggcggttaaa 25740
+ aaatgattga ttcccgctgc tttgctgaaa gcacaataaa tattgtttct gtttctggtg 25800
+ gaaaggacag ccttgctcaa tggattcttg cggtagagaa cgacgtaccg cgcaccactg 25860
+ tttttgcaga taccgggcat gagcattccc aaacaatgga gtatctggat tatcttgaat 25920
+ ccagactcgg cccggttatt cgagtgaaag ccgattttac tcggcggatt gaaggcaaac 25980
+ ggaaattcat tgctgaaaaa tggcctgtct ctctcgttga agaatgcgga atgtctcatg 26040
+ agcaggctgc agaacgaatc gcaaaggcac tggaaatcct taagccaacc ggtaatccgt 26100
+ ttctcgattt gtgcatgtgg aaaggacggt tcccgagcac gaaagcaagg ttttgttcac 26160
+ tggaactgaa acatgactca gtacgggaca agattgtact cccagcgctg gagaaatatg 26220
+ acgaagtaat tctatggcag ggtgttcgtg ctcaggagtc accagcccgc gctgcgttac 26280
+ ctatgtggga ggaggatgca gataataccc ccggtttgca tgtgtatcgc ccaattctta 26340
+ actggacaca tgaagacgta tttgccttag ctaaacgaca cggaattaaa ccgaacccac 26400
+ tctatcagca aggttgtagc agagttggct gcatgccatg tattcatgca agaaaatctg 26460
+ agctggcaga gatttttgct cgctggccgg aggagattgc gcgcgttgca gagtgggaac 26520
+ gtcttgttgc tgcctgttca cgtcggggaa actcaacatt tttcccttcg actcacgacc 26580
+ cgcggcgagc agaaaaacgt attgaagttg ttaccgtaga agaatatggg atagcttcat 26640
+ atcgtgactg ggcgatgact acgcgtggcg gttctcagta cgatttgctc gctgctacaa 26700
+ acgacaaaac tgtgtgcagt agcgtttatg ccggtgtatg tgaatgacgg gtgtcgttta 26760
+ cgcgtttccg tggaatgccc cacggtcggc aatagccagc tcatatctta cctatgacca 26820
+ acagcatcgc cgcgaccgta tgttcgcggc tttgctgcat gcgagaaagg tgctttttct 26880
+ ccagccagaa tgtgtgcgct ttgacgttta tcgcaccgct gcagttctgg agcaaaatca 26940
+ gggcagtcaa cgagccaatg cctttttaat cagcttctgc aaaaaggcat taccacgtct 27000
+ tgaactggtc gcaaaaaaat acgagtgctc gggcatcaac agcaatgtat cagccgctgt 27060
+ tttcgatggt cattttgata cccagcttat gcaatatctg gcgtcacgca tggtcaatat 27120
+ ggtcgccaga tttaaccgcc tcccggatat gtcgcgcgcc gatattgacc tgctggccgc 27180
+ ggatatcgct aattttattc gcgctgaact ggccgacatt gatgacaccg gatttagcga 27240
+ actcaaaacg ctgtacactt ggtacatgcg cgccggtttt atttccctgc aattcaacgt 27300
+ tacaccgccg aaatgggagc gtgtgactaa aaaatatttt tgtgaggatg aaatcgcacc 27360
+ ggcagtaatg cgcatgttta atgaggtttg gtggcgcggc cgcttgcgac gcattgcggc 27420
+ tgcatggcgc gaacatctgc aaattgcagt cggcaacgta agcaagaaac gacacgcata 27480
+ cgcgagtaaa aactgcgtca ccgactggcg cgagcagaag cgccgcacgc gcgaatttct 27540
+ caaggggctg gatctcgaag acgaagaagg caaccgcatc agtctgattg aaaaatacga 27600
+ cggctcggtc gctaatccag caatacgccg ctgtgagctg atggcccgca ttcgtgggtt 27660
+ tgaaaatatc tgtaatgagc tcggttatgt cggggagttc tatactctga ctgcaccgtc 27720
+ taaatatcac gccaccacca aagcgggcta ccgtaacagc aaatggaacg gtgcaagccc 27780
+ gtcagacacg cagagctatc tcacaggcct ttgggcacgc atacgcgcca agctgcaccg 27840
+ ggaagaaatc cgcattttcg gaatacgcgt cgctgaaccg catcacgatg gaacgccgca 27900
+ ctggcatatg cttatgttca tgttgccgga agatgtcgag cgtgtgcgac tcatcatccg 27960
+ agattatgcg tgggaggaag accactacga actgagaagc gataaagcca aaaaggcgcg 28020
+ cttccatgct gaggccattg acccggaaaa aggcagtgct actggctatg tcgctaaata 28080
+ catttccaaa aatatcgacg gttatgctct cgatggtgaa accgatgacg aaagtggtga 28140
+ gttgttaaaa gagactgcac ccgccgtttc agcatgggcg gcgcgctggc acatccgtca 28200
+ gtttcaattt atcggcggtg cgccggtgac ggtatacagg gagctacgca gaatggctga 28260
+ ccctgaaaca gccagggcgc tcagtgttga attcgccgca gtgcatgatg ctgctcacta 28320
+ tggacgctgg gctgattatg tgaatgctca aggcggacca ttcgttcgcc gtgacgattt 28380
+ acaggtacgt acattgtatg aacctcgaac tgaatttaat cagtatggcg aagaaactgt 28440
+ gtgcatcaaa ggtgtctacg atgcctcgat aggtgctggc tctcctattc taacccggtt 28500
+ aacgcagtgg aagattgttc caaagcgtgc cgttgatttg gccgttgacg ttaagggcgc 28560
+ ttctgcgccc tctcggagtt ctgtcaataa ctgtacggga agcgaaagcg atccaccgat 28620
+ actcgattta acaaaaccgc tgagtcggcg tgaaagacga gagttgacga accgactcag 28680
+ gaagaaaaag ccaacaacac ggcgaaaatt catccacgga acggataagc aaaacgtcgc 28740
+ tataacgaaa actatcgacg agatacactc tgacaaccgg catcacaatc agccggggcg 28800
+ aagccctgca cctgatggcc ggtggtaaaa gttgttttaa cggtcgatgg gtgcgcggaa 28860
+ cgtcaaaagg tgaaatcttt gctgcagctc cttcacatca ggcgagacgc ggaaaatcct 28920
+ taatcgtgtt gcagatttag ctgagctggc aacgaaaatg taaccgctaa tattcatcca 28980
+ tatcatgtac atacagtgta tttaactgtg atttttttct tcacaccttt tgccaatacg 29040
+ tgctactgta tgtttataca gtatctcgta gtggaggttg tgtggataga gagctaaatg 29100
+ agcacgttat gattgagcgg gtcgaaatga ttgcgcgtct gactgctgaa ggtacttgtc 29160
+ aggaaagaga ccgtgaaatc gcattgaatc taattgcgga aatagcaaga ggcaacctaa 29220
+ tgaaaaataa taatttttct gttgtttttt ccgcaccgcc tgttggtgaa acatttgcaa 29280
+ aggagggcaa agtgaaagta aatatcacgt tggataaaga ccaaaaaatc ggccagccgg 29340
+ taattgatgc ttttcagtgc gaattgacca agcgaataca gtccgttttc ccgtcaacgc 29400
+ gcgttactgt taaaaaggga tccatgaccg gtgtcgagct gatggggttc gataaagatt 29460
+ cagaccgcga agcgctggat agcatccttc aggaagtgtg ggaagatgag agctggcgtt 29520
+ aatccctgaa aaatgtgcaa ccatcgaccc catgtttgat agcatggggt tgttttgtat 29580
+ gggattacac acaaaggaaa atcatggata ccgtaatagc atttttatct ctggctctct 29640
+ ttattgcttt tatcgtcggg ttaatcaagc cgtcgctggt tcgaatgccg agccgtaagc 29700
+ gctccagtgc tgtttatctc ggtggctgtc tggcgctggg cgttattggc tcaatcttat 29760
+ ggccgactga aaaaactcag ccggtggcaa aaactgacgt accggcggtt aaagcggaac 29820
+ cggctacgcc aacgtttgag tacgcagata aaaccctcaa agaatatcgc aacgagccaa 29880
+ aagaaacccg gcacgaaatc gttaaagact atgttggatt caaaggtgtg caggccagcg 29940
+ ctaccgatgt tttttatgcc tgtatgagcg agtacacttt taccaaagat gatgcgttaa 30000
+ agctcagtga tgtgttgggg tggtgtttca acgacttcga gaaagatcca caatctctga 30060
+ ataataaaat caaccttgac gaatttcagg gtaattttag cggttgggat ggctcttatc 30120
+ gcccgttaga gaagctgata aaagccagta tgaatgatga ttcctcttat aaacatgttt 30180
+ caacggtcta ccatctgatt ttgaataaag acccgcatgc cgttgtaaaa acaacgtttc 30240
+ gcggcactaa tgcttatggt ggcgtggtca aacagaccgt agcagcacgc gtcaacgtgc 30300
+ gaacgggcga ggtcgattca atactcgaca attaaacatg acaaacgccg ccggtgctga 30360
+ aactcgcttt cagtgctggc ggggttgaac aacgagcccc gcgaggcgtt agcctacccc 30420
+ tgagcaaccg cccaagaccg gtactattaa agccggtttt tttatgccac ttttccacga 30480
+ atttcccgtt tttttagccg tgcatgcaac aggtgcattg ttttgcatgc gtcaggcttg 30540
+ cccgttctgg ttgtgcgtcg ccagagctgg cgcggctcca gagtggtcat gcaactgcat 30600
+ taaaaccgac ccataaagtg ggca 30624
+//
diff --git a/etc/b132222.fasta b/etc/b132222.fasta
new file mode 100644
index 0000000..03a6c62
--- /dev/null
+++ b/etc/b132222.fasta
@@ -0,0 +1,512 @@
+>B132222 U32222 Bacteriophage 186, complete sequence.
+ggcgtggcggggaaagcattgcgcgccagaggtggcgcgtgaatgataaaaattatcgtc
+tgagcgcctcgtaatggcgctatcgtggtgctgttggttcgttggtggtcgtgtgtgttt
+gtgcgcgtgtggcgcgtctgagacgtgatggtggcggggtatgaaaaagccgccatgctg
+gcggcttgagagggattattccgggttgtcgagggtgtactctttgaacctgatgacctc
+catgccgagccagtcgtttacctccctgaacctgtcctgcagcggcgacaggtcgttacg
+cacaaatacctttgccactttctcaacgtcaccgagcgagccgatattctcgggcttgcc
+gcccatgagctggaacggtacgcggtgcgcgtccatcaggtcggcggcgctggctttctt
+gatgttgaaaaagtcatcctttgtggcgacctcgctcagtggcacgattttaatgccgtc
+cggttttccattgggagcgtagaaaaacaggtttttaaaattgccgagccctttcgagtt
+gcgcatcgcctcgcgcagcgattcgacgtcggtcgcgctctgcgccgggtcggtcacata
+catgatgtaacccgcatgcgcgccgttctggtaatacttgcggcggaacagcgttgcgga
+ttcattcagccaggcggaattaagcgcgctgagatattcgggcaggccgtaaatctcctg
+attaatgtcgggctccagcaggtgaaacacggtatcaggcgcgaattcatgcggcagagt
+gaagttttccacaaaccagaaaaccgaatcgtcgaccccgcggcgggtgtatttggccgg
+tgaggccagtagcttgattagctggccggtaacgctgtggcgctgctcaagaaaggcgtt
+gccgaataccagatagtcgagcgcaaagcggctgaaatcctgacgggacagcagcgggtg
+cgggatgtaggtgctcgcgagcacgttgcgcttaacgtaaatcggtgagctgtgatgtac
+agcagagcgcaggctctttgccagcccggagaagctgaccggcggctcgtaccatttgcc
+gttactgatgcactcgacataatccagaatgtcgcgcttatcgagtaccggcactggctc
+gccaaaggtgaacgcctccattttttgcggggcgctggcggtcatggctacctgtttgcg
+tggcttgcgcttgctcatgctgccacctcacccgctgcaacagcgaaagaccagtcgcag
+ccaaacagcaggcgataatcatcgtcgctgtattcgtgtttaatttcatcgggcgcaaag
+agattgcacccgcgctggcatgctgcatccagagtgaccgactgacgccagacaccatct
+gtacaaaatacgctgtcgccggtattgatgagcggtgacggtcggtgcctgcgggttgtg
+ccgttccataccctgaaagcgtcataattatcagagggcgaggtaaacatcgtcaggctg
+tggcgtttatggcaggcgatacccgccgcgactttcgcagctcttagcgggttattgaac
+catccgaactcatcaagatagacattacccgccagtgcggcgcaatgggattcctcgccg
+acaaagctgataaccgcaccgtcgtcgagctgcaggctgtggccgttactcgtaagactg
+acgccgacgcgcgccgaaaggttactcatgtacatcagcgccacgcgcgcatgctcaata
+gtgtgagcaaaccagacatgattatcgcctgttgtcagcgcatcgagcagcgcctcacgg
+ctaaagagctgcgttgcgccaatctggcgcgatttggtgatgctgcggtcgatattgagt
+ttcccgacacgcaaccatgttgcctgatagtcaaagctgtcattgtgcagaatatcggcc
+attgcctgaatctggctttgtgagaaaacgttatttttcattaattaaactccagaatgg
+atttaggctgtatgccgctaccggcggaaagcggttcgtttaacagggcgtgcatggtcg
+cccatgcaatatcggcgtgactggcttcctcggtgcggctggcctcgtaggtggcgctgc
+gcccgctgctggtcatggttttgcggatggacataaacgactgcgtgacgtcggttgctc
+cggcgtcgtactccagacagccgcggcgaatggtgtcttttgccttgagtaccattgcgg
+ttttcatttcaggcgtgtaacggatgccgcgtgcggccgggtagaatgagcgcaccaact
+ggaacacgccgaggccgaggccggttgcgtcaatgccgatgtattcgacgctgtatttct
+cggtcagtttgcggatgccatctgcctgcgcggcaaagtccatgcccttccactggtggc
+gctccagcatgcggaacttgccaccagaaaccaccggcggcgcgagcacgacgcacccgg
+cgctgtcgccagtgtgtgacgggtcgtagccaatccagacggggcgagagccgaacggat
+ggtcggcgaacggggcgaagtcctcccattcttccatcacgtcgaccatgcagcgctgca
+gctcctcgaacgggaataccgacgctttatcgtcgacaaactcgcacataaacaggtttt
+taaagtcctcatcactgttttcgcgtttgagttggtcgaggtcgaacagggtgcagccac
+cggcaagggcgtcctcaatggtgacaatctgccgccactggccatcgtcgcagagctggc
+caccggcgagcgcgcggtgactgatgtcaatttcgatgcggtcggcaatacggctgcgcc
+ccttgttgaacagctcgccagaccagaagggataagcgccgtgcgccagcgttgagggtg
+tcgaaaagtaggttgagcgcagatgcttctgcgaggccatgcccgaggcgactttgcgca
+gcttctgaaaattcgggatccagaatatttcatcgacatacaggtcgccgttatggctct
+gagccgtgttggagttggtaccgagaaaaatcagctttgcgccgttgttgccaatgacaa
+tcgggtcgccggtcaggtcgacgtcaaccagtcgcgcaaactgtatgatgtattcgcgga
+acacgtaagcctgcgttttactggccgacagaaaaatctggttatggccggtcttgagtg
+ctcgcagcagtgcctcacgggaaaaatagaacgtcgcgccaatctggcgggatttgagaa
+tgtcgcgaatacggtgtgccagtcctgcgcggtaccactgcaactggtactcgaaagact
+ggtcgaaaaataattcctccagcttttcgatagcctcgtcgctgaaaaaattctttttcg
+gcttcttgcgctcacccttgttgcggttggcgacgttggggttaaggtcggcctcgttgc
+cggtctggctgtagcggttaacgcgtgccagtcgttcaatctgccgcccgagcaggtcaa
+tctctttgaagtcaccgcctgacttttgcggcttggcgatgagctgaatcaggcgcgcct
+caaggctgctttcaacgcgggaaatcggtgcgatgccatcccagccgtcgcgctgtttcc
+agctctgcacggtcgggcgcttgacctgcagcatttcggcaatctgcggcacggaaaaac
+cctgccagtaaagcagcgatgcctgtcgtcgcgggtcatgcaacaaggttgtatcggtgg
+aaatggtcattgatgcctcgccgtaatggattcagggcaaggctacttaatggccgtcag
+tgattcgctaaggtgctgttgtgtgggcggttgtccagtcgtcattggtggtctggcgtg
+tcctgagtctggaaactggcggtgaccagtaaccccaacctcaggactcctgacaatggc
+aaaaaaagtctcaaaattctttcgaatcggcgtcgagggtgatacctgcgacgggcgcat
+tatcagcgccagtgatattcaggaaatggccgaaacctatgacccgcgtgtttacggttg
+ccgtatcaaccttgaacacctgcgcggcctgttgcccgatggcatattcaagcgttacgg
+cgatgtggtcgagctgaaagccgagaagattgacgacgattctgcgcttaacggcaagtg
+ggcgttgttcgctaaaatcaccccgaccgatgaccttatcgcgatgaataaagccgcgca
+gaaggtctacacctcaatggaaattcagccgaattttgccaataccggcaaatgctacct
+cgtcggccttgctgtcaccgatgacccggcgagcctcggcactgaatatctcgaattctg
+ccgcactgcgaagcacaacccgctgcagcgctttaaagcccaccctgaaaacgtcttttc
+cgccgccacgctggccgaactggaatttgaagacgttcccgacacggtgctcaacagcct
+ggccgataaggtgaaagccattttcagccgtaagcaggtcagcgacgatgcgcgcctgaa
+tgatgtacatgaagcggtgaccgccgtcagcgagcatgtgcagaccaacctcactgcaca
+ggataagcgtctttccgatatggaaaccgcgtttgccaccttcaaacaggaactgaccgg
+caaggttgaagaaaccagccaggcattttccgtcctgaaaaccactctcgacaaaaccga
+aagtttcagccagccgcgacgcacgaaagccagcggcggtggtggcgatgagctgctgac
+tgactgctgataaaccgcagaccgaaaccgggcggaacccccgcccgatgctgtgactaa
+ccgattaaatcaaacaggaaatactatgcgtcaggaaacccgttttaagttcaatgccta
+tctgacccagctcgccaaactgaacggcatcagcgttgatgacgtcagcaaaaaattcac
+cgtcgagccgtccgtcacgcaaacgctgatgaacaccgtgcaggcgtcatccgcgtttct
+gcagatgattaacattctgccggtcgcagaaatgaagggtgagaaaatcggtgtcggtgt
+gaccggcaccatcgccagcacgaccgacacctcgggcgacaaagagcgccagaccgcaga
+ttttaccgcgcttgagtccaacaagtacgagtgcaaccagattaactttgacttccacct
+gacctataaacgcctcgacctgtgggcgcgttttcaggactttcagcgtcgtattcgcga
+cgccattgtccagcgtcaggcactggatttcatcatggccgggtttaacggtaccacccg
+cgctgacacctctgaccgtgttaaaaacccgatgctgcaggatgtggccgttggctggct
+gcagaaataccgcaatgaagccccggcgcgtgtgatgagcaacatcaccgacgctgacgg
+taaagtcgtttcggcagtgattcgcgtcggtaaaaacggcgactatgagaacctcgacgc
+actggtgatggatggtaccaataccctgattgacgagatttatcaggatgacccgaaact
+cgttgccatcgttggccgtaagctgctggccgacaaatattttccgctggtcaacaaaca
+gcaggaaaacaccgagtcgctcgcggcggatatcatcatcagccagaagcgcatcggcaa
+cctgccagccgtacgtgtgccgtacttcccggcgaacgcggtgttcgtgaccacgctgga
+aaacctctctatctatttcatggatgagagccaccgccgcagcattgatgaaaacccgaa
+aaaagaccgcgtggaaaactacgagtcgatgaacatcgactatgtggtcgaggcgtatgc
+cgccgggtgcctgctggaaaacatcaccctgggcgatttcaccgcacctgcagcaccgga
+aggcggagagtaaacccatgacgagccccgcacagcgtcacatgatgcgggtctcggcct
+ctcaagccgcgcagcgggaacaagccccgctgcgccatgcaaccgcctatgagcagatgc
+tggttaagctggccgatgaccgccgcacgttaaaaaacatccgttcaaacgaacgtaaag
+ccgagaaaaagcgcgagctgctgccgttctatgcgccgtgggtcgccggtgtgctgactg
+atggtcgtggtgcgcaggatgacattgttatgaccgtcatgctgtggcgtctcgatgccg
+gtgatatcgctggcgcgctggaaattgccccctacgcgctgaagtacggcctcacctctg
+accatcgccgcacgacaccttacatgctggtcgaggaagtggcacttgccgcgcagcgcc
+tgcgcgatgccggtgagtctgtcgacctttcctggctgcaggccactatcgacctgaccg
+acggcgctgacgttcccgatatggtgcgcgcccgtctgcataaggtgacaggcctgaccc
+tgcgtgatgccggtatgaatgcagaggcgctggcgcagtttcagcgcgcgatgcagctcg
+accgcaatgccggtgtgcgcaaggagattgagcgactggaacgggcattgaagccaaaga
+cagaggccgcgccccgtaaaacgactaaaccgcgcacgcgtaaacctgccaccaaaccgg
+cagcaaagcgcgggcgtccaccaaaggcggcaaaaaccgccggttaactgaacgctcccc
+gagccgggcggcacgccggtcaaagcaggcaaagacctgacggcgaccggcgtccaccgc
+ccaacctgatgaggttgtcatgacgacagtgatactgaaccagcccgacgaaccgcagga
+cgtaccgggcgtggtgattcccgcaccggagacgggcggtgcagtgattaaaaacacgtt
+ctttttccctgatgtggatccgaagcgcgtgcgcgaactgatgcgccttgagcagacggt
+ttccgatgcgcgcctgcgcaatgccatcaagaccggcatggccgagaccaatgcggagct
+ttacgactaccggctgcgccagactgccgccgggtttaagcaactggccgacgtgcctgc
+cgaggaaatcgacggcgagaatgtacgtgttttccactatctgagtgctgtgacggcaat
+ggcaaccgccaccctgtatgagcgctatcgcggcgttgaggccaccggcaaaggtgacaa
+aaaagccgacagcgtggaaaccaccattgatgacctgtggcgggatatgcgctggtcagt
+tgcgcgtctgcaggacaagccgcgctgcattgtggggcagctctgatgaaagtcaggtcg
+atgcagggcgataccctcgacacgatttgcgccaggtattacgggcgcactgagggcgtg
+gttgagacggtgctgcaggctaatccgggtctgtctgagctgggtgtcattctgccgaat
+ggtacagagattgacctgcccgatgtggcatcgtcacccgtaactaaaactatcaacctt
+tgggagtaaccatgacagaaggtgaaaaaggcgtcctgtcactgtttgtgattggcgtga
+tgattgttgttgggaaagtgctggcaggcggtgaacccatcaccccgcgtctgtttattg
+gccgcatgctgctcggtggttttgtttcaatggtcgccggtgttgttctggtgcagtttc
+ctgacatgtcactgcccgccgtgtgcgggattggatccatgctcggtattgccggttatc
+aggtggttgaaatcgccatacagcgccgctttaagtcacaaaagggggaaagtgatgccg
+gtcattaacactcaccagaatatcgctgcgtttctggacatgctggcgtattccgaagga
+acagcgaaccatccgctgacgaaaaatcgtggctacgacgtcattgtcactggccttgat
+ggcagaccagagattttcaccgattacagcgaccaccctttcgcgcatggccgaccaccg
+aaagtgtttaatcgccgtggcgagaaatccacagcctcgggacgttaccagcagctttat
+ctgttctggccgcactatcagaaacagctcgcattgcctgatttcagcccactgtcgcag
+gataagctcgcgatccagttaatccgggagcgcggtgctcttgacgatatccgggcgggg
+cgtattgagcgtgctgtttcacgttgtcgcaatatctgggcgtcattaccgggggccggt
+tacggccagcgcgagcacggtctcgaaaagctggtcaccatctggcgcacggctggcggg
+gtggtggcatgaaagtcctgataacgctgcttgtgatggccgtgctcgggttgctgtggc
+tgcaccatgagaacggcaatttatcccgctcatttgagacggcaaaccgcgtcgcgagcg
+agcaaaagacgacgattgggatgctgaaaaatcagctcagtgttgccggtcagctcgccc
+gacgtaatgaatccgcgcaggtgacactgcgcgaacagctcgcaaaggcaagtgcagaag
+ccagccgccgtgagcagacgataacgaggttacttaatgaaaatgaagcctttcgccgct
+ggtataacgctgctctgcctgatgctgtgcgtcggctgcacacccgaacagcctgcgcca
+gtgccggtgattgtggtcaacggatgcccgagggtgagcctttgccctatgccgggaagt
+gacccgaaaaccaatggtgacctgagcgcagatatccgccgtcttgagggggcgctgact
+gcctgcgcgctgcaggtcaaaaccgtcaaacactgtcaggatgaactcgatgcagaagca
+caaaagcctgcgcaaagcgctgattaacgccgtgccgcagctccgaaataaccccgatat
+gctgcgcctttttgccgacaacggccacaccgattcccgactggcgagctcgctgtcgtt
+tgaaaaggtgtatgtgcttaacgtggtggtgaccgactttactggcgacctcgatttgat
+attcgtgccggtgcaggcatggctgcgtgaacatcagccggacattatgaccaccgacga
+cgggcgggaaaaaggattcacctggattattgatatcaataacgacgattcgctcgatat
+cagtatcagcctgaggctcaccgagcgcacgctcgtcaaagaggtcgggggcgcgctgca
+tgtcagttatgcaccagaaccgccactgcctgagctggtgaagcgcccggttgctatgta
+tgcaaacggtgaattagtgagccagtgggatgagtgaattaaccgcgctgcaggagcgcc
+tcgccggtctgattgccagcctgtcaccggcggcacgtcgcaaaatggcggctgagattg
+cgaaaaagctgcgtaccagtcagcagcagcgcatcaagcgccagcaggcacccgacggca
+ccccgtatgccgcacgaaagcgccagccggtacggagcaagaaaggccgcattaagcgcg
+aaatgttcgccaaactgcgcaccagtcgctttatgaaagccaaaggcagcgatagtgcgg
+cggtggtggagtttaccggcaaggtgcagcgcatggcgcgggtgcatcagtacggcctca
+aagaccgcccaaaccgcaacagccgggatgtgcagtacgaggcgcgcccgttgctcggtt
+tcacccgcgacgatgagcaaatgattgaagacgtcattatcagccacctcggcaaataaa
+tattgtgtgaaccaccaccggaggcgcgtgatttggcgcggctaaagaccagaggcattc
+tttgcactatgaatacgttatccacgatacaggagctcgcgcgtgcgattcgtaacctca
+tccgctcaggtgtggtgacggaggtcgataccgtgcaggggctgtgccgcgtacaaagcg
+gcgggatccagacgacatggctgaactggctgaccacccgcgccggtcgttcgcgcacat
+ggtgggctccctcgctcggtgagcaggtgctgctgctggcaatcggtggcgagcttgata
+ccgctttcgtgctgccggggattttctccgacgataaccccgctccgtctgcctcggcgg
+atgcgtggcatgtggtttttcctgatggcgcggtcattgagtacgagccggagaccagtg
+cactgacggtcagaggcatcaaaactgctgacgtgaccgcgtcggaatccatcacggcca
+ctgtgcctgtggtgctggtgaaagcagaaacccgcatcaccctcgacaccccggaggtgg
+tatgcaccaacaaactgacgacggcaacgcttgaggtgcagaaaggcggggagatgagag
+gcgacattgtccacaacggcggcacatttaaatcaaacggtgtgcagctcgacgaccacg
+gtcacggtggtgtgcaaagaggcggtgcctggacggaggacaccaaatgacggcgcgcta
+tatggggatgaaccgcaacaccggtctcggtatcagtgacactgaacacatcagccagag
+catgcgcgacattctgctgacgccggtcggctcgcgggtgatgcgtcgtgaatatggctc
+gctcctgtctgcgctgattgatatgccgcaaaacccggcgctcaggctgcaaatcatggt
+ggcgtgctattcggccatccagaagtgggagccgcgtatcaggcttacatccatcagctt
+tgagactggtgatgctggcgagatgtatgtcgatattaccgggatgcgtaccgatacggg
+tgcgtcagtttcaaccactgtttcactgagttaaatcactatggcaactgttgacctgag
+cctgttacccgttcccgatgtggttgaggaactggattttgaaaccatccttgcggaacg
+cattgcgacgctaatttcgctataccccgaagaccagcaggaagctgtagcccggacgct
+cgcacttgagtctgaaccaattgttaaattgctgcaggaaaacgcctaccgtgaggttat
+ctggcgtcagcgtgttaatgaagctgctcgcgcaggcatgctggcttatgccagagatag
+cgacctcgataatctcggggcgaatttcaatgttgagcgcctggtcgtcaggcctgctga
+tgacaccaccatccccccgacccccgctgaaatggagcttgatgccgattttcgtctgcg
+tatacagcaggcatttgaagggatgagcgttgcgggatccaccggagcttatgaatttca
+tggccgcagtgctgacgggcgtgtcgcagatatttctgttatcagcccttccccagcatg
+tgtcacgatatctgtgctttcgcgtgagaataacggtgcggcgtctgatgagcttcagag
+catcgtgcgcaatgcgctgaatgctgaggacgtgaggccggttgctgaccgggttacagt
+gcagtcagctcagattattgactaccagatacgcgcaacacttttcgtttatccggggcc
+ggaaaatgagccgattcgtgcggcggctgaggcgaagctcaaagcttatatcagtgcaca
+gcacaggttggggcgggatattcgcctgtcagcaatttatgccgcgttgcatgttgaggg
+ggtgcaacgtgtcgaactggctgcacctgtggccgacattgtgcttgataaaacgcaggc
+atctttttgcacagactatcagatagtgattggtggctccgatgagtgatgtccgcctgt
+tacctgtagggtcatcacctcttgaggtggctgctgccagagcctgcgcagatattgaaa
+atacacctgttccgttacgccgcctgtggagtcctgacacctgtcctgcaaatcttttgc
+cgtggctggcgtgggcgttttccgttgaccgctgggatgagaactggccggaaaaaacaa
+agcgcgatgttattcgcagcgcgtatttcactcactgccacaaaggcacgataggcgctg
+tcaggcgagttattgagccgctcggatacataatcaacgttacggaatggtgggagacag
+gtgacccggcgggaacgtttcgtcttgatatcggtgtgctggaaagcggtattaccgaag
+aaatgtatttagaaatggagcggttgattgctgatgcgaaacctgcgagccgtcatttga
+ttggcctgaatattatccaggatattcccggttatatgtatacaggtggtgtgagctgtg
+acggcgacattattacagtttaccccggataagtgaggaataatgagcacgaaatttaaa
+actattatcaccactgccggagccgaaaaactggcagcggctactgtgccgggtggtaaa
+aaagtgaatatcaccgtgatggccgttggtgacggaggcggaaaactgccagtgccggac
+gccggtcaggtgcagctcgtgaatgaggtctggcgccatgccctgaataaaatcagccag
+gacaaccggaacagcaattacattgtagccgagctggttattccgcccgaggtgggagga
+ttctggatgcgcgagttgggtctttatgatgacgagggcactctcattgctgttgccaat
+atggcagaaagctacaagccagaacttgccgagggctcaggccgtgcgcagacatgtcgc
+atggtcattattgtcagcagtattgcctcagttgaattatccattgattcgacaatggta
+atggcgacgcaggaatatgttgatgacaggattgcggaacatgagaagtcacgtcgacat
+cctgacgccacactgaaagaaaaaggctttactcaactcagtagcgcaacagacagcgcg
+tctgaggtgcttgcggcgacgccgaaagcggttaaagcggcgtatgaccttgctaatgct
+aaatataccgctcaggacgcgagcacagcgcaaaaaggcattgtgaggctgagtagtgcg
+gcagacagtaccagtgaagctgaggccgcaacaccgaaagctgtcaaaattgcgatggat
+aatgcgaatgcgaggctggcaaaagaccggaacggcgctgatattccgaatccgccgttg
+tttgtccagaatatcggtttaaaacccacggtcgataaagctgctaatgccgttgataaa
+aatggcgacacgatgaacggaaacctgacactcaagggtgattaccggctgagttttatt
+atccagaatgaagacggttctattcgtgcttatatcttcaaggacaaaggtggcgacggt
+attcgcatcagtaatggcgatgacggcggcggtgattttgtttttggtaaaaatggacag
+ttttactgcccggatattatgcatgttggtaatacgattgtatggggtgacggaaatatc
+gagggggcgcgatggggcggtttgttgagcgactggttaactgctcaactggttgcccgt
+gacaataatattaacttgcgagcaccctatgaatgggttaaccaaaactttgtcaaccgt
+gcacagcgcggcgcacaggccagcatgactatggacggcggacttgtagaggctccatgg
+ggatgttttcttacgggggggaacggtaacgagggtaatcaggttggagtggctttatac
+cgtccattgcagattcttcgcaataatacttgggtaacgatagagaactgaaaatgaaac
+tgattaatttgcaacgctacattctgatgaattattttttaggtgatggcattcagtatt
+ttatagatgccacaggtaaggactggtataaatcactgcccaaattcacaaagaaataca
+gccttgctatcgaaaatgatacgggcgttattcgtagtatcagcgaagatgcatcccggc
+tttatcccggtggtttgaccgttgttgatgttgacagtattccggcaggttgtgacattt
+tcggcggatgggtatttgacggagagaaagtcattcctcgtgagtacacattggccgaac
+agcaacgccagtcagatgaaaagaaaaaaacacttcttgctgaggcggaggcaacaattt
+ccacgcttgagcgtgcggtaaggttaggcatggctaccggggaagaaataaggcgactcg
+aagcatgggagcgttacagcgtattggtgagcagggttaagcagtcagatgactggccgc
+aaaaacctgaataacccggcggcatttgcccgcttctttctcatccattagttgtgccag
+acatcatccagccctgacaaatagcccctcacaagaccagccaggacaataacactcgcc
+cactaactacggagttaaccggatgagtgattttcaccacggcacgaaggtcatcgaaat
+aaatgacggtacgcgtgttatttccacggtcgcaaccgcaatcgtcggcatggtctggac
+agccagcgatgcggatgccgagacatttcccctcaacgagccggtactgattaccaatgt
+gcagagcgccattgcgaaagccggtaaaaaaggcactctgtcagcttccctgcaggccat
+cgccgaccagtcaaagcctgtcaccgttgtcgtgcgcgttgccgaaggtaccggagacga
+tgcagaagcgcagaccacgtccaacattatcggcggcacggatgagaacggtaaatacac
+cggtatcaaggcgctgctaactgccgaagcggtcaccggcgttaagccgcgcattctcgg
+tgtgccgggtctcgatacgcaggaagtcgcaaccgcacttgcatcggtctgtatcagcct
+gcgtgcgtttggttacgtcagcgcatggggctgtaagaccatttccgaggcgatggcata
+tcgcgagaatttcagccagcgcgagctgatggtcatctggcctgatttcctcgcatggga
+taccaccgcgaacgccaccgccacggcatacgccaccgcccgcgcactcggtctgcgtgc
+ctacatcgaccagactatcggctggcacaaaacgctgtctaacgttggcgtgcagggcgt
+taccggcatcagcgcctcagtcttttgggatttgcaggcatccggcacagatgctgacct
+gctcaacgaggccggagtcaccacgctggtgcgcaaggatggtttccgtttttgggggaa
+ccgcacatgctctgatgacccgcttttcctgtttgagaactacacccgaaccgcgcaggt
+gctggccgacacaatggctgaggcgcacatgtgggcggtcgataagcccatcaccgcatc
+gctcatccgtgacattgtcgacggcattaacgccaaattccgcgagctgaaatcaaatgg
+ctacatcgtggacggtgaatgctggttcgacgaggaatcgaacgataaggaaaccctcaa
+ggccgggaaactgtatatcgactacgactatacaccggttcccccactggaaagcctgac
+cctgcgccagcgtatcaccgataaatatctggtgaatctggctgaatcggtcaacagcta
+aggagcctgaaacaacatggcactaccccgcaaacttaaatatctgaacatgttcaatga
+cggccttagctacatgggcgttgttgaatccgtgacgctgccgaagctgacccgcaagct
+cgaaaactatcgcggcggcggtatgaatggcgctgcagcgattgacctcggcctcgacga
+tgatgcgcttactgtcgaatggtctgtcggtggcctgcctgatgtggcgctgtgggcgca
+gtatgccgcgccgggggctgatgccgtgccgctgcgttttgctggctcttaccagcgtga
+cgacactggcgaaatcgttgcggtcgaggtggtcatgcgtggccgtcataaagaaatcga
+cggcggcgaaaataagcagggtgaaaatacctcgaccaaactgtcgaccgtttgcaccta
+ttaccgcctcacgattgatggtagcgacattatcgaaatcgacaccgtcaacatggtcga
+gaaggtgaacggcgtcgaccgtctggaacagcaccgccgcgcaatcgggctgctgtaatt
+ccctgaccggtcagcactgctggccggttattacccccattcagagcagagaaaaaatca
+tggcaaaagcaccacgcaaaacccctgaatttattgatacggctggcaatgaaattgaca
+ccgtaaacccgaatgtcgtgaccctcgacaagccgattaaacgcgccggtcagacaattg
+aaaaggtcacgctgattgaaccgaacgcaggcaccctgcgcggcgtcagtctggcggcgg
+tggcgcagtccgaggtcgatgcgctgattaaggtgctgccccgcatgacctacccggcac
+tcaccacgcaggagctcaccgcaatgaacctgcccgatatgctgtcgctggccgctaagg
+tgattggttttttgtcaccggcttcggcggaatagacttcccgcccggcctgtcgaccga
+tgacctgatggcggatatcgcagtgatattccactggccgccatcagagctctattccct
+gagcctgaccgagctcatcacatggcgcgaaaaggcgctgcagcgtagcggaaaccacaa
+tgagtaataacctgaggcttgaggtattgctgaaagcggtcgaccaggcgacccgaccgc
+ttaaatccatccagaccgcgagtaaaagcctgtcgggcgatattcgcgacacacaaaaag
+ggctgcgtgacctgaatggtcaggcgtcgaaaatcgacggctttcgtaaggcaagcgcgc
+aactggccgtaaccagtcaggcgcttgataaggcgaaacgcgaggccggtgagctggccg
+tgcagtttaaaaacaccaccaatccgacccgcgcgcaggcgcaggcacttgaagcggcaa
+ggcgtgccgcctctgagttgcagacgaaatacaacagcctgagaacgtcggtacagcgcc
+agcgctccgagctgatgcaggccggtattaatacccgcactctctctgcagatgagcgtc
+ggctcaaaacctccatcagcgaaacaactgcgcacgttaatcgacaacgtgaggcactgg
+cgcgcgtcagtgcgcagcaggcgaaattaagccgggtgaaagagcgatataaatcaggca
+aggagcttgccggtaacatggccgcagcaggcgctgcggggtcggtatcgcgacacggaa
+cgatggccggggttaaattactgatgcccggttatgactttacgcagaaaaattccgagc
+tgcaggctgtgctcggggtcgataagcagtcgccagaaatgcaggcgttacgcaaacagg
+cgcgccacgtcggcgacaatactgcagcctctgcagatgatgcagcaagcgcgcaaatca
+tcattgcgaaaagcggcggtgatgcagctgccattcaggcgcgacgccgagtcacgctga
+atatggcgctgtcaaaccggcgcacgatggaggaaaacgcagcgctgctgaccgggatga
+aatcagcgtttcagctttcaaacgacaagattgcgcacattggcgacgttctctcgatga
+cgatgaacaaaaccgccgccgattttgacggactgagcgacgcgctgacctatgccgcac
+cggtggcaaaaaatgccggggtgagcatcgagcaaaccgccgcaatggtcggtgcactgc
+acgatgccaaaatcaccgggtcaatggcgggcacgggtagccgcgccattctcagccgcc
+tgcagctcccaccggaaaaagcgtttgaggccattaaggagctcggcgtcaaaacgtcag
+acagcaaggggaacacgcgcccgatattctccatcctgaaagaaatgcagcgcagctttg
+agaaaaacaacctcgggacaagccagcgcggcgagtatatgaaaaccattttcggcgagg
+aggccagctcggcggcggcggtactgatggaagccgcctcaagcggcaaacttgaccggc
+tcaccgctgcgtttaaagcctcggacggtaaaaccgaggaactggttaaggttatgcagg
+ataacctcggcggcgactttaaagagttccagtctgcttatgaggcagtcggtactgacc
+tttttgaccagcaagagggctcgctgcgtaaactcacccaaaccgccacgcaatacgtgt
+taaagctcgacggctggatccagaagaacaaagggctggcgacaaccattggcatcatcg
+ccggtggcgctctagctctgattggtatcatcggcggtattggcctcgttgcgtggccgg
+ttgttatggggattaacgccattatcgctgctgctggcgtgctgggtacggtctttactg
+ttaccggtagtgccattgtgaccgcacttggcgcgattacctggccgattgtcgcagtgg
+gggcggctattgtggctggtgcgttactcatccgtaaatattgggagcccatcagcgcat
+ttttctcgggggtgattgagggcatcatgagtgcctttgcaccggtcgcagaaatgttcg
+ctccactggcacccatttttgacggtctcggtgagaaactgcgcggcgtctggcagtggt
+ttaaagacctgatagcaccggtcaaagccacgcaggaaacgctcgatagctgcaaaaatg
+tcggcgtcatatttggtcaggcgctggcctctgctttgatgcgtccgctcaatgtattta
+acaagttgcgcagcggtgtcgactggcttctcgaaaagctcggtatcatcaacaaagagt
+cagacaacctcgaccagactgccgccaaaaccaacgccgccacgcagggtaattcctaca
+tcccggcaaccagcacatatggcggctatcaagcttaccagccagttaccgcaccggcgg
+gacgctcttacattgaccagagtaaaagcgaatacaaaatcactctgccgggaggtgctg
+cgccggggcatcagcttgacagacagctacgcgacacgctcgaacagattgagcgcgaaa
+agcgtgcgcgtcagcgtgccagtatgagccatgactgagaggaataaacgatgatgcttg
+cgcttggaatgtttgtgtttgaacgtcgcaccctgccttatcagtcgatgcagcactcga
+aggattaccgctgggcgtctaatgaccgggtgggtaagccgcctgcttatcagtttctcg
+gcgagggggaaacgtcgatacagcttgccggtacactgtacccagccatcaccggcggct
+ggatctcactaaaggctgtagaggtaatggctaacgagggcagagcgtggccgttgattg
+agggaactggaaacattttagggatgtatatcgtcgataaggtgtcgaccacgcacgccg
+agtttttcagcgacggcgctgccagaaagattgatttcaccctttcgcttaaacgggtcg
+acgaatcactgacggcaatgtttggcgacctgaataagcaggccagcgagcttctaggct
+ctgccggaaatctgactgataagctgcagagtgcgctcggaggactgaccgcatgattac
+gggcatgactattgacgccggaactagccttgcaccggcatttatgttaacgctgaacag
+ccaggacattaccagcaattttagtgaccggctgatttctctcaccatgacagacaaccg
+gggctttgaggctgaccagctcgacattgagctcgacgacaccgacggcaaagtcgaatt
+accactgcgcggggcggtgctgacgttgtggcttggctggcagggttcggcgcttctgaa
+taagggcgatttcacggtcgatgagattgagcaccggggcgcgcctgatatcctgaccat
+ccgcgcgcgtagtgcagactttcgcggaacgctcaattcacggcgtgaagaatcatggca
+cgatactaccatcggtgagttggtcagcaccattgcaaagcgcaataaactgacggccag
+tgtcgcggattcactgaaaaaaattccggtaccgcatatcgaccagtcgcaggagtccga
+cgccgtatttctgacccgactggctgaccgcaatggggcgacggtgtcagtgaaagcggg
+gaaactcctgtttctgaaagccggtagtgcgatgacggccagcggcaagcccgtcccgca
+aatgacactaactcgcaacgatggtgaccgtcatcagtttgctattgccgaccgtggggc
+ttacaccggcgtaaccgcaaaatggttgcacaccaaagacccgaagccgcaaaagcagaa
+agtaacactgaaacgcaagccaaaagagaagcacctgcgcgcactggagcacccgaaagc
+aaagccagtcagcaaaaagacaaaggccaaaaaagagccggaagcgcgcgagggtgagta
+tatggccggtgaggccgataacgtgctggcgctgacgacggtctacgcttctaaggcgca
+ggcgatgcgtgccgctcaggctaagtgggataagctgcagcgaggcgttgcggagttttc
+aattacgctggcgcttggtagggctgatttattccctgagacaccggtgcgcgtatcagg
+ctttaagcgcgtcatagatgagcaggcatggttaatcagtaaggtaactcacaatctgaa
+taatagcggcttcacgacgggcttagagcttgaggttaaactctctgatgtggagtacaa
+cgcggaatcggatgatgaataaaatgtattcacaaaaagtgaatttatgattatcattta
+ttcacgaattgagaataaagggtgggttatgtttcattgtccgaagtgccatcatgccgc
+acatgcgcgaacaagccgctatctaaccgaaaacacgaaagaacgctaccaccagtgcca
+gaacatcaactgtagttgtacgtttatgacaatggaaacgatagagcgctttattgttac
+tccgggagccattgacccggcaccgcctcacccgactgtcggtggtcagcggccattgtg
+gctctgataaatttccgctaaatgcccgccgcgtgcgggtttttttatgcactcaggaaa
+gtggcggtaaaaaatccaccgccattctatcgccactcgaaaacgaggcaacaaaaaggc
+cactcgagagagtggcctaactgtatgtatttactacttaaatttggtggcccctgctgg
+acttgaaccagcgaccaagcgattatgagttcctacaagaacaaccgaaaatcaatagta
+tgcgttatttatcattgacatagattgccattgtttgccaataattacctattattcgcc
+atttctaccgccactttatcgccattctattatctgttactcagaaccatcgccgacata
+ggttccatcaggtgcgagatgatatcttccaccaacataagtcccatctggggcaagtgt
+tgggcggccagcaacataacttccatttggcgctaagcgtggaacgccaccgacataagt
+tccatcgggtgcaaggcgtggtgctcctcctccaacataagtcccattaggggcaagcat
+aggttgtccgtaaacataggtaccatcaggtgctaatcgaactgacatacaacctccata
+acttatttgctgttagtaatgtgaactaaaggatttagtttaactgcatcctctaaatgg
+tcgggtgcaaagtgcgcatatcgcatggtcatttttatatctgtatggccgagtacgcgc
+tgcaacaccaaaatattaccgccattcatcataaagtgactagcgaaggtgtgacgtaaa
+acgtgggtaagttgtcctgccggtagttcgatacctgttctttccaaagctgaccggaac
+gcgccataacaatcactgaacaaccggccctttttatcatcaggcagagactcatagagc
+tctttgctgattgggacggtgcgattttttctacctttcgtgttggtgtatgtgattttg
+tatttcgcgagttggctttttctcagactctcggcctcagaccaccgtgcgccagttgcg
+agacagattcttaccacggtttctaaatcagggtggtcatgtcggttacactctccgagc
+agttgcgaaatttggtcgtgagttagccaagtcatttccatttcttctgtgcggaatggg
+cgcatattttttagtgggttttcacccttccattctccgaggcggtttagctcattgaac
+accgcccggaagtaggccagctcaagattaagcgtgcgaggcgatacctctttcactctg
+tttgaacgggcatactcaccttttaaccgtttttctcggtagcgggaaaacatctgcgca
+tcgaaatcgcgtgcgagtggttcgcccatacactcaaaagcatggtgcatggctaactgg
+cgtttcaaaccatctttcagtgtaatgccatgagcgctataccatgaatcaaccagctct
+tttaacgtgcgcctgtcttccttttcttcctgccacgggttttgaacggtgtactgctca
+aacgccagagcctcgcctttagtagcgaatttctttctgatacgtttgccttttgcaccg
+tttgggtagagttcacaaatccaaccgccagccggatttttacggacggtcattagttaa
+cctcgctgtatacacccaccacacgccccaacgttttaatatcatcaatgccgcattcga
+aaggaaccttcccgcctgcaacatgtagttttctacccggtagttttgttaactctcgaa
+tgcttattgctccctcaatgtcgactagccataagccgtcagacaatgatgcttgcttgt
+ccacaaaataaattttcccctcggaacggatagccatcccatctgtgagcggttttgtga
+aaaattgggcatcaacactcaattgtttatcggatttgaggatttcttcacttaatgtga
+atccctcaatcctttttgcgtcgctcgattctctgttatttacaaatgcttctccttctc
+cggtaagtaaccactggagattagcacctgtttcaagggcacagtgagccgcaaagtcat
+aggagatagcgcctcgggtgtacctgtttgacaatgagctggacgcgatatcgaagtggt
+tagctaattgaattttctgcgaaaatccgtacgcctcgcagatgcggtcaagtacatcca
+cgttgctccatcctaaagaatctattctcatttcgataaaacctatttactatctctcaa
+ttgggagatatattttggctaaacccacgcaattgatggcaagtgttggcaaacagagtc
+aaatcaattgcaaactttggctaatagggaatcatgcaatatggcttctgaaatcgcaat
+catcaaagtgcctgcacctatcgttactctgcaacaattcgcagagcttgagggtgtttc
+tgaacgcaccgcctaccgctggacaaccggcgacaacccttgtgtaccaatcgaaccccg
+cacaatccgtaaaggctgcaagaaagcaggtggcccgattcgcatttattacgcacgctg
+gaaagaagagcagttgcgtaaggcgttgggacattcccgttttcaactcgtcatcggtgc
+ttaattcactttatgtgaattgtaaggatgcaacatgtttgattttcaggtttccaaaca
+tccccactatgacgaagcgtgccgggcttttgcgcagcgtcacaacatggcgaagctggc
+cgagcgtgcgggtatgaatgttcaaacgttacgtaacaagctcaacccagaacagcctca
+ccagttcacgccgcctgaattgtggctgctgactgacctgaccgaagactcaaccctcgt
+tgatggttttctggcgcagattcattgtctgccatgcgtgccggttaatgagctggctaa
+agataaattgcagtcttacgtcatgcgcgcaatgagtgaactcggtgaactggcgagcgg
+tgcggtatctgatgagcgtctgaccactgcccgtaagcacaacatgattgaaagcgttaa
+ctccggcattcgcatgttgtcattgtcggctctggcgctgcatgcacgtctgcagactaa
+tcccgctatgtcgagcgtggtcgataccatgagcggtattggcgcatcgtttggtctgat
+ttgaggtgcgtatgctgaaaagtgaaccgtcatttgcgtctctgctcgttaagcaaagcc
+ccggtatgcattacggccacggctggatcgcaggtaaggacggcaagcgctggcacccgt
+gccgctcacagtccgaattattaaaagggctgaaaacaaagtcgccgaaatcgtcaggtt
+ttttaattattcgtattgtccactttgtaattaaaggagtgaaacatgtcacgcgatgaa
+ttaagaattgttttgggtgccatgattccaaatatggaggaaggttttgaaattaaaacc
+cgcgacggcgcaatacttcgcgttgaccctgagtgggagtgctgcaaagaatttaaggat
+ggattaaaagccgaaatcatcaagcagttaaaaagcaaacctgctgttgtatttggatat
+agttaattaattaaacgtaattacttggcgtaaacccgccgggcattcttttgccaaaaa
+acaggaggatatatgagtcgaactatttatttatcaacgccgagtggtgctggcgaccac
+ttgctggagtctttgtttaaagaagccaaaaaagaagagcgcaaagaccgccgtctcgcc
+gtttcaatccgtctcgaagatctggccgttcacattaccaattcagatatgacaggcaaa
+gaagcggccgagctactgcgccgcgaagccactcgctttgagaacgaatcacaggagctt
+cactaatggccgacgcaatggatttagcacaactgcgcgagcaggaagaccgcgaacgcc
+acataagcaacgcgcgcagccgtcgccatgaggtttctgcatttatctgtgaggaatgcg
+atgcacctatcccggaagcgcgccgccgagccataccgggcgtgcagtgctgcgttacct
+gtcaggaaatcttagagctgaaaagtaaacattataacggaggtgctttatgagcattac
+caatgcaactattagccagcgtgcaaaaaaatggcttgaagatgaccgtatatttattga
+caccgaaactacgggtttgggtgatgatgcggaaatagtagaaatctgtttaatagatag
+cgctggttttatcatgctaaatacattggttaaaccaactaaaccaattccagcagaggc
+tacggccattcatggaataactgatgaaatggttatgtatgcgccaacgtggaaagatat
+tcacggcgcagtagcttctttattttttgagtatggctttgttatttataacgccgatta
+cgacacaagacttatatatcaaactgcgaaattatatgggcttgagaatgacggcttttg
+ttattttttaaatgagcgttcggcctgcgccatgatgctatatgcagagtatcgcggcga
+gccagggcgatttaaaggttataaatggcacaaattagttgatgccgctgcacatgaagg
+ggttagcgttgaaggaaaggcacaccgtgcattagcagattgccggatgactcttggcat
+tatcgacgctttggcaaaaggcggtgcagcatgagtatccgtatcgaaataggtgataaa
+tgggtaatcaccagcgaccaatatcaattcatcctgaatgaaaaaaaagtcgttaagacc
+ggcaataaagctggcgaggaatggctcgacaccatcggttattacccgaagattaatcag
+ctcatttctggtctggtacatcaccacattcatacggcaatgattatttcccttagtgca
+atggcagaggaaatagagaagttatcttttatctgtgaagaagcatttaaggcggttaaa
+aaatgattgattcccgctgctttgctgaaagcacaataaatattgtttctgtttctggtg
+gaaaggacagccttgctcaatggattcttgcggtagagaacgacgtaccgcgcaccactg
+tttttgcagataccgggcatgagcattcccaaacaatggagtatctggattatcttgaat
+ccagactcggcccggttattcgagtgaaagccgattttactcggcggattgaaggcaaac
+ggaaattcattgctgaaaaatggcctgtctctctcgttgaagaatgcggaatgtctcatg
+agcaggctgcagaacgaatcgcaaaggcactggaaatccttaagccaaccggtaatccgt
+ttctcgatttgtgcatgtggaaaggacggttcccgagcacgaaagcaaggttttgttcac
+tggaactgaaacatgactcagtacgggacaagattgtactcccagcgctggagaaatatg
+acgaagtaattctatggcagggtgttcgtgctcaggagtcaccagcccgcgctgcgttac
+ctatgtgggaggaggatgcagataatacccccggtttgcatgtgtatcgcccaattctta
+actggacacatgaagacgtatttgccttagctaaacgacacggaattaaaccgaacccac
+tctatcagcaaggttgtagcagagttggctgcatgccatgtattcatgcaagaaaatctg
+agctggcagagatttttgctcgctggccggaggagattgcgcgcgttgcagagtgggaac
+gtcttgttgctgcctgttcacgtcggggaaactcaacatttttcccttcgactcacgacc
+cgcggcgagcagaaaaacgtattgaagttgttaccgtagaagaatatgggatagcttcat
+atcgtgactgggcgatgactacgcgtggcggttctcagtacgatttgctcgctgctacaa
+acgacaaaactgtgtgcagtagcgtttatgccggtgtatgtgaatgacgggtgtcgttta
+cgcgtttccgtggaatgccccacggtcggcaatagccagctcatatcttacctatgacca
+acagcatcgccgcgaccgtatgttcgcggctttgctgcatgcgagaaaggtgctttttct
+ccagccagaatgtgtgcgctttgacgtttatcgcaccgctgcagttctggagcaaaatca
+gggcagtcaacgagccaatgcctttttaatcagcttctgcaaaaaggcattaccacgtct
+tgaactggtcgcaaaaaaatacgagtgctcgggcatcaacagcaatgtatcagccgctgt
+tttcgatggtcattttgatacccagcttatgcaatatctggcgtcacgcatggtcaatat
+ggtcgccagatttaaccgcctcccggatatgtcgcgcgccgatattgacctgctggccgc
+ggatatcgctaattttattcgcgctgaactggccgacattgatgacaccggatttagcga
+actcaaaacgctgtacacttggtacatgcgcgccggttttatttccctgcaattcaacgt
+tacaccgccgaaatgggagcgtgtgactaaaaaatatttttgtgaggatgaaatcgcacc
+ggcagtaatgcgcatgtttaatgaggtttggtggcgcggccgcttgcgacgcattgcggc
+tgcatggcgcgaacatctgcaaattgcagtcggcaacgtaagcaagaaacgacacgcata
+cgcgagtaaaaactgcgtcaccgactggcgcgagcagaagcgccgcacgcgcgaatttct
+caaggggctggatctcgaagacgaagaaggcaaccgcatcagtctgattgaaaaatacga
+cggctcggtcgctaatccagcaatacgccgctgtgagctgatggcccgcattcgtgggtt
+tgaaaatatctgtaatgagctcggttatgtcggggagttctatactctgactgcaccgtc
+taaatatcacgccaccaccaaagcgggctaccgtaacagcaaatggaacggtgcaagccc
+gtcagacacgcagagctatctcacaggcctttgggcacgcatacgcgccaagctgcaccg
+ggaagaaatccgcattttcggaatacgcgtcgctgaaccgcatcacgatggaacgccgca
+ctggcatatgcttatgttcatgttgccggaagatgtcgagcgtgtgcgactcatcatccg
+agattatgcgtgggaggaagaccactacgaactgagaagcgataaagccaaaaaggcgcg
+cttccatgctgaggccattgacccggaaaaaggcagtgctactggctatgtcgctaaata
+catttccaaaaatatcgacggttatgctctcgatggtgaaaccgatgacgaaagtggtga
+gttgttaaaagagactgcacccgccgtttcagcatgggcggcgcgctggcacatccgtca
+gtttcaatttatcggcggtgcgccggtgacggtatacagggagctacgcagaatggctga
+ccctgaaacagccagggcgctcagtgttgaattcgccgcagtgcatgatgctgctcacta
+tggacgctgggctgattatgtgaatgctcaaggcggaccattcgttcgccgtgacgattt
+acaggtacgtacattgtatgaacctcgaactgaatttaatcagtatggcgaagaaactgt
+gtgcatcaaaggtgtctacgatgcctcgataggtgctggctctcctattctaacccggtt
+aacgcagtggaagattgttccaaagcgtgccgttgatttggccgttgacgttaagggcgc
+ttctgcgccctctcggagttctgtcaataactgtacgggaagcgaaagcgatccaccgat
+actcgatttaacaaaaccgctgagtcggcgtgaaagacgagagttgacgaaccgactcag
+gaagaaaaagccaacaacacggcgaaaattcatccacggaacggataagcaaaacgtcgc
+tataacgaaaactatcgacgagatacactctgacaaccggcatcacaatcagccggggcg
+aagccctgcacctgatggccggtggtaaaagttgttttaacggtcgatgggtgcgcggaa
+cgtcaaaaggtgaaatctttgctgcagctccttcacatcaggcgagacgcggaaaatcct
+taatcgtgttgcagatttagctgagctggcaacgaaaatgtaaccgctaatattcatcca
+tatcatgtacatacagtgtatttaactgtgatttttttcttcacaccttttgccaatacg
+tgctactgtatgtttatacagtatctcgtagtggaggttgtgtggatagagagctaaatg
+agcacgttatgattgagcgggtcgaaatgattgcgcgtctgactgctgaaggtacttgtc
+aggaaagagaccgtgaaatcgcattgaatctaattgcggaaatagcaagaggcaacctaa
+tgaaaaataataatttttctgttgttttttccgcaccgcctgttggtgaaacatttgcaa
+aggagggcaaagtgaaagtaaatatcacgttggataaagaccaaaaaatcggccagccgg
+taattgatgcttttcagtgcgaattgaccaagcgaatacagtccgttttcccgtcaacgc
+gcgttactgttaaaaagggatccatgaccggtgtcgagctgatggggttcgataaagatt
+cagaccgcgaagcgctggatagcatccttcaggaagtgtgggaagatgagagctggcgtt
+aatccctgaaaaatgtgcaaccatcgaccccatgtttgatagcatggggttgttttgtat
+gggattacacacaaaggaaaatcatggataccgtaatagcatttttatctctggctctct
+ttattgcttttatcgtcgggttaatcaagccgtcgctggttcgaatgccgagccgtaagc
+gctccagtgctgtttatctcggtggctgtctggcgctgggcgttattggctcaatcttat
+ggccgactgaaaaaactcagccggtggcaaaaactgacgtaccggcggttaaagcggaac
+cggctacgccaacgtttgagtacgcagataaaaccctcaaagaatatcgcaacgagccaa
+aagaaacccggcacgaaatcgttaaagactatgttggattcaaaggtgtgcaggccagcg
+ctaccgatgttttttatgcctgtatgagcgagtacacttttaccaaagatgatgcgttaa
+agctcagtgatgtgttggggtggtgtttcaacgacttcgagaaagatccacaatctctga
+ataataaaatcaaccttgacgaatttcagggtaattttagcggttgggatggctcttatc
+gcccgttagagaagctgataaaagccagtatgaatgatgattcctcttataaacatgttt
+caacggtctaccatctgattttgaataaagacccgcatgccgttgtaaaaacaacgtttc
+gcggcactaatgcttatggtggcgtggtcaaacagaccgtagcagcacgcgtcaacgtgc
+gaacgggcgaggtcgattcaatactcgacaattaaacatgacaaacgccgccggtgctga
+aactcgctttcagtgctggcggggttgaacaacgagccccgcgaggcgttagcctacccc
+tgagcaaccgcccaagaccggtactattaaagccggtttttttatgccacttttccacga
+atttcccgtttttttagccgtgcatgcaacaggtgcattgttttgcatgcgtcaggcttg
+cccgttctggttgtgcgtcgccagagctggcgcggctccagagtggtcatgcaactgcat
+taaaaccgacccataaagtgggca
diff --git a/etc/c1215.blastn.crunch b/etc/c1215.blastn.crunch
new file mode 100644
index 0000000..13215ee
--- /dev/null
+++ b/etc/c1215.blastn.crunch
@@ -0,0 +1,126 @@
+9583 99.00 1474 6490 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1474 6490 SPBC1215 AL096846 S.pombe chromosome II cosmid c1215.
+2712 98.00 1 1437 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 1437 SPBC1215 AL096846 S.pombe chromosome II cosmid c1215.
+276 99.00 429 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 7588 7734 SPAC1142 AL159951 S.pombe chromosome I cosmid c1142.
+274 99.00 429 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 174 319 SPRG5SC K00770 yeast (s.pombe) 5s rrna gene and flanks, clone pspr36.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 48 169 SPRG5S K00569 yeast (s.pombe) 5s rrna gene, clone pym3.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21075 20954 SPCC63 AL049522 S.pombe chromosome III cosmid c63.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 33755 33876 SPCC417 AL035076 S.pombe chromosome III cosmid c417.
+228 99.00 456 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 22579 22697 SPCC417 AL035076 S.pombe chromosome III cosmid c417.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 31087 30966 SPCC338 AL023781 S.pombe chromosome III cosmid c338.
+129 97.00 35 107 big_blast_2_50000_embl_other_c1215.seq.00000001.out 22094 22022 SPCC338 AL023781 S.pombe chromosome III cosmid c338.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 6258 6379 SPBC17A3 AL109652 S.pombe chromosome II cosmid c17A3.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 3214 3335 SPAC5H10 Z49811 S.pombe chromosome I cosmid c5H10.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 8450 8571 SPAC4H3 Z69380 S.pombe chromosome I cosmid c4H3.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 30828 30707 SPAC25B8 AL133225 S.pombe chromosome I cosmid c25B8.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 24568 24447 SPAC1F7 Z67998 S.pombe chromosome I cosmid c1F7.
+242 100.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 17220 17099 SPAB4537 AB004537 Schizosaccharomyces pombe 37 kb genomic DNA, clone c213.
+240 100.00 453 573 big_blast_2_50000_embl_other_c1215.seq.00000001.out 7867 7747 SPAC926 AL110469 S.pombe chromosome I cosmid c926.
+238 100.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 29615 29496 SPCC965 AL023590 S.pombe chromosome III cosmid c965.
+238 100.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 24228 24347 SPCC24B10 AL157991 S.pombe chromosome III cosmid c24B10.
+238 100.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 5316 5435 SPCC10H11 AL162771 S.pombe chromosome III cosmid c10H11.
+238 100.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 23752 23871 SPBC428 AL034382 S.pombe chromosome II cosmid c428.
+238 100.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 15895 16014 SPBC4 AL121863 S.pombe chromosome II cosmid c4.
+238 100.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 9473 9354 SPBC342 AL096809 S.pombe chromosome II cosmid c342.
+238 100.00 456 575 big_blast_2_50000_embl_other_c1215.seq.00000001.out 9665 9546 SPBC21H7 AL023286 S.pombe chromosome II cosmid c21H7.
+238 100.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 24026 24145 SPBC1685 AL031154 S.pombe chromosome II cosmid c1685.
+236 100.00 456 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 202 320 SPRG5SE K00570 yeast (s.pombe) 5s rrna and asp-trna genes, clone pym116.
+236 100.00 456 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 34701 34819 SPBP23A10 AL136535 S.pombe chromosome II p1 p23A10.
+236 100.00 456 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 31520 31638 SPBC577 AL110506 S.pombe chromosome II cosmid c577.
+236 100.00 456 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 6163 6281 SPBC1677 AL035581 S.pombe chromosome II cosmid c1677.
+236 100.00 456 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 18162 18044 SPAC3G9 AL021046 S.pombe chromosome I cosmid c3G9.
+236 100.00 456 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 119 SPA K01049 Yeast (S.pombe) 5S ribosomal RNA.
+234 99.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1664 1543 SPCC188 AL049662 S.pombe chromosome III cosmid c188.
+234 99.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 20839 20718 SPCC11E10 AL121783 S.pombe chromosome III cosmid c11E10.
+234 99.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 23408 23287 SPBC21D10 AL031536 S.pombe chromosome II cosmid c21D10.
+232 99.00 455 575 big_blast_2_50000_embl_other_c1215.seq.00000001.out 3099 3219 SPBC24C6 AL031786 S.pombe chromosome II cosmid c24C6.
+230 99.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 200 319 SPRG5SA K00768 yeast (s.pombe) 5s rrna gene and flanks, clone pspr1.
+230 99.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 24833 24952 SPBC354 AL022071 S.pombe chromosome II cosmid c354.
+230 99.00 455 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 14612 14731 SPAC167 AL035248 S.pombe chromosome I cosmid c167.
+228 99.00 456 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 201 319 SPRG5SD K00771 yeast (s.pombe) 5s rrna gene and flanks, clone pspr41.
+226 98.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 198 319 SPRG5SB K00769 yeast (s.pombe) 5s rrna gene and flanks, clone pspr11.
+226 99.00 455 572 big_blast_2_50000_embl_other_c1215.seq.00000001.out 2544 2661 SPAC24H6 Z54142 S.pombe chromosome I cosmid c24H6.
+226 98.00 453 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 13105 12984 SPAC1B1 Z98532 S.pombe chromosome I cosmid c1B1.
+222 99.00 457 572 big_blast_2_50000_embl_other_c1215.seq.00000001.out 9084 8969 SPAC1687 AL035064 S.pombe chromosome I cosmid c1687.
+220 100.00 454 564 big_blast_2_50000_embl_other_c1215.seq.00000001.out 11460 11350 SPBC14F5 AL023780 S.pombe chromosome II cosmid c14F5.
+206 100.00 6387 6490 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 104 SPBC83 AL035536 S.pombe chromosome II cosmid c83.
+163 100.00 1 82 big_blast_2_50000_embl_other_c1215.seq.00000001.out 82 1 SPAB4534 AB004534 Schizosaccharomyces pombe 38 kb genomic DNA, clone 1750.
+157 100.00 496 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 79 SPBC725 AL034352 S.pombe chromosome II cosmid c725.
+157 100.00 496 574 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 79 SPBC530 AL023634 S.pombe chromosome II cosmid c530.
+103 100.00 455 506 big_blast_2_50000_embl_other_c1215.seq.00000001.out 52 1 SPRHP9 Y09431 S.pombe rhp9 gene
+93 85.00 463 575 big_blast_2_50000_embl_other_c1215.seq.00000001.out 14821 14716 SPBC609 AL035226 S.pombe chromosome II cosmid c609.
+65 89.00 456 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 57 UURNA1 X00998 Urechis unicinctus 5S rRNA sequence
+58 95.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 PGRNA01 X00994 Phascolopsis gouldii 5S rRNA sequence
+58 95.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 904 868 CFRRN285 X06056 Calanus finmarchicus DNA for 23S and 5S rRNA
+56 93.00 477 516 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1113 1074 SPBC1778 AL049489 S.pombe chromosome II cosmid c1778.
+52 100.00 6059 6084 big_blast_2_50000_embl_other_c1215.seq.00000001.out 233479 233504 AE003531 AE003531 Drosophila melanogaster genomic scaffold 142000013386050 section 37 of 54, complete sequence.
+50 92.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 PRRRN5S X01550 Planocera reticulata 5S ribosomal RNA
+50 92.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 EGRN01 X00022 Ribbon worm Emplectonema gracile 5S rRNA (Short)
+50 94.00 480 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 25 57 AMRRN5S X01518 Acyrthosiphon magnoliae (aphid) 5S ribosomal RNA
+50 89.00 461 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 6 58 CR1619 D10528 Candida rugosa 5S rRNA.
+50 88.00 521 569 big_blast_2_50000_embl_other_c1215.seq.00000001.out 66 114 AERRA K03161 Agaricus edulis 5S ribosomal RNA (5S rRNA).
+48 96.00 2466 2493 big_blast_2_50000_embl_other_c1215.seq.00000001.out 10841 10814 AC010292 AC010292 Homo sapiens chromosome 5 clone CTB-131H22, complete sequence.
+46 85.00 458 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 5564 5510 PDHISTCL X53330 P.dumerilii histone gene cluster for core histones H2A, H2B, H3 and H4
+46 100.00 4802 4824 big_blast_2_50000_embl_other_c1215.seq.00000001.out 28075 28053 HSU165H7 Z70225 Human DNA sequence from cosmid U165H7, between markers DXS366 and DXS87 on chromosome X.
+46 88.00 526 568 big_blast_2_50000_embl_other_c1215.seq.00000001.out 72 114 TDRRA M58385 Taphrina deformans 5S ribosomal RNA.
+46 96.00 487 513 big_blast_2_50000_embl_other_c1215.seq.00000001.out 3 29 SPRRAAS M19951 Schizosaccharomyces pombe 5S ribosomal RNA gene, partial sequence.
+44 100.00 4816 4837 big_blast_2_50000_embl_other_c1215.seq.00000001.out 88073 88052 AC084760 AC084760 Gallus gallus clone 65N20, complete sequence.
+44 88.00 57 98 big_blast_2_50000_embl_other_c1215.seq.00000001.out 69197 69238 CHPTTRPG D17510 Pinus thunbergii chloroplast DNA, complete sequence.
+44 88.00 57 98 big_blast_2_50000_embl_other_c1215.seq.00000001.out 48563 48522 CHNTXX Z00044 Nicotiana tabacum chloroplast genome DNA
+44 88.00 57 98 big_blast_2_50000_embl_other_c1215.seq.00000001.out 138 97 CHNTTB2 M16897 Tobacco Thr-tRNA gene.
+44 88.00 57 98 big_blast_2_50000_embl_other_c1215.seq.00000001.out 14879 14920 AP002983 AP002983 Lotus japonicus chloroplast DNA, complete genome.
+44 88.00 57 98 big_blast_2_50000_embl_other_c1215.seq.00000001.out 46263 46222 AP000423 AP000423 Arabidopsis thaliana chloroplast genomic DNA, complete sequence, strain:Columbia.
+44 100.00 893 914 big_blast_2_50000_embl_other_c1215.seq.00000001.out 49861 49882 HSEVMHC X87344 H.sapiens DMA, DMB, HLA-Z1, IPP2, LMP2, TAP1, LMP7, TAP2, DOB, DQB2 and RING8, 9, 13 and 14 genes
+44 100.00 6061 6082 big_blast_2_50000_embl_other_c1215.seq.00000001.out 143317 143296 HS377H14 AL022723 Human DNA sequence from clone 377H14 on chromosome 6p21.32-22.1. Contains the HLA-G gene for major histocompatibility complex, class I, G (HLA 6.0) two MHC class I pseudogenes, an RPL7A (60S Ribosomal Protein L7A) pseudogene, a gene for a novel MHC class 1 protein, an interferon-inducible protein 1-8U pseudogene, an RPL23A (60S Ribosomal Protein L23A) pseudogene, an HCGIX pseudogene, a [...]
+44 100.00 1237 1258 big_blast_2_50000_embl_other_c1215.seq.00000001.out 3149 3128 AP001207 AP001207 Homo sapiens genomic DNA, chromosome 8q23, clone:KB1562D12.
+44 100.00 1237 1258 big_blast_2_50000_embl_other_c1215.seq.00000001.out 201930 201909 AP000426 AP000426 Homo sapiens genomic DNA, chromosome 8q23, clone:KB1107E3.
+44 93.00 2454 2483 big_blast_2_50000_embl_other_c1215.seq.00000001.out 75294 75323 AC023426 AC023426 Homo sapiens 12 BAC RP11-396L8 (Roswell Park Cancer Institute Human BAC Library) complete sequence.
+44 100.00 6061 6082 big_blast_2_50000_embl_other_c1215.seq.00000001.out 34756 34735 AC004213 AC004213 Homo sapiens clone UWGC:y19c060 from 6p21, complete sequence.
+44 86.00 521 570 big_blast_2_50000_embl_other_c1215.seq.00000001.out 364 413 AI5SRRNA2 X99089 A.immersus 5S rRNA gene, b2-type
+42 100.00 1245 1265 big_blast_2_50000_embl_other_c1215.seq.00000001.out 12979 12959 BBU43414 U43414 Borrelia burgdorferi linear plasmid lp16 DNA, complete sequence.
+42 100.00 2454 2474 big_blast_2_50000_embl_other_c1215.seq.00000001.out 278691 278671 AP001119 AP001119 Buchnera sp. APS genomic DNA, complete sequence, segment 2/2.
+42 100.00 1245 1265 big_blast_2_50000_embl_other_c1215.seq.00000001.out 12951 12931 AE000793 AE000793 Borrelia burgdorferi plasmid lp17, complete plasmid sequence.
+42 100.00 818 838 big_blast_2_50000_embl_other_c1215.seq.00000001.out 2471 2451 OSU77637 U77637 Oryza sativa class III ADH enzyme (AdhIII) gene, complete cds.
+42 96.00 1241 1265 big_blast_2_50000_embl_other_c1215.seq.00000001.out 79773 79797 H0505A02 AL512543 Oryza sativa genomic DNA, chromosome 4, BAC clone: H0505A02, complete sequence
+42 93.00 343 371 big_blast_2_50000_embl_other_c1215.seq.00000001.out 29118 29146 AB018121 AB018121 Arabidopsis thaliana genomic DNA, chromosome 3, P1 clone: MXE2.
+42 84.00 456 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 57 SSRRAB K02349 Jellyfish (Spirocodon saltatrix) 5S ribosomal RNA.
+42 89.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 PCRN5SA X13039 Philosamia cynthia ricini 5S rRNA
+42 89.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 PCRN5S K02354 Philosamia cythia ricini (silkworm) 5S ribosomal RNA.
+42 89.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 OV5SRRN X06835 Octopus vulgaris 5S rRNA
+42 89.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 ME5SRNA J01869 Mussel (Mytilus edulis) 5S ribosomal RNA.
+42 89.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 LGRNO1 X00020 Ribbon worm Lineus geniculatus 5S rRNA
+42 89.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 HSRRN5S X01535 Hymeniacidon sanguinea 5S ribosomal RNA
+42 89.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 HJRN01 V00475 Halichondria japonica 5S ribosomal RNA (complete sequence).
+42 84.00 456 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 57 EARRN5S X03911 Enchytraeus albidus 5S rRNA
+42 100.00 6340 6360 big_blast_2_50000_embl_other_c1215.seq.00000001.out 25954 25974 CEK02B12 Z75711 Caenorhabditis elegans cosmid K02B12
+42 100.00 1213 1233 big_blast_2_50000_embl_other_c1215.seq.00000001.out 5436 5456 CEF14B8 U28737 Caenorhabditis elegans cosmid F14B8.
+42 84.00 456 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 57 CED132199 AJ132199 Cerastoderma edule 5S rRNA gene and nontranscribed spacer, clone 4
+42 84.00 456 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 1 57 CED132198 AJ132198 Cerastoderma edule 5S rRNA gene and nontranscribed spacer, clone 3
+42 89.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 BN5SRRN X06834 Bugula neritina (moss-animal) 5S rRNA
+42 100.00 5756 5776 big_blast_2_50000_embl_other_c1215.seq.00000001.out 2219 2239 BMU86876 U86876 Bombyx mori chitinase-like protein mRNA, complete cds.
+42 89.00 476 512 big_blast_2_50000_embl_other_c1215.seq.00000001.out 21 57 APRN5SA X13035 Antheraea pernyi 5S rRNA
+42 100.00 5756 5776 big_blast_2_50000_embl_other_c1215.seq.00000001.out 2256 2276 AF326596 AF326596 Bombyx mandarina chitinase mRNA, complete cds.
+42 100.00 136 156 big_blast_2_50000_embl_other_c1215.seq.00000001.out 6370 6350 AC084626 AC084626 Caenorhabditis briggsae cosmid G45C20, complete sequence.
+42 100.00 6091 6111 big_blast_2_50000_embl_other_c1215.seq.00000001.out 27211 27231 AC006830 AC006830 Caenorhabditis elegans cosmid ZK105, complete sequence.
+42 100.00 5756 5776 big_blast_2_50000_embl_other_c1215.seq.00000001.out 2225 2245 AB052914 AB052914 Bombyx mori chiB mRNA for chitinase precursor, complete cds.
+42 100.00 1395 1415 big_blast_2_50000_embl_other_c1215.seq.00000001.out 145576 145596 HSMX1A AL442166 Homo sapiens chromosome 21 from 5 PACs and 5 Cosmids map 21q22.2,D21S349-MX1; segment 1/2
+42 100.00 6065 6085 big_blast_2_50000_embl_other_c1215.seq.00000001.out 70745 70765 HS994L9 AL034554 Human DNA sequence from clone 994L9 on chromosome 20p12.3-13 Contains STS and GSSs.
+42 100.00 1395 1415 big_blast_2_50000_embl_other_c1215.seq.00000001.out 173941 173961 HS21C084 AL163284 Homo sapiens chromosome 21 segment HS21C084.
+42 100.00 5172 5192 big_blast_2_50000_embl_other_c1215.seq.00000001.out 8899 8879 HS21C013 AL163213 Homo sapiens chromosome 21 segment HS21C013.
+42 96.00 3691 3715 big_blast_2_50000_embl_other_c1215.seq.00000001.out 222756 222780 AP001732 AP001732 Homo sapiens genomic DNA, chromosome 21q, section 76/105.
+42 96.00 334 358 big_blast_2_50000_embl_other_c1215.seq.00000001.out 234259 234235 AP001677 AP001677 Homo sapiens genomic DNA, chromosome 21q, section 21/105.
+42 96.00 3691 3715 big_blast_2_50000_embl_other_c1215.seq.00000001.out 97170 97194 AP001038 AP001038 Homo sapiens genomic DNA, chromosome 21, clone:P178O22, ERG-ETS2 region.
+42 96.00 334 358 big_blast_2_50000_embl_other_c1215.seq.00000001.out 117565 117541 AP000946 AP000946 Homo sapiens genomic DNA, chromosome 21q21.1-q21.2, clone:B335N5, LL56-APP region.
+42 100.00 6106 6126 big_blast_2_50000_embl_other_c1215.seq.00000001.out 179419 179439 AP000810 AP000810 Homo sapiens genomic DNA, chromosome 11q, clone:RP11-765H23.
+42 100.00 5172 5192 big_blast_2_50000_embl_other_c1215.seq.00000001.out 94404 94384 AP000473 AP000473 Homo sapiens genomic DNA, chromosome 21q21.1-q21.2, clone:B291N6, LL56-APP region.
+42 96.00 1801 1825 big_blast_2_50000_embl_other_c1215.seq.00000001.out 6090 6114 AL355365 AL355365 Human DNA sequence from clone RP11-361F15 on chromosome 6
+42 96.00 5531 5555 big_blast_2_50000_embl_other_c1215.seq.00000001.out 79645 79669 AL354861 AL354861 Human DNA sequence from clone RP11-180I4 on chromosome 9
+42 100.00 1 21 big_blast_2_50000_embl_other_c1215.seq.00000001.out 28666 28686 AL139378 AL139378 Human DNA sequence from clone RP11-271B5 on chromosome 13 Contains a gene for a protein similar to ribosomal protein S7, the FGF9 (fibroblast growth factor 9 (glia-activating factor)) gene, ESTs, STSs, GSSs and CpG islands.
+42 100.00 5783 5803 big_blast_2_50000_embl_other_c1215.seq.00000001.out 15259 15279 AL139163 AL139163 Human DNA sequence from clone RP11-523O15 on chromosome 20 Contains GSSs and STSs.
+42 100.00 3803 3823 big_blast_2_50000_embl_other_c1215.seq.00000001.out 20782 20802 AL136301 AL136301 Human DNA sequence from clone RP11-195L15 on chromosome 13
+42 91.00 5752 5784 big_blast_2_50000_embl_other_c1215.seq.00000001.out 74511 74543 AL133462 AL133462 Human DNA sequence from clone RP11-108A16 on chromosome 20 Contains ESTs, STSs and GSSs.
+42 96.00 334 358 big_blast_2_50000_embl_other_c1215.seq.00000001.out 119422 119446 AF238375 AF238375 Homo sapiens chromosome 21 clone BAC 49B5 map 21q21, complete sequence.
+42 100.00 1242 1262 big_blast_2_50000_embl_other_c1215.seq.00000001.out 42069 42089 AF130342 AF130342 Homo sapiens chromosome 8 clone PAC 87.1 map 8q24.1, complete sequence.
+42 91.00 5752 5784 big_blast_2_50000_embl_other_c1215.seq.00000001.out 49339 49371 AC012380 AC012380 Genomic Sequence For Homo sapiens Clone 125H5, Chromosome 20, complete sequence.
+42 93.00 1230 1258 big_blast_2_50000_embl_other_c1215.seq.00000001.out 135503 135475 AC007392 AC007392 Homo sapiens BAC clone RP11-444B4 from 2, complete sequence.
+42 100.00 1414 1434 big_blast_2_50000_embl_other_c1215.seq.00000001.out 4290 4310 PCAROMX L18918 Pneumocystis carinii pentafunctional enzyme (arom) gene, complete cds.
+42 87.00 521 565 big_blast_2_50000_embl_other_c1215.seq.00000001.out 66 110 CH1457 D10531 Cryptococcus humicolus 5S rRNA.
diff --git a/etc/c1215.blastn.tab b/etc/c1215.blastn.tab
new file mode 100644
index 0000000..e2113c4
--- /dev/null
+++ b/etc/c1215.blastn.tab
@@ -0,0 +1,630 @@
+FT BLASTN_HIT 1474..6490
+FT /note="match to SPBC1215 1474..6490 blast score 9583 percent identity 99 AL096846 S.pombe chromosome II cosmid c1215."
+FT /label=SPBC1215
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 1..1437
+FT /note="match to SPBC1215 1..1437 blast score 2712 percent identity 98 AL096846 S.pombe chromosome II cosmid c1215."
+FT /label=SPBC1215
+FT /score=98
+FT /colour=255 5 5
+FT BLASTN_HIT 429..574
+FT /note="match to SPAC1142 7588..7734 blast score 276 percent identity 99 AL159951 S.pombe chromosome I cosmid c1142."
+FT /label=SPAC1142
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 429..574
+FT /note="match to SPRG5SC 174..319 blast score 274 percent identity 99 K00770 yeast (s.pombe) 5s rrna gene and flanks, clone pspr36."
+FT /label=SPRG5SC
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 453..574
+FT /note="match to SPRG5S 48..169 blast score 242 percent identity 100 K00569 yeast (s.pombe) 5s rrna gene, clone pym3."
+FT /label=SPRG5S
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 453..574
+FT /note="match to SPCC63 20954..21075 blast score 242 percent identity 100 AL049522 S.pombe chromosome III cosmid c63."
+FT /label=SPCC63
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 453..574
+FT /note="match to SPCC417 33755..33876 blast score 242 percent identity 100 AL035076 S.pombe chromosome III cosmid c417."
+FT /label=SPCC417
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 456..574
+FT /note="match to SPCC417 22579..22697 blast score 228 percent identity 99 AL035076 S.pombe chromosome III cosmid c417."
+FT /label=SPCC417
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 453..574
+FT /note="match to SPCC338 30966..31087 blast score 242 percent identity 100 AL023781 S.pombe chromosome III cosmid c338."
+FT /label=SPCC338
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 35..107
+FT /note="match to SPCC338 22022..22094 blast score 129 percent identity 97 AL023781 S.pombe chromosome III cosmid c338."
+FT /label=SPCC338
+FT /score=97
+FT /colour=255 7 7
+FT BLASTN_HIT 453..574
+FT /note="match to SPBC17A3 6258..6379 blast score 242 percent identity 100 AL109652 S.pombe chromosome II cosmid c17A3."
+FT /label=SPBC17A3
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 453..574
+FT /note="match to SPAC5H10 3214..3335 blast score 242 percent identity 100 Z49811 S.pombe chromosome I cosmid c5H10."
+FT /label=SPAC5H10
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 453..574
+FT /note="match to SPAC4H3 8450..8571 blast score 242 percent identity 100 Z69380 S.pombe chromosome I cosmid c4H3."
+FT /label=SPAC4H3
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 453..574
+FT /note="match to SPAC25B8 30707..30828 blast score 242 percent identity 100 AL133225 S.pombe chromosome I cosmid c25B8."
+FT /label=SPAC25B8
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 453..574
+FT /note="match to SPAC1F7 24447..24568 blast score 242 percent identity 100 Z67998 S.pombe chromosome I cosmid c1F7."
+FT /label=SPAC1F7
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 453..574
+FT /note="match to SPAB4537 17099..17220 blast score 242 percent identity 100 AB004537 Schizosaccharomyces pombe 37 kb genomic DNA, clone c213."
+FT /label=SPAB4537
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 453..573
+FT /note="match to SPAC926 7747..7867 blast score 240 percent identity 100 AL110469 S.pombe chromosome I cosmid c926."
+FT /label=SPAC926
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 455..574
+FT /note="match to SPCC965 29496..29615 blast score 238 percent identity 100 AL023590 S.pombe chromosome III cosmid c965."
+FT /label=SPCC965
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 455..574
+FT /note="match to SPCC24B10 24228..24347 blast score 238 percent identity 100 AL157991 S.pombe chromosome III cosmid c24B10."
+FT /label=SPCC24B10
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 455..574
+FT /note="match to SPCC10H11 5316..5435 blast score 238 percent identity 100 AL162771 S.pombe chromosome III cosmid c10H11."
+FT /label=SPCC10H11
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 455..574
+FT /note="match to SPBC428 23752..23871 blast score 238 percent identity 100 AL034382 S.pombe chromosome II cosmid c428."
+FT /label=SPBC428
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 455..574
+FT /note="match to SPBC4 15895..16014 blast score 238 percent identity 100 AL121863 S.pombe chromosome II cosmid c4."
+FT /label=SPBC4
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 455..574
+FT /note="match to SPBC342 9354..9473 blast score 238 percent identity 100 AL096809 S.pombe chromosome II cosmid c342."
+FT /label=SPBC342
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 456..575
+FT /note="match to SPBC21H7 9546..9665 blast score 238 percent identity 100 AL023286 S.pombe chromosome II cosmid c21H7."
+FT /label=SPBC21H7
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 455..574
+FT /note="match to SPBC1685 24026..24145 blast score 238 percent identity 100 AL031154 S.pombe chromosome II cosmid c1685."
+FT /label=SPBC1685
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 456..574
+FT /note="match to SPRG5SE 202..320 blast score 236 percent identity 100 K00570 yeast (s.pombe) 5s rrna and asp-trna genes, clone pym116."
+FT /label=SPRG5SE
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 456..574
+FT /note="match to SPBP23A10 34701..34819 blast score 236 percent identity 100 AL136535 S.pombe chromosome II p1 p23A10."
+FT /label=SPBP23A10
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 456..574
+FT /note="match to SPBC577 31520..31638 blast score 236 percent identity 100 AL110506 S.pombe chromosome II cosmid c577."
+FT /label=SPBC577
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 456..574
+FT /note="match to SPBC1677 6163..6281 blast score 236 percent identity 100 AL035581 S.pombe chromosome II cosmid c1677."
+FT /label=SPBC1677
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 456..574
+FT /note="match to SPAC3G9 18044..18162 blast score 236 percent identity 100 AL021046 S.pombe chromosome I cosmid c3G9."
+FT /label=SPAC3G9
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 456..574
+FT /note="match to SPA 1..119 blast score 236 percent identity 100 K01049 Yeast (S.pombe) 5S ribosomal RNA."
+FT /label=SPA
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 453..574
+FT /note="match to SPCC188 1543..1664 blast score 234 percent identity 99 AL049662 S.pombe chromosome III cosmid c188."
+FT /label=SPCC188
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 453..574
+FT /note="match to SPCC11E10 20718..20839 blast score 234 percent identity 99 AL121783 S.pombe chromosome III cosmid c11E10."
+FT /label=SPCC11E10
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 453..574
+FT /note="match to SPBC21D10 23287..23408 blast score 234 percent identity 99 AL031536 S.pombe chromosome II cosmid c21D10."
+FT /label=SPBC21D10
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 455..575
+FT /note="match to SPBC24C6 3099..3219 blast score 232 percent identity 99 AL031786 S.pombe chromosome II cosmid c24C6."
+FT /label=SPBC24C6
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 455..574
+FT /note="match to SPRG5SA 200..319 blast score 230 percent identity 99 K00768 yeast (s.pombe) 5s rrna gene and flanks, clone pspr1."
+FT /label=SPRG5SA
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 455..574
+FT /note="match to SPBC354 24833..24952 blast score 230 percent identity 99 AL022071 S.pombe chromosome II cosmid c354."
+FT /label=SPBC354
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 455..574
+FT /note="match to SPAC167 14612..14731 blast score 230 percent identity 99 AL035248 S.pombe chromosome I cosmid c167."
+FT /label=SPAC167
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 456..574
+FT /note="match to SPRG5SD 201..319 blast score 228 percent identity 99 K00771 yeast (s.pombe) 5s rrna gene and flanks, clone pspr41."
+FT /label=SPRG5SD
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 453..574
+FT /note="match to SPRG5SB 198..319 blast score 226 percent identity 98 K00769 yeast (s.pombe) 5s rrna gene and flanks, clone pspr11."
+FT /label=SPRG5SB
+FT /score=98
+FT /colour=255 5 5
+FT BLASTN_HIT 455..572
+FT /note="match to SPAC24H6 2544..2661 blast score 226 percent identity 99 Z54142 S.pombe chromosome I cosmid c24H6."
+FT /label=SPAC24H6
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 453..574
+FT /note="match to SPAC1B1 12984..13105 blast score 226 percent identity 98 Z98532 S.pombe chromosome I cosmid c1B1."
+FT /label=SPAC1B1
+FT /score=98
+FT /colour=255 5 5
+FT BLASTN_HIT 457..572
+FT /note="match to SPAC1687 8969..9084 blast score 222 percent identity 99 AL035064 S.pombe chromosome I cosmid c1687."
+FT /label=SPAC1687
+FT /score=99
+FT /colour=255 2 2
+FT BLASTN_HIT 454..564
+FT /note="match to SPBC14F5 11350..11460 blast score 220 percent identity 100 AL023780 S.pombe chromosome II cosmid c14F5."
+FT /label=SPBC14F5
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 6387..6490
+FT /note="match to SPBC83 1..104 blast score 206 percent identity 100 AL035536 S.pombe chromosome II cosmid c83."
+FT /label=SPBC83
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 1..82
+FT /note="match to SPAB4534 1..82 blast score 163 percent identity 100 AB004534 Schizosaccharomyces pombe 38 kb genomic DNA, clone 1750."
+FT /label=SPAB4534
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 496..574
+FT /note="match to SPBC725 1..79 blast score 157 percent identity 100 AL034352 S.pombe chromosome II cosmid c725."
+FT /label=SPBC725
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 496..574
+FT /note="match to SPBC530 1..79 blast score 157 percent identity 100 AL023634 S.pombe chromosome II cosmid c530."
+FT /label=SPBC530
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 455..506
+FT /note="match to SPRHP9 1..52 blast score 103 percent identity 100 Y09431 S.pombe rhp9 gene"
+FT /label=SPRHP9
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 463..575
+FT /note="match to SPBC609 14716..14821 blast score 93.7 percent identity 85 AL035226 S.pombe chromosome II cosmid c609."
+FT /label=SPBC609
+FT /score=85
+FT /colour=255 38 38
+FT BLASTN_HIT 456..512
+FT /note="match to UURNA1 1..57 blast score 65.9 percent identity 89 X00998 Urechis unicinctus 5S rRNA sequence"
+FT /label=UURNA1
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 476..512
+FT /note="match to PGRNA01 21..57 blast score 58.0 percent identity 95 X00994 Phascolopsis gouldii 5S rRNA sequence"
+FT /label=PGRNA01
+FT /score=95
+FT /colour=255 12 12
+FT BLASTN_HIT 476..512
+FT /note="match to CFRRN285 868..904 blast score 58.0 percent identity 95 X06056 Calanus finmarchicus DNA for 23S and 5S rRNA"
+FT /label=CFRRN285
+FT /score=95
+FT /colour=255 12 12
+FT BLASTN_HIT 477..516
+FT /note="match to SPBC1778 1074..1113 blast score 56.0 percent identity 93 AL049489 S.pombe chromosome II cosmid c1778."
+FT /label=SPBC1778
+FT /score=93
+FT /colour=255 17 17
+FT BLASTN_HIT 6059..6084
+FT /note="match to AE003531 233479..233504 blast score 52.0 percent identity 100 AE003531 Drosophila melanogaster genomic scaffold 142000013386050 section 37 of 54, complete sequence."
+FT /label=AE003531
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 476..512
+FT /note="match to PRRRN5S 21..57 blast score 50.1 percent identity 92 X01550 Planocera reticulata 5S ribosomal RNA"
+FT /label=PRRRN5S
+FT /score=92
+FT /colour=255 20 20
+FT BLASTN_HIT 476..512
+FT /note="match to EGRN01 21..57 blast score 50.1 percent identity 92 X00022 Ribbon worm Emplectonema gracile 5S rRNA (Short)"
+FT /label=EGRN01
+FT /score=92
+FT /colour=255 20 20
+FT BLASTN_HIT 480..512
+FT /note="match to AMRRN5S 25..57 blast score 50.1 percent identity 94 X01518 Acyrthosiphon magnoliae (aphid) 5S ribosomal RNA"
+FT /label=AMRRN5S
+FT /score=94
+FT /colour=255 15 15
+FT BLASTN_HIT 461..512
+FT /note="match to CR1619 6..58 blast score 50.1 percent identity 89 D10528 Candida rugosa 5S rRNA."
+FT /label=CR1619
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 521..569
+FT /note="match to AERRA 66..114 blast score 50.1 percent identity 88 K03161 Agaricus edulis 5S ribosomal RNA (5S rRNA)."
+FT /label=AERRA
+FT /score=88
+FT /colour=255 30 30
+FT BLASTN_HIT 2466..2493
+FT /note="match to AC010292 10814..10841 blast score 48.1 percent identity 96 AC010292 Homo sapiens chromosome 5 clone CTB-131H22, complete sequence."
+FT /label=AC010292
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 458..512
+FT /note="match to PDHISTCL 5510..5564 blast score 46.1 percent identity 85 X53330 P.dumerilii histone gene cluster for core histones H2A, H2B, H3 and H4"
+FT /label=PDHISTCL
+FT /score=85
+FT /colour=255 38 38
+FT BLASTN_HIT 4802..4824
+FT /note="match to HSU165H7 28053..28075 blast score 46.1 percent identity 100 Z70225 Human DNA sequence from cosmid U165H7, between markers DXS366 and DXS87 on chromosome X."
+FT /label=HSU165H7
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 526..568
+FT /note="match to TDRRA 72..114 blast score 46.1 percent identity 88 M58385 Taphrina deformans 5S ribosomal RNA."
+FT /label=TDRRA
+FT /score=88
+FT /colour=255 30 30
+FT BLASTN_HIT 487..513
+FT /note="match to SPRRAAS 3..29 blast score 46.1 percent identity 96 M19951 Schizosaccharomyces pombe 5S ribosomal RNA gene, partial sequence."
+FT /label=SPRRAAS
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 4816..4837
+FT /note="match to AC084760 88052..88073 blast score 44.1 percent identity 100 AC084760 Gallus gallus clone 65N20, complete sequence."
+FT /label=AC084760
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 57..98
+FT /note="match to CHPTTRPG 69197..69238 blast score 44.1 percent identity 88 D17510 Pinus thunbergii chloroplast DNA, complete sequence."
+FT /label=CHPTTRPG
+FT /score=88
+FT /colour=255 30 30
+FT BLASTN_HIT 57..98
+FT /note="match to CHNTXX 48522..48563 blast score 44.1 percent identity 88 Z00044 Nicotiana tabacum chloroplast genome DNA"
+FT /label=CHNTXX
+FT /score=88
+FT /colour=255 30 30
+FT BLASTN_HIT 57..98
+FT /note="match to CHNTTB2 97..138 blast score 44.1 percent identity 88 M16897 Tobacco Thr-tRNA gene."
+FT /label=CHNTTB2
+FT /score=88
+FT /colour=255 30 30
+FT BLASTN_HIT 57..98
+FT /note="match to AP002983 14879..14920 blast score 44.1 percent identity 88 AP002983 Lotus japonicus chloroplast DNA, complete genome."
+FT /label=AP002983
+FT /score=88
+FT /colour=255 30 30
+FT BLASTN_HIT 57..98
+FT /note="match to AP000423 46222..46263 blast score 44.1 percent identity 88 AP000423 Arabidopsis thaliana chloroplast genomic DNA, complete sequence, strain:Columbia."
+FT /label=AP000423
+FT /score=88
+FT /colour=255 30 30
+FT BLASTN_HIT 893..914
+FT /note="match to HSEVMHC 49861..49882 blast score 44.1 percent identity 100 X87344 H.sapiens DMA, DMB, HLA-Z1, IPP2, LMP2, TAP1, LMP7, TAP2, DOB, DQB2 and RING8, 9, 13 and 14 genes"
+FT /label=HSEVMHC
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 6061..6082
+FT /note="match to HS377H14 143296..143317 blast score 44.1 percent identity 100 AL022723 Human DNA sequence from clone 377H14 on chromosome 6p21.32-22.1. Contains the HLA-G gene for major histocompatibility complex, class I, G (HLA 6.0) two MHC class I pseudogenes, an RPL7A (60S Ribosomal Protein L7A) pseudogene, a gene for a novel MHC class 1 protein, an interferon-inducible protein 1-8U pseudogene, an RPL23A (60S Ribosomal Protein L23A) pseudogene, an HCGIX pseudo [...]
+FT /label=HS377H14
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 1237..1258
+FT /note="match to AP001207 3128..3149 blast score 44.1 percent identity 100 AP001207 Homo sapiens genomic DNA, chromosome 8q23, clone:KB1562D12."
+FT /label=AP001207
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 1237..1258
+FT /note="match to AP000426 201909..201930 blast score 44.1 percent identity 100 AP000426 Homo sapiens genomic DNA, chromosome 8q23, clone:KB1107E3."
+FT /label=AP000426
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 2454..2483
+FT /note="match to AC023426 75294..75323 blast score 44.1 percent identity 93 AC023426 Homo sapiens 12 BAC RP11-396L8 (Roswell Park Cancer Institute Human BAC Library) complete sequence."
+FT /label=AC023426
+FT /score=93
+FT /colour=255 17 17
+FT BLASTN_HIT 6061..6082
+FT /note="match to AC004213 34735..34756 blast score 44.1 percent identity 100 AC004213 Homo sapiens clone UWGC:y19c060 from 6p21, complete sequence."
+FT /label=AC004213
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 521..570
+FT /note="match to AI5SRRNA2 364..413 blast score 44.1 percent identity 86 X99089 A.immersus 5S rRNA gene, b2-type"
+FT /label=AI5SRRNA2
+FT /score=86
+FT /colour=255 35 35
+FT BLASTN_HIT 1245..1265
+FT /note="match to BBU43414 12959..12979 blast score 42.1 percent identity 100 U43414 Borrelia burgdorferi linear plasmid lp16 DNA, complete sequence."
+FT /label=BBU43414
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 2454..2474
+FT /note="match to AP001119 278671..278691 blast score 42.1 percent identity 100 AP001119 Buchnera sp. APS genomic DNA, complete sequence, segment 2/2."
+FT /label=AP001119
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 1245..1265
+FT /note="match to AE000793 12931..12951 blast score 42.1 percent identity 100 AE000793 Borrelia burgdorferi plasmid lp17, complete plasmid sequence."
+FT /label=AE000793
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 818..838
+FT /note="match to OSU77637 2451..2471 blast score 42.1 percent identity 100 U77637 Oryza sativa class III ADH enzyme (AdhIII) gene, complete cds."
+FT /label=OSU77637
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 1241..1265
+FT /note="match to H0505A02 79773..79797 blast score 42.1 percent identity 96 AL512543 Oryza sativa genomic DNA, chromosome 4, BAC clone: H0505A02, complete sequence"
+FT /label=H0505A02
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 343..371
+FT /note="match to AB018121 29118..29146 blast score 42.1 percent identity 93 AB018121 Arabidopsis thaliana genomic DNA, chromosome 3, P1 clone: MXE2."
+FT /label=AB018121
+FT /score=93
+FT /colour=255 17 17
+FT BLASTN_HIT 456..512
+FT /note="match to SSRRAB 1..57 blast score 42.1 percent identity 84 K02349 Jellyfish (Spirocodon saltatrix) 5S ribosomal RNA."
+FT /label=SSRRAB
+FT /score=84
+FT /colour=255 40 40
+FT BLASTN_HIT 476..512
+FT /note="match to PCRN5SA 21..57 blast score 42.1 percent identity 89 X13039 Philosamia cynthia ricini 5S rRNA"
+FT /label=PCRN5SA
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 476..512
+FT /note="match to PCRN5S 21..57 blast score 42.1 percent identity 89 K02354 Philosamia cythia ricini (silkworm) 5S ribosomal RNA."
+FT /label=PCRN5S
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 476..512
+FT /note="match to OV5SRRN 21..57 blast score 42.1 percent identity 89 X06835 Octopus vulgaris 5S rRNA"
+FT /label=OV5SRRN
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 476..512
+FT /note="match to ME5SRNA 21..57 blast score 42.1 percent identity 89 J01869 Mussel (Mytilus edulis) 5S ribosomal RNA."
+FT /label=ME5SRNA
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 476..512
+FT /note="match to LGRNO1 21..57 blast score 42.1 percent identity 89 X00020 Ribbon worm Lineus geniculatus 5S rRNA"
+FT /label=LGRNO1
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 476..512
+FT /note="match to HSRRN5S 21..57 blast score 42.1 percent identity 89 X01535 Hymeniacidon sanguinea 5S ribosomal RNA"
+FT /label=HSRRN5S
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 476..512
+FT /note="match to HJRN01 21..57 blast score 42.1 percent identity 89 V00475 Halichondria japonica 5S ribosomal RNA (complete sequence)."
+FT /label=HJRN01
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 456..512
+FT /note="match to EARRN5S 1..57 blast score 42.1 percent identity 84 X03911 Enchytraeus albidus 5S rRNA"
+FT /label=EARRN5S
+FT /score=84
+FT /colour=255 40 40
+FT BLASTN_HIT 6340..6360
+FT /note="match to CEK02B12 25954..25974 blast score 42.1 percent identity 100 Z75711 Caenorhabditis elegans cosmid K02B12"
+FT /label=CEK02B12
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 1213..1233
+FT /note="match to CEF14B8 5436..5456 blast score 42.1 percent identity 100 U28737 Caenorhabditis elegans cosmid F14B8."
+FT /label=CEF14B8
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 456..512
+FT /note="match to CED132199 1..57 blast score 42.1 percent identity 84 AJ132199 Cerastoderma edule 5S rRNA gene and nontranscribed spacer, clone 4"
+FT /label=CED132199
+FT /score=84
+FT /colour=255 40 40
+FT BLASTN_HIT 456..512
+FT /note="match to CED132198 1..57 blast score 42.1 percent identity 84 AJ132198 Cerastoderma edule 5S rRNA gene and nontranscribed spacer, clone 3"
+FT /label=CED132198
+FT /score=84
+FT /colour=255 40 40
+FT BLASTN_HIT 476..512
+FT /note="match to BN5SRRN 21..57 blast score 42.1 percent identity 89 X06834 Bugula neritina (moss-animal) 5S rRNA"
+FT /label=BN5SRRN
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 5756..5776
+FT /note="match to BMU86876 2219..2239 blast score 42.1 percent identity 100 U86876 Bombyx mori chitinase-like protein mRNA, complete cds."
+FT /label=BMU86876
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 476..512
+FT /note="match to APRN5SA 21..57 blast score 42.1 percent identity 89 X13035 Antheraea pernyi 5S rRNA"
+FT /label=APRN5SA
+FT /score=89
+FT /colour=255 28 28
+FT BLASTN_HIT 5756..5776
+FT /note="match to AF326596 2256..2276 blast score 42.1 percent identity 100 AF326596 Bombyx mandarina chitinase mRNA, complete cds."
+FT /label=AF326596
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 136..156
+FT /note="match to AC084626 6350..6370 blast score 42.1 percent identity 100 AC084626 Caenorhabditis briggsae cosmid G45C20, complete sequence."
+FT /label=AC084626
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 6091..6111
+FT /note="match to AC006830 27211..27231 blast score 42.1 percent identity 100 AC006830 Caenorhabditis elegans cosmid ZK105, complete sequence."
+FT /label=AC006830
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 5756..5776
+FT /note="match to AB052914 2225..2245 blast score 42.1 percent identity 100 AB052914 Bombyx mori chiB mRNA for chitinase precursor, complete cds."
+FT /label=AB052914
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 1395..1415
+FT /note="match to HSMX1A 145576..145596 blast score 42.1 percent identity 100 AL442166 Homo sapiens chromosome 21 from 5 PACs and 5 Cosmids map 21q22.2,D21S349-MX1; segment 1/2"
+FT /label=HSMX1A
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 6065..6085
+FT /note="match to HS994L9 70745..70765 blast score 42.1 percent identity 100 AL034554 Human DNA sequence from clone 994L9 on chromosome 20p12.3-13 Contains STS and GSSs."
+FT /label=HS994L9
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 1395..1415
+FT /note="match to HS21C084 173941..173961 blast score 42.1 percent identity 100 AL163284 Homo sapiens chromosome 21 segment HS21C084."
+FT /label=HS21C084
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 5172..5192
+FT /note="match to HS21C013 8879..8899 blast score 42.1 percent identity 100 AL163213 Homo sapiens chromosome 21 segment HS21C013."
+FT /label=HS21C013
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 3691..3715
+FT /note="match to AP001732 222756..222780 blast score 42.1 percent identity 96 AP001732 Homo sapiens genomic DNA, chromosome 21q, section 76/105."
+FT /label=AP001732
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 334..358
+FT /note="match to AP001677 234235..234259 blast score 42.1 percent identity 96 AP001677 Homo sapiens genomic DNA, chromosome 21q, section 21/105."
+FT /label=AP001677
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 3691..3715
+FT /note="match to AP001038 97170..97194 blast score 42.1 percent identity 96 AP001038 Homo sapiens genomic DNA, chromosome 21, clone:P178O22, ERG-ETS2 region."
+FT /label=AP001038
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 334..358
+FT /note="match to AP000946 117541..117565 blast score 42.1 percent identity 96 AP000946 Homo sapiens genomic DNA, chromosome 21q21.1-q21.2, clone:B335N5, LL56-APP region."
+FT /label=AP000946
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 6106..6126
+FT /note="match to AP000810 179419..179439 blast score 42.1 percent identity 100 AP000810 Homo sapiens genomic DNA, chromosome 11q, clone:RP11-765H23."
+FT /label=AP000810
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 5172..5192
+FT /note="match to AP000473 94384..94404 blast score 42.1 percent identity 100 AP000473 Homo sapiens genomic DNA, chromosome 21q21.1-q21.2, clone:B291N6, LL56-APP region."
+FT /label=AP000473
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 1801..1825
+FT /note="match to AL355365 6090..6114 blast score 42.1 percent identity 96 AL355365 Human DNA sequence from clone RP11-361F15 on chromosome 6"
+FT /label=AL355365
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 5531..5555
+FT /note="match to AL354861 79645..79669 blast score 42.1 percent identity 96 AL354861 Human DNA sequence from clone RP11-180I4 on chromosome 9"
+FT /label=AL354861
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 1..21
+FT /note="match to AL139378 28666..28686 blast score 42.1 percent identity 100 AL139378 Human DNA sequence from clone RP11-271B5 on chromosome 13 Contains a gene for a protein similar to ribosomal protein S7, the FGF9 (fibroblast growth factor 9 (glia-activating factor)) gene, ESTs, STSs, GSSs and CpG islands."
+FT /label=AL139378
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 5783..5803
+FT /note="match to AL139163 15259..15279 blast score 42.1 percent identity 100 AL139163 Human DNA sequence from clone RP11-523O15 on chromosome 20 Contains GSSs and STSs."
+FT /label=AL139163
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 3803..3823
+FT /note="match to AL136301 20782..20802 blast score 42.1 percent identity 100 AL136301 Human DNA sequence from clone RP11-195L15 on chromosome 13"
+FT /label=AL136301
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 5752..5784
+FT /note="match to AL133462 74511..74543 blast score 42.1 percent identity 91 AL133462 Human DNA sequence from clone RP11-108A16 on chromosome 20 Contains ESTs, STSs and GSSs."
+FT /label=AL133462
+FT /score=91
+FT /colour=255 22 22
+FT BLASTN_HIT 334..358
+FT /note="match to AF238375 119422..119446 blast score 42.1 percent identity 96 AF238375 Homo sapiens chromosome 21 clone BAC 49B5 map 21q21, complete sequence."
+FT /label=AF238375
+FT /score=96
+FT /colour=255 10 10
+FT BLASTN_HIT 1242..1262
+FT /note="match to AF130342 42069..42089 blast score 42.1 percent identity 100 AF130342 Homo sapiens chromosome 8 clone PAC 87.1 map 8q24.1, complete sequence."
+FT /label=AF130342
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 5752..5784
+FT /note="match to AC012380 49339..49371 blast score 42.1 percent identity 91 AC012380 Genomic Sequence For Homo sapiens Clone 125H5, Chromosome 20, complete sequence."
+FT /label=AC012380
+FT /score=91
+FT /colour=255 22 22
+FT BLASTN_HIT 1230..1258
+FT /note="match to AC007392 135475..135503 blast score 42.1 percent identity 93 AC007392 Homo sapiens BAC clone RP11-444B4 from 2, complete sequence."
+FT /label=AC007392
+FT /score=93
+FT /colour=255 17 17
+FT BLASTN_HIT 1414..1434
+FT /note="match to PCAROMX 4290..4310 blast score 42.1 percent identity 100 L18918 Pneumocystis carinii pentafunctional enzyme (arom) gene, complete cds."
+FT /label=PCAROMX
+FT /score=100
+FT /colour=255 0 0
+FT BLASTN_HIT 521..565
+FT /note="match to CH1457 66..110 blast score 42.1 percent identity 87 D10531 Cryptococcus humicolus 5S rRNA."
+FT /label=CH1457
+FT /score=87
+FT /colour=255 33 33
diff --git a/etc/c1215.embl b/etc/c1215.embl
new file mode 100644
index 0000000..5acb69e
--- /dev/null
+++ b/etc/c1215.embl
@@ -0,0 +1,258 @@
+ID SPBC1215 standard; DNA; FUN; 6490 BP.
+XX
+AC AL096846;
+XX
+DE S.pombe chromosome II cosmid c1215.
+XX
+KW SURF-family protein; COX complex biogenesis; DEC1 homologue;
+KW mitochondrial inheritance; actin cytoskeleton organisation.
+XX
+OS Schizosaccharomyces pombe (yeast)
+OC Eukaryota; Plantae; Thallobionta; Eumycota; Hemiascomycetes;
+OC Endomycetales; Saccharomycetaceae.
+XX
+RN [1]
+RP 1-6490
+RA Lyne M.H., Rajandream M.A., Barrell B.G., Seeger K., Quail M., Harris D.;
+RT ;
+RL Submitted (16-JUL-1999) to the EMBL/GenBank/DDBJ databases.
+RL European Schizosaccharomyces genome sequencing project,
+RL Sanger Centre, The Wellcome Trust Genome Campus, Hinxton,
+RL Cambridge CB10 1SA, E-mail: barrell at sanger.ac.uk and
+XX
+CC Notes:
+CC
+CC Details of yeast sequencing at the Sanger Centre are available on
+CC the World Wide Web.
+CC (URL, http://www.sanger.ac.uk/Projects/S_pombe/)
+CC During 1995 to 1996 about 66% of S. pombe chromosome 1 was sequenced
+CC by the Sanger Centre. The sequencing of the S. pombe genome is now
+CC being continued with funding from The European Commission.
+CC Fourteen European sequencing laboratories, including the Sanger Centre,
+CC are participating in the project.
+CC
+CC Protein coding regions (CDS) have been predicted with the help
+CC of computer analysis using the Genefinder program in PomBase
+CC (an ACEDB database) with additional predictions for the
+CC branch-acceptor sites supplied by the program Sp3splice.
+CC CAUTION: It is possible that for any individual CDS we may have
+CC underestimated or overestimated the number of introns/exons or
+CC we may not have chosen the correct splice donor/acceptor sites.
+CC
+CC CDS are numbered using the following system eg SPBC25H2.01c.
+CC SP (S. pombe), B (chromosome 2), c25H2 (cosmid name),
+CC .01 (first CDS), c (complementary strand).
+CC
+CC The more significant matches with motifs in the PROSITE
+CC database are also included but some of these may be fortuitous.
+CC
+CC The length in codons is given for each CDS.
+CC
+CC IMPORTANT: This sequence MAY NOT be the entire insert of
+CC the sequenced clone. It may be shorter because we only
+CC sequence overlapping sections once, or longer, because we
+CC arrange for a small overlap between neighbouring submissions.
+CC
+CC Cosmid c1215 is overlapped at the 5' end by cosmid c1750,
+CC EMBL entry SPAB4534, accession number AB004534, and at the 3'
+CC end by cosmid c83, EMBL entry SPBC83, accession number AL035536.
+XX
+XX
+FH Key Location/Qualifiers
+FH
+FT source 1..6490
+FT /organism="Schizosaccharomyces pombe"
+FT /strain="972h-"
+FT /chromosome="II"
+FT /clone="cosmid c1215"
+FT /map="IIL"
+FT misc_feature complement(1..82)
+FT /note="nominal overlap with cosmid SPAB4534 (c1750) S.
+FT pombe chromosome 2"
+FT tRNA 36..107
+FT /note="tRNA Thr anticodon TGT, Cove score 78.51"
+FT rRNA 415..581
+FT /note="SPRG5SD K00771 Yeast (s.pombe) 5s rrna gene and
+FT flanks"
+FT CDS join(1501..1760,1844..2456)
+FT /fasta_file="fasta/gf.tab.seq.00001.out"
+FT /note="SPBC1215.01, len:290, SIMILARITY:Saccharomyces
+FT cerevisiae, YG2X_YEAST, hypothetical 45.1 kd protein in
+FT clb6-spt6 intergenic region, (389 aa), fasta scores: opt:
+FT 385, E():2.8e-19, (29.0% identity in 314 aa)"
+FT /gene="SPBC1215.01"
+FT /product="putative SURF-family protein"
+FT /colour=7
+FT /label=SPBC1215.01
+FT misc_feature 1761..1766
+FT /note="gtacgt, splice donor sequence"
+FT /colour=6
+FT misc_feature 1827..1843
+FT /note="ctaacataatcacttag, splice branch and acceptor"
+FT /colour=6
+FT CDS complement(join(2495..2602,2663..2881,2953..4071,4111..4372,4413..4849,4889..5007,5251..5389,5434..5466))
+FT /fasta_file="fasta/gf.tab.seq.00002.out"
+FT /note="SPBC1215.02c, len:811, SIMILARITY:Saccharomyces
+FT cerevisiae, DEC1_YEAST, dec1 protein, (796 aa), fasta
+FT scores: opt: 184, E():0.00014, (23.4% identity in 577 aa)"
+FT /gene="SPBC1215.02c"
+FT /product="similar to yeast DEC1 mitochondrial inheritance
+FT and actin cytoskeleton organisation protein"
+FT /colour=7
+FT /label=SPBC1215.02c
+FT misc_feature complement(2603..2612)
+FT /note="ctaataatag, splice branch and acceptor"
+FT /colour=6
+FT misc_feature complement(2657..2662)
+FT /note="gtacgt, splice donor sequence"
+FT /colour=6
+FT misc_feature complement(2882..2896)
+FT /note="ctaaccgaattatag, splice branch and acceptor"
+FT /colour=6
+FT misc_feature complement(2947..2952)
+FT /note="gtaagt, splice donor sequence"
+FT /colour=6
+FT misc_feature complement(4072..4085)
+FT /note="ttaactcgtcgtag, splice branch and acceptor"
+FT /colour=6
+FT misc_feature complement(4105..4110)
+FT /note="gtaagt, splice donor sequence"
+FT /colour=6
+FT misc_feature complement(4373..4387)
+FT /note="ctaacttctccatag, splice branch and acceptor"
+FT /colour=6
+FT misc_feature complement(4407..4412)
+FT /note="gtaagt, splice donor sequence"
+FT /colour=6
+FT misc_feature complement(4850..4861)
+FT /note="ctaactcctcag, splice branch and acceptor"
+FT /colour=6
+FT misc_feature complement(4883..4888)
+FT /note="gtaaga, splice donor sequence"
+FT /colour=6
+FT misc_feature complement(5008..5021)
+FT /note="ttaactatttgaag, splice branch and acceptor"
+FT /colour=6
+FT misc_feature complement(5245..5250)
+FT /note="gtaagt, splice donor sequence"
+FT /colour=6
+FT misc_feature complement(5390..5405)
+FT /note="ttaacatcctttttag, splice branch and acceptor"
+FT /colour=6
+FT misc_feature complement(5428..5433)
+FT /note="gtaagt, splice donor sequence"
+FT /colour=6
+FT misc_feature 6387..6490
+FT /note="nominal overlap with cosmid SPBC83 S. pombe
+FT chromosome 2"
+SQ Sequence 6490 BP; 2097 A; 1138 C; 1138 G; 2117 T; 0 other;
+ tatatataat ttaataaata cattccgacg atactgcctc tatggcttag tggtacagca 60
+ tcgcacttgt aatgcgaaga tccttggttc gattccgagt ggaggcatat acattatatt 120
+ atattctttt tcatgcggaa aaaagatttc aaatttttgg gtatgatatt aatatgactg 180
+ taacgttaat agcaaagtga gtgttaataa tgataaaata gcagcaaaat ctcttttccg 240
+ agtaagacgt tttccagtct aaatttggag tctgcagttg tttcgcaatt cttaatgtat 300
+ ggttatacta aatacaaact ttaaagctct gatttatgtt tgcaataaac taaaataaaa 360
+ gcacaaaaac ctttacccat taatttcaaa caacttataa actaccggta aacttttttt 420
+ ctaaccttta taatttataa actagaatgt ttaatgtcta cggccatacc taggcgaaaa 480
+ caccagttcc cgtccgatca ctgcagttaa gcgtctgagg gcctcgttag tactatggtt 540
+ ggagacaaca tgggaatccg gggtgctgta ggctattttt ttatatccgt ctttcttact 600
+ acttgcctaa caagtcatga tgtactctca aaatatgttt gcatgccttg taatattggt 660
+ tatggatagc tccttctgga cttgatcttt tgtagccaag aacaatgggt atagactctg 720
+ accttgtgat gttgtagcca cagattataa taggtatttt caagtacagt aacaaaaatc 780
+ ttctagtttt tttttagaaa ggatacacca agtataagca aattcaggaa ttgttgatta 840
+ aactgtcaac ttcggtaaaa ctttgggcat aagtagtgtg ggagcaagtt taactaaaat 900
+ tctattcaga tgtcgaatcc aaaccgctaa ttttgctcaa ctagcttttc ataaaaacca 960
+ attcatagtt tcatactaat aaagacgatt gtttacttta aaacatacgt cgtaagaaca 1020
+ tatattgctt tatcgaaaga taacaaatgt taagctatta tattatttaa ctatagcgca 1080
+ gatttcgctt cctttactta aaaaagacat gtgacttgta gaagcttgga gtgaatacgc 1140
+ aaaggtacct acttagacat tcgcgtctct cttagctgtc aacatcaaca aactggcccc 1200
+ gtattgaaca gtatcttact tgtcgaagga tttgactaag aaaattttat ttctttatag 1260
+ caatattccg ttttcgctta gaagattcta gtcaattgcc ctattctact tacgctttac 1320
+ agtagtatca gaagacctga gtgggatttt gctgctagta gaggccattc aagttaactc 1380
+ cgttttgcaa cattttaaaa gtttttgaat tgaatataaa tatcaattgt ttgattcctt 1440
+ ttaggattta atcttttctt tttatttttg tttcgattga atcttggatt cctgtctatc 1500
+ atgttttggt ggaaaagtgc tactaaattc acattctcaa agcgtggacc gtgtgtcttt 1560
+ cgctatttga gtactcttga aggaacaact gtgaggccta aaaaaaataa atttttagtt 1620
+ ggattgcttt ctgccgttcc aattgtcacg tttgctttag gaacttggca ggtaaagcga 1680
+ cgagaatgga aaatgggtat catcaataca ctcacggaaa ggcttcaaca gcccgcaatt 1740
+ ttattaccga aaactgttac gtacgttaag ttaacatata cacaaattgc acgttttgca 1800
+ attgaactgt cgttttttac attaagctaa cataatcact tagagagcaa gatacaaaaa 1860
+ aacttgagtg gactagggtt ttgcttcgtg gtgtgttttg tcacgaccaa gaaatgttgg 1920
+ tcggtccaag aacgaaggaa ggccaacctg gctatcacgt agtaacccca tttattttag 1980
+ acgatgggcg tcgaatttta gtcaacagag gatggattgc tcgatcattt gctgaacagt 2040
+ cttctcgaga tcctagttct ttacctaaag gtccagtggt cattgaaggt cttttgagac 2100
+ aacatactga taagccaaga tttatgatga agaatgagcc tgaaaaaaat tctttttact 2160
+ tcttaaatgt tcgtgagttt gcacaattga aaggaactct ccccattttg ataacagaac 2220
+ tacaaccatc gcttacaccg ttgcaagaag ccgatcatgt taagagaggc ttgcctcttg 2280
+ gtcatcctct aaaagttgaa attttcaaca gtcatacaga atatattatc acttggtatt 2340
+ ctctaagtgt ggtatcagct ataatgcttt acgtctattt taagagaggt tcaggcacat 2400
+ cttctctgaa ttctgcatac gaaagaagca agattctaaa caacaaacga ttataaaaaa 2460
+ ttttcatatt tataagtttc taaatattat ctacctaaaa ttttacaaat tttggaagct 2520
+ tgcttactgc gtccgtcgtt tgaatgtatg aatcgatcat tccttcacca acttgttttg 2580
+ caaacttcgg atcgtaagga acctattatt aggaagttag ttctcatgcc tataatttta 2640
+ atgctctata atcatcacgt acctgagttt cagataaatt gctcagccaa gagtttgaca 2700
+ acaattctga tacatatttt ctggcggcct tctttttatg cttcgtaatt ccagagatac 2760
+ ttcctaattt gttactgctg atgtttttaa gaagttggta ttgtctggta aattcttttt 2820
+ tcttggtagc tgacacgtga tataggaagc tgtttagaca agtgataagg tcatttatga 2880
+ tctataattc ggttagttta gaaattttta tatcattttt tttaaaaaaa aaaaccagtt 2940
+ tggtaaactt acttcagtgt actttgtcaa ttgacttaaa ggcgtggagt tctcataatc 3000
+ gaatgactca attaaatttt caatcgtttc gaacgaggaa ttttcgtagt ctccgttctt 3060
+ gacctttacc gaaagtaatc ccagttggat caataatttc atatgaacaa tttcttccga 3120
+ agttaattgc ttcgataaat cattgttttc gcacaaaact tccatttcct tagcacttaa 3180
+ aacggccttt tcaaaatcgc cgtttactat gctgtcttga acgagagaat gaccaatgac 3240
+ agtcaaatgg atccaaagcg tgtccggttt aggcgaattt cttaaacttt cttcaactgt 3300
+ tggcaattta tcagatccat aatctgcaaa gacctttaaa tctcgattat ctttcggaga 3360
+ tgaacatttg ggaagatact gtttgggtgg tttaaaagcg gtaagataat gtatcctagc 3420
+ ccgctcgacc aaagaaatgc ttttccaggt actgtggtct aaacgggatc ggaaattacg 3480
+ catatcttcg atttgagagt aagcaccatc ttcataagcc atagaaatca tctctggtgt 3540
+ ttcaaactca ttcgaaccat atattttgag acttgagtta atatagtgag aagtaacaga 3600
+ agatggataa taggtagtag cacgagtcaa gagataatga tcaagggtat cgttttgaat 3660
+ ttgcttaatg gacatagtgt cgtaaacttt agcagcagca gggaagcctc catcaagaag 3720
+ aagatataaa cgaataaggg gtagctttaa atgaaagtta tgttgactat aagtaatgcc 3780
+ tttttctaaa agacagattg cgtcaaaaat taaagcttgt ttttcggcag gttttaaatc 3840
+ cttattcccc tcccacatat atatcaatga atgaactgct aataacaaag cttcataacc 3900
+ gtgcgtaaag tcagtgggta ataagccttt acttaaagac aaaccctttt caaatgcgac 3960
+ gaagcatcta cgcacgtaat cgacaaccga ttctgcagta aacgactcaa agagcaaaaa 4020
+ atggatcttc agaagcaaaa cctcagcata taatttatcg actttttgag actacgacga 4080
+ gttaatattg aaagatgaat aaatacttac ttcattggat tcacctagat cagccaactt 4140
+ aaatgcatct aataaccggt gttgtgcatc aacattcagc ttcaatagat aaggtctcaa 4200
+ atcttcaaaa acaattggtt tcatatacag ctttttgata tagccaagta atgctgattc 4260
+ gtgttcttca ggaaaaaacc gtgcgctggc ttcaatccaa agtaaatgaa gatttctttt 4320
+ tgtgctgctg gttgacaacg ctttcaatat acaatccttt aatgggacta atctatggag 4380
+ aagttagtaa taatgcaaaa aaaaaaactt actttgaatc atcattggaa gcactatcca 4440
+ gcaaagcttt gcaaaccttc caatcagtat tcccagtttg gaaaagagaa agcgaaaacg 4500
+ tgaataaaga atcccaacgc gcacaggaag caagcaattc aagcttcctc aaaagaaggt 4560
+ cggcgtcagc atcaacaaag cgatcagcat cttgatgtat taaagcatct aacgctctgt 4620
+ ccttgtctcc cactaaaagc aacacgtcta aataaagatg aaactcctca caagaatcga 4680
+ tataacctgt tggtttttcg aaaatgagtt tagcagtttt ttcagctagt gctttcagca 4740
+ aacgttgttc aacctcattt tccgactttt tggacaaaag atacaaagat gagataaccc 4800
+ ataaggtatg tttcctagaa ggaaagttct tttgcaattc tacagcagcc tgaggagtta 4860
+ gtaaaaaaaa aacaaattaa gatcttacct ttctttggtg agacaatgat ttgatgcgaa 4920
+ tagatgcttt gaagtaagct aataagtttt tttcctgctt tccataagtt tgcaagaact 4980
+ tttcccaaaa tacaaacgat tcttctcctt caaatagtta ataacttaaa agtaagaggg 5040
+ taaaaacaag tccattgaga aataaaacgt tgcacttgaa tcatcaagct acgaaaagaa 5100
+ caaagatcgt ttttcttaag caaagtttaa ccgatcaaat aactaaagcc tttatctcaa 5160
+ ggaaatagta gaatattcaa aataaaaaat acgaccaaag tgctatcaac atttatgttc 5220
+ atctgttcaa tagaacctaa ttatacttac ccttcttttg atcatcataa acagcttgta 5280
+ taatgtctaa aagttccaag ctgttgatgg gagtcgattt taacggttct aaaagtgcta 5340
+ gagcctcggg accacgtcct gcctgagcca aggataacgc tgaataaacc taaaaaggat 5400
+ gttaacagct aaaaacataa ctcgattact tacaattgtt gattctttac tcccagaacg 5460
+ acgcattgta gtaggcaatg aaattaaagc ccaaacagta gtcagaagtt ggtaggtgtt 5520
+ gcagtcaaat tattattcta cagaggagaa tattatagcc agcgtggtag aatctggata 5580
+ tatatctact gcaaaagtgt aattgcattg gtttaaaggg tatactatgg ttaagtaata 5640
+ tattcacagc tgtacaattt acagtcataa ctaaaacttc cttaagccgt aaagaaatac 5700
+ ctggtgttgt aaaatttgtt gtatatccac ggcatggtca tataatgtga ttttgtgctc 5760
+ aaataaatat aaaatatgca taatttttgt acatttaatt tgagaaaccc atcttttgtt 5820
+ gagaggctgt caatgaatag cagtttcatt gaaaagcagc gggatgacca gaaaagtatt 5880
+ ttacaatggc aagggagtag aaagctagcg taatattcag aaagctaggt aattgagcaa 5940
+ tcctttaatt cattgctaag catgctaggt aaacgcagta aacctttcag ttttcattta 6000
+ ggtataaggc tgtttaatga gtatctccac taaatttaaa gatcaaaact cagtatcaat 6060
+ tcttaaaagt tttattttat ttaataatca tatacttctc ataatctttc aattttttcc 6120
+ ccattttgat gatattttta ttaatcctac agtaagctct atgatatcgt tattcttcaa 6180
+ ataggctggt cagcacgtgg acggtgttac ttatcgttaa ataaatcgta ctaaggaggt 6240
+ gcgatgtaaa tgatatgctt gtcaagtatt aactgctctc caccaaccgc cggtttaact 6300
+ gattattgtt gaaaagcgca gacgaagttt agagaattac tagcgtattt taaatttaat 6360
+ caacggacta ttttttattc ctttgagatc cgactttatc gctttgcttc taattttcca 6420
+ aaattcagtc tatctacgcg atccagccct gtttgcgtaa atttcatatt atttttcttt 6480
+ aaacgtttgg 6490
+//
diff --git a/etc/c1215.genbank b/etc/c1215.genbank
new file mode 100644
index 0000000..6ba6090
--- /dev/null
+++ b/etc/c1215.genbank
@@ -0,0 +1,275 @@
+LOCUS SPBC1215 6490 bp DNA PLN 16-JUL-1999
+DEFINITION S.pombe chromosome II cosmid c1215.
+ACCESSION AL096846
+VERSION AL096846.1 GI:5531459
+KEYWORDS actin cytoskeleton organisation; COX complex biogenesis; DEC1
+ homologue; mitochondrial inheritance; SURF-family protein.
+SOURCE fission yeast.
+ ORGANISM Schizosaccharomyces pombe
+ Eukaryota; Fungi; Ascomycota; Schizosaccharomycetes;
+ Schizosaccharomycetales; Schizosaccharomycetaceae;
+ Schizosaccharomyces.
+REFERENCE 1 (bases 1 to 6490)
+ AUTHORS Lyne,M.H., Rajandream,M.A., Barrell,B.G., Seeger,K., Quail,M. and
+ Harris,D.
+ TITLE Direct Submission
+ JOURNAL Submitted (16-JUL-1999) European Schizosaccharomyces genome
+ sequencing project, Sanger Centre, The Wellcome Trust Genome
+ Campus, Hinxton, Cambridge CB10 1SA, E-mail: barrell at sanger.ac.uk
+ and
+COMMENT Notes:
+ Details of yeast sequencing at the Sanger Centre are available on
+ the World Wide Web.
+ (URL, http://www.sanger.ac.uk/Projects/S_pombe/)
+ During 1995 to 1996 about 66% of S. pombe chromosome 1 was
+ sequenced by the Sanger Centre. The sequencing of the S. pombe
+ genome is now being continued with funding from The European
+ Commission. Fourteen European sequencing laboratories, including
+ the Sanger Centre, are participating in the project.
+ Protein coding regions (CDS) have been predicted with the help of
+ computer analysis using the Genefinder program in PomBase (an ACEDB
+ database) with additional predictions for the branch-acceptor sites
+ supplied by the program Sp3splice. CAUTION: It is possible that for
+ any individual CDS we may have underestimated or overestimated the
+ number of introns/exons or we may not have chosen the correct
+ splice donor/acceptor sites.
+ CDS are numbered using the following system eg SPBC25H2.01c. SP (S.
+ pombe), B (chromosome 2), c25H2 (cosmid name), .01 (first CDS), c
+ (complementary strand).
+ The more significant matches with motifs in the PROSITE database
+ are also included but some of these may be fortuitous.
+ The length in codons is given for each CDS.
+ IMPORTANT: This sequence MAY NOT be the entire insert of the
+ sequenced clone. It may be shorter because we only sequence
+ overlapping sections once, or longer, because we arrange for a
+ small overlap between neighbouring submissions.
+ Cosmid c1215 is overlapped at the 5' end by cosmid c1750, EMBL
+ entry SPAB4534, accession number AB004534, and at the 3' end by
+ cosmid c83, EMBL entry SPBC83, accession number AL035536.
+FEATURES Location/Qualifiers
+ source 1..6490
+ /organism="Schizosaccharomyces pombe"
+ /strain="972h-"
+ /db_xref="taxon:4896"
+ /chromosome="II"
+ /map="IIL"
+ /clone="cosmid c1215"
+ misc_feature complement(1..82)
+ /note="nominal overlap with cosmid SPAB4534 (c1750) S.
+ pombe chromosome 2"
+ tRNA 36..107
+ /note="tRNA Thr anticodon TGT, Cove score 78.51"
+ /product="tRNA-Thr"
+ rRNA 415..581
+ /note="SPRG5SD K00771 Yeast (s.pombe) 5s rrna gene and
+ flanks"
+ CDS join(1501..1760,1844..2456)
+ /gene="SPBC1215.01"
+ /note="SPBC1215.01, len:290, SIMILARITY:Saccharomyces
+ cerevisiae, YG2X_YEAST, hypothetical 45.1 kd protein in
+ clb6-spt6 intergenic region, (389 aa), fasta scores: opt:
+ 385, E():2.8e-19, (29.0% identity in 314 aa)"
+ /codon_start=1
+ /label=SPBC1215.01
+ /product="putative SURF-family protein"
+ /protein_id="CAB50922.1"
+ /db_xref="GI:5531460"
+ /translation="MFWWKSATKFTFSKRGPCVFRYLSTLEGTTVRPKKNKFLVGLLS
+ AVPIVTFALGTWQVKRREWKMGIINTLTERLQQPAILLPKTVTEQDTKKLEWTRVLLR
+ GVFCHDQEMLVGPRTKEGQPGYHVVTPFILDDGRRILVNRGWIARSFAEQSSRDPSSL
+ PKGPVVIEGLLRQHTDKPRFMMKNEPEKNSFYFLNVREFAQLKGTLPILITELQPSLT
+ PLQEADHVKRGLPLGHPLKVEIFNSHTEYIITWYSLSVVSAIMLYVYFKRGSGTSSLN
+ SAYERSKILNNKRL"
+ gene join(1501..1760,1844..2456)
+ /gene="SPBC1215.01"
+ misc_feature 1761..1766
+ /gene="SPBC1215.01"
+ /note="gtacgt, splice donor sequence"
+ misc_feature 1827..1843
+ /gene="SPBC1215.01"
+ /note="ctaacataatcacttag, splice branch and acceptor"
+ CDS complement(join(2495..2602,2663..2881,2953..4071,4111..4372,4413..4849,4889..5007,5251..5389,5434..5466))
+ /gene="SPBC1215.02c"
+ /note="SPBC1215.02c, len:811, SIMILARITY:Saccharomyces
+ cerevisiae, DEC1_YEAST, dec1 protein, (796 aa), fasta
+ scores: opt: 184, E():0.00014, (23.4% identity in 577 aa)"
+ /codon_start=1
+ /label=SPBC1215.02c
+ /product="similar to yeast DEC1 mitochondrial inheritance
+ and actin cytoskeleton organisation protein"
+ /protein_id="CAB50923.1"
+ /db_xref="GI:5531461"
+ /translation="MRRSGSKESTIVYSALSLAQAGRGPEALALLEPLKSTPINSLEL
+ LDIIQAVYDDQKKGEESFVFWEKFLQTYGKQEKNLLAYFKASIRIKSLSHQRKAAVEL
+ QKNFPSRKHTLWVISSLYLLSKKSENEVEQRLLKALAEKTAKLIFEKPTGYIDSCEEF
+ HLYLDVLLLVGDKDRALDALIHQDADRFVDADADLLLRKLELLASCARWDSLFTFSLS
+ LFQTGNTDWKVCKALLDSASNDDSKLVPLKDCILKALSTSSTKRNLHLLWIEASARFF
+ PEEHESALLGYIKKLYMKPIVFEDLRPYLLKLNVDAQHRLLDAFKLADLGESNESQKV
+ DKLYAEVLLLKIHFLLFESFTAESVVDYVRRCFVAFEKGLSLSKGLLPTDFTHGYEAL
+ LLAVHSLIYMWEGNKDLKPAEKQALIFDAICLLEKGITYSQHNFHLKLPLIRLYLLLD
+ GGFPAAAKVYDTMSIKQIQNDTLDHYLLTRATTYYPSSVTSHYINSSLKIYGSNEFET
+ PEMISMAYEDGAYSQIEDMRNFRSRLDHSTWKSISLVERARIHYLTAFKPPKQYLPKC
+ SSPKDNRDLKVFADYGSDKLPTVEESLRNSPKPDTLWIHLTVIGHSLVQDSIVNGDFE
+ KAVLSAKEMEVLCENNDLSKQLTSEEIVHMKLLIQLGLLSVKVKNGDYENSSFETIEN
+ LIESFDYENSTPLSQLTKYTEIINDLITCLNSFLYHVSATKKKEFTRQYQLLKNISSN
+ KLGSISGITKHKKKAARKYVSELLSNSWLSNLSETQVPYDPKFAKQVGEGMIDSYIQT
+ TDAVSKLPKFVKF"
+ gene complement(join(2495..2602,2663..2881,2953..4071,4111..4372,4413..4849,4889..5007,5251..5389,5434..5466))
+ /gene="SPBC1215.02c"
+ misc_feature complement(2603..2612)
+ /gene="SPBC1215.02c"
+ /note="ctaataatag, splice branch and acceptor"
+ misc_feature complement(2657..2662)
+ /gene="SPBC1215.02c"
+ /note="gtacgt, splice donor sequence"
+ misc_feature complement(2882..2896)
+ /gene="SPBC1215.02c"
+ /note="ctaaccgaattatag, splice branch and acceptor"
+ misc_feature complement(2947..2952)
+ /gene="SPBC1215.02c"
+ /note="gtaagt, splice donor sequence"
+ misc_feature complement(4072..4085)
+ /gene="SPBC1215.02c"
+ /note="ttaactcgtcgtag, splice branch and acceptor"
+ misc_feature complement(4105..4110)
+ /gene="SPBC1215.02c"
+ /note="gtaagt, splice donor sequence"
+ misc_feature complement(4373..4387)
+ /gene="SPBC1215.02c"
+ /note="ctaacttctccatag, splice branch and acceptor"
+ misc_feature complement(4407..4412)
+ /gene="SPBC1215.02c"
+ /note="gtaagt, splice donor sequence"
+ misc_feature complement(4850..4861)
+ /gene="SPBC1215.02c"
+ /note="ctaactcctcag, splice branch and acceptor"
+ misc_feature complement(4883..4888)
+ /gene="SPBC1215.02c"
+ /note="gtaaga, splice donor sequence"
+ misc_feature complement(5008..5021)
+ /gene="SPBC1215.02c"
+ /note="ttaactatttgaag, splice branch and acceptor"
+ misc_feature complement(5245..5250)
+ /gene="SPBC1215.02c"
+ /note="gtaagt, splice donor sequence"
+ misc_feature complement(5390..5405)
+ /gene="SPBC1215.02c"
+ /note="ttaacatcctttttag, splice branch and acceptor"
+ misc_feature complement(5428..5433)
+ /gene="SPBC1215.02c"
+ /note="gtaagt, splice donor sequence"
+ misc_feature 6387..6490
+ /note="nominal overlap with cosmid SPBC83 S. pombe
+ chromosome 2"
+BASE COUNT 0 a 0 c 0 g 0 t
+ORIGIN
+ 1 tatatataat ttaataaata cattccgacg atactgcctc tatggcttag tggtacagca
+ 61 tcgcacttgt aatgcgaaga tccttggttc gattccgagt ggaggcatat acattatatt
+ 121 atattctttt tcatgcggaa aaaagatttc aaatttttgg gtatgatatt aatatgactg
+ 181 taacgttaat agcaaagtga gtgttaataa tgataaaata gcagcaaaat ctcttttccg
+ 241 agtaagacgt tttccagtct aaatttggag tctgcagttg tttcgcaatt cttaatgtat
+ 301 ggttatacta aatacaaact ttaaagctct gatttatgtt tgcaataaac taaaataaaa
+ 361 gcacaaaaac ctttacccat taatttcaaa caacttataa actaccggta aacttttttt
+ 421 ctaaccttta taatttataa actagaatgt ttaatgtcta cggccatacc taggcgaaaa
+ 481 caccagttcc cgtccgatca ctgcagttaa gcgtctgagg gcctcgttag tactatggtt
+ 541 ggagacaaca tgggaatccg gggtgctgta ggctattttt ttatatccgt ctttcttact
+ 601 acttgcctaa caagtcatga tgtactctca aaatatgttt gcatgccttg taatattggt
+ 661 tatggatagc tccttctgga cttgatcttt tgtagccaag aacaatgggt atagactctg
+ 721 accttgtgat gttgtagcca cagattataa taggtatttt caagtacagt aacaaaaatc
+ 781 ttctagtttt tttttagaaa ggatacacca agtataagca aattcaggaa ttgttgatta
+ 841 aactgtcaac ttcggtaaaa ctttgggcat aagtagtgtg ggagcaagtt taactaaaat
+ 901 tctattcaga tgtcgaatcc aaaccgctaa ttttgctcaa ctagcttttc ataaaaacca
+ 961 attcatagtt tcatactaat aaagacgatt gtttacttta aaacatacgt cgtaagaaca
+ 1021 tatattgctt tatcgaaaga taacaaatgt taagctatta tattatttaa ctatagcgca
+ 1081 gatttcgctt cctttactta aaaaagacat gtgacttgta gaagcttgga gtgaatacgc
+ 1141 aaaggtacct acttagacat tcgcgtctct cttagctgtc aacatcaaca aactggcccc
+ 1201 gtattgaaca gtatcttact tgtcgaagga tttgactaag aaaattttat ttctttatag
+ 1261 caatattccg ttttcgctta gaagattcta gtcaattgcc ctattctact tacgctttac
+ 1321 agtagtatca gaagacctga gtgggatttt gctgctagta gaggccattc aagttaactc
+ 1381 cgttttgcaa cattttaaaa gtttttgaat tgaatataaa tatcaattgt ttgattcctt
+ 1441 ttaggattta atcttttctt tttatttttg tttcgattga atcttggatt cctgtctatc
+ 1501 atgttttggt ggaaaagtgc tactaaattc acattctcaa agcgtggacc gtgtgtcttt
+ 1561 cgctatttga gtactcttga aggaacaact gtgaggccta aaaaaaataa atttttagtt
+ 1621 ggattgcttt ctgccgttcc aattgtcacg tttgctttag gaacttggca ggtaaagcga
+ 1681 cgagaatgga aaatgggtat catcaataca ctcacggaaa ggcttcaaca gcccgcaatt
+ 1741 ttattaccga aaactgttac gtacgttaag ttaacatata cacaaattgc acgttttgca
+ 1801 attgaactgt cgttttttac attaagctaa cataatcact tagagagcaa gatacaaaaa
+ 1861 aacttgagtg gactagggtt ttgcttcgtg gtgtgttttg tcacgaccaa gaaatgttgg
+ 1921 tcggtccaag aacgaaggaa ggccaacctg gctatcacgt agtaacccca tttattttag
+ 1981 acgatgggcg tcgaatttta gtcaacagag gatggattgc tcgatcattt gctgaacagt
+ 2041 cttctcgaga tcctagttct ttacctaaag gtccagtggt cattgaaggt cttttgagac
+ 2101 aacatactga taagccaaga tttatgatga agaatgagcc tgaaaaaaat tctttttact
+ 2161 tcttaaatgt tcgtgagttt gcacaattga aaggaactct ccccattttg ataacagaac
+ 2221 tacaaccatc gcttacaccg ttgcaagaag ccgatcatgt taagagaggc ttgcctcttg
+ 2281 gtcatcctct aaaagttgaa attttcaaca gtcatacaga atatattatc acttggtatt
+ 2341 ctctaagtgt ggtatcagct ataatgcttt acgtctattt taagagaggt tcaggcacat
+ 2401 cttctctgaa ttctgcatac gaaagaagca agattctaaa caacaaacga ttataaaaaa
+ 2461 ttttcatatt tataagtttc taaatattat ctacctaaaa ttttacaaat tttggaagct
+ 2521 tgcttactgc gtccgtcgtt tgaatgtatg aatcgatcat tccttcacca acttgttttg
+ 2581 caaacttcgg atcgtaagga acctattatt aggaagttag ttctcatgcc tataatttta
+ 2641 atgctctata atcatcacgt acctgagttt cagataaatt gctcagccaa gagtttgaca
+ 2701 acaattctga tacatatttt ctggcggcct tctttttatg cttcgtaatt ccagagatac
+ 2761 ttcctaattt gttactgctg atgtttttaa gaagttggta ttgtctggta aattcttttt
+ 2821 tcttggtagc tgacacgtga tataggaagc tgtttagaca agtgataagg tcatttatga
+ 2881 tctataattc ggttagttta gaaattttta tatcattttt tttaaaaaaa aaaaccagtt
+ 2941 tggtaaactt acttcagtgt actttgtcaa ttgacttaaa ggcgtggagt tctcataatc
+ 3001 gaatgactca attaaatttt caatcgtttc gaacgaggaa ttttcgtagt ctccgttctt
+ 3061 gacctttacc gaaagtaatc ccagttggat caataatttc atatgaacaa tttcttccga
+ 3121 agttaattgc ttcgataaat cattgttttc gcacaaaact tccatttcct tagcacttaa
+ 3181 aacggccttt tcaaaatcgc cgtttactat gctgtcttga acgagagaat gaccaatgac
+ 3241 agtcaaatgg atccaaagcg tgtccggttt aggcgaattt cttaaacttt cttcaactgt
+ 3301 tggcaattta tcagatccat aatctgcaaa gacctttaaa tctcgattat ctttcggaga
+ 3361 tgaacatttg ggaagatact gtttgggtgg tttaaaagcg gtaagataat gtatcctagc
+ 3421 ccgctcgacc aaagaaatgc ttttccaggt actgtggtct aaacgggatc ggaaattacg
+ 3481 catatcttcg atttgagagt aagcaccatc ttcataagcc atagaaatca tctctggtgt
+ 3541 ttcaaactca ttcgaaccat atattttgag acttgagtta atatagtgag aagtaacaga
+ 3601 agatggataa taggtagtag cacgagtcaa gagataatga tcaagggtat cgttttgaat
+ 3661 ttgcttaatg gacatagtgt cgtaaacttt agcagcagca gggaagcctc catcaagaag
+ 3721 aagatataaa cgaataaggg gtagctttaa atgaaagtta tgttgactat aagtaatgcc
+ 3781 tttttctaaa agacagattg cgtcaaaaat taaagcttgt ttttcggcag gttttaaatc
+ 3841 cttattcccc tcccacatat atatcaatga atgaactgct aataacaaag cttcataacc
+ 3901 gtgcgtaaag tcagtgggta ataagccttt acttaaagac aaaccctttt caaatgcgac
+ 3961 gaagcatcta cgcacgtaat cgacaaccga ttctgcagta aacgactcaa agagcaaaaa
+ 4021 atggatcttc agaagcaaaa cctcagcata taatttatcg actttttgag actacgacga
+ 4081 gttaatattg aaagatgaat aaatacttac ttcattggat tcacctagat cagccaactt
+ 4141 aaatgcatct aataaccggt gttgtgcatc aacattcagc ttcaatagat aaggtctcaa
+ 4201 atcttcaaaa acaattggtt tcatatacag ctttttgata tagccaagta atgctgattc
+ 4261 gtgttcttca ggaaaaaacc gtgcgctggc ttcaatccaa agtaaatgaa gatttctttt
+ 4321 tgtgctgctg gttgacaacg ctttcaatat acaatccttt aatgggacta atctatggag
+ 4381 aagttagtaa taatgcaaaa aaaaaaactt actttgaatc atcattggaa gcactatcca
+ 4441 gcaaagcttt gcaaaccttc caatcagtat tcccagtttg gaaaagagaa agcgaaaacg
+ 4501 tgaataaaga atcccaacgc gcacaggaag caagcaattc aagcttcctc aaaagaaggt
+ 4561 cggcgtcagc atcaacaaag cgatcagcat cttgatgtat taaagcatct aacgctctgt
+ 4621 ccttgtctcc cactaaaagc aacacgtcta aataaagatg aaactcctca caagaatcga
+ 4681 tataacctgt tggtttttcg aaaatgagtt tagcagtttt ttcagctagt gctttcagca
+ 4741 aacgttgttc aacctcattt tccgactttt tggacaaaag atacaaagat gagataaccc
+ 4801 ataaggtatg tttcctagaa ggaaagttct tttgcaattc tacagcagcc tgaggagtta
+ 4861 gtaaaaaaaa aacaaattaa gatcttacct ttctttggtg agacaatgat ttgatgcgaa
+ 4921 tagatgcttt gaagtaagct aataagtttt tttcctgctt tccataagtt tgcaagaact
+ 4981 tttcccaaaa tacaaacgat tcttctcctt caaatagtta ataacttaaa agtaagaggg
+ 5041 taaaaacaag tccattgaga aataaaacgt tgcacttgaa tcatcaagct acgaaaagaa
+ 5101 caaagatcgt ttttcttaag caaagtttaa ccgatcaaat aactaaagcc tttatctcaa
+ 5161 ggaaatagta gaatattcaa aataaaaaat acgaccaaag tgctatcaac atttatgttc
+ 5221 atctgttcaa tagaacctaa ttatacttac ccttcttttg atcatcataa acagcttgta
+ 5281 taatgtctaa aagttccaag ctgttgatgg gagtcgattt taacggttct aaaagtgcta
+ 5341 gagcctcggg accacgtcct gcctgagcca aggataacgc tgaataaacc taaaaaggat
+ 5401 gttaacagct aaaaacataa ctcgattact tacaattgtt gattctttac tcccagaacg
+ 5461 acgcattgta gtaggcaatg aaattaaagc ccaaacagta gtcagaagtt ggtaggtgtt
+ 5521 gcagtcaaat tattattcta cagaggagaa tattatagcc agcgtggtag aatctggata
+ 5581 tatatctact gcaaaagtgt aattgcattg gtttaaaggg tatactatgg ttaagtaata
+ 5641 tattcacagc tgtacaattt acagtcataa ctaaaacttc cttaagccgt aaagaaatac
+ 5701 ctggtgttgt aaaatttgtt gtatatccac ggcatggtca tataatgtga ttttgtgctc
+ 5761 aaataaatat aaaatatgca taatttttgt acatttaatt tgagaaaccc atcttttgtt
+ 5821 gagaggctgt caatgaatag cagtttcatt gaaaagcagc gggatgacca gaaaagtatt
+ 5881 ttacaatggc aagggagtag aaagctagcg taatattcag aaagctaggt aattgagcaa
+ 5941 tcctttaatt cattgctaag catgctaggt aaacgcagta aacctttcag ttttcattta
+ 6001 ggtataaggc tgtttaatga gtatctccac taaatttaaa gatcaaaact cagtatcaat
+ 6061 tcttaaaagt tttattttat ttaataatca tatacttctc ataatctttc aattttttcc
+ 6121 ccattttgat gatattttta ttaatcctac agtaagctct atgatatcgt tattcttcaa
+ 6181 ataggctggt cagcacgtgg acggtgttac ttatcgttaa ataaatcgta ctaaggaggt
+ 6241 gcgatgtaaa tgatatgctt gtcaagtatt aactgctctc caccaaccgc cggtttaact
+ 6301 gattattgtt gaaaagcgca gacgaagttt agagaattac tagcgtattt taaatttaat
+ 6361 caacggacta ttttttattc ctttgagatc cgactttatc gctttgcttc taattttcca
+ 6421 aaattcagtc tatctacgcg atccagccct gtttgcgtaa atttcatatt atttttcttt
+ 6481 aaacgtttgg
+//
diff --git a/etc/c1215.seq b/etc/c1215.seq
new file mode 100644
index 0000000..94d788a
--- /dev/null
+++ b/etc/c1215.seq
@@ -0,0 +1,110 @@
+>all bases
+tatatataatttaataaatacattccgacgatactgcctctatggcttagtggtacagca
+tcgcacttgtaatgcgaagatccttggttcgattccgagtggaggcatatacattatatt
+atattctttttcatgcggaaaaaagatttcaaatttttgggtatgatattaatatgactg
+taacgttaatagcaaagtgagtgttaataatgataaaatagcagcaaaatctcttttccg
+agtaagacgttttccagtctaaatttggagtctgcagttgtttcgcaattcttaatgtat
+ggttatactaaatacaaactttaaagctctgatttatgtttgcaataaactaaaataaaa
+gcacaaaaacctttacccattaatttcaaacaacttataaactaccggtaaacttttttt
+ctaacctttataatttataaactagaatgtttaatgtctacggccatacctaggcgaaaa
+caccagttcccgtccgatcactgcagttaagcgtctgagggcctcgttagtactatggtt
+ggagacaacatgggaatccggggtgctgtaggctatttttttatatccgtctttcttact
+acttgcctaacaagtcatgatgtactctcaaaatatgtttgcatgccttgtaatattggt
+tatggatagctccttctggacttgatcttttgtagccaagaacaatgggtatagactctg
+accttgtgatgttgtagccacagattataataggtattttcaagtacagtaacaaaaatc
+ttctagtttttttttagaaaggatacaccaagtataagcaaattcaggaattgttgatta
+aactgtcaacttcggtaaaactttgggcataagtagtgtgggagcaagtttaactaaaat
+tctattcagatgtcgaatccaaaccgctaattttgctcaactagcttttcataaaaacca
+attcatagtttcatactaataaagacgattgtttactttaaaacatacgtcgtaagaaca
+tatattgctttatcgaaagataacaaatgttaagctattatattatttaactatagcgca
+gatttcgcttcctttacttaaaaaagacatgtgacttgtagaagcttggagtgaatacgc
+aaaggtacctacttagacattcgcgtctctcttagctgtcaacatcaacaaactggcccc
+gtattgaacagtatcttacttgtcgaaggatttgactaagaaaattttatttctttatag
+caatattccgttttcgcttagaagattctagtcaattgccctattctacttacgctttac
+agtagtatcagaagacctgagtgggattttgctgctagtagaggccattcaagttaactc
+cgttttgcaacattttaaaagtttttgaattgaatataaatatcaattgtttgattcctt
+ttaggatttaatcttttctttttatttttgtttcgattgaatcttggattcctgtctatc
+atgttttggtggaaaagtgctactaaattcacattctcaaagcgtggaccgtgtgtcttt
+cgctatttgagtactcttgaaggaacaactgtgaggcctaaaaaaaataaatttttagtt
+ggattgctttctgccgttccaattgtcacgtttgctttaggaacttggcaggtaaagcga
+cgagaatggaaaatgggtatcatcaatacactcacggaaaggcttcaacagcccgcaatt
+ttattaccgaaaactgttacgtacgttaagttaacatatacacaaattgcacgttttgca
+attgaactgtcgttttttacattaagctaacataatcacttagagagcaagatacaaaaa
+aacttgagtggactagggttttgcttcgtggtgtgttttgtcacgaccaagaaatgttgg
+tcggtccaagaacgaaggaaggccaacctggctatcacgtagtaaccccatttattttag
+acgatgggcgtcgaattttagtcaacagaggatggattgctcgatcatttgctgaacagt
+cttctcgagatcctagttctttacctaaaggtccagtggtcattgaaggtcttttgagac
+aacatactgataagccaagatttatgatgaagaatgagcctgaaaaaaattctttttact
+tcttaaatgttcgtgagtttgcacaattgaaaggaactctccccattttgataacagaac
+tacaaccatcgcttacaccgttgcaagaagccgatcatgttaagagaggcttgcctcttg
+gtcatcctctaaaagttgaaattttcaacagtcatacagaatatattatcacttggtatt
+ctctaagtgtggtatcagctataatgctttacgtctattttaagagaggttcaggcacat
+cttctctgaattctgcatacgaaagaagcaagattctaaacaacaaacgattataaaaaa
+ttttcatatttataagtttctaaatattatctacctaaaattttacaaattttggaagct
+tgcttactgcgtccgtcgtttgaatgtatgaatcgatcattccttcaccaacttgttttg
+caaacttcggatcgtaaggaacctattattaggaagttagttctcatgcctataatttta
+atgctctataatcatcacgtacctgagtttcagataaattgctcagccaagagtttgaca
+acaattctgatacatattttctggcggccttctttttatgcttcgtaattccagagatac
+ttcctaatttgttactgctgatgtttttaagaagttggtattgtctggtaaattcttttt
+tcttggtagctgacacgtgatataggaagctgtttagacaagtgataaggtcatttatga
+tctataattcggttagtttagaaatttttatatcattttttttaaaaaaaaaaaccagtt
+tggtaaacttacttcagtgtactttgtcaattgacttaaaggcgtggagttctcataatc
+gaatgactcaattaaattttcaatcgtttcgaacgaggaattttcgtagtctccgttctt
+gacctttaccgaaagtaatcccagttggatcaataatttcatatgaacaatttcttccga
+agttaattgcttcgataaatcattgttttcgcacaaaacttccatttccttagcacttaa
+aacggccttttcaaaatcgccgtttactatgctgtcttgaacgagagaatgaccaatgac
+agtcaaatggatccaaagcgtgtccggtttaggcgaatttcttaaactttcttcaactgt
+tggcaatttatcagatccataatctgcaaagacctttaaatctcgattatctttcggaga
+tgaacatttgggaagatactgtttgggtggtttaaaagcggtaagataatgtatcctagc
+ccgctcgaccaaagaaatgcttttccaggtactgtggtctaaacgggatcggaaattacg
+catatcttcgatttgagagtaagcaccatcttcataagccatagaaatcatctctggtgt
+ttcaaactcattcgaaccatatattttgagacttgagttaatatagtgagaagtaacaga
+agatggataataggtagtagcacgagtcaagagataatgatcaagggtatcgttttgaat
+ttgcttaatggacatagtgtcgtaaactttagcagcagcagggaagcctccatcaagaag
+aagatataaacgaataaggggtagctttaaatgaaagttatgttgactataagtaatgcc
+tttttctaaaagacagattgcgtcaaaaattaaagcttgtttttcggcaggttttaaatc
+cttattcccctcccacatatatatcaatgaatgaactgctaataacaaagcttcataacc
+gtgcgtaaagtcagtgggtaataagcctttacttaaagacaaacccttttcaaatgcgac
+gaagcatctacgcacgtaatcgacaaccgattctgcagtaaacgactcaaagagcaaaaa
+atggatcttcagaagcaaaacctcagcatataatttatcgactttttgagactacgacga
+gttaatattgaaagatgaataaatacttacttcattggattcacctagatcagccaactt
+aaatgcatctaataaccggtgttgtgcatcaacattcagcttcaatagataaggtctcaa
+atcttcaaaaacaattggtttcatatacagctttttgatatagccaagtaatgctgattc
+gtgttcttcaggaaaaaaccgtgcgctggcttcaatccaaagtaaatgaagatttctttt
+tgtgctgctggttgacaacgctttcaatatacaatcctttaatgggactaatctatggag
+aagttagtaataatgcaaaaaaaaaaacttactttgaatcatcattggaagcactatcca
+gcaaagctttgcaaaccttccaatcagtattcccagtttggaaaagagaaagcgaaaacg
+tgaataaagaatcccaacgcgcacaggaagcaagcaattcaagcttcctcaaaagaaggt
+cggcgtcagcatcaacaaagcgatcagcatcttgatgtattaaagcatctaacgctctgt
+ccttgtctcccactaaaagcaacacgtctaaataaagatgaaactcctcacaagaatcga
+tataacctgttggtttttcgaaaatgagtttagcagttttttcagctagtgctttcagca
+aacgttgttcaacctcattttccgactttttggacaaaagatacaaagatgagataaccc
+ataaggtatgtttcctagaaggaaagttcttttgcaattctacagcagcctgaggagtta
+gtaaaaaaaaaacaaattaagatcttacctttctttggtgagacaatgatttgatgcgaa
+tagatgctttgaagtaagctaataagtttttttcctgctttccataagtttgcaagaact
+tttcccaaaatacaaacgattcttctccttcaaatagttaataacttaaaagtaagaggg
+taaaaacaagtccattgagaaataaaacgttgcacttgaatcatcaagctacgaaaagaa
+caaagatcgtttttcttaagcaaagtttaaccgatcaaataactaaagcctttatctcaa
+ggaaatagtagaatattcaaaataaaaaatacgaccaaagtgctatcaacatttatgttc
+atctgttcaatagaacctaattatacttacccttcttttgatcatcataaacagcttgta
+taatgtctaaaagttccaagctgttgatgggagtcgattttaacggttctaaaagtgcta
+gagcctcgggaccacgtcctgcctgagccaaggataacgctgaataaacctaaaaaggat
+gttaacagctaaaaacataactcgattacttacaattgttgattctttactcccagaacg
+acgcattgtagtaggcaatgaaattaaagcccaaacagtagtcagaagttggtaggtgtt
+gcagtcaaattattattctacagaggagaatattatagccagcgtggtagaatctggata
+tatatctactgcaaaagtgtaattgcattggtttaaagggtatactatggttaagtaata
+tattcacagctgtacaatttacagtcataactaaaacttccttaagccgtaaagaaatac
+ctggtgttgtaaaatttgttgtatatccacggcatggtcatataatgtgattttgtgctc
+aaataaatataaaatatgcataatttttgtacatttaatttgagaaacccatcttttgtt
+gagaggctgtcaatgaatagcagtttcattgaaaagcagcgggatgaccagaaaagtatt
+ttacaatggcaagggagtagaaagctagcgtaatattcagaaagctaggtaattgagcaa
+tcctttaattcattgctaagcatgctaggtaaacgcagtaaacctttcagttttcattta
+ggtataaggctgtttaatgagtatctccactaaatttaaagatcaaaactcagtatcaat
+tcttaaaagttttattttatttaataatcatatacttctcataatctttcaattttttcc
+ccattttgatgatatttttattaatcctacagtaagctctatgatatcgttattcttcaa
+ataggctggtcagcacgtggacggtgttacttatcgttaaataaatcgtactaaggaggt
+gcgatgtaaatgatatgcttgtcaagtattaactgctctccaccaaccgccggtttaact
+gattattgttgaaaagcgcagacgaagtttagagaattactagcgtattttaaatttaat
+caacggactattttttattcctttgagatccgactttatcgctttgcttctaattttcca
+aaattcagtctatctacgcgatccagccctgtttgcgtaaatttcatattatttttcttt
+aaacgtttgg
diff --git a/etc/chado_extra.sql b/etc/chado_extra.sql
new file mode 100644
index 0000000..37ead60
--- /dev/null
+++ b/etc/chado_extra.sql
@@ -0,0 +1,65 @@
+--
+-- Add extra terms used by Artemis with Chado
+--
+
+--
+-- add term for evidence codes
+--
+INSERT INTO dbxref
+ ( db_id, accession )
+VALUES
+ ( (SELECT db_id FROM db WHERE name = 'null'), 'Typically an evidence code' );
+
+INSERT INTO cvterm
+ ( cv_id, name, dbxref_id )
+VALUES
+ ( (SELECT cv_id FROM cv WHERE name ='feature_property'), 'evidence',
+ (SELECT dbxref_id FROM dbxref WHERE accession='Typically an evidence code') );
+
+--
+-- database used for controlled curation terms
+--
+INSERT INTO db ( name ) VALUES ( 'CCGEN' );
+
+--
+-- add terms for literature
+--
+INSERT INTO cv
+ ( name, definition)
+VALUES
+ ( 'genedb_literature', 'terms for literature' );
+
+INSERT INTO dbxref
+ ( db_id, accession )
+VALUES
+ ( (SELECT db_id FROM db WHERE name = 'null'), 'unfetched literature type');
+
+INSERT INTO dbxref
+ ( db_id, accession )
+VALUES
+ ( (SELECT db_id FROM db WHERE name = 'null'), 'journal literature type' );
+
+INSERT INTO dbxref
+ ( db_id, accession )
+VALUES
+ ( (SELECT db_id FROM db WHERE name = 'null'), 'unknown literature type' );
+
+INSERT INTO cvterm
+ ( cv_id, name, dbxref_id )
+VALUES
+ ( (SELECT cv_id FROM cv WHERE name = 'genedb_literature'), 'unfetched',
+ (SELECT dbxref_id FROM dbxref WHERE accession='unfetched literature type') );
+
+INSERT INTO cvterm
+ ( cv_id, name, dbxref_id )
+VALUES
+ ( (SELECT cv_id FROM cv WHERE name = 'genedb_literature'), 'journal',
+ (SELECT dbxref_id FROM dbxref WHERE accession='journal literature type') );
+
+INSERT INTO cvterm
+ ( cv_id, name, dbxref_id )
+VALUES
+ ( (SELECT cv_id FROM cv WHERE name = 'genedb_literature'), 'unknown',
+ (SELECT dbxref_id FROM dbxref WHERE accession='unknown literature type') );
+
+
diff --git a/etc/feature_keys b/etc/feature_keys
new file mode 100644
index 0000000..d8569eb
--- /dev/null
+++ b/etc/feature_keys
@@ -0,0 +1,68 @@
+# All feature keys and qualifiers for EMBL/GenBank feature tables
+
+# From: http://www.ebi.ac.uk/~faruque/art/keyqual.txt
+
+# key Qualifiers (@=mandatory)
+# ======= ========================
+
+-10_signal allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag operon partial standard_name
+-35_signal allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag operon partial standard_name
+3'UTR allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name trans_splicing
+5'UTR allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name trans_splicing
+CAAT_signal allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial
+CDS EC_number allele artificial_location citation codon_start db_xref exception experiment function gene gene_synonym inference locus_tag map note number old_locus_tag operon partial product protein_id pseudogene ribosomal_slippage standard_name trans_splicing transl_except transl_table translation
+C_region allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+D-loop allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial
+D_segment allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+GC_signal allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial
+J_segment allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+LTR allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name
+N_region allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+RBS allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name
+STS allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name
+S_region allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+TATA_signal allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial
+V_region allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+V_segment allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+assembly_gap @gap_type @estimated_length linkage_evidence
+attenuator allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag operon partial phenotype
+enhancer allele bound_moiety citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name
+exon EC_number allele citation db_xref experiment function gene gene_synonym inference locus_tag map note number old_locus_tag partial product pseudogene standard_name
+gap @estimated_length experiment inference map note
+gene allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial phenotype product pseudogene standard_name trans_splicing
+iDNA allele citation db_xref experiment function gene gene_synonym inference locus_tag map note number old_locus_tag partial standard_name
+intron allele citation db_xref experiment function gene gene_synonym inference locus_tag map note number old_locus_tag partial pseudogene standard_name
+mRNA allele artificial_location citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial product pseudogene standard_name trans_splicing
+mat_peptide EC_number allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+misc_RNA allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial product pseudogene standard_name trans_splicing
+misc_binding @bound_moiety allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial
+misc_difference allele citation clone compare db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial phenotype replace standard_name
+misc_feature allele citation db_xref experiment function gene gene_synonym inference locus_tag map note number old_locus_tag partial phenotype product pseudogene standard_name
+misc_recomb allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name
+misc_signal allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial phenotype standard_name
+misc_structure allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name
+mobile_element @mobile_element_type allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial rpt_family rpt_type standard_name
+modified_base @mod_base allele citation db_xref experiment frequency gene gene_synonym inference locus_tag map note old_locus_tag
+ncRNA @ncRNA_class allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial product pseudogene standard_name trans_splicing
+old_sequence allele citation compare db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial replace
+operon @operon allele citation db_xref experiment function inference map note phenotype pseudogene standard_name
+oriT allele bound_moiety citation db_xref direction experiment gene gene_synonym inference locus_tag map note old_locus_tag rpt_family rpt_type rpt_unit_range rpt_unit_seq standard_name
+polyA_signal allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial
+polyA_site allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag
+precursor_RNA allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial product standard_name trans_splicing
+prim_transcript allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial standard_name
+primer_bind PCR_conditions allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name
+promoter allele bound_moiety citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial phenotype pseudogene standard_name
+protein_bind @bound_moiety allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial standard_name
+rRNA allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial product pseudogene standard_name
+rep_origin allele citation db_xref direction experiment gene gene_synonym inference locus_tag map note old_locus_tag partial standard_name
+repeat_region allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial rpt_family rpt_type rpt_unit_range rpt_unit_seq satellite standard_name
+sig_peptide allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+source @mol_type @organism PCR_primers bio_material cell_line cell_type chromosome citation clone clone_lib collected_by collection_date country cultivar culture_collection db_xref dev_stage ecotype environmental_sample focus frequency germline haplogroup haplotype host identified_by isolate isolation_source lab_host lat_lon macronuclear map mating_type note organelle plasmid pop_variant proviral rearranged segment serotype serovar sex specimen_voucher strain sub_clone sub_speci [...]
+stem_loop allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial standard_name
+tRNA allele anticodon citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag operon partial product pseudogene standard_name trans_splicing
+terminator allele citation db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag operon partial standard_name
+tmRNA allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag product pseudogene standard_name tag_peptide
+transit_peptide allele citation db_xref experiment function gene gene_synonym inference locus_tag map note old_locus_tag partial product pseudogene standard_name
+unsure allele citation compare db_xref experiment gene gene_synonym inference locus_tag map note old_locus_tag replace
+variation allele citation compare db_xref experiment frequency gene gene_synonym inference locus_tag map note old_locus_tag partial phenotype product replace standard_name
diff --git a/etc/feature_keys_gff b/etc/feature_keys_gff
new file mode 100644
index 0000000..9f0b36a
--- /dev/null
+++ b/etc/feature_keys_gff
@@ -0,0 +1,81 @@
+#
+# Keys (Column 3: "type") constrained to be either:
+# (a) a term from the "lite" sequence ontology, SOFA;
+# (b) a SOFA accession number (SO:000000).
+#
+# key Qualifiers (@=mandatory)
+# ======= ========================
+centromere ID Name Alias Parent Note Target Gap Derives_from feature_id isObsolete timelastmodified note comment private curation
+region ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score
+contig ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score comment
+#supercontig ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score
+#chromosome ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified note comment description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score
+gene ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score private
+#CDS ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified codon_start description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score results note comment job
+polypeptide ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref product molecule_type size organism_name strain topology localization gff_source gff_seqname score mass isoelectric charge source private signal_peptide membrane_structure cytoplasmic_polypeptide_region non_cytoplasmic_polypeptide_region transmembrane_polypeptide_regio [...]
+pseudogene ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score private
+pseudogenic_exon ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score private
+pseudogenic_transcript ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score product
+intron ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score
+exon ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified codon_start description locus stable_id EC_number gene_symbol Ontology_term Dbxref molecule_type size organism_name strain topology localization gff_source gff_seqname score
+three_prime_UTR ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified systematic_id note comment private curation
+five_prime_UTR ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified systematic_id note comment private curation
+transcript ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified private comment
+primary_transcript ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified product note comment controlled_curation
+tRNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified product note comment controlled_curation private locus_tag
+rRNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified product note comment controlled_curation private locus_tag
+tmRNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified product note comment controlled_curation private locus_tag
+mRNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified product note comment controlled_curation private locus_tag
+ncRNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified product note comment controlled_curation private locus_tag
+snRNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified product note comment controlled_curation private locus_tag
+scRNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified product note comment controlled_curation private locus_tag
+snoRNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term Start_range End_range feature_id isObsolete timelastmodified product note comment controlled_curation private locus_tag
+polypeptide_domain ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified
+polypeptide_motif ID Name Alias Parent Note Target Gap Derives_from feature_id isObsolete timelastmodified
+signal_peptide ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified
+match ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified
+nucleotide_match ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified gff_source gff_seqname score
+translated_nucleotide_match ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified
+protein_match ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified cluster gff_source gff_seqname score
+sequence_difference ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified
+direct_repeat ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified
+inverted_repeat ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified
+dispersed_repeat ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified
+repeat_family ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified gff_source gff_seqname score
+transposable_element ID Name Alias Parent Target Gap Derives_from Note Dbxref Ontology_term feature_id isObsolete timelastmodified
+binding_site ID Name Alias Parent Target Gap Derives_from Note Dbxref Ontology_term feature_id isObsolete timelastmodified
+
+#minus_10_signal ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment operon partial standard_name
+#minus_35_signal ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment operon partial standard_name
+#three_prime_clip ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment partial standard_name
+#five_prime_clip ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment partial standard_name
+#CAAT_signal ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment partial
+#D_loop ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment partial
+#GC_signal ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment partial
+three_prime_LTR ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment partial standard_name
+five_prime_LTR ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment partial standard_name
+N_region ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation codon codon_start evidence gene label locus_tag map note comment partial product pseudo standard_name
+#STS ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment partial standard_name
+#TATA_box ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment partial
+#attenuator ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment operon partial phenotype
+#enhancer ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment partial standard_name
+gap ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified @estimated_length score
+#iDNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment number partial standard_name
+#mature_peptide ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified EC_number allele citation codon codon_start evidence function gene label locus_tag map note comment partial product pseudo standard_name
+#modified_base_site ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified @mod_base allele citation evidence frequency gene label locus_tag map note comment
+operon ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified @operon allele citation evidence function label map note comment phenotype pseudo standard_name
+polyA_signal_sequence ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment partial
+polyA_site ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment
+#precursor_RNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment operon partial product standard_name
+primary_transcript ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment operon partial standard_name
+#primer_binding_site ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified PCR_conditions allele citation evidence gene label locus_tag map note comment partial standard_name
+promoter ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment operon partial phenotype pseudo standard_name
+#protein_binding_site ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified @bound_moiety allele citation evidence function gene label locus_tag map note comment partial standard_name
+origin_of_replication ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation direction evidence gene label locus_tag map note comment partial standard_name
+repeat_region ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene insertion_seq label locus_tag map note comment partial rpt_family rpt_type rpt_unit standard_name DNA_transposon
+repeat_unit ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment partial rpt_family rpt_type rpt_unit
+#satellite_DNA ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment partial rpt_family rpt_type rpt_unit standard_name
+#stem_loop ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence function gene label locus_tag map note comment operon partial standard_name
+#terminator ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation evidence gene label locus_tag map note comment operon partial standard_name
+#transit_peptide ID Name Alias Parent Note Target Gap Derives_from Dbxref Ontology_term feature_id isObsolete timelastmodified allele citation codon codon_start evidence function gene label locus_tag map note comment partial product pseudo standard_name
+
diff --git a/etc/gene_builder b/etc/gene_builder
new file mode 100755
index 0000000..f325809
--- /dev/null
+++ b/etc/gene_builder
@@ -0,0 +1,103 @@
+#!/bin/sh -
+
+# This script will start Artemis on a UNIX system. This script should
+# be left in the same directory as the rest of the Artemis
+# distribution, so that the java class files can be found. If
+# necessary a symbolic link can be made to this script from
+# /usr/local/bin/ or elsewhere.
+
+# $Header: //tmp/pathsoft/artemis/etc/gene_builder,v 1.1 2009-06-30 15:47:36 tjc Exp $
+
+# resolve links - $0 may be a link
+PRG=$0
+progname=`basename $0`
+
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '.*/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname $PRG`/$link"
+ fi
+done
+
+ARTEMIS_HOME=`dirname "$PRG"`/..
+
+CLASSPATH=$ARTEMIS_HOME:$ARTEMIS_HOME/lib/biojava.jar:$ARTEMIS_HOME/lib/jemAlign.jar:$ARTEMIS_HOME/lib/jakarta-regexp-1.2.jar:$ARTEMIS_HOME/lib/macos.jar:$ARTEMIS_HOME/lib/postgresql-8.1-407.jdbc2ee.jar:$ARTEMIS_HOME/lib/chado-14-interface.jar:$CLASSPATH
+
+# j2ssh jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/j2ssh/commons-logging.jar:$ARTEMIS_HOME/lib/j2ssh/j2ssh-core.jar:$ARTEMIS_HOME/lib/j2ssh/
+
+# iBatis jars
+CLASSPATH=$CLASSPATH:$ARTEMIS_HOME/lib/ibatis/ibatis-2.3.4.726.jar:$ARTEMIS_HOME/lib/ibatis/:$ARTEMIS_HOME/lib/ibatis/log4j-1.2.14.jar:$ARTEMIS_HOME/lib/ibatis/cglib-nodep-2.2.jar:$ARTEMIS_HOME/lib/retrotranslator-runtime-1.1.0.jar:$ARTEMIS_HOME/etc
+export CLASSPATH
+
+
+ARTEMIS_PROPERTIES="-Dartemis.environment=UNIX"
+
+# Allow URLs to work from behind firewalls
+if [ "$http_proxy" = "" ]
+then
+ http_proxy=$HTTP_PROXY
+fi
+
+if [ "$http_proxy" = "" ]
+then
+ http_proxy=$HTTP_proxy
+fi
+
+if [ "$http_proxy" != "" ]
+then
+ ARTEMIS_PROPERTIES="$ARTEMIS_PROPERTIES -DproxySet=true "`echo $http_proxy | sed 's/http:\/\/\(.*\):\(.*\)/ -Dhttp.proxyHost=\1 -Dhttp.proxyPort=\2/'`
+fi
+
+
+# "-mx500m" sets the maximum amount of memory that Artemis can use. This may
+# need to be increased when dealing with large files
+MEM="-mx500m -ms20m"
+
+if [ "$JVM_FLAGS" = "" ]
+then
+ FLAGS="$MEM -noverify"
+else
+ FLAGS="$MEM -noverify $JVM_FLAGS"
+fi
+
+
+if [ $# = 0 ]
+then
+ :
+else
+ while test $# != 0
+ do
+ case $1 in
+ -options) FLAGS="$FLAGS -Dextra_options=$2"; shift ;;
+ -D*) FLAGS="$FLAGS $1" ;;
+ *) break ;;
+ esac
+ shift
+ done
+fi
+
+FLAGS=$FAST_FLAG$FLAGS
+
+if [ "$JAVA_VM" = "" ]
+then
+ if [ "$DEBUG" = yes ]
+ then
+ JAVA=java_g
+ else
+ JAVA=java
+ fi
+else
+ JAVA=$JAVA_VM
+fi
+
+PLATTMP=`uname`
+if [ "$PLATTMP" = "Darwin" ]
+then
+ FLAGS="$FLAGS -Dapple.laf.useScreenMenuBar=true -Dcom.apple.mrj.application.apple.menu.about.name=Artemis"
+fi
+
+$JAVA -Djdbc.drivers=org.postgresql.Driver $FLAGS $ARTEMIS_PROPERTIES uk.ac.sanger.artemis.components.genebuilder.GeneEdit $*
diff --git a/etc/go_associations.pl b/etc/go_associations.pl
new file mode 100755
index 0000000..cb36488
--- /dev/null
+++ b/etc/go_associations.pl
@@ -0,0 +1,114 @@
+#!/usr/local/bin/perl5.6.1
+
+use strict;
+
+BEGIN
+{
+
+# my $configFile = "/nfs/pathdb/dev/go-cgi/config.pl";
+ my $configFile = "/nfs/pathdb/amigo/conf/config.pl";
+
+ if (-f $configFile)
+ {
+ require $configFile;
+ }
+
+}
+
+use Getopt::Long;
+
+my $associations;
+my $definition;
+
+&GetOptions("assoc" => \$associations,
+ "def" => \$definition);
+
+use GO::AppHandle;
+
+if(@ARGV == 0)
+{
+ print "USAGE: go -def -assoc acc\n"; exit 0;
+}
+
+my $acc_num = $ARGV[0];
+
+
+my $dbname = $ENV{GO_DBNAME};
+my $dbport = $ENV{GO_DBPORT};
+my $dbhost = $ENV{GO_DBHOST};
+my $dbuser = $ENV{GO_DBUSER};
+my $dbauth = $ENV{GO_DBAUTH};
+
+if (not $dbname =~ /^go/) {
+ print STDERR "GO database name uncorrect, must start 'go...' !!";
+ exit 1;
+}
+
+my $apph = GO::AppHandle->connect (
+ -dbname => $dbname,
+ -dbport => $dbport,
+ -dbhost => $dbhost,
+ -dbuser => $dbuser,
+ -dbauth => $dbauth,
+ )
+ or die "can't connect to GO database, $dbname!!!\n";
+
+
+#my @accs = qw(O00221);
+my @accs;
+push(@accs, $acc_num);
+
+my @pqlist = map { {acc=>$_} } @accs;
+my $term_l = $apph->get_terms({products=>[@pqlist]});
+
+foreach my $term (@$term_l)
+{
+ my $type = $term->term_type;
+ if($type =~ m/_component/i)
+ {
+ printf "/GO_component=\"";
+ }
+ elsif($type =~ m/_function/i)
+ {
+ printf "/GO_function=\"";
+ }
+ elsif($type =~ m/_process/i)
+ {
+ printf "/GO_process=\"";
+ }
+
+ printf "%s (%s);", $term->acc, $term->name;
+ if($associations)
+ {
+ foreach my $assoc (@{$term->selected_association_list || []})
+ {
+ my $gp = $assoc->gene_product;
+ my $ev_l = $assoc->evidence_list || [];
+ printf " %s; %s:%s", $assoc->evidence_as_str, $gp->speciesdb, $gp->acc;
+
+ foreach my $syn (@{$gp->synonym_list || []})
+ {
+ print " ($syn)";
+ }
+ printf ";";
+
+ printf " db_xref=";
+ foreach my $ref (@{$term->dbxref_list || []})
+ {
+ printf "%s", $ref->as_str;
+ }
+ printf ";";
+ }
+ }
+
+ if($definition)
+ {
+ printf " %s;", $term->definition;
+ }
+
+# printf "Synonyms:%s\n", join(", ", @{$term->synonym_list});
+
+ print "\n";
+
+}
+
diff --git a/etc/key_mapping b/etc/key_mapping
new file mode 100644
index 0000000..31afa98
--- /dev/null
+++ b/etc/key_mapping
@@ -0,0 +1,26 @@
+# CHADO (SO) database key mappings (used for writing out to file)
+#
+# CHADO map to qualifier (optional)
+pseudogenic_transcript mRNA pseudogene=unknown
+pseudogenic_exon CDS pseudogene=unknown
+pseudogene gene pseudogene=unknown
+polypeptide_motif CDS_motif
+five_prime_UTR 5'UTR
+three_prime_UTR 3'UTR
+polypeptide_domain CDS_domain
+region misc_feature
+remark misc_feature
+sequence_difference misc_feature
+SECIS_element misc_feature note=SECIS_element
+contig source
+PCR_product misc_feature note=PCR_product
+dinucleotide_repeat_microsatellite_feature misc_feature note=dinucleotide_repeat_microsatellite_feature
+EST_match misc_feature note=EST_match
+match_part misc_feature note=match_part
+direct_repeat repeat_region rpt_type=direct
+polypeptide_domain misc_feature
+centromere misc_feature note=centromere
+modified_amino_acid_feature misc_feature note=modified_amino_acid_feature
+snRNA ncRNA ncRNA_class="snRNA"
+snoRNA ncRNA ncRNA_class="snoRNA"
+nucleotide_match misc_feature note=nucleotide_match
diff --git a/etc/log4j.properties b/etc/log4j.properties
new file mode 100644
index 0000000..a5bcd2c
--- /dev/null
+++ b/etc/log4j.properties
@@ -0,0 +1,86 @@
+!-----------------------------------------------------------------------------!
+! Configure categories (loggers) !
+!-----------------------------------------------------------------------------!
+
+#log4j.rootLogger=debug, R
+log4j.rootLogger=R
+
+
+log4j.logger.com.ibatis=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.Splash=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.EditMenu=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.EntryEdit=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.ProjectProperty=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.Selector=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.TransferAnnotationTool=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.ShortCut=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.UserDefinedQualifier=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.j2ssh.SshPSUClient=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.ExternalProgram=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.chado.ChadoTransactionManager=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.chado.Similarity=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.chado.IBatisDAO=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.chado.GmodDAO=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.chado.SqlMapClientWrapper=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.genebuilder.ortholog.AbstractMatchTable=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.util.DatabaseDocument=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.util.DatabaseLocationParser=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.editor.FastaTextPane=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.editor.DataCollectionPane=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.j2ssh.SshPSUClient=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.alignment.BamView=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.variant.VCFview=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.genebuilder.cv.CVPanel=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.genebuilder.cv.GoBox=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.chado.ArtemisUtils=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.database.DatabaseJPanel=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.alignment.LookSeqPanel=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.components.variant=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.io.ReadAndWriteEntry=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.io.GFFStreamFeature=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.util.FTPSeekableStream=DEBUG, R
+log4j.logger.uk.ac.sanger.artemis.plot.UserDataAlgorithm=DEBUG, R
+
+#log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG, stdout, R
+#log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG, stdout, R
+#log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG, stdout, R
+log4j.logger.java.sql.Connection=DEBUG, R
+#log4j.logger.java.sql.Statement=DEBUG, R
+log4j.logger.java.sql.PreparedStatement=DEBUG, R
+log4j.logger.java.sql.ResultSet=INFO, R
+
+# j2ssh
+log4j.logger.com.sshtools.j2ssh=WARN, R
+# Pattern to output the caller's file name and line number.
+
+!-----------------------------------------------------------------------------!
+! Configure appenders (log destinations/targets) and their options !
+!-----------------------------------------------------------------------------!
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
+#log4j.appender.stdout.layout.ConversionPattern=%m%n
+log4j.appender.Default=uk.ac.sanger.artemis.components.LogViewer
+log4j.appender.R=uk.ac.sanger.artemis.components.LogViewer
+log4j.appender.R.MaxLogLines=2000
+log4j.appender.R.layout=org.apache.log4j.PatternLayout
+#log4j.appender.R.layout.ConversionPattern=%5p [%t] %c - %m%n
+log4j.appender.R.layout.ConversionPattern=%d{dd MMM HH:mm:ss} - %m%n
+
+!-------------------------RollingFileAppender OPTIONS-------------------------!
+log4j.appender.LOGFILE=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.LOGFILE.File=artemis-chado.log
+log4j.appender.LOGFILE.Append=true
+log4j.appender.LOGFILE.DatePattern='.'yyyy-MM-dd
+#log4j.appender.LOGFILE.Threshold=INFO
+log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
+log4j.appender.LOGFILE.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss,SSS} %-4r [%t] %-5p %c %x - %m%n
+
+
+#log4j.appender.srv=org.apache.log4j.net.SocketAppender
+#log4j.appender.srv.layout=org.apache.log4j.PatternLayout
+#log4j.appender.srv.RemoteHost=localhost
+#log4j.appender.srv.Port=10105
+#log4j.appender.srv.LocationInfo=false
+#log4j.appender.srv.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss,SSS} %X{username} %X{host} %-4r [%t] %-5p %c %x - %m%n
diff --git a/etc/log4j.properties.txt b/etc/log4j.properties.txt
new file mode 100644
index 0000000..2c4f22e
--- /dev/null
+++ b/etc/log4j.properties.txt
@@ -0,0 +1,31 @@
+#log4j.rootLogger=debug, R
+log4j.rootLogger=R
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+
+# SqlMap logging configuration...
+log4j.logger.com.ibatis=DEBUG, stdout, R
+log4j.logger.uk.ac.sanger.artemis.components.Splash=DEBUG, stdout, R
+log4j.logger.uk.ac.sanger.artemis.chado.ChadoTransactionManager=DEBUG, stdout, R
+log4j.logger.uk.ac.sanger.artemis.chado.Similarity=DEBUG, stdout, R
+log4j.logger.uk.ac.sanger.artemis.util.DatabaseDocument=DEBUG, stdout, R
+
+#log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG, stdout, R
+#log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG, stdout, R
+#log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG, stdout, R
+#log4j.logger.java.sql.Connection=DEBUG, stdout, R
+log4j.logger.java.sql.Statement=DEBUG, stdout, R
+log4j.logger.java.sql.PreparedStatement=DEBUG, stdout, R
+#log4j.logger.java.sql.ResultSet=DEBUG, stdout, R
+
+# Pattern to output the caller's file name and line number.
+log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
+
+
+log4j.appender.R=org.apache.log4j.RollingFileAppender
+log4j.appender.R.File=artemis.log
+log4j.appender.R.MaxFileSize=100KB
+log4j.appender.R.layout=org.apache.log4j.PatternLayout
+log4j.appender.R.layout.ConversionPattern=%5p [%t] %c - %m%n
+
diff --git a/etc/options b/etc/options
new file mode 100644
index 0000000..1987d3f
--- /dev/null
+++ b/etc/options
@@ -0,0 +1,898 @@
+# The standard options file for Artemis
+
+# (Note that comment lines start with a hash (#) symbol)
+
+# $Header: //tmp/pathsoft/artemis/etc/options,v 1.73 2009-08-06 16:15:19 tjc Exp $
+
+# This file should contain option settings that look like this:
+#
+# option_name = option_value
+#
+# If the value of an options is too long to fit on one line it can be split
+# over several lines by ending each line with a backslash like this:
+#
+# option_name = option_value another_option_value \
+# a_third_option_value a_forth_option_value
+
+
+# This option will set the font size for all the Artemis windows.
+
+font_size = 12
+
+
+# Set the name of the font to use in Artemis. These possibilites are
+# available on all platforms:
+# Dialog, DialogInput, Monospaced, Serif, SansSerif, Symbol.
+
+font_name = Monospaced
+
+# This option is used to set the default minimum size (in amino acids)
+# of a "large" open reading frame, which controls which ORFS are
+# marked by the "Mark Open Reading Frames" menu item.
+
+minimum_orf_size = 100
+
+# Set the default value for the direct edit option (see
+# http://www.sanger.ac.uk/Software/Artemis/stable/manual/launch-window.html#LAUNCH-WINDOW-OPTIONS-DIRECT-EDIT
+# for more)
+direct_edit = yes
+
+# This setting controls which set of codons will be used for start codons.
+# This can be changed from the options menu.
+# Give the translation table number.
+#
+genetic_code_default = 1
+
+# This option gives the bases of the possible start codons
+eukaryotic_start_codons = atg
+prokaryotic_start_codons = atg gtg ttg
+
+
+#
+# Genetic Codes :
+# http://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi?mode=c
+#
+
+genetic_codes = \
+ Standard \
+ Vertebrate_Mitochondrial \
+ Yeast_Mitochondrial \
+ Mold,_Protozoan,_Coelenterate_Mitochondrial_and_Mycoplasma/Spiroplasma \
+ Invertebrate_Mitochondrial \
+ Ciliate_Dasycladacean_and_Hexamita \
+ - \
+ - \
+ Echinoderm_Flatworm_Mitochondrial \
+ Euplotid \
+ Bacterial_and_Plant_Plastid \
+ Alternative_Yeast_Nuclear \
+ Ascidian_Mitochondrial \
+ Alternative_Flatworm_Mitochondrial \
+ Blepharisma_Nuclear \
+ Chlorophycean_Mitochondrial \
+ - \
+ - \
+ - \
+ - \
+ Trematode_Mitochondrial \
+ Scenedesmus_Obliquus_Mitochondrial \
+ Thraustochytrium_Mitochondrial \
+ Pterobranchia_mitochondrial
+
+# The translation_table option is used to lookup codon translations. The
+# table must have exactly 64 entries, and there is one entry for each codon.
+# The entries should appear in this order:
+# TTT, TTC, TTA, TTG,
+# TCT, TCC, ...,
+# ...
+
+# 1. standard code (default)
+translation_table_1 = \
+ F F L L \
+ S S S S \
+ Y Y # + \
+ C C * W \
+ \
+ L L L L \
+ P P P P \
+ H H Q Q \
+ R R R R \
+ \
+ I I I M \
+ T T T T \
+ N N K K \
+ S S R R \
+ \
+ V V V V \
+ A A A A \
+ D D E E \
+ G G G G
+
+start_codons_1 = atg
+
+#
+# For the following Genetic Code tables the differences
+# from the Standard Code are given.
+#
+#
+# 2. Vertebrate Mitochondrial Code
+#
+translation_table_2 = \
+ aga* agg* atam tgaw
+
+start_codons_2 = atg
+#start_codons_2_bos = ata
+#start_codons_2_homo = ata att
+#start_codons_2_mus = ata att atc
+#start_codons_2_coturnix_gallus = gtg
+
+# 3. Yeast Mitochondrial Code
+translation_table_3 = \
+ atam cttt ctct ctat ctgt tgaw
+
+start_codons_3 = ata atg
+
+# 4. Mold, Protozoan, and Coelenterate Mitochondrial Code and the
+# Mycoplasma/Spiroplasma Code
+translation_table_4 = \
+ tgaw
+
+start_codons_4 = atg
+
+#start_codons_4_Trypanosoma = tta ttg ctg
+#start_codons_4_Leishmania = att ata
+#start_codons_4_Tertrahymena = att ata atg
+#start_codons_4_Paramecium = att ata atg atc gtg gta
+
+# 5. Invertebrate Mitochondrial Code
+translation_table_5 = \
+ agas aggs atam tgaw
+
+start_codons_5 = atg ata att
+#start_codons_5_apis = atg ata atc att
+#start_codons_5_polyplacophora = atg ata gtg
+
+# 6. Ciliate, Dasycladacean and Hexamita Nuclear Code
+translation_table_6 = \
+ taaq tagq
+
+start_codons_6 = atg
+
+# 9. Echinoderm and Flatworm Mitochondrial Code
+translation_table_9 = \
+ aaan agas aggs tgaw
+
+start_codons_9 = atg gtg
+
+# 10. Euplotid Nuclear Code
+translation_table_10 = \
+ tgac
+
+start_codons_10 = atg
+
+# 11. Bacterial and Plant Plastid
+translation_table_11 =
+
+start_codons_11 = atg gtg ttg
+
+# 12. Alternative Yeast Nuclear Code
+translation_table_12 = \
+ ctgs
+
+start_codons_12 = ctg atg
+
+# 13. Ascidian Mitochondrial Code
+translation_table_13 = \
+ agag aggg atam tgaw
+
+start_codons_13 = atg
+
+# 14. Alternative Flatworm Mitochondrial Code
+translation_table_14 = \
+ aaan agas aggs taay tgaw
+
+start_codons_14 = atg
+
+# 15. Blepharisma
+translation_table_15 = \
+ tagq
+
+start_codons_15 = atg
+
+# 16. Chlorophycean Mitochondrial
+translation_table_16 = \
+ tagl
+
+start_codons_16 = atg
+
+# 21. Trematode Mitochondrial
+translation_table_21 = \
+ tgaw atam aaan agas aggs
+
+start_codons_21 = atg gtg
+
+# 22. Scenedesmus obliquus mitochondrial
+translation_table_22 = \
+ tca* tagl
+
+start_codons_22 = atg
+
+# 23. Thraustochytrium Mitochondrial
+translation_table_23 = \
+ tta*
+
+start_codons_23 = att atg gtg
+
+# 24. Pterobranchia mitochondrial
+translation_table_24 = \
+ agas aggk tgaw
+
+start_codons_24 = ttg ctg atg gtg
+
+# the sequence of colour numbers must not have any gaps - if for example
+# colour_5 is missing then all colours with higher numbers will be ignored
+
+# the three numbers for each colour correspond to red, green and blue
+# respectively. each number is an intensity from 0 to 255
+
+# white
+colour_0 = 255 255 255
+# dark grey
+colour_1 = 100 100 100
+# red
+colour_2 = 255 0 0
+# green
+colour_3 = 0 255 0
+# blue
+colour_4 = 0 0 255
+# cyan
+colour_5 = 0 255 255
+# magenta
+colour_6 = 255 0 255
+# yellow
+colour_7 = 245 245 0
+# pale green
+colour_8 = 152 251 152
+# light sky blue
+colour_9 = 135 206 250
+# orange
+colour_10 = 255 165 0
+# brown
+colour_11 = 200 150 100
+# pink
+colour_12 = 255 200 200
+# light grey
+colour_13 = 170 170 170
+# black
+colour_14 = 0 0 0
+# reds:
+colour_15 = 255 63 63
+colour_16 = 255 127 127
+colour_17 = 255 191 191
+
+# GeneDB colours
+#
+colour_101 = 102 51 153
+colour_102 = 153 102 204
+colour_103 = 255 248 220
+
+#
+#
+
+colour_of_CDS = 5
+colour_of_cds? = 7
+colour_of_BLASTCDS = 2
+colour_of_BLASTN_HIT = 6
+colour_of_CRUNCH_D = 2
+colour_of_CRUNCH_X = 15
+colour_of_source = 0
+colour_of_prim_tran = 0
+colour_of_stem_loop = 2
+colour_of_misc_feature = 3
+colour_of_misc_RNA = 12
+colour_of_delta = 3
+colour_of_LTR = 4
+colour_of_repeat_region = 9
+colour_of_repeat_unit = 9
+colour_of_terminator = 3
+colour_of_promoter = 3
+colour_of_intron = 1
+colour_of_exon = 7
+colour_of_mRNA = 1
+colour_of_tRNA = 8
+colour_of_TATA = 3
+colour_of_bldA = 2
+colour_of_GFF = 11
+
+colour_of_start_codon = 6
+
+# suffixes used on files that contain features - used in file requesters
+feature_file_suffixes = tab embl gbk genbank tab_embl gff feature feat \
+ art artemis
+
+# suffixes used on files that contain sequence - used in file requesters
+sequence_file_suffixes = embl gbk genbank gff tab_embl seq dna \
+ art artemis fa fasta fas gb fna ffn
+
+# this is the URL that contains the IOR of the EMBL server
+#embl_ior_url = http://corba.ebi.ac.uk/EMBL/IOR/Embl.IOR
+
+# this is the URL that contains the IOR of the EnsEMBL server
+# ensembl_ior_url = file:///nfs/disk12/kmr/powmap/db.ior
+
+
+# the default height for the base plot window
+base_plot_height = 100
+
+
+# the default height for the feature plot window
+feature_plot_height = 160
+
+
+# if this option is no then the feature labels in the overview will be off at
+# startup (the default is yes)
+# overview_feature_labels = no
+
+
+# if this option is yes then the overview will start in one line per entry
+# mode (the default is no)
+# overview_one_line_per_entry = yes
+
+
+# if this option is yes then the overview will start showing the feature
+# stack view (the default is no)
+# overview_feature_stack_view = yes
+
+
+# if this option is "yes" then the feature list will be displayed on startup
+# (this is the default)
+show_list = yes
+
+
+# if this option is "yes" then the base view will be displayed on startup
+# (this is the default)
+show_base_view = yes
+
+
+# if this option is "yes" then the entry buttons will be displayed on
+# startup
+show_entry_buttons = yes
+
+
+# if this option is "yes" then artemis will offer to show the results of a
+# search when it finishes
+show_results = no
+
+
+# if this option is "yes" the "all features on frame lines" option will
+# default to true on start up
+features_on_frame_lines = no
+
+# if these options are "yes" the forward and reverse feature lines
+# are displayed
+show_forward_lines = yes
+show_reverse_lines = yes
+
+# features to show on the frame line
+#frame_line_features = \
+# CDS \
+# polypeptide
+
+# if this option is "yes" the "feature labels" option will
+# default to true on start up
+feature_labels = yes
+
+
+# if this option is "yes" the "one line per entry" option will default to
+# true on start up
+one_line_per_entry = no
+
+
+# if this option is "yes" Sanger specific menu items and functions will be
+# visible in the display
+sanger_options = no
+
+
+# the full path to the editor used for editing the qualifiers
+#external_editor = emacs
+
+
+# if set to yes, borders will be drawn around each feature and each exon. if
+# set to no borders will only be drawn around the selected features.
+draw_feature_borders = yes
+
+
+# if set to yes, a direction arrow will be drawn around at the end of each
+# feature. if set to no, no arrows will be drawn.
+draw_feature_arrows = yes
+
+# if yes then shortcut changed in Preferences are saved between sessions
+shortcut_cache = yes
+
+# the number of levels of undo to save or 0 if undo is disabled. more undo
+# levels will require more memory.
+undo_levels = 20
+
+
+# this list is added to the keys from the feature_keys file
+extra_keys = \
+ BLASTN_HIT CDS_BEFORE CDS_AFTER CDS_before CDS_after \
+ CDS_motif BLASTCDS polymorphism GFF WUBLASTN_HIT \
+ WUBLASTX_HIT BLASTX_HIT TBLASTX_HIT BLASTN_HIT \
+ CRUNCH_D CRUNCH_X fasta_record allele mutation splicesite \
+ TMM signalP
+
+# this list is added to the keys from the feature_keys_gff file
+extra_keys_gff = CDS spliced_leader_RNA sequence_variant fasta_record
+
+# Names of qualifiers to search when attempting to find the primary or display
+# name of a gene. These qualifiers names are searched in order when looking
+# for gene names.
+display_name_qualifiers = primary_name Name synonym systematic_id \
+ temporary_systematic_id gene locus_tag label ID
+
+# Names of qualifiers to search when attempting to find the systematic name of
+# a gene
+systematic_name_qualifiers = systematic_id temporary_systematic_id \
+ locus_tag gene label ID Name
+
+
+# this list is added to the qualifiers from the qualifier_types file
+extra_qualifiers = \
+ CHROMO_LINK text \
+ C_processing "text" \
+ C_processing_BigPi "text" \
+ C_processing_DGPI "text" \
+ COM_NAME "text" \
+ FEAT_NAME text \
+ GO_component "text" \
+ GO_function "text" \
+ GO_process "text" \
+ GO_slim "text" \
+ GO "text" \
+ LOCUS "text" \
+ PUB_LOCUS text \
+ PUB_COMMENT "text" \
+ REPEAT_TYPE "text" \
+ SNP "text" \
+ algorithm "text" \
+ anchor "text" \
+ annotation_source "text" \
+ assembly_id "text" \
+ bb_orthologue "text" \
+ bound_moiety "text" \
+ bpp_orthologue "text" \
+ bp_orthologue "text" \
+ bicsw_file "text" \
+ blast_score text \
+ blast_file "text" \
+ blastn_file "text" \
+ blastp_file "text" \
+ blastp+go_file "text" \
+ blastp_match "text" \
+ blastx_file "text" \
+ cds_id "text" \
+ chloroplast "text" \
+ chromoplast "text" \
+ class "text" \
+ cleavage "text" \
+ cluster "text" \
+ color text \
+ colour text \
+ comment_Cterm "text" \
+ comment_Nterm "text" \
+ confidence_level "text" \
+ controlled_curation "text" \
+ coord "text" \
+ contig_id "text" \
+ created "text" \
+ curation "text" \
+ curated_ortholog "text" \
+ cyanelle "text" \
+ domain "text" \
+ end_phase text \
+ exon_id "text" \
+ fasta_file "text" \
+ fasta_match "text" \
+ fastx_file "text" \
+ filename "text" \
+ function "text" \
+ gene "text" \
+ gene_id "text" \
+ gff_feature text \
+ gff_group text \
+ gff_seqname text \
+ gff_source text \
+ go_from_interpro "text" \
+ hp_match "text" \
+ hth_file "text" \
+ id "text" \
+ interaction "text" \
+ interpro "text" \
+ job "text" \
+ label text \
+ literature "text" \
+ manual none \
+ mitochondrion "text" \
+ modified "text" \
+ mutation "text" \
+ note "text" \
+ obsolete_name "text" \
+ obsolete_product "text" \
+ origid "text" \
+ ortholog "text" \
+ other_transcript "text" \
+ paralog "text" \
+ pepstats_file "text" \
+ percent_id text \
+ pfam_match "text" \
+ previous_other_transcript "text" \
+ previous_shared_id "text" \
+ previous_systematic_id "text" \
+ primary_name "text" \
+ prosite_match "text" \
+ pseudo none \
+ psu_db_xref "text" \
+ psu_domain "text" \
+ reserved_name "text" \
+ query_id text \
+ score text \
+ sequence_source "text" \
+ sequence_status "text" \
+ shared_id "text" \
+ sigcleave_file "text" \
+ signal "text" \
+ similarity "text" \
+ smart_file "text" \
+ sptr_display "text" \
+ start_phase text \
+ subject_end text \
+ subject_id text \
+ subject_start text \
+ synonym "text" \
+ synteny "text" \
+ systematic_id "text" \
+ taxon_id "text" \
+ tblastn_file "text" \
+ tblastx_file "text" \
+ tb_orthologue "text" \
+ temporary_systematic_id "text" \
+ tmhelix "text" \
+ transferred_gene "text" \
+ transferred_locus_tag "text" \
+ transferred_note "text" \
+ transferred_primary_name "text" \
+ transferred_product "text" \
+ transferred_synonym "text" \
+ transferred_systematic_id "text" \
+ type "text"
+
+# this list is added to the qualifiers from the qualifier_types_gff file
+extra_qualifiers_gff = \
+ blast_score text \
+ blast_file "text" \
+ blastn_file "text" \
+ blastp_file "text" \
+ blastp+go_file "text" \
+ blastx_file "text" \
+ cluster "text" \
+ colour text \
+ color text \
+ controlled_curation "text" \
+ fasta_file "text" \
+ GO "text" \
+ history "text" \
+ literature "text" \
+ note "text" \
+ orthologous_to "text" \
+ paralogous_to "text" \
+ polypeptide_domain "text" \
+ previous_systematic_id "text" \
+ primary_name "text" \
+ product_synonym "text" \
+ similarity "text" \
+ stop_codon_redefined_as_selenocysteine none \
+ synonym "text" \
+ systematic_id "text" \
+ temporary_systematic_id "text"
+
+# this is a list of extra qualifiers that are legal but are not displayed in
+# popup menus (such as the one in the feature editor window). this hack is
+# used by diana.components.QualifierChoice to limit the number of qualifers
+# that are displayed in the popup menu. on some VMs if there are too many in
+# the popup the bottom ones aren't visible
+invisible_qualifiers = \
+ CHROMO_LINK \
+ C_processing \
+ C_processing_BigPi \
+ C_processing_DGPI \
+ COM_NAME \
+ FEAT_NAME \
+ LOCUS \
+ PUB_LOCUS \
+ PUB_COMMENT \
+ REPEAT_TYPE \
+ SNP \
+ bicsw_file \
+ blast_file \
+ blast_score \
+ blastn_file \
+ blastp+go_file \
+ blastp_file \
+ blastx_file \
+ cds_id \
+ chloroplast \
+ chromoplast \
+ codon \
+ comment_Cterm \
+ comment_Nterm \
+ created \
+ cyanelle \
+ end_phase \
+ exception \
+ exon_id \
+ fasta_file \
+ fasta_match \
+ gene_id \
+ go_from_interpro \
+ hp_match \
+ hth_file \
+ interpro \
+ map \
+ mitochondrion \
+ modified \
+ number \
+ obsolete_gene_name \
+ pepstats_file \
+ percent_id \
+ pfam_match \
+ prosite_match \
+ pseudo \
+ psu_domain \
+ reserved_gene_name \
+ query_id \
+ sigcleave_file \
+ score \
+ smart_file \
+ start_phase \
+ tblastn_file \
+ tblastx_file \
+ temporary_systematic_id \
+ transl_table \
+ translation \
+ type \
+ usedin
+
+invisible_qualifiers_gff= \
+ Alias \
+ allele \
+ bound_moiety \
+ cell_line \
+ cell_type \
+ charge \
+ chromosome \
+ class \
+ clone \
+ clone_lib \
+ cluster \
+ controlled_curation \
+ country \
+ cultivar \
+ cytoplasm_location \
+ cytoplasmic_polypeptide_region \
+ Dbxref \
+ DNA_transposon \
+ Derives_from \
+ dev_stage \
+ ecotype \
+ environmental_sample \
+ estimated_length \
+ exception \
+ feature_id \
+ feature_relationship_rank \
+ focus \
+ Gap \
+ gff_feature \
+ gff_group \
+ gff_seqname \
+ gff_source \
+ GO \
+ GPI_anchored \
+ GPI_anchor_cleavage_site \
+ history \
+ ID \
+ insertion_seq \
+ isObsolete \
+ isoelectric \
+ isolation_source \
+ isolate \
+ kinetoplast \
+ lab_host \
+ literature \
+ locus_tag \
+ macronuclear \
+ map \
+ mass \
+ membrane_structure \
+ mod_base \
+ mol_type \
+ Name \
+ non_cytoplasm_location \
+ non_cytoplasmic_polypeptide_region \
+ Note \
+ note \
+ Ontology_term \
+ operon \
+ organell \
+ orthologous_to \
+ paralogous_to \
+ Parent \
+ PCR_conditions \
+ plasmid \
+ PlasmoAP_score \
+ pop_variant \
+ previous_systematic_id \
+ primary_name \
+ product \
+ product_synonym \
+ protein_id \
+ replace \
+ results \
+ segment \
+ sequenced_mol \
+ serotype \
+ serovar \
+ sex \
+ signal_anchor_probability \
+ signal_peptide \
+ signal_peptide_probability \
+ SignalP_prediction \
+ similarity \
+ source \
+ specific_host \
+ specimen_voucher \
+ standard_name \
+ strain \
+ sub_clone \
+ sub_species \
+ sub_strain \
+ synonym \
+ systematic_id \
+ Target \
+ temporary_systematic_id \
+ timelastmodified \
+ tissue_lib \
+ tissue_type \
+ transmembrane \
+ transmembrane_polypeptide_region \
+ transgenic \
+ translation
+
+
+# These pairs consist of a program name and a parameter string.
+# For blast and fasta the parameter string is the name of the database to
+# search.
+#
+# /nfs/pathsoft/databases/GO/new
+# /nfs/pathsoft/databases/protein/go_all
+feature_protein_programs = \
+ fasta %uniprot \
+ fasta %uniprot_archaea \
+ fasta %uniprot_bacteria \
+ fasta %uniprot_eukaryota \
+ fasta %uniprot_viruses \
+ fasta %uniprot_rest \
+ fasta %malaria \
+ fasta %kineto_aa \
+ sigcleave 0 \
+ pepstats - \
+ blastp uniprot \
+ blastp uniprot_archaea \
+ blastp uniprot_bacteria \
+ blastp uniprot_eukaryota \
+ blastp uniprot_viruses \
+ blastp uniprot_rest \
+ blastp /lustre/scratch101/blastdb/Pathogen/Kineto_aa \
+ tblastn %embl_other \
+ hth - \
+ smart - \
+ clustalx PROTEIN \
+ jalview PROTEIN
+
+feature_dna_programs = \
+ tblastx %embl_other \
+ blastn %embl_other \
+ blastx %uniprot \
+ fastx %uniprot \
+ clustalx DNA
+
+application_programs = \
+ jalview
+
+ncbi_dna_search = \
+ blastn http://blast.ncbi.nlm.nih.gov/Blast.cgi?PAGE=Nucleotides&PROGRAM=blastn&MEGABLAST=on&BLAST_PROGRAMS=blastn&PAGE_TYPE=BlastSearch&DATABASE=nr&SHOW_DEFAULTS=on&QUERY= \
+ blastx http://blast.ncbi.nlm.nih.gov/Blast.cgi?PAGE=Translations&PROGRAM=blastx&BLAST_PROGRAMS=blastx&PAGE_TYPE=BlastSearch&SHOW_DEFAULTS=on&QUERY= \
+ tblastx http://blast.ncbi.nlm.nih.gov/Blast.cgi?PAGE=Translations&PROGRAM=tblastx&BLAST_PROGRAMS=tblastx&PAGE_TYPE=BlastSearch&SHOW_DEFAULTS=on&QUERY=
+
+ncbi_protein_search = \
+ blastp http://blast.ncbi.nlm.nih.gov/Blast.cgi?PAGE=Proteins&PROGRAM=blastp&BLAST_PROGRAMS=blastp&PAGE_TYPE=BlastSearch&QUERY= \
+ tblastn http://blast.ncbi.nlm.nih.gov/Blast.cgi?PAGE=Translations&PROGRAM=tblastn&BLAST_PROGRAMS=tblastn&PAGE_TYPE=BlastSearch&SHOW_DEFAULTS=on&QUERY=
+
+mess_fasta_hits = 10
+
+# this is the list of keys that should be displayed by default in the edit
+# window
+common_keys = \
+ allele attenuator CDS conflict exon intron LTR misc_feature misc_RNA mRNA \
+ mutation polyA_signal polyA_site promoter protein_bind RBS repeat_region \
+ repeat_unit rRNA scRNA snRNA source stem_loop STS TATA_signal terminator \
+ tRNA unsure variation -10_signal -35_signal CDS_motif gene \
+ BLASTN_HIT BLASTCDS 3'UTR 5'UTR
+
+# SRS
+srs_url = http://srs.ebi.ac.uk/srsbin/cgi-bin/
+
+# hyperlinked databases in feature editor
+hyperlinks = \
+ SWALL+UniProt+UniProtKB srs_url \
+ EMBL http://www.ebi.ac.uk/Tools/dbfetch/expasyfetch? \
+ PubMed+PMID http://www.ncbi.nlm.nih.gov/sites/entrez?Db=pubmed&Cmd=ShowDetailView&TermToSearch= \
+ InterPro http://www.ebi.ac.uk/interpro/entry/ \
+ OrthoMCLDB http://www.orthomcl.org/cgi-bin/OrthoMclWeb.cgi?rm=groupList&type=ackeyword&in=Accession&q= \
+ PlasmoDB http://plasmodb.org/plasmodb/servlet/sv?page=gene&source_id= \
+ Pfam http://pfam.sanger.ac.uk/family?acc= \
+ SMART http://smart.embl-heidelberg.de/smart/do_annotation.pl?DOMAIN= \
+ Prosite http://www.expasy.org/prosite/ \
+ ProDom http://prodom.prabi.fr/prodom/current/cgi-bin/request.pl?question=DBEN&query= \
+ PIRSF http://pir.georgetown.edu/cgi-bin/ipcSF?id= \
+ TIGR_TIGRFAMS http://cmr.tigr.org/tigr-scripts/CMR/HmmReport.cgi?hmm_acc= \
+ OPI http://chemlims.com/OPI/MServlet.ChemInfo?module=GeneGo&act=findGenes&Gene_Name_= \
+ GO http://amigo.geneontology.org/cgi-bin/amigo/term_details?term=GO: \
+ PANTHER http://www.pantherdb.org/panther/family.do?clsAccession= \
+ Superfamily http://supfam.org/SUPERFAMILY/cgi-bin/scop.cgi?ipid= \
+ PDB http://www.rcsb.org/pdb/explore.do?structureId= \
+ AID http://pubchem.ncbi.nlm.nih.gov/assay/assay.cgi?aid= \
+ GeneID http://www.ncbi.nlm.nih.gov/sites/entrez?db=gene&cmd=Retrieve&dopt=full_report&list_uids= \
+ Rfam http://rfam.sanger.ac.uk/family/ \
+ GI http://www.ncbi.nlm.nih.gov/entrez/sutils/girevhist.cgi?val=
+
+# BamView
+# No. threads used to read from multiple BAM files
+bam_read_thread = 2
+# Max read coverage to display
+bam_max_coverage = 10000
+
+#
+# CHADO DATABASE OPTIONS
+#
+# chado gene model features default types
+chado_exon_model=CDS
+
+# infer CDS and UTR features from gene model
+chado_infer_CDS_UTR=no
+#chado_transcript=transcript
+
+# provide a list of available servers
+#chado_servers = \
+# malaria_workshop localhost:10101/malaria_workshop?pathdb \
+# bigtest localhost:10120/test?pathdb
+
+# define how product qualifiers are stored (as a cv or as a featureprop)
+product_cv=yes
+product_cvname = genedb_products
+# cv containing synonym names
+synonym_cvname = genedb_synonym_type
+# cv containing annotation types for history qualifier
+history_cvname = annotation_change
+
+# automatically add a history qualifier when a new CV term (product,
+# GO, controlled curation) term is added to the annotation
+automatic_history_annotation=yes
+
+# set default delete behaviour to make things obsolete, if
+# this is not provided the default is to permanently delete
+set_obsolete_on_delete=yes
+
+# list of features to record residues for in the database
+# - these are included when inserting or updating their featurelocs
+sequence_update_features = polypeptide mRNA rRNA tRNA snRNA snoRNA
diff --git a/etc/pombe.usage b/etc/pombe.usage
new file mode 100644
index 0000000..bb7176e
--- /dev/null
+++ b/etc/pombe.usage
@@ -0,0 +1,20 @@
+UUU 32.2( 48423) UCU 30.5( 45913) UAU 21.8( 32829) UGU 8.9( 13371)
+UUC 13.0( 19519) UCC 12.1( 18149) UAC 11.8( 17721) UGC 5.6( 8372)
+UUA 26.0( 39138) UCA 17.9( 26850) UAA 1.3( 1944) UGA 0.5( 733)
+UUG 24.0( 36134) UCG 8.0( 12055) UAG 0.5( 705) UGG 10.9( 16364)
+
+CUU 25.3( 38015) CCU 21.9( 32964) CAU 16.3( 24577) CGU 16.3( 24495)
+CUC 7.3( 10922) CCC 8.4( 12619) CAC 6.4( 9653) CGC 6.2( 9316)
+CUA 8.6( 12957) CCA 12.7( 19075) CAA 27.3( 41066) CGA 7.9( 11896)
+CUG 6.3( 9503) CCG 4.6( 6910) CAG 10.9( 16457) CGG 3.0( 4487)
+
+AUU 35.0( 52636) ACU 22.9( 34419) AAU 33.9( 51009) AGU 14.7( 22108)
+AUC 12.6( 19000) ACC 10.9( 16378) AAC 17.9( 26895) AGC 9.2( 13905)
+AUA 13.1( 19726) ACA 13.9( 20898) AAA 39.3( 59079) AGA 11.1( 16742)
+AUG 20.9( 31376) ACG 6.5( 9744) AAG 25.2( 37825) AGG 5.1( 7615)
+
+GUU 29.3( 44015) GCU 30.2( 45397) GAU 38.1( 57240) GGU 22.0( 33101)
+GUC 11.0( 16497) GCC 11.6( 17518) GAC 15.8( 23749) GGC 8.5( 12717)
+GUA 12.3( 18451) GCA 15.7( 23649) GAA 44.3( 66550) GGA 15.7( 23623)
+GUG 8.3( 12422) GCG 5.3( 8011) GAG 21.3( 31979) GGG 4.3( 6497)
+
diff --git a/etc/project.properties b/etc/project.properties
new file mode 100644
index 0000000..df33aa8
--- /dev/null
+++ b/etc/project.properties
@@ -0,0 +1,19 @@
+
+project.Styphi.sequence=ftp://ftp.sanger.ac.uk/pub/pathogens/Salmonella/typhi/St.dna
+project.Styphi.annotation=ftp://ftp.sanger.ac.uk/pub/pathogens/Salmonella/typhi/St.art
+project.Styphi.title=Styphi
+
+#
+project.PF3D7.sequence = ftp://ftp.sanger.ac.uk/pub4/pathogens/Plasmodium/falciparum/3D7/3D7.latest_version/September_2011/Pf3D7_01.embl.gz
+project.PF3D7.bam = \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_0h.bam \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_8h.bam \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_16h.bam \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_24h.bam \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_48h.bam
+project.PF3D7.title = Plasmodium\\ falciparum
+
+#
+project.Pberghei.sequence = Pberghei:berg01
+project.Pberghei.chado = genedb-db.sanger.ac.uk:5432/snapshot?genedb_ro
+project.Pberghei.title = Pberghei
diff --git a/etc/qualifier_mapping b/etc/qualifier_mapping
new file mode 100644
index 0000000..ce55735
--- /dev/null
+++ b/etc/qualifier_mapping
@@ -0,0 +1,37 @@
+# CHADO database qualifier mappings (used for writing out to file)
+#
+# CHADO map
+comment note
+Note note
+Dbxref db_xref
+orthologous_to ortholog
+paralogous_to paralog
+polypeptide_domain note
+PlasmoAP_score note
+membrane_structure note
+non_cytoplasm_location note
+non_cytoplasmic_polypeptide_region note
+transmembrane_polypeptide_region note
+cytoplasmic_polypeptide_region note
+cytoplasm_location note
+transmembrane note
+signal_anchor_probability note
+signal_peptide_probability note
+signal_peptide note
+SignalP_prediction note
+GPI_anchored note
+GPI_anchor_cleavage_site note
+ID locus_tag
+Name primary_name
+product_synonym product
+history note
+#
+# CHADO qualifiers to be ignored
+timelastmodified
+feature_id
+Parent
+Derives_from
+feature_relationship_rank
+isObsolete
+isFminPartial
+private
diff --git a/etc/qualifier_types b/etc/qualifier_types
new file mode 100644
index 0000000..7dfd32a
--- /dev/null
+++ b/etc/qualifier_types
@@ -0,0 +1,120 @@
+# Feature qualifiers for EMBL/GenBank feature tables
+#
+# From: http://www.ebi.ac.uk/~faruque/art/keyqual.txt
+#
+# valid qualifier types are:
+# (::) (text:text,text:text)
+# EC "n.n.n.n"
+# feature dbname::accno:location
+# item single text item
+# list one of the listed values
+# modbase one of the stored modbase values
+# none no value
+# number nn
+# (other) (text)
+# location a valid location range (or feature label)
+# real n.n within specified range
+# ref [nn]
+# text any text, no quotes added, spaces allowed if quoted
+# "text" quoted text, double any contained quotes
+# "opt" optional quoted text, double any contained quotes
+# "list" quoted text, list of accepted values
+#
+# if not specified, the default is text (so anything is accepted)
+#
+# Name once_only Type Value(s) ..
+allele no "text"
+anticodon yes (::) pos aa
+artificial_location yes list "heterogenous population sequenced" "low-quality sequence region"
+bio_material no "text"
+bound_moiety yes "text"
+cell_line no "text"
+cell_type no "text"
+chromosome yes "opt"
+citation no ref
+clone no "text"
+clone_lib no "text"
+codon_start yes number 1 3
+collected_by yes "text"
+collection_date yes "text"
+compare no text
+country no "text"
+cultivar no "text"
+culture_collection no "text"
+db_xref no "text"
+dev_stage no "text"
+direction yes list BOTH LEFT RIGHT
+EC_number no "text"
+ecotype yes "text"
+environmental_sample yes none
+estimated_length yes text
+exception yes list "rearrangement required for product" "RNA editing" "reasons given in citation" "annotated by transcript or proteomic data"
+experiment no "text"
+focus yes none
+frequency yes real 0.0 1.0
+function no "text"
+gap_type yes "list" "between scaffolds" "within scaffold" "telomere" "centromere" "short arm" "heterochromatin" "repeat within scaffold" "repeat between scaffolds"
+gene yes "text"
+gene_synonym no "text"
+germline yes none
+haplogroup yes "text"
+haplotype yes "text"
+host no "text"
+identified_by yes "text"
+inference no "text"
+isolate no "text"
+isolation_source yes "text"
+lab_host no "text"
+lat_lon yes "text"
+linkage_evidence no "list" "paired-ends" "align genus" "align xgenus" "align trnscpt" "within clone" "clone contig" "map" "strobe" "unspecified"
+locus_tag yes "text"
+macronuclear yes none
+map no "text"
+mating_type yes "text"
+mobile_element_type no "text"
+mod_base yes list OTHER ac4c chm5u cm cmnm5s2u cmnm5u d fm gal q gm i i6a m1a m1f m1g m1i m22g m2a m2g m3c m5c m6a m7g mam5s2u mam5u man q mcm5s2u mcm5u mo5u ms2i6a ms2t6a mt6a mv o5u osyw p q s2c s2t s2u s4u t t6a tm um x yw
+mol_type yes "list" "unassigned DNA" "unassigned RNA" "genomic DNA" "genomic RNA" "mRNA" "tRNA" "rRNA" "transcribed RNA" "other RNA" "other DNA" "protein" "viral cRNA"
+ncRNA_class yes list "antisense_RNA" "autocatalytically_spliced_intron" "hammerhead_ribozyme" "RNase_P_RNA" "RNase_MRP_RNA" "telomerase_RNA" "guide_RNA" "rasiRNA" "scRNA" "siRNA" "miRNA" "piRNA" "snoRNA" "snRNA" "SRP_RNA" "vault_RNA" "Y_RNA" "other" "ribozyme"
+note no "text"
+number yes number 1 99999999
+old_locus_tag no "text"
+operon yes "text"
+organelle yes list "mitochondrion" "plastid:chloroplast" "mitochondrion:kinetoplast" "plastid:chromoplast" "plastid:cyanelle" "plastid:leucoplast" "plastid:proplastid" "plastid:apicoplast" "plastid" "nucleomorph" "hydrogenosome" "chromatophore"
+organism yes "text"
+partial yes none
+PCR_conditions no "text"
+PCR_primers no "text"
+phenotype no "text"
+plasmid no "text"
+pop_variant no "text"
+product no "text"
+protein_id yes "text"
+proviral yes none
+pseudogene yes "list" "processed" "unprocessed" "unitary" "allelic" "unknown"
+rearranged yes none
+replace yes "text"
+ribosomal_slippage yes text
+rpt_family yes "text"
+rpt_type no list INVERTED DIRECT TANDEM FLANKING TERMINAL DISPERSED OTHER
+rpt_unit_range no text
+rpt_unit_seq no "text"
+satellite yes "text"
+segment yes "text"
+serotype yes "text"
+serovar yes "text"
+sex yes "text"
+specimen_voucher no "text"
+standard_name no "text"
+strain no "text"
+sub_clone no "text"
+sub_species no "text"
+sub_strain no "text"
+tag_peptide no text
+tissue_lib no "text"
+tissue_type no "text"
+trans_splicing yes text
+transgenic yes text
+transl_except no (::) pos aa
+transl_table yes number 1 11
+translation yes "text"
+variety no "text"
\ No newline at end of file
diff --git a/etc/qualifier_types_gff b/etc/qualifier_types_gff
new file mode 100644
index 0000000..e960dfe
--- /dev/null
+++ b/etc/qualifier_types_gff
@@ -0,0 +1,144 @@
+# Feature qualifiers for EMBL/GenBank feature tables
+#
+# From: http://www.ebi.ac.uk/~faruque/art/keyqual.txt
+#
+# valid qualifier types are:
+# (::) (text:text,text:text)
+# EC "n.n.n.n"
+# feature dbname::accno:location
+# item single text item
+# list one of the listed values
+# modbase one of the stored modbase values
+# none no value
+# number nn
+# (other) (text)
+# location a valid location range (or feature label)
+# real n.n within specified range
+# ref [nn]
+# text any text, no quotes added, spaces allowed if quoted
+# "text" quoted text, double any contained quotes
+# "opt" optional quoted text, double any contained quotes
+# "list" quoted text, list of accepted values
+#
+# if not specified, the default is text (so anything is accepted)
+#
+# Name required Type Value(s) ..
+ID no text
+Name no text
+Alias no text
+Parent no text
+Note no "text"
+Ontology_term no text
+gff_source no text
+gff_seqname no text
+score no text
+Target no text
+Gap no text
+Derives_from no text
+Dbxref no feature
+Start_range no text
+End_range no text
+feature_id no number 1 99999999
+colour no text
+color no text
+mass no text
+isoelectric no text
+charge no text
+source no text
+ortholog no "text"
+partial no "text"
+private no "text"
+curation no "text"
+results no "text"
+allele no "text"
+anticodon yes (::) pos aa
+bound_moiety no "text"
+cell_line no "text"
+cell_type no "text"
+chromosome no "opt"
+citation no ref
+clone no "text"
+clone_lib no "text"
+codon no (::) seq aa
+codon_start yes number 1 3
+comment no "text"
+cons_splice yes (::) 5'site 3'site
+country no "text"
+cultivar no "text"
+cytoplasm_location no "text"
+cytoplasmic_polypeptide_region no "text"
+db_xref no "text"
+dev_stage no "text"
+direction yes list LEFT RIGHT BOTH
+ecotype no "text"
+EC_number no "text"
+environmental_sample no none
+estimated_length no "text"
+evidence yes list EXPERIMENTAL NOT_EXPERIMENTAL
+exception no "text"
+focus no none
+frequency yes real 0.0 1.0
+function yes "text"
+gene no "text"
+germline yes none
+GPI_anchor_cleavage_site no "text"
+haplotype no "text"
+insertion_seq no "text"
+isObsolete no text
+isolation_source no "text"
+isolate no "text"
+kinetoplast no "opt"
+label yes item
+lab_host no "text"
+locus_tag no "text"
+macronuclear no none
+map no "text"
+membrane_structure no "text"
+mod_base no modbase
+mol_type no "text"
+non_cytoplasm_location no "text"
+non_cytoplasmic_polypeptide_region no "text"
+note no "text"
+number yes number 1 99999999
+operon no "text"
+organell no "text"
+organism yes "text"
+partial yes none
+PCR_conditions no "text"
+phenotype no "text"
+plasmid no "text"
+pop_variant no "text"
+product no "text"
+protein_id no "text"
+proviral yes none
+pseudo yes none
+rearranged yes none
+replace no "text"
+rpt_family no "text"
+rpt_type no list TANDEM INVERTED FLANKING TERMINAL DIRECT \
+ DISPERSED OTHER
+rpt_unit no "text"
+segment no "text"
+sequenced_mol no "list" "cDNA" "cDNA to genomic RNA" \
+ "cDNA to mRNA" "cDNA to other RNA" \
+ "cDNA to rRNA" "DNA" "mRNA" \
+ "RNA" "rRNA" "scRNA" "snRNA" "tRNA"
+serotype no "text"
+serovar no "text"
+sex no "text"
+signal_peptide no "text"
+specific_host no "text"
+specimen_voucher no "text"
+standard_name no "text"
+strain no "text"
+sub_clone no "text"
+sub_species no "text"
+sub_strain no "text"
+timelastmodified no text
+tissue_lib no "text"
+tissue_type no "text"
+transgenic no "text"
+translation no "text"
+transmembrane no "text"
+transmembrane_polypeptide_region no "text"
+DNA_transposon no "text"
\ No newline at end of file
diff --git a/etc/readmeDNADraw.html b/etc/readmeDNADraw.html
new file mode 100644
index 0000000..72d8649
--- /dev/null
+++ b/etc/readmeDNADraw.html
@@ -0,0 +1,77 @@
+<html>
+<head>
+ <meta name="keywords" content="java, biology, bioinformatics, EMBOSS, Jemboss, Artemis">
+
+ <title>Jemboss & Artemis - Circular Plot</title>
+</head>
+<body text="#000000" bgcolor="#FFFFFF" link="#0000EE" vlink="#551A8B" alink="#FF0000">
+
+
+<h1>DNA Circular Plot</h1>
+
+
+ This is a collaborative project between <a href="http://sf.net/projects/emboss">Jemboss</a> and
+ <a href="http://www.sanger.ac.uk/Software/Artemis">Artemis</a>. It makes use
+ of the existing circular plot in Jemboss and the Artemis sequence libraries.
+
+<p>
+ It can be used to generate images of circular DNA maps to display regions
+ and features of interest. The images can be inserted
+ into a document or printed out directly. As this uses Artemis it can read in the
+ common file formats EMBL, GenBank and GFF3.
+</p>
+<p>
+ The circular plots are interactive. The sections below describe how the
+ circular plot can be altered and enhanced to arrive at the best view.
+</p>
+
+ <h2>Editing Features</h2>
+ Each feature drawn can be double clicked on to open up a properties window.
+ This allows the label, start and end coordinates, colour and line width to
+ be changed. There is also an option to show or hide the label.
+
+ <h2>Dragging Features in a Track</h2>
+
+ Features can be dragged by selecting a feature and holding the mouse button
+ down then dragging towards or away from the circular plot center. This alters
+ all the features in that track, moving them towards or away from the center.
+
+ <h2>Defining Tracks</h2>
+
+ Tracks of features of different radii can be set up. The features included in
+ a track can be defined using the "Track Manager". Features can then be filtered
+ to define what types of features are displayed on a given track. The feature
+ key (e.g. CDS) and a qualifier can be used along with which strand (forward or
+ reverse) the feature is on. The qualifier value can be a single value or a list of
+ values. For example, if the qualifier "gene" is used with the values "a b c", then those
+ three genes will be displayed on the track. Selecting 'Not' means that it will pick
+ out everyting for a track except that qualifier.
+
+<p>
+ The size of the track can be defined in the track manager. Also the position of
+ the track can be set as a fraction of the radii. This is an alternative to
+ dragging the features in a track to a different position. Features can be either
+ coloured based on the colour qualifier or given all given the same colour within
+ that track.
+</p>
+
+ <h2>Graphs</h2>
+
+ The %GC and GC skew ((G-C)/(G+C)) can be displayed. There is an options menu
+ for eah plot to define the step size and window size for the calculation. The
+ colours above and below the average for the plot can be selected to highlight
+ the different regions. A user defined plot can be read in, this takes the same
+ form as a single user plot in Artemis. This will prompt the user for the name
+ of a data file which should contain one line per base of sequence and
+ one floating point number per line.
+
+ <h2>Ticks</h2>
+
+ Major and minor ticks can be adjusted from the 'Tick Marks...' menu. An interval
+ for each of these can be set. Numbering for the ticks can optionally be turned off.
+
+ <h2>Read in Entry on Separate Track</h2>
+
+ As with Artemis, it is possible to overlay features from a separate file. From the
+ 'File' menu there is an option to 'Read in Entry on Separate Track'. This creates
+ an extra track and adds all the features to the track.
diff --git a/etc/results_to_netscape b/etc/results_to_netscape
new file mode 100755
index 0000000..f313d38
--- /dev/null
+++ b/etc/results_to_netscape
@@ -0,0 +1,272 @@
+#!/bin/sh -
+
+# This script will send a file to netscape with database IDs linked to SRS.
+# To customize this script edit the $DATABASES and $SRS_SERVER variable in the
+# perl code below and the NETSCAPE variable at the top of the script.
+
+# To enable this in Artemis, sanger_options must be set to true in the options
+# file.
+
+# Default browser is mozilla
+# If you are under KDE, you should use Konqueror then
+# If you are under MAC OS X, use Safari
+
+if [ `uname` = Darwin ]; then
+ NETSCAPE=/usr/bin/open
+else
+ for f in /usr/bin/x-www-browser \
+ /usr/bin/X11/real-netscape \
+ /usr/bin/firefox \
+ /usr/bin/iceweasel \
+ /usr/bin/mozilla; do
+ if [ -f $f ]; then
+ NETSCAPE=$f
+ break
+ fi
+ done
+fi
+
+
+if [ -f "$DIANA_ENVIRONMENT_FILE" ]
+then
+ . $DIANA_ENVIRONMENT_FILE
+fi
+
+if [ $# = 0 ]
+then
+ echo no argument given 1>&2
+ exit 1
+fi
+
+file_arg=$1
+
+unique_bit=$$.`hostname`
+
+# sanger hack:
+file_arg=`echo $file_arg | sed 's@^/tmp_mnt/nfs/@/nfs/@' | sed 's@^/tmp_mnt/tmp_nfs/@/nfs/@'`
+# fix for pcs3:
+file_arg=`echo $file_arg | sed 's@^/yeastpub4/@/nfs/disk222/yeastpub4/@'`
+# fix for pcs4:
+file_arg=`echo $file_arg | sed 's@^/.automount/pcs3/root/nfs/@/nfs/@'`
+file_arg=`echo $file_arg | sed 's@^/.automount/evs-users2/root/@/nfs/@'`
+
+if [ -f ./$file_arg ]
+then
+ # the file is in the current directory - we need the full path so netscape
+ # can find the file
+ new_file=$PWD/$file_arg.$unique_bit.html
+else
+ new_file=$file_arg.$unique_bit.html
+fi
+
+###
+# sanger fix for /nfs/repository which isn't mounted on workstations:
+REPOSITORY=`echo $new_file | sed -n -e 's|^\/nfs\/repository\/\(.*\)\(\/\)\(.*\)$|\3|p'`
+
+if [ "$REPOSITORY" != "" ]; then
+
+ if [ ! -d $HOME/artemis_tmp ]; then
+ mkdir $HOME/artemis_tmp
+ fi
+ new_file="$HOME/artemis_tmp/$REPOSITORY"
+
+fi
+#
+###
+
+cat <<EOF > $new_file
+<HTML>
+ <HEAD>
+ <TITLE>
+ Results for $file_arg
+ </TITLE>
+ </HEAD>
+ <BODY>
+<PRE>
+EOF
+
+perl -e '
+BEGIN {
+ # change these variable to list the databases to search for the IDs - the
+ # database names should be separated by spaces
+ $PROTEIN_DATABASES = "uniprot";
+ $DNA_DATABASES = "embl";
+
+ # change this to point to the wgetz script of your SRS server
+ $SRS_SERVER = "www.sanger.ac.uk/srs6bin/cgi-bin/wgetz?-e+";
+ $SRS_SERVER = "srs.ebi.ac.uk/srsbin/cgi-bin/wgetz?-e+";
+
+ $PROTEIN_DATABASES =~ s/ /%20/g;
+ $DNA_DATABASES =~ s/ /%20/g;
+
+ %GENEDB_PATTERNS = (tryp => q!Tb\d+\.\d+\.\d+|TRYP_\S+!,
+ leish => q!LmjF\d+.\d+!);
+
+ $GENEDB_PATTERN = join ("|", values %GENEDB_PATTERNS);
+
+ $BLAST_START_LINE = "Sequences producing High-scoring Segment Pairs|" .
+ "Sequences producing significant alignments:";
+ $FASTA_START_LINE = "The best scores are";
+
+ # the list of IDs we have seen so far
+ @ids = ();
+
+ # the list of IDs we have made anchors for so far
+ @anchored_ids = ();
+
+ $db_type = "unknown";
+}
+
+sub hyperlink_to_anchor
+{
+ $id = shift;
+ qq(<a href="#$id">$id</a>);
+}
+
+sub hyperlink_id
+{
+ $id = shift;
+
+ if ($db_type eq "dna") {
+ $r = qq#<a href="http://$SRS_SERVER\[\{$DNA_DATABASES\}-ID:$id*]|[\{$DNA_DATABASES\}-AccNumber:$id*]">$id</a>#;
+ } else {
+ for my $org (keys %GENEDB_PATTERNS) {
+ my $pattern = $GENEDB_PATTERNS{$org};
+
+ if ($id =~ /$pattern/) {
+ $r = qq#<a href="http://www.genedb.org/genedb/Search?organism=$org&name=$id">$id</a>#;
+ return $r
+ }
+ }
+
+ # Text Entry
+ $r = qq#<a href="http://$SRS_SERVER-id+1+\[\{$PROTEIN_DATABASES\}-ID:$id*]|[\{$PROTEIN_DATABASES\}-AccNumber:$id*]+-vn+2">$id</a>#;
+
+ #$r = qq#<a href="http://$SRS_SERVER-id+1+\[\{$PROTEIN_DATABASES\}-ID:$id*]|[\{$PROTEIN_DATABASES\}-AccNumber:$id*]">$id</a>#;
+ }
+ return $r
+}
+
+$file_name = $ARGV[0];
+
+if ($file_name =~ /\.gz$/) {
+ open IN_FILE, "gzip -d < $file_name |" or die "failed to open $file_name\n";
+} else {
+ open IN_FILE, "$file_name" or die "failed to open $file_name\n";
+}
+
+while (<IN_FILE>) {
+ if ($. == 1) {
+ if (/^\s*([^\s]+)/) {
+ if (lc $1 eq "blastn" or lc $1 eq "tblastn" or lc $1 eq "tblastx") {
+ $db_type = "dna";
+ } else {
+ if (lc $1 eq "fasta" or lc $1 eq "blastp" or lc $1 eq "blastx") {
+ $db_type = "protein";
+ } else {
+ print "\nWARNING: could not identify file type: $1\n";
+ }
+ }
+ } else {
+ print "\nWARNING: could not identify file type\n";
+ }
+ }
+
+
+ # ignore header lines
+ if (1..m/$BLAST_START_LINE|$FASTA_START_LINE/) {
+ print;
+ next;
+ }
+
+ if (@ids && /^\s*$/) {
+ $summary_finished = 1;
+ }
+
+ if (/^>?\w+\|\w+\|(\w+)|^(?:(?:>?>?(?:[A-Z]+:)?)(\w+)|($GENEDB_PATTERN))|^(?:(?:>?>?(?:[A-Z]+:)?)(\w+)\\.\d+\s+) /) {
+
+ $id = $1;
+
+ if (!defined $id) {
+ $id = $2;
+ }
+
+ if ($summary_finished) {
+ if ((grep {$_ eq $id} @ids) && (!grep {$_ eq $id} @anchored_ids)) {
+ # not anchored yet so make it an anchor
+ if (s/\b$id\b/"<a name=\"$id\">" . (hyperlink_id($id)) . "<\/a>"/e) {
+ push @anchored_ids, $id;
+ }
+ }
+ } else {
+ if (!grep {$_ eq $id} @ids) {
+ push @ids, $id;
+ }
+
+ s/$id/hyperlink_to_anchor($id)/ei;
+
+# if (!s/ $id/" " . hyperlink_id($id)/ei) {
+ # if the id occurs once in the line put a link at end of line
+ s/$/" LINK:" . hyperlink_id($id)/e;
+# }
+ }
+ }
+ print;
+}
+
+' $file_arg >> $new_file
+
+cat <<EOF >> $new_file;
+</PRE>
+ </BODY>
+</HTML>
+EOF
+
+# delete it at some point
+echo "rm -f $new_file > /dev/null 2>&1" | at now + 12 hours
+
+# MAC OS X => Safari browser
+# The command then is the following
+if [ -f "/usr/bin/open" -a `uname` = Darwin ]
+then
+ /usr/bin/open $new_file
+ exit 0
+elif [ -f "/Applications/Safari.app/Contents/MacOS/Safari" ]
+then
+ if $NETSCAPE $new_file
+ then
+ exit 0
+ fi
+# For Netscape or mozilla
+# Use openURL($new_file, new-tab) to get new-tab
+elif $NETSCAPE -remote "openURL($new_file)"
+then
+ sleep 1m
+ rm -f $new_file
+ exit 0
+else
+ echo starting new netscape 2>&1
+ # netscape isn't running - so start it
+ ($NETSCAPE &)&
+
+ # now send the URL. we do things this way so that the script doesn't exit
+ # until netscape has successfully shown the URL
+
+ sleep 1
+
+ # don't exit the script until the file is successfully displayed or until
+ # 40 seconds is up
+ for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+ do
+ if $NETSCAPE -remote "openURL($new_file)" 2> /dev/null
+ then
+ sleep 1m
+ rm -f $new_file
+ exit 0
+ else
+ sleep 2
+ fi
+ done
+
+ exit 1
+fi
diff --git a/etc/run_blastn b/etc/run_blastn
new file mode 100755
index 0000000..1364163
--- /dev/null
+++ b/etc/run_blastn
@@ -0,0 +1,98 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_blastn,v 1.4 2005-12-20 13:49:49 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database
+ echo or: $0 file_of_filenames database
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+#EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+eval EXPANDED_DATABASE="$DATABASE"
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+ EXEC=${EXEC-`which blastall 2>/dev/null`}
+
+ if [ ! -x "$EXEC" ]; then
+ EXEC=`find Artemis* -name blastall 2>/dev/null`
+ fi
+
+ if [ ! -d "$BLASTDB" ]; then
+ DATABASE_TMP="$PWD/"`find Artemis* -name blast-data 2>/dev/null`"/$DATABASE"
+ if [ -f "$DATABASE_TMP" ]; then
+ DATABASE="$DATABASE_TMP"
+ fi
+ fi
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to"
+ echo "$OUTPUT_FILE using database $DATABASE"
+
+
+ EXTRA_ARGS=
+
+ # add/change the flags to suit your site:
+ nice -19 $EXEC -d $DATABASE -i $INPUT_FILE -p blastn \
+ $EXTRA_ARGS 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_blastn.sanger b/etc/run_blastn.sanger
new file mode 100755
index 0000000..4ecc728
--- /dev/null
+++ b/etc/run_blastn.sanger
@@ -0,0 +1,122 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_blastn.sanger,v 1.2 2005-06-09 12:48:51 tjc Exp $"
+
+cleanUp()
+{
+ echo $1
+ cat $1 | while thisLine=`line`
+ do
+ rm -f $thisLine
+ done
+ rm -f $1
+}
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database
+ echo or: $0 file_of_filenames database
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+ DIRNAME=$4
+
+ ### change these lines:
+ EXEC=blastall
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to"
+ echo "$OUTPUT_FILE using database $DATABASE"
+
+
+ EXTRA_ARGS=
+
+ # add/change the flags to suit your site:
+
+ HOSTNAME=`hostname`
+ REMOTE=N
+
+ case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+ esac
+
+ echo $DIRNAME
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+# rsh babel "cd $WDIR; lsrun -v blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p blastn \
+# $EXTRA_ARGS >! $OUTPUT_FILE"
+ rsh babel "cd $WDIR; bsub -q longblastq -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl $DATABASE $INPUT_FILE $EXTRA_ARGS"
+ else
+# lsrun -v blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p blastn \
+# $EXTRA_ARGS 2>&1 > $OUTPUT_FILE |
+# tee ${PROG}_errors.new 1>&2
+
+ bsub -q long -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl $DATABASE $INPUT_FILE $EXTRA_ARGS
+ fi
+
+ #### end of changes
+ count=0; while (test ! -f $OUTPUT_FILE) && (test $count -lt 1000) ; do sleep 2; count+=1; done
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE
+ zip -j ${DIRNAME}blastn.zip ${OUTPUT_FILE}.gz
+ rm -f ${OUTPUT_FILE}.gz
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+DIR=`dirname $PWD/$1`
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE "$DIR/"
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE "$DIR/"
+fi
+
+cleanUp $1
+exit 0
diff --git a/etc/run_blastp b/etc/run_blastp
new file mode 100755
index 0000000..b26e6d7
--- /dev/null
+++ b/etc/run_blastp
@@ -0,0 +1,99 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_blastp,v 1.6 2007-02-27 10:59:41 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database
+ echo or: $0 file_of_filenames database
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+#EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+eval EXPANDED_DATABASE="$DATABASE"
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+ EXEC=${EXEC-`which blastall 2>/dev/null`}
+
+ if [ ! -x "$EXEC" ]; then
+ EXEC=`find Artemis* -name blastall 2>/dev/null`
+ fi
+
+ if [ ! -d "$BLASTDB" ]; then
+ DATABASE_TMP=`echo $DATABASE | sed 's|\%||'`
+ DATABASE_TMP="$PWD/"`find Artemis* -name blast-data 2>/dev/null`"/$DATABASE_TMP"
+ if [ -f "$DATABASE_TMP" ]; then
+ DATABASE="$DATABASE_TMP"
+ fi
+ fi
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to"
+ echo "$OUTPUT_FILE using database $DATABASE"
+
+
+ EXTRA_ARGS=
+
+ # add/change the flags to suit your site:
+ nice -19 $EXEC -d $DATABASE -i $INPUT_FILE -p blastp \
+ $EXTRA_ARGS 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_blastp.sanger b/etc/run_blastp.sanger
new file mode 100755
index 0000000..c70da91
--- /dev/null
+++ b/etc/run_blastp.sanger
@@ -0,0 +1,437 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: /cvsroot/pathsoft/artemis/etc/run_blastp.sanger,v 1.5 2007/02/27 10:56:30 tjc Exp $"
+
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+if [ $DATABASE = "psu/Kineto_aa" ]; then
+ BLASTDB=/lustre/pathogen/blast; export BLASTDB
+fi
+
+
+
+# expand any ~ or environment variables
+EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+ EXEC=blastall
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to"
+ echo "$OUTPUT_FILE using database $DATABASE"
+
+
+ EXTRA_ARGS=
+
+ HOSTNAME=`hostname`
+ REMOTE=N
+
+ case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+ esac
+
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+# rsh babel "cd $WDIR; lsrun -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -v \
+# blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p blastp \
+# $EXTRA_ARGS >! $OUTPUT_FILE"
+ ssh babel "cd $WDIR; bsub -q longblastq -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl -p $DATABASE $INPUT_FILE $EXTRA_ARGS"
+ else
+ # add/change the flags to suit your site:
+# lsrun -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -v \
+# blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p blastp \
+# $EXTRA_ARGS 2>&1 > $OUTPUT_FILE |
+# tee ${PROG}_errors.new 1>&2
+ bsub -q longblastq -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl -p $DATABASE $INPUT_FILE $EXTRA_ARGS
+ fi
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+PERL_PROG_1='
+
+local *BSUB;
+
+my $file = $ARGV[0];
+my $database = $ARGV[1];
+my $pwd = $ARGV[2];
+chomp $file;
+chomp $database;
+
+open(BSUB, "| bsub -q normal -o fasta_errors -n 1 -R \"select[long && mem > 500] rusage[r1m=1:mem=500]\" -K") or die "could not open bsub pipe : $!";
+open(LIST_FILE,$file);
+
+$EXEC="blastall";
+
+while(my $inFile = <LIST_FILE>)
+{
+ chomp($inFile);
+
+ if($inFile =~ m/^($pwd)(.*)/)
+ {
+ my $inFile_tmp = $2;
+ while($inFile_tmp =~ m/^(\/)(.*)/)
+ {
+ $inFile_tmp = $2;
+ }
+
+ if( -e $inFile_tmp )
+ {
+ $inFile = $inFile_tmp;
+ }
+ }
+
+ if($inFile =~ m/^(\S{100})/)
+ {
+ if($inFile =~ m/^(\S{90,})(blastp\/\S+)/)
+ {
+ my $inFile_tmp = $1;
+
+ if( -e $inFile_tmp )
+ {
+ print BSUB "cd $inFile_tmp\n";
+ $inFile = $2;
+ }
+ }
+ }
+
+ print BSUB "flexi_blast.pl -p $database $inFile $database ktup 2 > $inFile\.out\n";
+ print BSUB "gzip -9 $inFile\.out\n";
+}
+close BSUB or die "--Could not submit job : $!";
+close LIST_FILE;
+'
+
+
+
+PERL_PROG='
+
+local *BSUB;
+
+my $file = $ARGV[0];
+my $database = $ARGV[1];
+my $pwd = $ARGV[2];
+my $dirname = $ARGV[3];
+
+chomp $file;
+chomp $database;
+
+open(LIST_FILE,$file);
+
+$NEW_WDIR=".";
+$NUM_JOBS=0;
+
+while(my $inFile = <LIST_FILE>)
+{
+ $NUM_JOBS++;
+ chomp($inFile);
+
+ if($inFile =~ m/^($pwd)(.*)/)
+ {
+ my $inFile_tmp = $2;
+ while($inFile_tmp =~ m/^(\/)(.*)/)
+ {
+ $inFile_tmp = $2;
+ }
+
+ if( -e $inFile_tmp )
+ {
+ $inFile = $inFile_tmp;
+ }
+ }
+
+
+ if($inFile =~ m/^(\S{100})/)
+ {
+ if($inFile =~ m/^(\S{90,})(blastp\/\S+)/)
+ {
+ my $inFile_tmp = $1;
+
+ if( -e $inFile_tmp )
+ {
+ $NEW_WDIR=$inFile_tmp;
+ $inFile = $2;
+ }
+ }
+ }
+ # find number of leading zero
+
+ if($inFile =~ m/^(.*)(blastp\/)([^\/]*)(0{4})(\d{1})$/)
+ {
+ push(@jobs10, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(blastp\/)([^\/]*)(0{3})(\d{2})$/)
+ {
+ push(@jobs100, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(blastp\/)([^\/]*)(0{2})(\d{3})$/)
+ {
+ push(@jobs1000, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(blastp\/)([^\/]*)(0{1})(\d{4})$/)
+ {
+ push(@jobs10000, $inFile);
+ }
+ else
+ {
+ push(@jobs100000, $inFile);
+ }
+}
+close LIST_FILE;
+
+if(defined @jobs10)
+{
+ my( $num ) = sprintf( "%04d", 0);
+ submit($num, @jobs10);
+}
+
+if(defined @jobs100)
+{
+ my( $num ) = sprintf( "%03d", 0);
+ submit($num, @jobs100);
+}
+
+if(defined @jobs1000)
+{
+ my( $num ) = sprintf( "%02d", 0);
+ submit($num, @jobs1000);
+}
+
+if(defined @jobs10000)
+{
+ my( $num ) = sprintf( "%01d", 0);
+ submit($num, @jobs10000);
+}
+
+if(defined @jobs100000)
+{
+ submit("", @jobs100000);
+}
+
+for($count = 0; $count < @bsub_jobs; $count++)
+{
+ open(BSUB, "| bsub -q normal -R \"select[mem > 1] rusage[mem=1]\" -o /dev/null -e /dev/null -w \"ended($bsub_jobs[$count])\" -K -J \"$bsub_jobs[$count]\_fin\"") or die "could not open bsub pipe : $!";
+
+ if($count == @bsub_jobs-1)
+ {
+ print BSUB "find ${dirname} -maxdepth 1 -name \"\*out.gz\" -print | xargs zip -j ${dirname}blastp.zip\n";
+ print BSUB "find ${dirname} -maxdepth 1 -name \"\*out.gz\" -print | xargs rm -f\n";
+ }
+ print BSUB "\"echo finished > /dev/null\" > /dev/null 2> /dev/null";
+ close BSUB; # or die "--Could not submit job : $!";
+}
+
+
+sub submit
+{
+ my ($num, @jobs) = @_;
+
+ my $prefix;
+ my @starts;
+ my @prefixes;
+
+ if($jobs[0] =~ m/^(.*)(\/blastp\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2.$3;
+ push(@starts, "$4");
+ }
+ elsif($jobs[0] =~ m/^(blastp\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2;
+ push(@starts, "$3");
+ }
+
+ # escape characters
+ if($prefix =~ /(\+|\?|\*|\[|\])/)
+ {
+ $prefix =~ s/\+/\\+/;
+ $prefix =~ s/\?/\\?/;
+ $prefix =~ s/\*/\\*/;
+ $prefix =~ s/\[/\\[/;
+ $prefix =~ s/\]/\\]/;
+ }
+ push(@prefixes, $prefix);
+
+
+ #different entries have different prefixes
+ for($count =0; $count < @jobs; $count++)
+ {
+ if($jobs[$count] !~ m/^$prefix(.*)/)
+ {
+ if($jobs[$count] =~ m/^(.*)(blastp\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2.$3;
+
+ push(@starts, "$4");
+
+ if($prefix =~ /(\+|\?|\*|\[|\])/)
+ {
+ $prefix =~ s/\+/\\+/;
+ $prefix =~ s/\?/\\?/;
+ $prefix =~ s/\*/\\*/;
+ $prefix =~ s/\[/\\[/;
+ $prefix =~ s/\]/\\]/;
+ }
+
+ push(@prefixes, $prefix);
+ }
+ }
+ }
+
+ for($count =0; $count < @prefixes; $count++)
+ {
+ $prefix = $prefixes[$count];
+ $start = $starts[$count];
+
+ # build the index description that need to be run
+ $index="$start-";
+ $end="$start";
+ for($j =0; $j < @jobs; $j++)
+ {
+ if($jobs[$j] =~ m/^$prefix(.*)/)
+ {
+ if($jobs[$j] =~ m/^(.*)(blastp\/)([^\/]*)(\d{5})/)
+ {
+ if($j == @jobs-1)
+ {
+ $index = "$index$4";
+ }
+ elsif($end+1 >= $4)
+ {
+ $end = "$4";
+ }
+ else
+ {
+ $index = "$index$end,$4-"
+ }
+ }
+ }
+ }
+
+ if($index =~ m/(\-)$/)
+ {
+ $index = "$index$end";
+ }
+
+ print "$prefix, $index, $num";
+ bsub($prefix, $index, $num);
+ }
+}
+
+
+# start job arrays
+sub bsub
+{
+ my ($prefix, $index, $num) = @_;
+
+ my $name = $prefix;
+
+ if($prefix =~ m/(\/blastp\/)(.*)/)
+ {
+ $name = "$2";
+ }
+
+ my $random = int( rand( 999+1 ) );
+
+ push(@bsub_jobs, "$name$random\_blastp");
+
+ my $QUEUE="long";
+ if($NUM_JOBS <= 6)
+ {
+ $QUEUE="normal";
+ }
+
+ print "flexi_blast.pl -p $database $prefix$num\n";
+
+ open(BSUB, "| bsub -q $QUEUE -o /dev/null -n 1 -R \"select[mem > 500] rusage[mem=500]\" -J$name$random\_blastp\"[$index]%16\"") or die "could not open bsub pipe : $!";
+ print BSUB "cd $NEW_WDIR\n";
+ print BSUB "flexi_blast.pl -p $database $prefix$num";
+ print BSUB "\${LSB_JOBINDEX} > $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\.out\n";
+
+ print BSUB "gzip -9f $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\.out\n";
+ print BSUB "rm -f $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\n";
+
+
+ close BSUB or die "--Could not submit job : $!";
+}
+
+'
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+HOSTNAME=`hostname`
+REMOTE=N
+
+case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+esac
+
+if [ x$ONEFILE = x ]
+then
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+ CMD=`echo $PERL_PROG_1`
+ ssh babel "cd $WDIR; perl -w -e '$CMD' \"$1\" \"$EXPANDED_DATABASE\" \"$PWD\""
+ else
+ DIR=`dirname $PWD/$1`
+ perl -w -e "$PERL_PROG" "$1" "$EXPANDED_DATABASE" "$PWD" "$DIR/"
+ fi
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_blastp.sanger.linux b/etc/run_blastp.sanger.linux
new file mode 100755
index 0000000..6097f63
--- /dev/null
+++ b/etc/run_blastp.sanger.linux
@@ -0,0 +1,420 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_blastp.sanger.linux,v 1.1 2008-01-24 10:48:38 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+
+
+# expand any ~ or environment variables
+EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+ EXEC=blastall
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to"
+ echo "$OUTPUT_FILE using database $DATABASE"
+
+
+ EXTRA_ARGS=
+
+ HOSTNAME=`hostname`
+ REMOTE=N
+
+ case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+ esac
+
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+# rsh babel "cd $WDIR; lsrun -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -v \
+# blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p blastp \
+# $EXTRA_ARGS >! $OUTPUT_FILE"
+ ssh babel "cd $WDIR; bsub -q longblastq -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl -p $DATABASE $INPUT_FILE $EXTRA_ARGS"
+ else
+ # add/change the flags to suit your site:
+# lsrun -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -v \
+# blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p blastp \
+# $EXTRA_ARGS 2>&1 > $OUTPUT_FILE |
+# tee ${PROG}_errors.new 1>&2
+ bsub -q longblastq -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl -p $DATABASE $INPUT_FILE $EXTRA_ARGS
+ fi
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+PERL_PROG_1='
+
+local *BSUB;
+
+my $file = $ARGV[0];
+my $database = $ARGV[1];
+my $pwd = $ARGV[2];
+chomp $file;
+chomp $database;
+
+open(BSUB, "| bsub -q normal -o fasta_errors -n 1 -R \"select[long && mem > 500] rusage[r1m=1:mem=500]\" -K") or die "could not open bsub pipe : $!";
+open(LIST_FILE,$file);
+
+$EXEC="blastall";
+
+while(my $inFile = <LIST_FILE>)
+{
+ chomp($inFile);
+
+ if($inFile =~ m/^($pwd)(.*)/)
+ {
+ my $inFile_tmp = $2;
+ while($inFile_tmp =~ m/^(\/)(.*)/)
+ {
+ $inFile_tmp = $2;
+ }
+
+ if( -e $inFile_tmp )
+ {
+ $inFile = $inFile_tmp;
+ }
+ }
+
+ if($inFile =~ m/^(\S{100})/)
+ {
+ if($inFile =~ m/^(\S{90,})(blastp\/\S+)/)
+ {
+ my $inFile_tmp = $1;
+
+ if( -e $inFile_tmp )
+ {
+ print BSUB "cd $inFile_tmp\n";
+ $inFile = $2;
+ }
+ }
+ }
+
+ print BSUB "flexi_blast.pl -p $database $inFile $database ktup 2 > $inFile\.out\n";
+ print BSUB "gzip -9 $inFile\.out\n";
+}
+close BSUB or die "--Could not submit job : $!";
+close LIST_FILE;
+'
+
+
+
+PERL_PROG='
+
+local *BSUB;
+
+my $file = $ARGV[0];
+my $database = $ARGV[1];
+my $pwd = $ARGV[2];
+chomp $file;
+chomp $database;
+
+open(LIST_FILE,$file);
+
+$NEW_WDIR=".";
+$NUM_JOBS=0;
+
+while(my $inFile = <LIST_FILE>)
+{
+ $NUM_JOBS++;
+ chomp($inFile);
+
+ if($inFile =~ m/^($pwd)(.*)/)
+ {
+ my $inFile_tmp = $2;
+ while($inFile_tmp =~ m/^(\/)(.*)/)
+ {
+ $inFile_tmp = $2;
+ }
+
+ if( -e $inFile_tmp )
+ {
+ $inFile = $inFile_tmp;
+ }
+ }
+
+
+ if($inFile =~ m/^(\S{100})/)
+ {
+ if($inFile =~ m/^(\S{90,})(blastp\/\S+)/)
+ {
+ my $inFile_tmp = $1;
+
+ if( -e $inFile_tmp )
+ {
+ $NEW_WDIR=$inFile_tmp;
+ $inFile = $2;
+ }
+ }
+ }
+ # find number of leading zero
+
+ if($inFile =~ m/^(.*)(blastp\/)([^\/]*)(0{4})(\d{1})$/)
+ {
+ push(@jobs10, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(blastp\/)([^\/]*)(0{3})(\d{2})$/)
+ {
+ push(@jobs100, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(blastp\/)([^\/]*)(0{2})(\d{3})$/)
+ {
+ push(@jobs1000, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(blastp\/)([^\/]*)(0{1})(\d{4})$/)
+ {
+ push(@jobs10000, $inFile);
+ }
+ else
+ {
+ push(@jobs100000, $inFile);
+ }
+}
+close LIST_FILE;
+
+if(defined @jobs10)
+{
+ my( $num ) = sprintf( "%04d", 0);
+ submit($num, @jobs10);
+}
+
+if(defined @jobs100)
+{
+ my( $num ) = sprintf( "%03d", 0);
+ submit($num, @jobs100);
+}
+
+if(defined @jobs1000)
+{
+ my( $num ) = sprintf( "%02d", 0);
+ submit($num, @jobs1000);
+}
+
+if(defined @jobs10000)
+{
+ my( $num ) = sprintf( "%01d", 0);
+ submit($num, @jobs10000);
+}
+
+if(defined @jobs100000)
+{
+ submit("", @jobs100000);
+}
+
+for($count = 0; $count < @bsub_jobs; $count++)
+{
+ open(BSUB, "| bsub -q normal -R \"select[mem > 1] rusage[mem=1]\" -o /dev/null -e /dev/null -w \"ended($bsub_jobs[$count])\" -K -J \"$bsub_jobs[$count]\_fin\"") or die "could not open bsub pipe : $!";
+ print BSUB "\"echo finished > /dev/null\" > /dev/null 2> /dev/null";
+ close BSUB; # or die "--Could not submit job : $!";
+}
+
+
+sub submit
+{
+ my ($num, @jobs) = @_;
+
+ my $prefix;
+ my @starts;
+ my @prefixes;
+
+ if($jobs[0] =~ m/^(.*)(\/blastp\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2.$3;
+ push(@starts, "$4");
+ }
+ elsif($jobs[0] =~ m/^(blastp\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2;
+ push(@starts, "$3");
+ }
+
+ # escape characters
+ if($prefix =~ /(\+|\?|\*|\[|\])/)
+ {
+ $prefix =~ s/\+/\\+/;
+ $prefix =~ s/\?/\\?/;
+ $prefix =~ s/\*/\\*/;
+ $prefix =~ s/\[/\\[/;
+ $prefix =~ s/\]/\\]/;
+ }
+ push(@prefixes, $prefix);
+
+
+ #different entries have different prefixes
+ for($count =0; $count < @jobs; $count++)
+ {
+ if($jobs[$count] !~ m/^$prefix(.*)/)
+ {
+ if($jobs[$count] =~ m/^(.*)(blastp\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2.$3;
+
+ push(@starts, "$4");
+
+ if($prefix =~ /(\+|\?|\*|\[|\])/)
+ {
+ $prefix =~ s/\+/\\+/;
+ $prefix =~ s/\?/\\?/;
+ $prefix =~ s/\*/\\*/;
+ $prefix =~ s/\[/\\[/;
+ $prefix =~ s/\]/\\]/;
+ }
+
+ push(@prefixes, $prefix);
+ }
+ }
+ }
+
+ for($count =0; $count < @prefixes; $count++)
+ {
+ $prefix = $prefixes[$count];
+ $start = $starts[$count];
+
+ # build the index description that need to be run
+ $index="$start-";
+ $end="$start";
+ for($j =0; $j < @jobs; $j++)
+ {
+ if($jobs[$j] =~ m/^$prefix(.*)/)
+ {
+ if($jobs[$j] =~ m/^(.*)(blastp\/)([^\/]*)(\d{5})/)
+ {
+ if($j == @jobs-1)
+ {
+ $index = "$index$4";
+ }
+ elsif($end+1 >= $4)
+ {
+ $end = "$4";
+ }
+ else
+ {
+ $index = "$index$end,$4-"
+ }
+ }
+ }
+ }
+
+ if($index =~ m/(\-)$/)
+ {
+ $index = "$index$end";
+ }
+
+ print "$prefix, $index, $num";
+ bsub($prefix, $index, $num);
+ }
+}
+
+
+# start job arrays
+sub bsub
+{
+ my ($prefix, $index, $num) = @_;
+
+ my $name = $prefix;
+
+ if($prefix =~ m/(\/blastp\/)(.*)/)
+ {
+ $name = "$2";
+ }
+
+ my $random = int( rand( 999+1 ) );
+
+ push(@bsub_jobs, "$name$random\_blastp");
+
+ my $QUEUE="long";
+ if($NUM_JOBS <= 6)
+ {
+ $QUEUE="normal";
+ }
+
+ print "flexi_blast.pl -p $database $prefix$num\n";
+
+ open(BSUB, "| bsub -q $QUEUE -o /dev/null -n 1 -R \"select[mem > 500] rusage[mem=500]\" -J$name$random\_blastp\"[$index]%16\"") or die "could not open bsub pipe : $!";
+ print BSUB "cd $NEW_WDIR\n";
+ print BSUB "flexi_blast.pl -p $database $prefix$num";
+ print BSUB "\${LSB_JOBINDEX} > $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\.out\n";
+
+ print BSUB "gzip -9f $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\.out\n";
+
+ close BSUB or die "--Could not submit job : $!";
+}
+
+'
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+HOSTNAME=`hostname`
+REMOTE=N
+
+case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+esac
+
+if [ x$ONEFILE = x ]
+then
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+ CMD=`echo $PERL_PROG_1`
+ ssh babel "cd $WDIR; perl -w -e '$CMD' \"$1\" \"$EXPANDED_DATABASE\" \"$PWD\""
+ else
+ perl -w -e "$PERL_PROG" "$1" "$EXPANDED_DATABASE" "$PWD"
+ fi
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_blastx b/etc/run_blastx
new file mode 100755
index 0000000..4ee0396
--- /dev/null
+++ b/etc/run_blastx
@@ -0,0 +1,98 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_blastx,v 1.4 2005-12-20 13:49:57 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database
+ echo or: $0 file_of_filenames database
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+#EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+eval EXPANDED_DATABASE="$DATABASE"
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+ EXEC=${EXEC-`which blastall 2>/dev/null`}
+
+ if [ ! -x "$EXEC" ]; then
+ EXEC=`find Artemis* -name blastall 2>/dev/null`
+ fi
+
+ if [ ! -d "$BLASTDB" ]; then
+ DATABASE_TMP="$PWD/"`find Artemis* -name blast-data 2>/dev/null`"/$DATABASE"
+ if [ -f "$DATABASE_TMP" ]; then
+ DATABASE="$DATABASE_TMP"
+ fi
+ fi
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to"
+ echo "$OUTPUT_FILE using database $DATABASE"
+
+
+ EXTRA_ARGS=
+
+ # add/change the flags to suit your site:
+ nice -19 $EXEC -d $DATABASE -i $INPUT_FILE -p blastx \
+ $EXTRA_ARGS 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_blastx.sanger b/etc/run_blastx.sanger
new file mode 100755
index 0000000..f2637bc
--- /dev/null
+++ b/etc/run_blastx.sanger
@@ -0,0 +1,121 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: /cvsroot/pathsoft/artemis/etc/run_blastx.sanger,v 1.2 2005/07/11 15:06:21 tjc Exp $"
+
+cleanUp()
+{
+ echo $1
+ cat $1 | while thisLine=`line`
+ do
+ rm -f $thisLine
+ done
+ rm -f $1
+}
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database
+ echo or: $0 file_of_filenames database
+ exit 1
+ fi
+fi
+
+if [ $DATABASE = "psu/Kineto_aa" ]; then
+ BLASTDB=/lustre/pathogen/blast; export BLASTDB
+fi
+
+# expand any ~ or environment variables
+EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+
+echo $BLASTDB
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+ DIRNAME=$4
+
+ ### change these lines:
+ EXEC=blastall
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to"
+ echo "$OUTPUT_FILE using database $DATABASE"
+
+
+ EXTRA_ARGS=
+
+ # add/change the flags to suit your site:
+
+ HOSTNAME=`hostname`
+ REMOTE=N
+
+ case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+ esac
+
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+ rsh babel "cd $WDIR; lsrun -v blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p blastx \
+ $EXTRA_ARGS >! $OUTPUT_FILE"
+ else
+ bsub -q long -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl -x $DATABASE $INPUT_FILE $EXTRA_ARGS
+ fi
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ count=0; while (test ! -f $OUTPUT_FILE) && (test $count -lt 1000) ; do sleep 2; count+=1; done
+
+ gzip -9 $OUTPUT_FILE
+ zip -j ${DIRNAME}blastx.zip ${OUTPUT_FILE}.gz
+ rm -f ${OUTPUT_FILE}.gz
+
+# if [ -s ${PROG}_errors.new ]
+# then
+# ( echo ERROR running $PROG: ; echo;
+# echo ===================================================
+# cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+# cat ${PROG}_errors.new >> ${PROG}_errors
+# fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+DIR=`dirname $PWD/$1`
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $echo $EXPANDED_DATABASE "$DIR/"
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE "$DIR/"
+fi
+
+cleanUp $1
+exit 0
diff --git a/etc/run_clustalx b/etc/run_clustalx
new file mode 100755
index 0000000..d9ade6d
--- /dev/null
+++ b/etc/run_clustalx
@@ -0,0 +1,41 @@
+#!/bin/sh -
+
+# this script will run clustalx on a temporary file containing the concatenated
+# contents of files listed in a file of filenames
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_clustalx,v 1.3 2005-02-21 11:50:01 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# != 1 ]
+then
+ echo usage: $0 file_of_filenames
+fi
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+file_of_filenames=$1
+date=`date +"%y_%m_%d"`
+hostname=`hostname`
+temp_file_name=/tmp/artemis_temp.$$.$hostname.$date.clustalx_input.fasta
+
+cat $file_of_filenames | xargs cat > $temp_file_name
+
+# make sure that the identifiers are unique for clustalx
+perl -pne 'if (/^>(\S+)/) {
+ $name = $1;
+ if (exists $h{$name}) {
+ $i = $h{$name}++;
+ s/^>(\S+)/>$name.$i/;
+ } else {
+ $h{$name} = 0;
+ }
+}' $temp_file_name > $temp_file_name.processed
+
+clustalx -INFILE=$temp_file_name.processed
+
+exit 0
diff --git a/etc/run_clustalx.sanger b/etc/run_clustalx.sanger
new file mode 100755
index 0000000..f2714f4
--- /dev/null
+++ b/etc/run_clustalx.sanger
@@ -0,0 +1,59 @@
+#!/bin/sh -
+
+# this script will run clustalx on a temporary file containing the concatenated
+# contents of files listed in a file of filenames
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_clustalx.sanger,v 1.1 2005-02-21 11:49:41 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# != 1 ]
+then
+ echo usage: $0 file_of_filenames
+fi
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+file_of_filenames=$1
+date=`date +"%y_%m_%d"`
+hostname=`hostname`
+temp_file_name=clustalx/artemis_temp.$$.$hostname.$date.clustalx_input.fasta
+
+cat $file_of_filenames | xargs cat > $temp_file_name
+
+# make sure that the identifiers are unique for clustalx
+perl -pne 'if (/^>(\S+)/) {
+ $name = $1;
+ if (exists $h{$name}) {
+ $i = $h{$name}++;
+ s/^>(\S+)/>$name.$i/;
+ } else {
+ $h{$name} = 0;
+ }
+}' $temp_file_name > $temp_file_name.processed
+
+# delete it at some point
+echo "rm -f $temp_file_name* > /dev/null 2>&1" | at now + 8 hours
+
+HOSTNAME=`hostname`
+REMOTE=N
+
+case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+esac
+
+if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+ ssh babel "cd $WDIR; clustalx $temp_file_name.processed"
+else
+ clustalx $temp_file_name.processed
+fi
+
+exit 0
diff --git a/etc/run_fasta b/etc/run_fasta
new file mode 100755
index 0000000..554cf0d
--- /dev/null
+++ b/etc/run_fasta
@@ -0,0 +1,121 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_fasta,v 1.18 2008-05-16 10:15:49 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+#
+# Exchange DB name for fasta reference
+#
+if [ -f "$FASTLIBS" ]; then
+ if [ "$DATABASE" = "%uniprot" ]; then
+ DATABASE="%U";
+ elif [ "$DATABASE" = "%uniprot_archaea" ]; then
+ DATABASE="%A";
+ elif [ "$DATABASE" = "%uniprot_bacteria" ]; then
+ DATABASE="%B";
+ elif [ "$DATABASE" = "%uniprot_eukaryota" ]; then
+ DATABASE="%E";
+ elif [ "$DATABASE" = "%uniprot_viruses" ]; then
+ DATABASE="%V";
+ elif [ "$DATABASE" = "%uniprot_rest" ]; then
+ DATABASE="%R";
+ elif [ "$DATABASE" = "%malaria" ]; then
+ DATABASE="%M";
+ elif [ "$DATABASE" = "%kineto_aa" ]; then
+ DATABASE="%K";
+ fi
+fi
+
+# expand any ~ or environment variables
+#EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+eval EXPANDED_DATABASE="$DATABASE"
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+ EXEC=${EXEC-`which fasta34 2>/dev/null`}
+
+ if [ ! -x "$EXEC" ]; then
+ EXEC=`find Artemis* -name fasta34 2>/dev/null`
+ fi
+
+ if [ ! -f "$FASTLIBS" ]; then
+ DATABASE_TMP=`echo $DATABASE | sed 's|\%||'`
+ DATABASE_TMP="$PWD/"`find Artemis* -name blast-data 2>/dev/null`"/$DATABASE_TMP"
+ if [ -f "$DATABASE_TMP" ]; then
+ DATABASE="$DATABASE_TMP"
+ fi
+ fi
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using database $DATABASE" 1>&2
+
+ # add/change the flags to suit your site:
+ COMMAND="$EXEC -B -S -q -b 40 -H $INPUT_FILE $DATABASE ktup 2"
+
+ echo "command line: $COMMAND" 1>&2
+
+ nice -19 $COMMAND 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_fasta.sanger b/etc/run_fasta.sanger
new file mode 100755
index 0000000..d3f5fbc
--- /dev/null
+++ b/etc/run_fasta.sanger
@@ -0,0 +1,470 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: /cvsroot/pathsoft/artemis/etc/run_fasta.sanger,v 1.8 2005/11/29 10:13:07 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+
+#
+# Exchange DB name for fasta reference
+#
+if [ "$DATABASE" = "%uniprot" ]; then
+ DATABASE="%U";
+elif [ "$DATABASE" = "%uniprot_archaea" ]; then
+ DATABASE="%A";
+elif [ "$DATABASE" = "%uniprot_bacteria" ]; then
+ DATABASE="%B";
+elif [ "$DATABASE" = "%uniprot_eukaryota" ]; then
+ DATABASE="%E";
+elif [ "$DATABASE" = "%uniprot_viruses" ]; then
+ DATABASE="%V";
+elif [ "$DATABASE" = "%uniprot_rest" ]; then
+ DATABASE="%R";
+elif [ "$DATABASE" = "%malaria" ]; then
+ DATABASE="%M";
+elif [ "$DATABASE" = "%kineto_aa" ]; then
+ DATABASE="%K";
+fi
+
+# expand any ~ or environment variables
+EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+### strip out directory from command line
+
+ IN=`echo $INPUT_FILE | sed -n -e "s|$PWD//||p"`
+
+ if test "$IN" != "" && test -f $IN; then
+ INPUT_FILE="$IN"
+ fi
+
+ echo "\n\nIN=$IN\nPWD=$PWD\nINPUT_FILE=$INPUT_FILE\n\n"
+ ### change these lines:
+
+ ### get sequence size
+ seq_size=`infoseq "$INPUT_FILE" -length -only -auto | awk '{ sum += $1 } END { print sum }'`
+
+ FASTLIBS=/software/pathogen/external/etc/pubseqgbs export FASTLIBS
+# EXEC=/nfs/disk222/yeastpub/bio-soft/fasta/fasta33_t
+# EXEC=/nfs/disk222/yeastpub/bio-soft/fasta/fasta33
+ EXEC=/software/pathogen/external/applications/fasta/fasta35
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using database $DATABASE" 1>&2
+
+ # add/change the flags to suit your site:
+ COMMAND="$EXEC -S -q -b 100 -H $INPUT_FILE $DATABASE ktup 2"
+
+ echo "command line: $COMMAND" 1>&2
+
+# lsrun -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -v $COMMAND 2>&1 > $OUTPUT_FILE |
+
+ if [ "$seq_size" -lt 50000 ]
+ then
+ bsub -q normal -n 1 -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -I $COMMAND 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+ else
+ bsub -q "longblastq" -n 1 -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -I $COMMAND 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+ fi
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+PERL_PROG_1='
+
+local *BSUB;
+
+my $file = $ARGV[0];
+my $database = $ARGV[1];
+my $pwd = $ARGV[2];
+my $dirname = $ARGV[3];
+chomp $file;
+chomp $database;
+
+$ENV{'FASTLIBS'} = "/software/pathogen/external/etc/pubseqgbs";
+
+
+open(BSUB, "| bsub -q normal -o fasta_errors -n 1 -R \"select[blast && mem > 500] rusage[r1m=1:mem=500]\" -K") or die "could not open bsub pipe : $!";
+open(LIST_FILE,$file);
+
+$EXEC="/software/pathogen/external/apps/usr/bin/fasta36";
+
+while(my $inFile = <LIST_FILE>)
+{
+ chomp($inFile);
+
+ if($inFile =~ m/^($pwd)(.*)/)
+ {
+ my $inFile_tmp = $2;
+ while($inFile_tmp =~ m/^(\/)(.*)/)
+ {
+ $inFile_tmp = $2;
+ }
+
+ if( -e $inFile_tmp )
+ {
+ $inFile = $inFile_tmp;
+ }
+ }
+
+ if($inFile =~ m/^(\S{100})/)
+ {
+ if($inFile =~ m/^(\S{90,})(fasta\/\S+)/)
+ {
+ my $inFile_tmp = $1;
+
+ if( -e $inFile_tmp )
+ {
+ print BSUB "cd $inFile_tmp\n";
+ $inFile = $2;
+ }
+ }
+ }
+
+ print BSUB "$EXEC -S -q -b 100 -H $inFile $database ktup 2 > $inFile\.out\n";
+ print BSUB "gzip -9 $inFile\.out\n";
+}
+close BSUB or die "--Could not submit job : $!";
+close LIST_FILE;
+'
+
+
+
+PERL_PROG='
+
+local *BSUB;
+
+my $file = $ARGV[0];
+my $database = $ARGV[1];
+my $pwd = $ARGV[2];
+my $dirname = $ARGV[3];
+
+chomp $file;
+chomp $database;
+
+$ENV{'FASTLIBS'} = "/software/pathogen/external/etc/pubseqgbs";
+open(LIST_FILE,$file);
+
+$EXEC="/software/pathogen/external/apps/usr/bin/fasta36";
+
+$NEW_WDIR=".";
+$NUM_JOBS=0;
+
+while(my $inFile = <LIST_FILE>)
+{
+ $NUM_JOBS++;
+ chomp($inFile);
+
+ $inFile =~ s/.automount\/evs-users2\/root/nfs/;
+
+ if($inFile =~ m/^($pwd)(.*)/)
+ {
+ my $inFile_tmp = $2;
+ while($inFile_tmp =~ m/^(\/)(.*)/)
+ {
+ $inFile_tmp = $2;
+ }
+
+ if( -e $inFile_tmp )
+ {
+ $inFile = $inFile_tmp;
+ }
+ }
+
+
+ if($inFile =~ m/^(\S{100})/)
+ {
+ if($inFile =~ m/^(\S{90,})(fasta\/\S+)/)
+ {
+ my $inFile_tmp = $1;
+
+ if( -e $inFile_tmp )
+ {
+ $NEW_WDIR=$inFile_tmp;
+ $inFile = $2;
+ }
+ }
+ }
+ # find number of leading zero
+
+ if($inFile =~ m/^(.*)(fasta\/)([^\/]*)(0{4})(\d{1})$/)
+ {
+ push(@jobs10, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(fasta\/)([^\/]*)(0{3})(\d{2})$/)
+ {
+ push(@jobs100, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(fasta\/)([^\/]*)(0{2})(\d{3})$/)
+ {
+ push(@jobs1000, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(fasta\/)([^\/]*)(0{1})(\d{4})$/)
+ {
+ push(@jobs10000, $inFile);
+ }
+ else
+ {
+ push(@jobs100000, $inFile);
+ }
+}
+close LIST_FILE;
+
+if(defined @jobs10)
+{
+ my( $num ) = sprintf( "%04d", 0);
+ submit($num, @jobs10);
+}
+
+if(defined @jobs100)
+{
+ my( $num ) = sprintf( "%03d", 0);
+ submit($num, @jobs100);
+}
+
+if(defined @jobs1000)
+{
+ my( $num ) = sprintf( "%02d", 0);
+ submit($num, @jobs1000);
+}
+
+if(defined @jobs10000)
+{
+ my( $num ) = sprintf( "%01d", 0);
+ submit($num, @jobs10000);
+}
+
+if(defined @jobs100000)
+{
+ submit("", @jobs100000);
+}
+
+for($count = 0; $count < @bsub_jobs; $count++)
+{
+ open(BSUB, "| bsub -q normal -R \"select[mem > 1] rusage[mem=1]\" -o /dev/null -e /dev/null -w \"ended($bsub_jobs[$count])\" -K -J \"$bsub_jobs[$count]\_fin\"") or die "could not open bsub pipe : $!";
+
+ if($count == @bsub_jobs-1)
+ {
+ print BSUB "find ${dirname} -maxdepth 1 -name \"\*out.gz\" -print | xargs zip -j ${dirname}fasta.zip\n";
+ print BSUB "find ${dirname} -maxdepth 1 -name \"\*out.gz\" -print | xargs rm -f\n";
+ }
+
+ print BSUB "\"echo finished > /dev/null\" > /dev/null 2> /dev/null";
+ close BSUB; # or die "--Could not submit job : $!";
+}
+
+
+sub submit
+{
+ my ($num, @jobs) = @_;
+
+ my $prefix;
+ my @starts;
+ my @prefixes;
+
+ if($jobs[0] =~ m/^(.*)(\/fasta\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2.$3;
+ push(@starts, "$4");
+ }
+ elsif($jobs[0] =~ m/^(fasta\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2;
+ push(@starts, "$3");
+ }
+
+ # escape characters
+ if($prefix =~ /(\+|\?|\*|\[|\])/)
+ {
+ $prefix =~ s/\+/\\+/;
+ $prefix =~ s/\?/\\?/;
+ $prefix =~ s/\*/\\*/;
+ $prefix =~ s/\[/\\[/;
+ $prefix =~ s/\]/\\]/;
+ }
+ push(@prefixes, $prefix);
+
+
+ #different entries have different prefixes
+ for($count =0; $count < @jobs; $count++)
+ {
+ if($jobs[$count] !~ m/^$prefix(.*)/)
+ {
+ if($jobs[$count] =~ m/^(.*)(fasta\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2.$3;
+
+ push(@starts, "$4");
+
+ if($prefix =~ /(\+|\?|\*|\[|\])/)
+ {
+ $prefix =~ s/\+/\\+/;
+ $prefix =~ s/\?/\\?/;
+ $prefix =~ s/\*/\\*/;
+ $prefix =~ s/\[/\\[/;
+ $prefix =~ s/\]/\\]/;
+ }
+
+ push(@prefixes, $prefix);
+ }
+ }
+ }
+
+ for($count =0; $count < @prefixes; $count++)
+ {
+ $prefix = $prefixes[$count];
+ $start = $starts[$count];
+
+ # build the index description that need to be run
+ $index="$start-";
+ $end="$start";
+ for($j =0; $j < @jobs; $j++)
+ {
+ if($jobs[$j] =~ m/^$prefix(.*)/)
+ {
+ if($jobs[$j] =~ m/^(.*)(fasta\/)([^\/]*)(\d{5})/)
+ {
+ if($j == @jobs-1)
+ {
+ $index = "$index$4";
+ }
+ elsif($end+1 >= $4)
+ {
+ $end = "$4";
+ }
+ else
+ {
+ $index = "$index$end,$4-"
+ }
+ }
+ }
+ }
+
+ if($index =~ m/(\-)$/)
+ {
+ $index = "$index$end";
+ }
+
+ bsub($prefix, $index, $num);
+ }
+}
+
+
+# start job arrays
+sub bsub
+{
+ my ($prefix, $index, $num) = @_;
+
+ my $name = $prefix;
+
+ if($prefix =~ m/(\/fasta\/)(.*)/)
+ {
+ $name = "$2";
+ }
+
+ # substitute characters
+ if($name =~ /(\+|\#)/)
+ {
+ $name =~ s/\\\+/plus/;
+ $name =~ s/\#/\_/;
+ }
+
+ my $random = int( rand( 999+1 ) );
+
+ push(@bsub_jobs, "$name$random\_fasta");
+
+ my $QUEUE="long";
+ if($NUM_JOBS <= 6)
+ {
+ $QUEUE="normal";
+ }
+
+ open(BSUB, "| bsub -q $QUEUE -o /dev/null -R \"select[mem > 10000] rusage[mem=10000]\" -M10000 -J$name$random\_fasta\"[$index]%16\"") or die "could not open bsub pipe : $!";
+ print BSUB "cd $NEW_WDIR\n";
+ print BSUB "$EXEC -S -q -b 100 -H $prefix$num";
+ print BSUB "\${LSB_JOBINDEX} $database ktup 2 > $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\.out\n";
+
+ print BSUB "gzip -9f $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\.out\n";
+ print BSUB "rm -f $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\n";
+
+
+ close BSUB or die "--Could not submit job : $!";
+}
+
+'
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+HOSTNAME=`hostname`
+REMOTE=N
+
+case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+esac
+
+if [ x$ONEFILE = x ]
+then
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+ CMD=`echo $PERL_PROG_1`
+ ssh babel "cd $WDIR; perl -w -e '$CMD' \"$1\" \"$EXPANDED_DATABASE\" \"$PWD\""
+ else
+ DIR=`dirname $PWD/$1`
+ echo $DIR
+ perl -w -e "$PERL_PROG" "$1" "$EXPANDED_DATABASE" "$PWD" "$DIR/"
+ fi
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_fasta.sanger.linux b/etc/run_fasta.sanger.linux
new file mode 100755
index 0000000..52b9a92
--- /dev/null
+++ b/etc/run_fasta.sanger.linux
@@ -0,0 +1,447 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_fasta.sanger.linux,v 1.1 2008-01-24 10:48:29 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+
+#
+# Exchange DB name for fasta reference
+#
+if [ "$DATABASE" = "%uniprot" ]; then
+ DATABASE="%U";
+elif [ "$DATABASE" = "%uniprot_archaea" ]; then
+ DATABASE="%A";
+elif [ "$DATABASE" = "%uniprot_bacteria" ]; then
+ DATABASE="%B";
+elif [ "$DATABASE" = "%uniprot_eukaryota" ]; then
+ DATABASE="%E";
+elif [ "$DATABASE" = "%uniprot_viruses" ]; then
+ DATABASE="%V";
+elif [ "$DATABASE" = "%uniprot_rest" ]; then
+ DATABASE="%R";
+elif [ "$DATABASE" = "%malaria" ]; then
+ DATABASE="%M";
+elif [ "$DATABASE" = "%kineto_aa" ]; then
+ DATABASE="%K";
+fi
+
+# expand any ~ or environment variables
+EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+### strip out directory from command line
+
+ IN=`echo $INPUT_FILE | sed -n -e "s|$PWD//||p"`
+
+ if test "$IN" != "" && test -f $IN; then
+ INPUT_FILE="$IN"
+ fi
+
+ echo "\n\nIN=$IN\nPWD=$PWD\nINPUT_FILE=$INPUT_FILE\n\n"
+ ### change these lines:
+
+ ### get sequence size
+ seq_size=`infoseq "$INPUT_FILE" -length -only -auto | awk '{ sum += $1 } END { print sum }'`
+
+ FASTLIBS=/nfs/disk222/yeastpub/bio-soft/fasta/pubseqgbs export FASTLIBS
+# EXEC=/nfs/disk222/yeastpub/bio-soft/fasta/fasta33_t
+# EXEC=/nfs/disk222/yeastpub/bio-soft/fasta/fasta33
+ EXEC=/software/pathogen/external/applications/fasta/fasta35
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using database $DATABASE" 1>&2
+
+ # add/change the flags to suit your site:
+ COMMAND="$EXEC -B -S -q -b 100 -H $INPUT_FILE $DATABASE ktup 2"
+
+ echo "command line: $COMMAND" 1>&2
+
+# lsrun -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -v $COMMAND 2>&1 > $OUTPUT_FILE |
+
+ if [ "$seq_size" -lt 50000 ]
+ then
+ bsub -q normal -n 1 -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -I $COMMAND 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+ else
+ bsub -q "longblastq" -n 1 -R 'select[blast && mem > 500] rusage[r1m=1:mem=500]' -I $COMMAND 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+ fi
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+PERL_PROG_1='
+
+local *BSUB;
+
+my $file = $ARGV[0];
+my $database = $ARGV[1];
+my $pwd = $ARGV[2];
+chomp $file;
+chomp $database;
+
+$ENV{'FASTLIBS'} = "/nfs/disk222/yeastpub/bio-soft/fasta/pubseqgbs_test";
+
+
+open(BSUB, "| bsub -q normal -o fasta_errors -n 1 -R \"select[blast && mem > 500] rusage[r1m=1:mem=500]\" -K") or die "could not open bsub pipe : $!";
+open(LIST_FILE,$file);
+
+$EXEC="/nfs/disk222/yeastpub/bio-soft/fasta/fasta33_t";
+$EXEC="/software/pathogen/external/applications/fasta/fasta35";
+
+while(my $inFile = <LIST_FILE>)
+{
+ chomp($inFile);
+
+ if($inFile =~ m/^($pwd)(.*)/)
+ {
+ my $inFile_tmp = $2;
+ while($inFile_tmp =~ m/^(\/)(.*)/)
+ {
+ $inFile_tmp = $2;
+ }
+
+ if( -e $inFile_tmp )
+ {
+ $inFile = $inFile_tmp;
+ }
+ }
+
+ if($inFile =~ m/^(\S{100})/)
+ {
+ if($inFile =~ m/^(\S{90,})(fasta\/\S+)/)
+ {
+ my $inFile_tmp = $1;
+
+ if( -e $inFile_tmp )
+ {
+ print BSUB "cd $inFile_tmp\n";
+ $inFile = $2;
+ }
+ }
+ }
+
+ print BSUB "$EXEC -B -S -q -b 100 -H $inFile $database ktup 2 > $inFile\.out\n";
+ print BSUB "gzip -9 $inFile\.out\n";
+}
+close BSUB or die "--Could not submit job : $!";
+close LIST_FILE;
+'
+
+
+
+PERL_PROG='
+
+local *BSUB;
+
+my $file = $ARGV[0];
+my $database = $ARGV[1];
+my $pwd = $ARGV[2];
+chomp $file;
+chomp $database;
+
+$ENV{'FASTLIBS'} = "/nfs/disk222/yeastpub/bio-soft/fasta/pubseqgbs_test";
+open(LIST_FILE,$file);
+
+$EXEC="/nfs/disk222/yeastpub/bio-soft/fasta/fasta33";
+$EXEC="/software/pathogen/external/applications/fasta/fasta35";
+$NEW_WDIR=".";
+$NUM_JOBS=0;
+
+while(my $inFile = <LIST_FILE>)
+{
+ $NUM_JOBS++;
+ chomp($inFile);
+
+ if($inFile =~ m/^($pwd)(.*)/)
+ {
+ my $inFile_tmp = $2;
+ while($inFile_tmp =~ m/^(\/)(.*)/)
+ {
+ $inFile_tmp = $2;
+ }
+
+ if( -e $inFile_tmp )
+ {
+ $inFile = $inFile_tmp;
+ }
+ }
+
+
+ if($inFile =~ m/^(\S{100})/)
+ {
+ if($inFile =~ m/^(\S{90,})(fasta\/\S+)/)
+ {
+ my $inFile_tmp = $1;
+
+ if( -e $inFile_tmp )
+ {
+ $NEW_WDIR=$inFile_tmp;
+ $inFile = $2;
+ }
+ }
+ }
+ # find number of leading zero
+
+ if($inFile =~ m/^(.*)(fasta\/)([^\/]*)(0{4})(\d{1})$/)
+ {
+ push(@jobs10, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(fasta\/)([^\/]*)(0{3})(\d{2})$/)
+ {
+ push(@jobs100, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(fasta\/)([^\/]*)(0{2})(\d{3})$/)
+ {
+ push(@jobs1000, $inFile);
+ }
+ elsif($inFile =~ m/^(.*)(fasta\/)([^\/]*)(0{1})(\d{4})$/)
+ {
+ push(@jobs10000, $inFile);
+ }
+ else
+ {
+ push(@jobs100000, $inFile);
+ }
+}
+close LIST_FILE;
+
+if(defined @jobs10)
+{
+ my( $num ) = sprintf( "%04d", 0);
+ submit($num, @jobs10);
+}
+
+if(defined @jobs100)
+{
+ my( $num ) = sprintf( "%03d", 0);
+ submit($num, @jobs100);
+}
+
+if(defined @jobs1000)
+{
+ my( $num ) = sprintf( "%02d", 0);
+ submit($num, @jobs1000);
+}
+
+if(defined @jobs10000)
+{
+ my( $num ) = sprintf( "%01d", 0);
+ submit($num, @jobs10000);
+}
+
+if(defined @jobs100000)
+{
+ submit("", @jobs100000);
+}
+
+for($count = 0; $count < @bsub_jobs; $count++)
+{
+ open(BSUB, "| bsub -q normal -R \"select[mem > 1] rusage[mem=1]\" -o /dev/null -e /dev/null -w \"ended($bsub_jobs[$count])\" -K -J \"$bsub_jobs[$count]\_fin\"") or die "could not open bsub pipe : $!";
+ print BSUB "\"echo finished > /dev/null\" > /dev/null 2> /dev/null";
+ close BSUB; # or die "--Could not submit job : $!";
+}
+
+
+sub submit
+{
+ my ($num, @jobs) = @_;
+
+ my $prefix;
+ my @starts;
+ my @prefixes;
+
+ if($jobs[0] =~ m/^(.*)(\/fasta\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2.$3;
+ push(@starts, "$4");
+ }
+ elsif($jobs[0] =~ m/^(fasta\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2;
+ push(@starts, "$3");
+ }
+
+ # escape characters
+ if($prefix =~ /(\+|\?|\*|\[|\])/)
+ {
+ $prefix =~ s/\+/\\+/;
+ $prefix =~ s/\?/\\?/;
+ $prefix =~ s/\*/\\*/;
+ $prefix =~ s/\[/\\[/;
+ $prefix =~ s/\]/\\]/;
+ }
+ push(@prefixes, $prefix);
+
+
+ #different entries have different prefixes
+ for($count =0; $count < @jobs; $count++)
+ {
+ if($jobs[$count] !~ m/^$prefix(.*)/)
+ {
+ if($jobs[$count] =~ m/^(.*)(fasta\/)([^\/]*)(\d{5})/)
+ {
+ $prefix = $1.$2.$3;
+
+ push(@starts, "$4");
+
+ if($prefix =~ /(\+|\?|\*|\[|\])/)
+ {
+ $prefix =~ s/\+/\\+/;
+ $prefix =~ s/\?/\\?/;
+ $prefix =~ s/\*/\\*/;
+ $prefix =~ s/\[/\\[/;
+ $prefix =~ s/\]/\\]/;
+ }
+
+ push(@prefixes, $prefix);
+ }
+ }
+ }
+
+ for($count =0; $count < @prefixes; $count++)
+ {
+ $prefix = $prefixes[$count];
+ $start = $starts[$count];
+
+ # build the index description that need to be run
+ $index="$start-";
+ $end="$start";
+ for($j =0; $j < @jobs; $j++)
+ {
+ if($jobs[$j] =~ m/^$prefix(.*)/)
+ {
+ if($jobs[$j] =~ m/^(.*)(fasta\/)([^\/]*)(\d{5})/)
+ {
+ if($j == @jobs-1)
+ {
+ $index = "$index$4";
+ }
+ elsif($end+1 >= $4)
+ {
+ $end = "$4";
+ }
+ else
+ {
+ $index = "$index$end,$4-"
+ }
+ }
+ }
+ }
+
+ if($index =~ m/(\-)$/)
+ {
+ $index = "$index$end";
+ }
+
+ bsub($prefix, $index, $num);
+ }
+}
+
+
+# start job arrays
+sub bsub
+{
+ my ($prefix, $index, $num) = @_;
+
+ my $name = $prefix;
+
+ if($prefix =~ m/(\/fasta\/)(.*)/)
+ {
+ $name = "$2";
+ }
+
+ my $random = int( rand( 999+1 ) );
+
+ push(@bsub_jobs, "$name$random\_fasta");
+
+ my $QUEUE="long";
+ if($NUM_JOBS <= 6)
+ {
+ $QUEUE="normal";
+ }
+
+ open(BSUB, "| bsub -q $QUEUE -o /dev/null -n 1 -R \"select[mem > 500] rusage[mem=500]\" -J$name$random\_fasta\"[$index]%16\"") or die "could not open bsub pipe : $!";
+ print BSUB "cd $NEW_WDIR\n";
+ print BSUB "$EXEC -B -S -q -b 100 -H $prefix$num";
+ print BSUB "\${LSB_JOBINDEX} $database ktup 2 > $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\.out\n";
+
+ print BSUB "gzip -9f $prefix$num";
+ print BSUB "\${LSB_JOBINDEX}\.out\n";
+
+ close BSUB or die "--Could not submit job : $!";
+}
+
+'
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+HOSTNAME=`hostname`
+REMOTE=N
+
+case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+esac
+
+if [ x$ONEFILE = x ]
+then
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+ CMD=`echo $PERL_PROG_1`
+ ssh babel "cd $WDIR; perl -w -e '$CMD' \"$1\" \"$EXPANDED_DATABASE\" \"$PWD\""
+ else
+ perl -w -e "$PERL_PROG" "$1" "$EXPANDED_DATABASE" "$PWD"
+ fi
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_fastx b/etc/run_fastx
new file mode 100644
index 0000000..a639550
--- /dev/null
+++ b/etc/run_fastx
@@ -0,0 +1,89 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_fastx,v 1.3 2005-02-21 11:54:10 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+#EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+eval EXPANDED_DATABASE="$DATABASE"
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+
+ FASTLIBS=/nfs/disk222/yeastpub/bio-soft/fasta/pubseqgbs export FASTLIBS
+ EXEC=/nfs/disk222/yeastpub/bio-soft/fasta/fastx33_t
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using database $DATABASE" 1>&2
+
+ # add/change the flags to suit your site:
+ COMMAND="$EXEC -B -S -q -b 40 -H $INPUT_FILE $DATABASE ktup 2"
+
+ echo "command line: $COMMAND" 1>&2
+
+ $COMMAND 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_fastx.sanger b/etc/run_fastx.sanger
new file mode 100644
index 0000000..1204924
--- /dev/null
+++ b/etc/run_fastx.sanger
@@ -0,0 +1,103 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_fastx.sanger,v 1.1 2005-02-21 11:53:49 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+
+ FASTLIBS=/nfs/disk222/yeastpub/bio-soft/fasta/pubseqgbs export FASTLIBS
+ EXEC=/nfs/disk222/yeastpub/bio-soft/fasta/fastx33_t
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using database $DATABASE" 1>&2
+
+ # add/change the flags to suit your site:
+ HOSTNAME=`hostname`
+ REMOTE=N
+
+ case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+ esac
+
+ COMMAND="$EXEC -B -S -q -b 40 -H $INPUT_FILE $DATABASE ktup 2"
+ echo "command line: $COMMAND" 1>&2
+
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+ rsh babel "cd $WDIR; $COMMAND >! $OUTPUT_FILE"
+ else
+ $COMMAND 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+ fi
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_hth b/etc/run_hth
new file mode 100755
index 0000000..a61aeca
--- /dev/null
+++ b/etc/run_hth
@@ -0,0 +1,148 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_hth,v 1.1 2004-06-09 10:03:06 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ PARAMETERS=$3 export PARAMETERS
+else
+ if [ $# = 2 ]
+ then
+ PARAMETERS=$2 export PARAMETERS
+ else
+ echo usage: $0 -onefile input_file output_file parameters 1>&2
+ echo or: $0 file_of_filenames parameters 1>&2
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+EXPANDED_PARAMETERS=`echo "echo $PARAMETERS" | /bin/csh -f`
+
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ PARAMETERS=$3
+
+
+ ### change these lines:
+ EXEC=helixturnhelix
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using database $PARAMETERS" 1>&2
+ echo "command line: $EXEC $PARAMETERS $INPUT_FILE" 1>&2
+
+ $EXEC $INPUT_FILE $OUTPUT_FILE $PARAMETERS 2> hth_errors.new
+
+ # add/change the flags to suit your site:
+ $EXEC $INPUT_FILE -outfile $OUTPUT_FILE $PARAMETERS 2>&1 |
+ tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_PARAMETERS
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_PARAMETERS
+fi
+
+if [ x$ONEFILE = x ]
+then
+ PEPFILES=`cat $1`
+else
+ PEPFILES=$1
+fi
+
+if [ -e hth/hth.tab ]
+then
+ mv -f hth/hth.tab hth/hth.tab.old
+fi
+
+for i in $PEPFILES
+do
+# write a tab file containing a feature for each hth
+perl -e '
+open PEPFILE, "$ARGV[0]" or die "$0: cannot open $ARGV[0]\n";
+
+my $first_line = <PEPFILE>;
+
+close PEPFILE;
+
+if ($first_line !~ m/^>(.*?), (.*) (\d+):(\d+)\s+(\S+)/) {
+ die "first line of $ARGV[0] is badly formatted\n";
+}
+
+my ($gene_name, $description, $pep_first_base,
+ $pep_last_base, $forward_or_back) = ($1, $2, $3, $4, $5);
+
+open HTHFILE, "$ARGV[1]" or die "$0: cannot open $ARGV[1]\n";
+
+my $score;
+my $first_match_base;
+
+while (<HTHFILE>) {
+ if (m/^Score (\d+) .* in .* at residue (\d+)/) {
+ $score = $1;
+ $first_match_base = $2;
+ }
+
+ if (m/^\s+Sequence:\s+(\S+)/ &&
+ defined ($score) && defined ($pep_first_base)) {
+ if ($forward_or_back eq "forward") {
+ my $start_base = $pep_first_base + ($first_match_base - 1) * 3;
+ my $end_base = $start_base + (length $1) * 3 - 1;
+ print <<EOF;
+FT CDS_motif $start_base..$end_base
+FT /note="Helix-turn-helix from $gene_name score $score"
+FT /label=hth
+EOF
+ } else {
+ my $end_base = $pep_last_base - ($first_match_base - 1) * 3;
+ my $start_base = $end_base - ((length $1) * 3 - 1);
+ print <<EOF;
+FT CDS_motif complement($start_base..$end_base)
+FT /note="Helix-turn-helix from $gene_name score $score"
+FT /label=hth
+EOF
+ }
+ }
+}
+' $i $i.out >> hth/hth.tab
+
+done
+
+exit 0
diff --git a/etc/run_jalview b/etc/run_jalview
new file mode 100755
index 0000000..968b9c8
--- /dev/null
+++ b/etc/run_jalview
@@ -0,0 +1,250 @@
+#!/bin/sh -
+
+# This script needs one argument - a file of file names.
+
+PATH=/nfs/disk100/pubseq/emboss/bin/:$PATH
+export PATH
+
+PERL_PROG='
+
+use strict;
+
+if (@ARGV < 1)
+{
+ die "$0 needs one argument - a file of sequence file names\n";
+}
+
+my $file = $ARGV[0];
+
+chomp $file;
+
+if (-e $file)
+{
+ my $msf_file = "$file.fasta_msf";
+
+ my $seqnum = 1;
+ printf "$msf_file\n";
+
+ open(LIST_FILE,$file);
+ while(my $line = <LIST_FILE>)
+ {
+ printf "$line\n";
+ chomp($line);
+
+ #
+ # run descseq to ensure unique names
+ #
+ my $emboss_command_line = "descseq -append -name _$seqnum $line $line.new -auto";
+
+ open EMBOSS, "|$emboss_command_line" or
+ die "cannot open pipe to descseq: $!\n";
+ close EMBOSS;
+
+ unlink("$line");
+ rename("$line.new","$line");
+ $seqnum++;
+ }
+
+ my $emboss_command_line = "emma \"\@$file\" -filter -stdout -osf msf -dendoutfile /dev/null -outseq $msf_file";
+
+ open EMBOSS, "|$emboss_command_line" or
+ die "cannot open pipe to emma: $!\n";
+ close EMBOSS;
+ system "jalview", "$msf_file";
+}
+else
+{
+ die "$file does not exist";
+}
+'
+
+PERL_PROG_FASTA='
+
+use strict;
+
+if (@ARGV != 1)
+{
+ die "$0 needs one argument - a file of feature file names\n";
+}
+
+
+sub process_fasta
+{
+ my $feature_file = shift;
+ my $fasta_results_file = shift;
+
+ my $sequence_file;
+
+ if ($fasta_results_file =~ /(.*).out/) {
+ $sequence_file = $1;
+ } else {
+ die "cannot understand $fasta_results_file\n";
+ }
+
+
+ if (! -e $sequence_file) {
+ if ($feature_file =~ /(.*)(\/jalview\/)(.+)$/)
+ {
+ $sequence_file = "$1/$sequence_file";
+ if (! -e $fasta_results_file &&
+ ! -e "$fasta_results_file.gz")
+ {
+ $fasta_results_file = "$1/$fasta_results_file";
+ }
+ }
+ if (! -e $sequence_file)
+ {
+ die "cannot find $sequence_file\n";
+ }
+ }
+
+
+ my $fasta_seq_for_alignment = "";
+
+ open SEQ_FILE, $sequence_file or die "cannot open $sequence_file: $!\n";
+
+ while (<SEQ_FILE>) {
+ $fasta_seq_for_alignment .= $_;
+ }
+
+ close SEQ_FILE;
+
+ if (-e $fasta_results_file) {
+ open FASTA_OUTPUT, $fasta_results_file
+ or die "cannot open $fasta_results_file: $!\n";
+ } else {
+ my $gzip_fasta_results_file = $fasta_results_file . ".gz";
+ if (-e $gzip_fasta_results_file) {
+ open FASTA_OUTPUT, "gzip -d < $gzip_fasta_results_file |"
+ or die "cannot open $gzip_fasta_results_file: $!\n";
+ } else {
+ die "cannot find $fasta_results_file or $gzip_fasta_results_file\n";
+ }
+ }
+
+
+ my $top_re = "^The best scores are:";
+
+ my $seen_top = 0;
+ my $seen_bottom = 0;
+
+ my @protein_ids = ();
+
+ while (<FASTA_OUTPUT>) {
+ if (/$top_re/) {
+ $seen_top = 1;
+ next;
+ }
+
+ if ($seen_top && /^\s*$/) {
+ $seen_bottom = 1;
+ next;
+ }
+
+ if ($seen_top && !$seen_bottom) {
+ if (/^(\S+)/) {
+ if (@protein_ids < 20) {
+ push @protein_ids, "$1"
+ } else {
+ last;
+ }
+ } else {
+ warn "cannot understand this line:\n$_\n";
+ }
+ }
+ }
+
+ my %hash = ();
+
+ @hash{@protein_ids} = (1) x @protein_ids;
+
+ @protein_ids = sort keys %hash;
+
+ my $protein_db = "uniprot";
+
+ # look for each of the IDs from the FASTA output in each of the DBs
+
+ for my $id (@protein_ids) {
+ my $fetch = "getz -sf fasta -f seq [uniprot-acc:$id]";
+
+ my $temp_seq = "";
+
+ open FETCH, "$fetch |" or
+ die "cannot open pipe to $fetch: $!\n";
+
+ while (<FETCH>) {
+ $temp_seq .= $_;
+ }
+
+ close FETCH;
+
+ if ($? == 0) {
+ $fasta_seq_for_alignment .= $temp_seq;
+ } else {
+ print STDERR "$id was not found in $protein_db\n";
+ }
+ }
+
+ my $msf_file = "$feature_file.fasta_msf";
+
+ my $emboss_prog = "emma";
+
+ my $emboss_command_line = "$emboss_prog -filter -stdout -osf msf -dendoutfile /dev/null > $msf_file";
+
+ open EMBOSS, "|$emboss_command_line" or
+ die "cannot open pipe to $emboss_prog: $!\n";
+
+ print EMBOSS $fasta_seq_for_alignment;
+
+ close EMBOSS;
+
+ my $jalview_prog = "jalview";
+
+ print STDERR "\nstarting $jalview_prog:\n";
+
+ system "$jalview_prog", "$msf_file";
+}
+
+
+my $file;
+
+while (defined ($file = <>)) {
+ chomp $file;
+
+ if (-e $file) {
+ open IN_FILE, "$file\n" or die "cannot open $file\n";
+
+ my $line;
+
+ while (defined ($line = <IN_FILE>)) {
+ if ($line =~ m!/fasta_file="(.*)"!) {
+ my $fasta_results_file = $1;
+
+ process_fasta $file, $fasta_results_file;
+
+ last;
+ }
+ }
+
+ close IN_FILE;
+ }
+}'
+
+echo "Arguments: $@"
+test=`wc -l "$@" | awk '{print $1}'`
+
+if [ $test != 1 ]
+then
+ perl -w -e "$PERL_PROG" "$@"
+else
+ perl -w -e "$PERL_PROG_FASTA" "$@"
+fi
+
+#if [ $# != 1 ]
+#then
+# perl -w -e "$PERL_PROG" "$@"
+#else
+# perl -w -e "$PERL_PROG_FASTA" "$@"
+#fi
+
+
diff --git a/etc/run_netblastp b/etc/run_netblastp
new file mode 100755
index 0000000..ff6bef2
--- /dev/null
+++ b/etc/run_netblastp
@@ -0,0 +1,93 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_netblastp,v 1.1 2008-12-01 08:52:51 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database
+ echo or: $0 file_of_filenames database
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+ EXEC=${EXEC-`which blastcl3 2>/dev/null`}
+
+ if [ ! -x "$EXEC" ]; then
+ EXEC=`find Artemis* -name blastcl3 2>/dev/null`
+ if [ ! -x "$EXEC" ]; then
+ EXEC=`find ../.. -name blastcl3 2>/dev/null`
+ fi
+ fi
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to"
+ echo "$OUTPUT_FILE using database $DATABASE"
+
+ EXTRA_ARGS=
+
+ # add/change the flags to suit your site:
+ $EXEC -d $DATABASE -i $INPUT_FILE -p blastp \
+ $EXTRA_ARGS -o $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_pepstats b/etc/run_pepstats
new file mode 100755
index 0000000..22239de
--- /dev/null
+++ b/etc/run_pepstats
@@ -0,0 +1,77 @@
+#!/bin/sh -
+
+# this script will run pepstats on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_pepstats,v 1.1 2004-06-09 10:03:10 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ PARAMETERS=$3 export PARAMETERS
+else
+ if [ $# = 2 ]
+ then
+ PARAMETERS=$2 export PARAMETERS
+ else
+ echo usage: $0 -onefile input_file output_file parameters 1>&2
+ echo or: $0 file_of_filenames parameters 1>&2
+ exit 1
+ fi
+fi
+
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+
+ ### change these lines:
+ EXEC=pepstats
+
+ COMMAND_LINE="$EXEC -filter $INPUT_FILE"
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using parameters: $PARAMETERS" 1>&2
+ echo "command line: $COMMAND_LINE" 1>&2
+
+ # add/change the flags to suit your site:
+ (head -1 $INPUT_FILE | sed 's/^>//'; echo;
+ $COMMAND_LINE) 2>&1 > $OUTPUT_FILE | tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out
+ done
+
+else
+ run_one_prog $1 $2
+fi
+
+exit 0
diff --git a/etc/run_sigcleave b/etc/run_sigcleave
new file mode 100755
index 0000000..a8a0733
--- /dev/null
+++ b/etc/run_sigcleave
@@ -0,0 +1,78 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_sigcleave,v 1.1 2004-06-09 10:03:11 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ MINWEIGHT=$3
+else
+ if [ $# = 2 ]
+ then
+ MINWEIGHT=$2
+ else
+ echo usage: $0 -onefile input_file output_file minimum_weight 1>&2
+ echo or: $0 file_of_filenames minimum_weight 1>&2
+ exit 1
+ fi
+fi
+
+PARAMETERS="-minweight $MINWEIGHT"
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ PARAMETERS="$3 $4 $5 $6 $7 $8 $9"
+
+ ### change these lines:
+ EXEC=sigcleave
+
+ COMMAND_LINE="$EXEC $INPUT_FILE -outfile $OUTPUT_FILE $PARAMETERS"
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using parameters: $PARAMETERS" 1>&2
+ echo "command line: $COMMAND_LINE" 1>&2
+
+ # add/change the flags to suit your site:
+ $COMMAND_LINE 2>&1 | tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $PARAMETERS
+ done
+
+else
+ run_one_prog $1 $2 $PARAMETERS
+fi
+
+exit 0
diff --git a/etc/run_smart b/etc/run_smart
new file mode 100755
index 0000000..cc9ffb5
--- /dev/null
+++ b/etc/run_smart
@@ -0,0 +1,95 @@
+#!/bin/sh -
+
+# this script will run SMART (http://smart.embl-heidelberg.de/) for an
+# sequence input file or on each file in a file of filenames. it uses
+# lynx to access send a query to the SMART web site and then writes
+# the output to a file in the same way as run_fasta and run_blastp.
+# see:
+# http://www.sanger.ac.uk/Software/Artemis/stable/manual/runmenu.html#RUNMENU-CONFIGURATION
+# for more details.
+
+# you will need the "GET" command from the perl LWP module to use this script
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_smart,v 1.1 2004-06-09 10:03:12 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 3 -a x$1 = x-onefile -o $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+else
+ if [ $# != 2 -a $# = != 1 ]
+ then
+ echo usage: $0 -onefile input_file output_file 1>&2
+ echo or: $0 file_of_filenames 1>&2
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+EXPANDED_PARAMETERS=`echo "echo $PARAMETERS" | /bin/csh -f`
+
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ PARAMETERS=$3
+
+ # remove the rubbish from the FASTA header and change the sequence
+ # into URL form
+ SEQUENCE=`perl -pne 'chomp; s/^>.*/>sequence/; s/ /\%20/g; $_ .= "%0D"' $INPUT_FILE`
+
+ SEQUENCE=`echo $SEQUENCE | sed 's/%0D$//'`
+
+ URL='http://smart.embl-heidelberg.de/smart/show_motifs.pl?INCLUDE_SIGNALP=INCLUDE_SIGNALP&DO_PROSPERO=DO_PROSPERO&DO_PFAM=DO_PFAM&SEQUENCE='"$SEQUENCE"
+
+ ### change these lines:
+ EXEC=lynx
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE" 1>&2
+
+ COMMAND="$EXEC -force_html -dump $URL"
+
+ echo "command line: $COMMAND" 1>&2
+
+ (echo "read from $URL"; echo) > $OUTPUT_FILE
+
+ $COMMAND 2>&1 >> $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+
+ #### end of changes
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_PARAMETERS
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_PARAMETERS
+fi
+
+exit 0
diff --git a/etc/run_tblastn b/etc/run_tblastn
new file mode 100755
index 0000000..151b783
--- /dev/null
+++ b/etc/run_tblastn
@@ -0,0 +1,115 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_tblastn,v 1.3 2005-12-20 13:50:05 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+#EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+eval EXPANDED_DATABASE="$DATABASE"
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+ EXEC=${EXEC-`which blastall 2>/dev/null`}
+
+ if [ ! -x "$EXEC" ]; then
+ EXEC=`find Artemis* -name blastall 2>/dev/null`
+ fi
+
+ if [ ! -d "$BLASTDB" ]; then
+ DATABASE_TMP="$PWD/"`find Artemis* -name blast-data 2>/dev/null`"/$DATABASE"
+ if [ -f "$DATABASE_TMP" ]; then
+ DATABASE="$DATABASE_TMP"
+ fi
+ fi
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using database $DATABASE" 1>&2
+ echo "command line: $EXEC $DATABASE $INPUT_FILE" 1>&2
+
+
+ EXTRA_ARGS=
+
+ # add/change the flags to suit your site:
+ HOSTNAME=`hostname`
+ REMOTE=N
+
+ case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+ esac
+
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+ rsh babel "cd $WDIR; lsrun -v blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p tblastn \
+ $EXTRA_ARGS >! $OUTPUT_FILE"
+ else
+ lsrun -v blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p tblastn \
+ $EXTRA_ARGS 2>&1 > $OUTPUT_FILE |
+ tee ${PROG}_errors.new 1>&2
+ fi
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_tblastx b/etc/run_tblastx
new file mode 100755
index 0000000..4f7fee6
--- /dev/null
+++ b/etc/run_tblastx
@@ -0,0 +1,116 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: //tmp/pathsoft/artemis/etc/run_tblastx,v 1.4 2005-12-20 13:50:10 tjc Exp $"
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+#EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+eval EXPANDED_DATABASE="$DATABASE"
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+
+
+ ### change these lines:
+ EXEC=${EXEC-`which blastall 2>/dev/null`}
+
+ if [ ! -x "$EXEC" ]; then
+ EXEC=`find Artemis* -name blastall 2>/dev/null`
+ fi
+
+ if [ ! -d "$BLASTDB" ]; then
+ DATABASE_TMP="$PWD/"`find Artemis* -name blast-data 2>/dev/null`"/$DATABASE"
+ if [ -f "$DATABASE_TMP" ]; then
+ DATABASE="$DATABASE_TMP"
+ fi
+ fi
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using database $DATABASE" 1>&2
+
+
+ EXTRA_ARGS=
+
+ # add/change the flags to suit your site:
+ HOSTNAME=`hostname`
+ REMOTE=N
+
+ case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+ esac
+
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+# rsh babel "cd $WDIR; lsrun -v blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p tblastx \
+# $EXTRA_ARGS >! $OUTPUT_FILE"
+ rsh babel "cd $WDIR; bsub -q longblastq -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl -tx $DATABASE $INPUT_FILE $EXTRA_ARGS"
+ else
+# lsrun -v blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p tblastx \
+# $EXTRA_ARGS 2>&1 > $OUTPUT_FILE |
+# tee ${PROG}_errors.new 1>&2
+ bsub -q longblastq -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl -tx $DATABASE $INPUT_FILE $EXTRA_ARGS
+ fi
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ gzip -9 $OUTPUT_FILE &
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE
+fi
+
+exit 0
diff --git a/etc/run_tblastx.sanger b/etc/run_tblastx.sanger
new file mode 100755
index 0000000..ab69ee8
--- /dev/null
+++ b/etc/run_tblastx.sanger
@@ -0,0 +1,132 @@
+#!/bin/sh -
+
+# this script will run a search program on a sequence input file or on each
+# file in a file of filenames
+
+# to customise this script see the function called run_one_prog below
+
+
+RCS_HEADER="$Header: /cvsroot/pathsoft/artemis/etc/run_tblastx,v 1.4 2005-12-20 13:50:10 tjc Exp $"
+
+cleanUp()
+{
+ echo $1
+ cat $1 | while thisLine=`line`
+ do
+ rm -f $thisLine
+ done
+ rm -f $1
+}
+
+PROG=`echo $RCS_HEADER | sed 's/.*run_\(.*\),v.*/\1/'`
+
+
+if [ $# = 4 -a x$1 = x-onefile ]
+then
+ shift
+ ONEFILE=t
+ DATABASE=$3 export DATABASE
+else
+ if [ $# = 2 ]
+ then
+ DATABASE=$2 export DATABASE
+ else
+ echo usage: $0 -onefile input_file output_file database 1>&2
+ echo or: $0 file_of_filenames database 1>&2
+ exit 1
+ fi
+fi
+
+
+# expand any ~ or environment variables
+EXPANDED_DATABASE=`echo "echo $DATABASE" | /bin/csh -f`
+
+
+### change this function to suit your site:
+
+run_one_prog () {
+ INPUT_FILE=$1
+ OUTPUT_FILE=$2
+ DATABASE=$3
+ DIRNAME=$4
+
+ ### change these lines:
+ EXEC=${EXEC-`which blastall 2>/dev/null`}
+
+ if [ ! -x "$EXEC" ]; then
+ EXEC=`find Artemis* -name blastall 2>/dev/null`
+ fi
+
+ if [ ! -d "$BLASTDB" ]; then
+ DATABASE_TMP="$PWD/"`find Artemis* -name blast-data 2>/dev/null`"/$DATABASE"
+ if [ -f "$DATABASE_TMP" ]; then
+ DATABASE="$DATABASE_TMP"
+ fi
+ fi
+
+ echo "about to start $EXEC with input from $INPUT_FILE and output to" 1>&2
+ echo "$OUTPUT_FILE using database $DATABASE" 1>&2
+
+
+ EXTRA_ARGS=
+
+ # add/change the flags to suit your site:
+ HOSTNAME=`hostname`
+ REMOTE=N
+
+ case $HOSTNAME in
+ deskpro*)
+ REMOTE=Y ;;
+ *)
+ esac
+
+ if [ $REMOTE = "Y" ]; then
+ WDIR=`pwd`
+ export WDIR
+# rsh babel "cd $WDIR; lsrun -v blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p tblastx \
+# $EXTRA_ARGS >! $OUTPUT_FILE"
+ rsh babel "cd $WDIR; bsub -q longblastq -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl -tx $DATABASE $INPUT_FILE $EXTRA_ARGS"
+ else
+# lsrun -v blastwrap.pl $EXEC -d $DATABASE -i $INPUT_FILE -p tblastx \
+# $EXTRA_ARGS 2>&1 > $OUTPUT_FILE |
+# tee ${PROG}_errors.new 1>&2
+ bsub -q long -o $OUTPUT_FILE -e ${PROG}_errors.new -I flexi_blast.pl -tx $DATABASE $INPUT_FILE $EXTRA_ARGS
+ fi
+
+ #### end of changes
+
+
+ # Artemis can read compressed files
+ count=0; while (test ! -f $OUTPUT_FILE) && (test $count -lt 1000) ; do sleep 2; count+=1; done
+
+ if [ -s ${PROG}_errors.new ]
+ then
+ ( echo ERROR running $PROG: ; echo;
+ echo ===================================================
+ cat ${PROG}_errors.new ) >> $OUTPUT_FILE
+ cat ${PROG}_errors.new >> ${PROG}_errors
+ fi
+
+ gzip -9 $OUTPUT_FILE
+ zip -j ${DIRNAME}tblastx.zip ${OUTPUT_FILE}.gz
+ rm -f ${OUTPUT_FILE}.gz
+}
+
+(echo "#!/bin/sh -"; echo "kill $$") > $PROG.kill
+
+chmod a+x $PROG.kill
+DIR=`dirname $PWD/$1`
+
+if [ x$ONEFILE = x ]
+then
+ for i in `cat $1`
+ do
+ run_one_prog $i $i.out $EXPANDED_DATABASE "$DIR/"
+ done
+
+else
+ run_one_prog $1 $2 $EXPANDED_DATABASE "$DIR/"
+fi
+
+cleanUp $1
+exit 0
diff --git a/etc/versions b/etc/versions
new file mode 100644
index 0000000..8ed02d7
--- /dev/null
+++ b/etc/versions
@@ -0,0 +1,4 @@
+Artemis Release 16.0.0
+ACT Release 13.0.0
+DNAPlotter Release 1.11
+BamView 1.2.10
\ No newline at end of file
diff --git a/etc/writedb_entry b/etc/writedb_entry
new file mode 100755
index 0000000..d747fc4
--- /dev/null
+++ b/etc/writedb_entry
@@ -0,0 +1,71 @@
+#!/bin/sh -
+#
+# This script reads and writes entries from a database.
+# Examples:
+# writedb_entry -help
+# writedb_entry -s Pf3D7_01 Pf3D7_05 Pf3D7_07
+#
+
+#if [ -z "$ARTEMIS_HOME" ]; then
+# PRG=`readlink -f "$0"`
+# echo $PRG
+# ARTEMIS_HOME=`dirname "$PRG"`/..
+#fi
+
+PRG=$0
+progname=`basename $0`
+
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '.*/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname $PRG`/$link"
+ fi
+done
+
+ARTEMIS_HOME=`dirname "$PRG"`/..
+
+CLASSPATH="$ARTEMIS_HOME:$ARTEMIS_HOME/lib/biojava.jar:$ARTEMIS_HOME/lib/jemAlign.jar:$ARTEMIS_HOME/lib/jakarta-regexp-1.2.jar:$ARTEMIS_HOME/lib/macos.jar:$ARTEMIS_HOME/lib/postgresql-8.4-701.jdbc3.jar:$ARTEMIS_HOME/lib/chado-14-interface.jar:$CLASSPATH"
+
+# iBatis jars
+CLASSPATH="$CLASSPATH:$ARTEMIS_HOME/lib/ibatis/ibatis-2.3.4.726.jar:$ARTEMIS_HOME/lib/ibatis/:$ARTEMIS_HOME/lib/ibatis/log4j-1.2.14.jar:$ARTEMIS_HOME/lib/ibatis/cglib-nodep-2.2.jar:$ARTEMIS_HOME/lib/retrotranslator-runtime-1.1.0.jar"
+CLASSPATH="$CLASSPATH:$ARTEMIS_HOME/lib/picard/picard.jar"
+export CLASSPATH
+
+FLAGS="-mx2048m -ms20m -Djdbc.drivers=org.postgresql.Driver -Dibatis"
+DEFAULT_CONNECTION="-Dchado=db.genedb.org:5432/snapshot?genedb_ro -Dread_only"
+
+while test $# != 0
+ do
+ case $1 in
+ -Dchado*)
+ DEFAULT_CONNECTION="$1" ;;
+ -D*)
+ FLAGS="$FLAGS $1" ;;
+ *) break ;;
+ esac
+ shift
+done
+
+# searches for the -c option (needs to know the index)
+idx=0
+for arg in "$@"
+do
+ if [ '-c' == "${arg}" ]; then
+ let "nextID = $idx + 1";
+ DEFAULT_CONNECTION="-Dchado=${!nextID}"
+ fi
+ if [ '-l' == "${arg}" ]; then
+ let "nextID = $idx + 2";
+ eval MAPPING_PATH="${!nextID}"
+ CLASSPATH="${MAPPING_PATH}:$CLASSPATH"
+ export CLASSPATH
+ fi
+ let idx++
+done
+
+echo starting to write with flags: $DEFAULT_CONNECTION $FLAGS 1>&2
+
+java $DEFAULT_CONNECTION $FLAGS uk.ac.sanger.artemis.io.ReadAndWriteEntry "$@"
diff --git a/images/PSUlogo.gif b/images/PSUlogo.gif
new file mode 100644
index 0000000..0d64f41
Binary files /dev/null and b/images/PSUlogo.gif differ
diff --git a/images/act.gif b/images/act.gif
new file mode 100644
index 0000000..5afc344
Binary files /dev/null and b/images/act.gif differ
diff --git a/images/helix.gif b/images/helix.gif
new file mode 100644
index 0000000..1f6c7af
Binary files /dev/null and b/images/helix.gif differ
diff --git a/images/icon.gif b/images/icon.gif
new file mode 100644
index 0000000..d47e3c0
Binary files /dev/null and b/images/icon.gif differ
diff --git a/images/sanger-centre.gif b/images/sanger-centre.gif
new file mode 100644
index 0000000..92dfe5a
Binary files /dev/null and b/images/sanger-centre.gif differ
diff --git a/org/gmod/schema/analysis/Analysis.java b/org/gmod/schema/analysis/Analysis.java
new file mode 100644
index 0000000..280da69
--- /dev/null
+++ b/org/gmod/schema/analysis/Analysis.java
@@ -0,0 +1,242 @@
+package org.gmod.schema.analysis;
+
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+
+
+
+
+
+
+
+
+
+public class Analysis implements Serializable {
+
+ // Fields
+
+
+ private int analysisId;
+
+
+ private String name;
+
+
+ private String description;
+
+
+ private String program;
+
+
+ private String programVersion;
+
+
+ private String algorithm;
+
+
+ private String sourceName;
+
+
+ private String sourceVersion;
+
+
+ private String sourceUri;
+
+
+ private Date timeExecuted;
+
+
+ private Set<AnalysisFeature> analysisFeatures = new HashSet<AnalysisFeature>(0);
+
+
+ private Set<AnalysisProp> analysisProps = new HashSet<AnalysisProp>(0);
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getAnalysisId()
+ */
+ public int getAnalysisId() {
+ return this.analysisId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setAnalysisId(int)
+ */
+ public void setAnalysisId(int analysisId) {
+ this.analysisId = analysisId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getName()
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setName(java.lang.String)
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getDescription()
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setDescription(java.lang.String)
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getProgram()
+ */
+ public String getProgram() {
+ return this.program;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setProgram(java.lang.String)
+ */
+ public void setProgram(String program) {
+ this.program = program;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getProgramversion()
+ */
+ public String getProgramVersion() {
+ return this.programVersion;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setProgramversion(java.lang.String)
+ */
+ public void setProgramVersion(String programVersion) {
+ this.programVersion = programVersion;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getAlgorithm()
+ */
+ public String getAlgorithm() {
+ return this.algorithm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setAlgorithm(java.lang.String)
+ */
+ public void setAlgorithm(String algorithm) {
+ this.algorithm = algorithm;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getSourcename()
+ */
+ public String getSourceName() {
+ return this.sourceName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setSourcename(java.lang.String)
+ */
+ public void setSourceName(String sourceName) {
+ this.sourceName = sourceName;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getSourceversion()
+ */
+ public String getSourceVersion() {
+ return this.sourceVersion;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setSourceversion(java.lang.String)
+ */
+ public void setSourceVersion(String sourceVersion) {
+ this.sourceVersion = sourceVersion;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getSourceuri()
+ */
+ public String getSourceUri() {
+ return this.sourceUri;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setSourceuri(java.lang.String)
+ */
+ public void setSourceUri(String sourceUri) {
+ this.sourceUri = sourceUri;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getTimeexecuted()
+ */
+ public Date getTimeExecuted() {
+ return this.timeExecuted;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setTimeexecuted(java.util.Date)
+ */
+ public void setTimeExecuted(Date timeExecuted) {
+ this.timeExecuted = timeExecuted;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getAnalysisfeatures()
+ */
+ public Collection<AnalysisFeature> getAnalysisFeatures() {
+ return this.analysisFeatures;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setAnalysisfeatures(java.util.Set)
+ */
+ public void setAnalysisFeatures(Set<AnalysisFeature> analysisFeatures) {
+ this.analysisFeatures = analysisFeatures;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#getAnalysisprops()
+ */
+ public Collection<AnalysisProp> getAnalysisProps() {
+ return this.analysisProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisI#setAnalysisprops(java.util.Set)
+ */
+ private void setAnalysisProps(Set<AnalysisProp> analysisProps) {
+ this.analysisProps = analysisProps;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/analysis/AnalysisFeature.java b/org/gmod/schema/analysis/AnalysisFeature.java
new file mode 100644
index 0000000..5bff330
--- /dev/null
+++ b/org/gmod/schema/analysis/AnalysisFeature.java
@@ -0,0 +1,176 @@
+package org.gmod.schema.analysis;
+
+import org.gmod.schema.sequence.Feature;
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class AnalysisFeature implements Serializable {
+
+ // Fields
+
+
+
+ private int analysisFeatureId;
+
+
+
+ private Analysis analysis;
+
+
+
+ private Feature feature;
+
+
+ private Double rawScore;
+
+
+ private Double normScore;
+
+
+ private Double significance;
+
+
+ private Double identity;
+
+ // Constructors
+
+ /** default constructor */
+ public AnalysisFeature() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public AnalysisFeature(Analysis analysis, Feature feature) {
+ this.analysis = analysis;
+ this.feature = feature;
+ }
+ /** full constructor */
+ public AnalysisFeature(Analysis analysis, Feature feature, Double rawScore, Double normScore, Double significance, Double identity) {
+ this.analysis = analysis;
+ this.feature = feature;
+ this.rawScore = rawScore;
+ this.normScore = normScore;
+ this.significance = significance;
+ this.identity = identity;
+ }
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#getAnalysisFeatureId()
+ */
+ public int getAnalysisFeatureId() {
+ return this.analysisFeatureId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#setAnalysisFeatureId(int)
+ */
+ public void setAnalysisFeatureId(int analysisFeatureId) {
+ this.analysisFeatureId = analysisFeatureId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#getAnalysis()
+ */
+ public Analysis getAnalysis() {
+ return this.analysis;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#setAnalysis(org.genedb.db.jpa.Analysis)
+ */
+ public void setAnalysis(Analysis analysis) {
+ this.analysis = analysis;
+ }
+
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#getFeature()
+ */
+ public Feature getFeature() {
+ return this.feature;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#setFeature(org.genedb.db.jpa.Feature)
+ */
+ public void setFeature(Feature feature) {
+ this.feature = feature;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#getRawscore()
+ */
+ public Double getRawScore() {
+ return this.rawScore;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#setRawscore(java.lang.Double)
+ */
+ public void setRawScore(Double rawScore) {
+ this.rawScore = rawScore;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#getNormscore()
+ */
+ public Double getNormScore() {
+ return this.normScore;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#setNormscore(java.lang.Double)
+ */
+ public void setNormScore(Double normScore) {
+ this.normScore = normScore;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#getSignificance()
+ */
+ public Double getSignificance() {
+ return this.significance;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#setSignificance(java.lang.Double)
+ */
+ public void setSignificance(Double significance) {
+ this.significance = significance;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#getIdentity()
+ */
+ public Double getIdentity() {
+ return this.identity;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisFeatureI#setIdentity(java.lang.Double)
+ */
+ public void setIdentity(Double identity) {
+ this.identity = identity;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/analysis/AnalysisProp.java b/org/gmod/schema/analysis/AnalysisProp.java
new file mode 100644
index 0000000..8770dfd
--- /dev/null
+++ b/org/gmod/schema/analysis/AnalysisProp.java
@@ -0,0 +1,104 @@
+package org.gmod.schema.analysis;
+
+
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.utils.propinterface.PropertyI;
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class AnalysisProp implements Serializable, PropertyI {
+
+ // Fields
+
+
+
+ private int analysisPropId;
+
+
+
+ private Analysis analysis;
+
+
+
+ private CvTerm cvTerm;
+
+
+ private String value;
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisPropI#getAnalysispropId()
+ */
+ private int getAnalysisPropId() {
+ return this.analysisPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisPropI#setAnalysispropId(int)
+ */
+ private void setAnalysisPropId(int analysisPropId) {
+ this.analysisPropId = analysisPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisPropI#getAnalysis()
+ */
+ private Analysis getAnalysis() {
+ return this.analysis;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisPropI#setAnalysis(org.genedb.db.jpa.Analysis)
+ */
+ private void setAnalysis(Analysis analysis) {
+ this.analysis = analysis;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisPropI#getCvterm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisPropI#setCvterm(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisPropI#getValue()
+ */
+ private String getValue() {
+ return this.value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.AnalysisPropI#setValue(java.lang.String)
+ */
+ private void setValue(String value) {
+ this.value = value;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/cv/Cv.java b/org/gmod/schema/cv/Cv.java
new file mode 100644
index 0000000..2f15e4f
--- /dev/null
+++ b/org/gmod/schema/cv/Cv.java
@@ -0,0 +1,111 @@
+package org.gmod.schema.cv;
+
+
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+
+
+
+
+
+
+
+
+
+public class Cv implements Serializable {
+
+ // Fields
+
+
+ private int cvId;
+
+
+ private String name;
+
+
+ private String definition;
+
+
+ private Set<CvTermPath> cvTermPaths = new HashSet<CvTermPath>(0);
+
+
+ private Set<CvTerm> cvTerms = new HashSet<CvTerm>(0);
+
+
+ // Property accessors
+
+ public int getCvId() {
+ return this.cvId;
+ }
+
+ public void setCvId(int cvId) {
+ this.cvId = cvId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvI#getName()
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvI#setName(java.lang.String)
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvI#getDefinition()
+ */
+ public String getDefinition() {
+ return this.definition;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvI#setDefinition(java.lang.String)
+ */
+ public void setDefinition(String definition) {
+ this.definition = definition;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvI#getCvTermPaths()
+ */
+ public Collection<CvTermPath> getCvTermPaths() {
+ return this.cvTermPaths;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvI#setCvTermPaths(java.util.Set)
+ */
+ public void setCvTermPaths(Set<CvTermPath> cvTermPaths) {
+ this.cvTermPaths = cvTermPaths;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvI#getCvTerms()
+ */
+ public Collection<CvTerm> getCvTerms() {
+ return this.cvTerms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvI#setCvTerms(java.util.Set)
+ */
+ public void setCvTerms(Set<CvTerm> cvTerms) {
+ this.cvTerms = cvTerms;
+ }
+
+}
+
+
diff --git a/org/gmod/schema/cv/CvTerm.java b/org/gmod/schema/cv/CvTerm.java
new file mode 100644
index 0000000..12ff609
--- /dev/null
+++ b/org/gmod/schema/cv/CvTerm.java
@@ -0,0 +1,648 @@
+package org.gmod.schema.cv;
+
+
+import org.gmod.schema.analysis.AnalysisProp;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.organism.OrganismProp;
+import org.gmod.schema.phylogeny.Phylonode;
+import org.gmod.schema.phylogeny.PhylonodeProp;
+import org.gmod.schema.phylogeny.PhylonodeRelationship;
+import org.gmod.schema.phylogeny.Phylotree;
+import org.gmod.schema.pub.Pub;
+import org.gmod.schema.pub.PubProp;
+import org.gmod.schema.pub.PubRelationship;
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureCvTermProp;
+import org.gmod.schema.sequence.FeatureProp;
+import org.gmod.schema.sequence.FeatureRelationship;
+import org.gmod.schema.sequence.FeatureRelationshipProp;
+import org.gmod.schema.sequence.Synonym;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+
+
+
+
+
+
+
+
+
+
+
+
+public class CvTerm implements Serializable {
+
+ private Collection<Phylotree> phylotrees;
+ private Collection<PhylonodeProp> phylonodeProps;
+ private Collection<PhylonodeRelationship> phylonodeRelationships;
+ private Collection<Phylonode> phylonodes;
+
+
+
+
+ public Collection<Phylotree> getPhylotrees() {
+ return this.phylotrees;
+ }
+
+ public void setPhylotrees(Collection<Phylotree> phylotrees) {
+ this.phylotrees = phylotrees;
+ }
+
+
+ public Collection<PhylonodeProp> getPhylonodeProps() {
+ return this.phylonodeProps;
+ }
+
+ public void setPhylonodeProps(Collection<PhylonodeProp> phylonodeProps) {
+ this.phylonodeProps = phylonodeProps;
+ }
+
+
+
+
+ public Collection<PhylonodeRelationship> getPhylonodeRelationships() {
+ return this.phylonodeRelationships;
+ }
+
+ public void setPhylonodeRelationships(Collection<PhylonodeRelationship> phylonodeRelationships) {
+ this.phylonodeRelationships = phylonodeRelationships;
+ }
+
+
+ public Collection<Phylonode> getPhylonodes() {
+ return this.phylonodes;
+ }
+
+ public void setPhylonodes(Collection<Phylonode> phylonodes) {
+ this.phylonodes = phylonodes;
+ }
+
+
+
+
+
+
+
+
+
+
+
+ // Fields
+
+
+ private int cvTermId;
+
+
+
+ private DbXRef dbXRef;
+
+
+
+ private Cv cv;
+
+
+ private String name;
+
+
+ private String definition;
+
+
+ private int isObsolete;
+
+
+ private int isRelationshipType;
+
+
+ private Collection<AnalysisProp> analysisProps;
+
+
+ private Collection<CvTermProp> cvTermPropsForTypeId;
+
+
+ private Collection<CvTermProp> cvTermPropsForCvTermId;
+
+
+ private Collection<DbXRefProp> dbXRefProps;
+
+
+ private Collection<Synonym> synonyms;
+
+
+ private Collection<CvTermDbXRef> cvTermDbXRefs;
+
+
+ private Collection<CvTermPath> cvTermPathsForTypeId;
+
+
+ private Collection<FeatureCvTermProp> featureCvTermProps;
+
+
+ private Collection<FeatureCvTerm> featureCvTerms;
+
+
+ private Collection<CvTermRelationship> cvTermRelationshipsForTypeId;
+
+
+ private Collection<CvTermRelationship> cvTermRelationshipsForObjectId;
+
+
+ private Collection<PubProp> pubProps;
+
+
+ private Collection<OrganismProp> organismProps;
+
+
+ private Collection<CvTermRelationship> cvTermRelationshipsForSubjectId;
+
+
+ private Collection<CvTermSynonym> cvTermSynonymsForCvTermId;
+
+
+ private Collection<FeatureProp> featureProps;
+
+
+ private Collection<CvTermPath> cvTermPathsForSubjectId;
+
+
+ private Collection<CvTermPath> cvTermPathsForObjectId;
+
+
+ private Collection<CvTermSynonym> cvTermSynonymsForTypeId;
+
+
+ private Collection<Pub> pubs;
+
+
+ private Collection<FeatureRelationshipProp> featureRelationshipProps;
+
+
+ private Collection<Feature> features;
+
+
+ private Collection<PubRelationship> pubRelationships;
+
+
+ private Collection<FeatureRelationship> featureRelationships;
+
+ // Constructors
+
+ /** default constructor */
+ public CvTerm() {
+ // Deliberately empty default constructor
+ }
+
+ /** useful constructor! */
+ public CvTerm(Cv cv, DbXRef dbXRef, String name, String definition) {
+ this.dbXRef = dbXRef;
+ this.cv = cv;
+ this.name = name;
+ this.definition = definition;
+ }
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermId()
+ */
+ public int getCvTermId() {
+ return this.cvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermId(int)
+ */
+ public void setCvTermId(int cvTermId) {
+ this.cvTermId = cvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getDbXRef()
+ */
+ public DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setDbXRef(org.genedb.db.jpa.DbXRef)
+ */
+ public void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCv()
+ */
+ public Cv getCv() {
+ return this.cv;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCv(org.gmod.schema.cv.CvI)
+ */
+ public void setCv(Cv cv) {
+ this.cv = cv;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getName()
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setName(java.lang.String)
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getDefinition()
+ */
+ public String getDefinition() {
+ return this.definition;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setDefinition(java.lang.String)
+ */
+ public void setDefinition(String definition) {
+ this.definition = definition;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getIsObsolete()
+ */
+ public int getIsObsolete() {
+ return this.isObsolete;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setIsObsolete(int)
+ */
+ public void setIsObsolete(int isObsolete) {
+ this.isObsolete = isObsolete;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getIsRelationshipType()
+ */
+ public int getIsRelationshipType() {
+ return this.isRelationshipType;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setIsRelationshipType(int)
+ */
+ public void setIsRelationshipType(int isRelationshipType) {
+ this.isRelationshipType = isRelationshipType;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getAnalsisProps()
+ */
+ private Collection<AnalysisProp> getAnalysisProps() {
+ return this.analysisProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setAnalsisProps(java.util.Set)
+ */
+ private void setAnalysisProps(Collection<AnalysisProp> analysisProps) {
+ this.analysisProps = analysisProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermPropsForTypeId()
+ */
+ private Collection<CvTermProp> getCvTermPropsForTypeId() {
+ return this.cvTermPropsForTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermPropsForTypeId(java.util.Set)
+ */
+ private void setCvTermPropsForTypeId(Collection<CvTermProp> cvTermPropsForTypeId) {
+ this.cvTermPropsForTypeId = cvTermPropsForTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermPropsForCvTermId()
+ */
+ private Collection<CvTermProp> getCvTermPropsForCvTermId() {
+ return this.cvTermPropsForCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermPropsForCvTermId(java.util.Set)
+ */
+ private void setCvTermPropsForCvTermId(Collection<CvTermProp> cvTermPropsForCvTermId) {
+ this.cvTermPropsForCvTermId = cvTermPropsForCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getDbXRefProps()
+ */
+ private Collection<DbXRefProp> getDbXRefProps() {
+ return this.dbXRefProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setDbXRefProps(java.util.Set)
+ */
+ private void setDbXRefProps(Collection<DbXRefProp> dbXRefProps) {
+ this.dbXRefProps = dbXRefProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getSynonyms()
+ */
+ private Collection<Synonym> getSynonyms() {
+ return this.synonyms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setSynonyms(java.util.Set)
+ */
+ private void setSynonyms(Collection<Synonym> synonyms) {
+ this.synonyms = synonyms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermDbXRefs()
+ */
+ private Collection<CvTermDbXRef> getCvTermDbXRefs() {
+ return this.cvTermDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermDbXRefs(java.util.Set)
+ */
+ private void setCvTermDbXRefs(Collection<CvTermDbXRef> cvTermDbXRefs) {
+ this.cvTermDbXRefs = cvTermDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermPathsForTypeId()
+ */
+ private Collection<CvTermPath> getCvTermPathsForTypeId() {
+ return this.cvTermPathsForTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermPathsForTypeId(java.util.Set)
+ */
+ private void setCvTermPathsForTypeId(Collection<CvTermPath> cvTermPathsForTypeId) {
+ this.cvTermPathsForTypeId = cvTermPathsForTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getFeatureCvTermProps()
+ */
+ private Collection<FeatureCvTermProp> getFeatureCvTermProps() {
+ return this.featureCvTermProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setFeatureCvTermProps(java.util.Set)
+ */
+ private void setFeatureCvTermProps(Collection<FeatureCvTermProp> featureCvTermProps) {
+ this.featureCvTermProps = featureCvTermProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getFeatureCvTerms()
+ */
+ private Collection<FeatureCvTerm> getFeatureCvTerms() {
+ return this.featureCvTerms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setFeatureCvTerms(java.util.Set)
+ */
+ private void setFeatureCvTerms(Collection<FeatureCvTerm> featureCvTerms) {
+ this.featureCvTerms = featureCvTerms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermRelationshipsForTypeId()
+ */
+ private Collection<CvTermRelationship> getCvTermRelationshipsForTypeId() {
+ return this.cvTermRelationshipsForTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermRelationshipsForTypeId(java.util.Set)
+ */
+ private void setCvTermRelationshipsForTypeId(Collection<CvTermRelationship> cvTermRelationshipsForTypeId) {
+ this.cvTermRelationshipsForTypeId = cvTermRelationshipsForTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermRelationshipsForObjectId()
+ */
+ public Collection<CvTermRelationship> getCvTermRelationshipsForObjectId() {
+ return this.cvTermRelationshipsForObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermRelationshipsForObjectId(java.util.Set)
+ */
+ public void setCvTermRelationshipsForObjectId(Collection<CvTermRelationship> cvTermRelationshipsForObjectId) {
+ this.cvTermRelationshipsForObjectId = cvTermRelationshipsForObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getPubProps()
+ */
+ private Collection<PubProp> getPubProps() {
+ return this.pubProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setPubProps(java.util.Set)
+ */
+ private void setPubProps(Collection<PubProp> pubProps) {
+ this.pubProps = pubProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getOrganismProps()
+ */
+ private Collection<OrganismProp> getOrganismProps() {
+ return this.organismProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setOrganismProps(java.util.Set)
+ */
+ private void setOrganismProps(Collection<OrganismProp> organismProps) {
+ this.organismProps = organismProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermRelationshipsForSubjectId()
+ */
+ public Collection<CvTermRelationship> getCvTermRelationshipsForSubjectId() {
+ return this.cvTermRelationshipsForSubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermRelationshipsForSubjectId(java.util.Set)
+ */
+ public void setCvTermRelationshipsForSubjectId(Collection<CvTermRelationship> cvTermRelationshipsForSubjectId) {
+ this.cvTermRelationshipsForSubjectId = cvTermRelationshipsForSubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermSynonymsForCvTermId()
+ */
+ private Collection<CvTermSynonym> getCvTermSynonymsForCvTermId() {
+ return this.cvTermSynonymsForCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermSynonymsForCvTermId(java.util.Set)
+ */
+ private void setCvTermSynonymsForCvTermId(Collection<CvTermSynonym> cvTermSynonymsForCvTermId) {
+ this.cvTermSynonymsForCvTermId = cvTermSynonymsForCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getFeatureProps()
+ */
+ private Collection<FeatureProp> getFeatureProps() {
+ return this.featureProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setFeatureProps(java.util.Set)
+ */
+ private void setFeatureProps(Collection<FeatureProp> featureProps) {
+ this.featureProps = featureProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermPathsForSubjectId()
+ */
+ private Collection<CvTermPath> getCvTermPathsForSubjectId() {
+ return this.cvTermPathsForSubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermPathsForSubjectId(java.util.Set)
+ */
+ private void setCvTermPathsForSubjectId(Collection<CvTermPath> cvTermPathsForSubjectId) {
+ this.cvTermPathsForSubjectId = cvTermPathsForSubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermPathsForObjectId()
+ */
+ private Collection<CvTermPath> getCvTermPathsForObjectId() {
+ return this.cvTermPathsForObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermPathsForObjectId(java.util.Set)
+ */
+ private void setCvTermPathsForObjectId(Collection<CvTermPath> cvTermPathsForObjectId) {
+ this.cvTermPathsForObjectId = cvTermPathsForObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getCvTermSynonymsForTypeId()
+ */
+ private Collection<CvTermSynonym> getCvTermSynonymsForTypeId() {
+ return this.cvTermSynonymsForTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setCvTermSynonymsForTypeId(java.util.Set)
+ */
+ private void setCvTermSynonymsForTypeId(Collection<CvTermSynonym> cvTermSynonymsForTypeId) {
+ this.cvTermSynonymsForTypeId = cvTermSynonymsForTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getPubs()
+ */
+ private Collection<Pub> getPubs() {
+ return this.pubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setPubs(java.util.Set)
+ */
+ private void setPubs(Collection<Pub> pubs) {
+ this.pubs = pubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getFeatureRelationshipProps()
+ */
+ private Collection<FeatureRelationshipProp> getFeatureRelationshipProps() {
+ return this.featureRelationshipProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setFeatureRelationshipProps(java.util.Set)
+ */
+ private void setFeatureRelationshipProps(Collection<FeatureRelationshipProp> featureRelationshipProps) {
+ this.featureRelationshipProps = featureRelationshipProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getFeatures()
+ */
+ private Collection<Feature> getFeatures() {
+ return this.features;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setFeatures(java.util.Set)
+ */
+ private void setFeatures(Collection<Feature> features) {
+ this.features = features;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getPubRelationships()
+ */
+ private Collection<PubRelationship> getPubRelationships() {
+ return this.pubRelationships;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setPubRelationships(java.util.Set)
+ */
+ private void setPubRelationships(Collection<PubRelationship> pubRelationships) {
+ this.pubRelationships = pubRelationships;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#getFeatureRelationships()
+ */
+ private Collection<FeatureRelationship> getFeatureRelationships() {
+ return this.featureRelationships;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermI#setFeatureRelationships(java.util.Set)
+ */
+ private void setFeatureRelationships(Collection<FeatureRelationship> featureRelationships) {
+ this.featureRelationships = featureRelationships;
+ }
+
+
+}
+
diff --git a/org/gmod/schema/cv/CvTermDbXRef.java b/org/gmod/schema/cv/CvTermDbXRef.java
new file mode 100644
index 0000000..138bf9a
--- /dev/null
+++ b/org/gmod/schema/cv/CvTermDbXRef.java
@@ -0,0 +1,103 @@
+package org.gmod.schema.cv;
+
+
+
+import org.gmod.schema.general.DbXRef;
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+
+public class CvTermDbXRef implements Serializable {
+
+ // Fields
+
+
+ private int cvTermDbXRefId;
+
+
+
+ private CvTerm cvTerm;
+
+
+
+ private DbXRef dbXRef;
+
+
+ private int isForDefinition;
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermDbXRefI#getCvTermDbXRefId()
+ */
+ private int getCvTermDbXRefId() {
+ return this.cvTermDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermDbXRefI#setCvTermDbXRefId(int)
+ */
+ private void setCvTermDbXRefId(int cvTermDbXRefId) {
+ this.cvTermDbXRefId = cvTermDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermDbXRefI#getCvTerm()
+ */
+ private CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermDbXRefI#setCvTerm(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermDbXRefI#getDbXRef()
+ */
+ private DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermDbXRefI#setDbXRef(org.gmod.schema.general.DbXRefI)
+ */
+ private void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermDbXRefI#getIsForDefinition()
+ */
+ private int getIsForDefinition() {
+ return this.isForDefinition;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermDbXRefI#setIsForDefinition(int)
+ */
+ private void setIsForDefinition(int isForDefinition) {
+ this.isForDefinition = isForDefinition;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/cv/CvTermPath.java b/org/gmod/schema/cv/CvTermPath.java
new file mode 100644
index 0000000..0c21d5c
--- /dev/null
+++ b/org/gmod/schema/cv/CvTermPath.java
@@ -0,0 +1,139 @@
+package org.gmod.schema.cv;
+
+
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class CvTermPath implements Serializable {
+
+ // Fields
+
+
+ private int cvTermPathId;
+
+
+
+ private CvTerm cvTermBySubjectId;
+
+
+
+
+ private CvTerm cvTermByObjectId;
+
+
+
+
+ private CvTerm cvTermByTypeId;
+
+
+
+
+ private Cv cv;
+
+
+ private Integer pathDistance;
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#getCvTermPathId()
+ */
+ private int getCvTermPathId() {
+ return this.cvTermPathId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#setCvTermPathId(int)
+ */
+ private void setCvTermPathId(int cvTermPathId) {
+ this.cvTermPathId = cvTermPathId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#getCvTermBySubjectId()
+ */
+ private CvTerm getCvTermBySubjectId() {
+ return this.cvTermBySubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#setCvTermBySubjectId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermBySubjectId(CvTerm cvTermBySubjectId) {
+ this.cvTermBySubjectId = cvTermBySubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#getCvTermByObjectId()
+ */
+ private CvTerm getCvTermByObjectId() {
+ return this.cvTermByObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#setCvTermByObjectId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermByObjectId(CvTerm cvTermByObjectId) {
+ this.cvTermByObjectId = cvTermByObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#getCvTermByTypeId()
+ */
+ private CvTerm getCvTermByTypeId() {
+ return this.cvTermByTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#setCvTermByTypeId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermByTypeId(CvTerm cvTermByTypeId) {
+ this.cvTermByTypeId = cvTermByTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#getCv()
+ */
+ private Cv getCv() {
+ return this.cv;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#setCv(org.gmod.schema.cv.CvI)
+ */
+ private void setCv(Cv cv) {
+ this.cv = cv;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#getPathDistance()
+ */
+ private Integer getPathDistance() {
+ return this.pathDistance;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPathI#setPathDistance(java.lang.Integer)
+ */
+ private void setPathDistance(Integer pathDistance) {
+ this.pathDistance = pathDistance;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/cv/CvTermProp.java b/org/gmod/schema/cv/CvTermProp.java
new file mode 100644
index 0000000..db9eeae
--- /dev/null
+++ b/org/gmod/schema/cv/CvTermProp.java
@@ -0,0 +1,118 @@
+package org.gmod.schema.cv;
+
+
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+
+public class CvTermProp implements Serializable {
+
+ // Fields
+
+
+ private int cvTermPropId;
+
+
+
+ private CvTerm cvTermByCvTermId;
+
+
+
+ private CvTerm cvTermByTypeId;
+
+
+ private String value;
+
+
+ private int rank;
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#getCvTermpropId()
+ */
+ private int getCvTermPropId() {
+ return this.cvTermPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#setCvTermpropId(int)
+ */
+ private void setCvTermPropId(int cvTermPropId) {
+ this.cvTermPropId = cvTermPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#getCvTermByCvTermId()
+ */
+ private CvTerm getCvTermByCvTermId() {
+ return this.cvTermByCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#setCvTermByCvTermId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermByCvTermId(CvTerm cvTermByCvTermId) {
+ this.cvTermByCvTermId = cvTermByCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#getCvTermByTypeId()
+ */
+ private CvTerm getCvTermByTypeId() {
+ return this.cvTermByTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#setCvTermByTypeId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermByTypeId(CvTerm cvTermByTypeId) {
+ this.cvTermByTypeId = cvTermByTypeId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#getValue()
+ */
+ private String getValue() {
+ return this.value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#setValue(java.lang.String)
+ */
+ private void setValue(String value) {
+ this.value = value;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#getRank()
+ */
+ private int getRank() {
+ return this.rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermPropI#setRank(int)
+ */
+ private void setRank(int rank) {
+ this.rank = rank;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/cv/CvTermRelationship.java b/org/gmod/schema/cv/CvTermRelationship.java
new file mode 100644
index 0000000..c71b354
--- /dev/null
+++ b/org/gmod/schema/cv/CvTermRelationship.java
@@ -0,0 +1,113 @@
+package org.gmod.schema.cv;
+
+
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class CvTermRelationship implements Serializable {
+
+ // Fields
+
+
+ private int cvTermRelationshipId;
+
+
+
+ private CvTerm cvTermBySubjectId;
+
+
+
+ private CvTerm cvTermByObjectId;
+
+
+
+ private CvTerm cvTermByTypeId;
+
+ // Constructors
+
+ /** default constructor */
+ public CvTermRelationship() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public CvTermRelationship(CvTerm cvTermBySubjectId, CvTerm cvTermByObjectId, CvTerm cvTermByTypeId) {
+ this.cvTermBySubjectId = cvTermBySubjectId;
+ this.cvTermByObjectId = cvTermByObjectId;
+ this.cvTermByTypeId = cvTermByTypeId;
+ }
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermRelationshipI#getCvTermRelationshipId()
+ */
+ private int getCvTermRelationshipId() {
+ return this.cvTermRelationshipId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermRelationshipI#setCvTermRelationshipId(int)
+ */
+ private void setCvTermRelationshipId(int cvTermRelationshipId) {
+ this.cvTermRelationshipId = cvTermRelationshipId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermRelationshipI#getCvTermBySubjectId()
+ */
+ private CvTerm getCvTermBySubjectId() {
+ return this.cvTermBySubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermRelationshipI#setCvTermBySubjectId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermBySubjectId(CvTerm cvTermBySubjectId) {
+ this.cvTermBySubjectId = cvTermBySubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermRelationshipI#getCvTermByObjectId()
+ */
+ private CvTerm getCvTermByObjectId() {
+ return this.cvTermByObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermRelationshipI#setCvTermByObjectId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermByObjectId(CvTerm cvTermByObjectId) {
+ this.cvTermByObjectId = cvTermByObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermRelationshipI#getCvTermByTypeId()
+ */
+ private CvTerm getCvTermByTypeId() {
+ return this.cvTermByTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermRelationshipI#setCvTermByTypeId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermByTypeId(CvTerm cvTermByTypeId) {
+ this.cvTermByTypeId = cvTermByTypeId;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/cv/CvTermSynonym.java b/org/gmod/schema/cv/CvTermSynonym.java
new file mode 100644
index 0000000..9401b36
--- /dev/null
+++ b/org/gmod/schema/cv/CvTermSynonym.java
@@ -0,0 +1,101 @@
+package org.gmod.schema.cv;
+
+
+
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class CvTermSynonym implements Serializable {
+
+ // Fields
+
+
+ private int cvTermSynonymId;
+
+
+
+ private CvTerm cvTermByCvTermId;
+
+
+
+ private CvTerm cvTermByTypeId;
+
+
+ private String synonym;
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermSynonymI#getCvTermSynonymId()
+ */
+ private int getCvTermSynonymId() {
+ return this.cvTermSynonymId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermSynonymI#setCvTermSynonymId(int)
+ */
+ private void setCvTermSynonymId(int cvTermSynonymId) {
+ this.cvTermSynonymId = cvTermSynonymId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermSynonymI#getCvTermByCvTermId()
+ */
+ private CvTerm getCvTermByCvTermId() {
+ return this.cvTermByCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermSynonymI#setCvTermByCvTermId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermByCvTermId(CvTerm cvTermByCvTermId) {
+ this.cvTermByCvTermId = cvTermByCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermSynonymI#getCvTermByTypeId()
+ */
+ private CvTerm getCvTermByTypeId() {
+ return this.cvTermByTypeId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermSynonymI#setCvTermByTypeId(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTermByTypeId(CvTerm cvTermByTypeId) {
+ this.cvTermByTypeId = cvTermByTypeId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermSynonymI#getSynonym()
+ */
+ private String getSynonym() {
+ return this.synonym;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.CvTermSynonymI#setSynonym(java.lang.String)
+ */
+ private void setSynonym(String synonym) {
+ this.synonym = synonym;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/cv/DbXRefProp.java b/org/gmod/schema/cv/DbXRefProp.java
new file mode 100644
index 0000000..97ce2ae
--- /dev/null
+++ b/org/gmod/schema/cv/DbXRefProp.java
@@ -0,0 +1,120 @@
+package org.gmod.schema.cv;
+
+
+
+
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.utils.propinterface.PropertyI;
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class DbXRefProp implements Serializable, PropertyI {
+
+ // Fields
+
+
+ private int dbXRefPropId;
+
+
+
+ private CvTerm cvTerm;
+
+
+
+ private DbXRef dbXRef;
+
+
+ private String value;
+
+
+ private int rank;
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#getDbXRefpropId()
+ */
+ private int getDbXRefPropId() {
+ return this.dbXRefPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#setDbXRefpropId(int)
+ */
+ private void setDbXRefPropId(int dbXRefPropId) {
+ this.dbXRefPropId = dbXRefPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#getCvTerm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#setCvTerm(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#getDbXRef()
+ */
+ private DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#setDbXRef(org.gmod.schema.general.DbXRefI)
+ */
+ private void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#getValue()
+ */
+ private String getValue() {
+ return this.value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#setValue(java.lang.String)
+ */
+ private void setValue(String value) {
+ this.value = value;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#getRank()
+ */
+ private int getRank() {
+ return this.rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefPropI#setRank(int)
+ */
+ private void setRank(int rank) {
+ this.rank = rank;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/dao/BaseDaoI.java b/org/gmod/schema/dao/BaseDaoI.java
new file mode 100644
index 0000000..971a8fa
--- /dev/null
+++ b/org/gmod/schema/dao/BaseDaoI.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2006 Genome Research Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307 USA
+ */
+
+package org.gmod.schema.dao;
+
+public interface BaseDaoI {
+
+
+ /**
+ * Save the object to the database (at the end of the current transaction,
+ * or depending upon flush mode). This method is available in all the DAOs.
+ * It's recommended to call it through an appropriate one eg SequenceDaoI
+ * for FeatureI
+ * @param o the new instance to persist
+ */
+ public abstract void persist(Object o);
+
+ /**
+ * Delete an object from the database
+ * @param o the object to remove
+ *
+ */
+ public abstract void delete(Object o);
+
+ /**
+ * Merge (update) an already persistent object back to the database (at the end of
+ * the current transaction, or depending upon flush mode). This method is defined in
+ * all the DAOs. It's recommended to call it through an appropriate one eg SequenceDaoI
+ * for FeatureI
+ *
+ * @param o the existing feature to merge
+ */
+ public abstract void merge(Object o);
+
+}
diff --git a/org/gmod/schema/dao/CvDaoI.java b/org/gmod/schema/dao/CvDaoI.java
new file mode 100644
index 0000000..686026f
--- /dev/null
+++ b/org/gmod/schema/dao/CvDaoI.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2006 Genome Research Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307 USA
+ */
+
+package org.gmod.schema.dao;
+
+
+import org.gmod.schema.cv.Cv;
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.utils.CountedName;
+
+import java.util.List;
+
+public interface CvDaoI extends BaseDaoI {
+
+ /**
+ * Get a CV by id
+ *
+ * @param id the cv id (primary key)
+ * @return the corresponding Cv, or null
+ */
+ public abstract Cv getCvById(int id);
+
+ // TODO Should this return a list or just one?
+ /**
+ * Retrieve a controlled vocabulary by its name
+ *
+ * @param name the name to lookup
+ * @return the List<Cv> of matches, or null
+ */
+ public abstract List<Cv> getCvByName(String name);
+
+ /**
+ * Retrieve a CvTerm by id
+ *
+ * @param id then cvterm id (primary key)
+ * @return the corresponding CvTerm, or null
+ */
+ public abstract CvTerm getCvTermById(int id);
+
+
+ // TODO Should this return a list or just one?
+ /**
+ * Retrieve a named CvTerm from a given Cv
+ *
+ * @param cvTermName the name of the cvterm
+ * @param cv the controlled vocabulary this cvterm is part of
+ * @return a (possibly empty) list of matching cvterms
+ */
+ public abstract List<CvTerm> getCvTermByNameInCv(String cvTermName, Cv cv);
+
+
+ /**
+ * Retrieve a CvTerm from the Gene Ontology
+ *
+ * @param value the
+ * @return the corresponding CvTerm, or null
+ */
+ public abstract CvTerm getGoCvTermByAcc(String value);
+
+
+ /**
+ * Retrieve a CvTerm from the Gene Ontology via it's database entry
+ *
+ * @param id the database name eg GO:123456
+ * @return the corresponding CvTerm, or null
+ */
+ public abstract CvTerm getGoCvTermByAccViaDb(final String id);
+
+
+ /**
+ * Retrieve all CvTerms
+ * @return a list of all cvterms
+ */
+ public abstract List<CvTerm> getCvTerms();
+
+ /**
+ * Retrieve a named CvTerm from a given Cv
+ *
+ * @param cvTermName the name of the cvterm
+ * @param name the controlled vocabulary name this cvterm could be part of
+ * @return a (possibly empty) cvterm
+ */
+ public abstract CvTerm getCvTermByNameAndCvName(String cvTermName, String name);
+
+ /**
+ * Get a CvTerm by DbXRef
+ *
+ * @param dbXRef the DbXRef
+ * @return the corresponding CvTerm, or null
+ */
+ public abstract CvTerm getCvTermByDbXRef(DbXRef dbXRef);
+
+ public boolean existsNameInOntology(String name, Cv ontology);
+
+
+ public List<String> getPossibleMatches(String search, Cv cv, int limit);
+
+ public List<CountedName> getAllTermsInCvWithCount(Cv cv);
+
+}
diff --git a/org/gmod/schema/dao/GeneralDaoI.java b/org/gmod/schema/dao/GeneralDaoI.java
new file mode 100644
index 0000000..3a18ae1
--- /dev/null
+++ b/org/gmod/schema/dao/GeneralDaoI.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2006 Genome Research Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307 USA
+ */
+
+package org.gmod.schema.dao;
+
+import org.gmod.schema.analysis.AnalysisFeature;
+import org.gmod.schema.general.Db;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.sequence.Feature;
+
+
+
+public interface GeneralDaoI extends BaseDaoI{
+
+ /**
+ * Retrieve a database by name
+ *
+ * @param name the name to lookup
+ * @return the corresponding db, or null
+ */
+ public abstract Db getDbByName(String name);
+
+ /**
+ * Retrieve the db xref corresponding to a given DB and accession number
+ *
+ * @param db the db the dbxref refers to
+ * @param accession the accession "number" the dbxref refers to
+ * @return the dbxref, or null
+ */
+ public abstract DbXRef getDbXRefByDbAndAcc(Db db, String accession);
+
+ /**
+ * Retrieve the analysisfeature corresponding to the given feature
+ *
+ * @param feature the feature whose analysisfeature has to be found
+ * @return the analysisfeature
+ */
+ public abstract AnalysisFeature getAnalysisFeatureFromFeature(Feature feature);
+}
diff --git a/org/gmod/schema/dao/OrganismDaoI.java b/org/gmod/schema/dao/OrganismDaoI.java
new file mode 100644
index 0000000..4edfb6f
--- /dev/null
+++ b/org/gmod/schema/dao/OrganismDaoI.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2006 Genome Research Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307 USA
+ */
+
+package org.gmod.schema.dao;
+
+import org.gmod.schema.organism.Organism;
+
+import java.util.List;
+
+public interface OrganismDaoI extends BaseDaoI {
+
+ /**
+ * Get the organism corresponding to this id
+ *
+ * @param id the organism id (primary key) to lookup by
+ * @return the corresponding organism, or null
+ */
+ public abstract Organism getOrganismById(int id);
+
+ /**
+ * Get the organism corresponding to this common name
+ *
+ * @param commonName the short name to look up
+ * @return the corresponding organism, or null
+ */
+ public abstract Organism getOrganismByCommonName(String commonName);
+
+ /**
+ * Get a list of the common name of all the organisms.
+ *
+ * @return a (possibly empty) List<String> of all the organisms' common names
+ */
+ public abstract List<String> findAllOrganismCommonNames();
+
+ /**
+ * Get a list of all the organisms
+ *
+ * @return a (possibly empty) List<Organism> of all the organisms'
+ */
+ public abstract List<Organism> getOrganisms();
+
+}
diff --git a/org/gmod/schema/dao/PhylogenyDaoI.java b/org/gmod/schema/dao/PhylogenyDaoI.java
new file mode 100644
index 0000000..e3241eb
--- /dev/null
+++ b/org/gmod/schema/dao/PhylogenyDaoI.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2006 Genome Research Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307 USA
+ */
+
+package org.gmod.schema.dao;
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.phylogeny.Phylonode;
+import org.gmod.schema.phylogeny.Phylotree;
+
+import java.util.List;
+
+
+
+public interface PhylogenyDaoI extends BaseDaoI {
+
+ public Phylotree getPhyloTreeByName(String name);
+
+ public List<Phylonode> getPhyloNodesByCvTermInTree(CvTerm type, Phylotree tree);
+
+ public List<Phylonode> getAllPhylonodes();
+
+ public List<Phylonode> getPhylonodeByDepthAndParent(double depth,Phylonode parent);
+
+ public List<Phylonode> getPhylonodeByName(String name);
+
+ public List<Phylonode> getPhylonodesByParent(Phylonode parent);
+
+ //public List<Phylonode> getPhylonodeByDepth(double depth);
+}
diff --git a/org/gmod/schema/dao/PubDaoI.java b/org/gmod/schema/dao/PubDaoI.java
new file mode 100644
index 0000000..e196c19
--- /dev/null
+++ b/org/gmod/schema/dao/PubDaoI.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2006 Genome Research Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307 USA
+ */
+
+package org.gmod.schema.dao;
+
+import java.util.List;
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.pub.Pub;
+import org.gmod.schema.pub.PubProp;
+import org.gmod.schema.pub.PubDbXRef;
+
+public interface PubDaoI extends BaseDaoI {
+
+ /**
+ * Retrieve the publication with this primary key
+ *
+ * @param id the publication id
+ * @return the corresponding publication, or null
+ */
+ public abstract Pub getPubById(int id);
+
+ /**
+ * Retrieve the publication with this primary key
+ *
+ * @param uniqueName
+ * @return the publication with this unique name, or null
+ */
+ public abstract Pub getPubByUniqueName(String uniqueName);
+
+
+ public Pub getPubByDbXRef(DbXRef dbXRef);
+
+ /**
+ * Get a list of all PubDbXRef's
+ * @return list of PubDbXRef's
+ */
+ public List<PubDbXRef> getPubDbXRef();
+
+ /**
+ * Retrieve the publication property with Pub and Cvterm
+ *
+ * @param pub the Publication
+ * @param cvTerm the cvTerm
+ * @return the publication or null
+ */
+ public abstract List<PubProp> getPubPropByPubAndCvTerm(Pub pub,CvTerm cvTerm);
+}
diff --git a/org/gmod/schema/dao/SchemaDaoI.java b/org/gmod/schema/dao/SchemaDaoI.java
new file mode 100644
index 0000000..484a529
--- /dev/null
+++ b/org/gmod/schema/dao/SchemaDaoI.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2006 Genome Research Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307 USA
+ */
+
+package org.gmod.schema.dao;
+
+
+import org.gmod.schema.sequence.Feature;
+
+import java.sql.SQLException;
+import java.util.List;
+
+public interface SchemaDaoI extends BaseDaoI {
+
+ /**
+ * Return a list of features with residues.
+ * @return the <code>List</code> of <code>Feature</code> objects
+ * @throws SQLException
+ */
+ public List<Feature> getResidueFeatures() throws SQLException;
+
+
+ /**
+ * For a schema return the type_id's with residues.
+ * @param schema schema/organism name or null
+ * @return the <code>List</code> of type_id's as <code>String</code> objects
+ * @throws SQLException
+ */
+ public List<String> getResidueType(String schema) throws SQLException;
+
+ /**
+ * Get available schemas (as a <code>List</code> of <code>String</code>
+ * objects).
+ * @return the available schemas
+ * @throws SQLException
+ */
+ public List<String> getSchema() throws SQLException;
+
+
+}
diff --git a/org/gmod/schema/dao/SequenceDaoI.java b/org/gmod/schema/dao/SequenceDaoI.java
new file mode 100644
index 0000000..b752a05
--- /dev/null
+++ b/org/gmod/schema/dao/SequenceDaoI.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2006 Genome Research Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307 USA
+ */
+
+package org.gmod.schema.dao;
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureCvTermDbXRef;
+import org.gmod.schema.sequence.FeatureCvTermPub;
+import org.gmod.schema.sequence.FeatureDbXRef;
+import org.gmod.schema.sequence.FeatureSynonym;
+import org.gmod.schema.sequence.Synonym;
+import org.gmod.schema.utils.CountedName;
+
+import java.util.List;
+
+public interface SequenceDaoI extends BaseDaoI {
+
+ /**
+ * Return the feature corresponding to this feature_id
+ *
+ * @param id the systematic id
+ * @return the Feature, or null
+ */
+ public Feature getFeatureById(int id);
+
+ /**
+ * Return a list of features with this uniquename
+ *
+ * @param name the uniquename
+ * @return the Feature, or null
+ */
+ public List<Feature> getFeaturesByUniqueName(String name);
+
+ /**
+ *
+ * @param name the uniquename
+ * @param featureType the type of feature to return eg "gene". <b>NB</> String, not a type argument
+ * @return
+ */
+ public Feature getFeatureByUniqueName(String name, String featureType);
+
+ /**
+ * Return a list of features with any current (ie non-obsolete) name or synonym
+ *
+ * @param name the lookup name
+ * @return a (possibly empty) List<Feature> of children with this current name
+ */
+ public List<Feature> getFeaturesByAnyCurrentName(String name);
+
+ /**
+ * Return a list of features with this name or synonym (including obsolete names). The
+ * name can contain an SQL wildcard (%)
+ *
+ * @param name the lookup name
+ * @param featureType the type of feature to return eg "gene"
+ * @return a (possibly empty) List<Feature> of children with this name
+ */
+ public List<Feature> getFeaturesByAnyName(String name, String featureType);
+
+
+ // TODO Document overlap behaviour
+ /**
+ * Return a list of features located on a source Feature, within a given range
+ *
+ * @param min the minimum (interbase) coordinate
+ * @param max the maximum (interbase) coordinate
+ * @param strand
+ * @param parent the source feature
+ * @param type
+ * @return a List<Feature> which ??? this range
+ */
+ public List<Feature> getFeaturesByRange(int min, int max, int strand,
+ Feature parent, String type);
+
+ /**
+ * Return a list of features located on a source Feature
+ *
+ * @param parent the parent feature
+ * @return a (possibly empty) List<Feature> of children located on this parent
+ */
+ public List<Feature> getFeaturesByLocatedOnFeature(Feature parent);
+
+ /**
+ * Return the FeatureCvTerm that links a given Feature and CvTerm, with a given value of 'not'
+ *
+ * @param feature the Feature to test the link for
+ * @param cvTerm the CvTerm to test the link for
+ * @param not test for the not flag in the FeatureCvTerm
+ * @return the Feature, or null
+ */
+ public List<FeatureCvTerm> getFeatureCvTermsByFeatureAndCvTermAndNot(Feature feature,
+ CvTerm cvTerm, boolean not);
+
+ /**
+ * Return a list of FeatureCvterm's for a Feature, or a list
+ * of all FeatureCvTerm's if Feature is null.
+ * @param feature the Feature to retrieve associated FeatureCvTerm's
+ * @return the FeatureCvTerm's
+ */
+ public List<FeatureCvTerm> getFeatureCvTermsByFeature(Feature feature);
+
+ /**
+ * Get a list of all FeatureCvTermDbXRef's for a Feature, or a list
+ * of all FeatureCvTermDbXRef's if Feature is null.
+ * @param feature the Feature to retrieve associated FeatureCvTermDbXRef's
+ * @return the FeatureCvTermDbXRef's
+ */
+ public List<FeatureCvTermDbXRef> getFeatureCvTermDbXRefByFeature(Feature feature);
+
+ /**
+ * Get a list of all FeatureCvTermPub's for a Feature, or a list
+ * of all FeatureCvTermPub's if Feature is null.
+ * @param feature the Feature to retrieve associated FeatureCvTermPub's
+ * @return the FeatureCvTermPub's
+ */
+ public List<FeatureCvTermPub> getFeatureCvTermPubByFeature(Feature feature);
+
+ /**
+ * Return a synonym of the given name and type if it exists
+ *
+ * @param name the name to lookup
+ * @param type the type of the Synonym
+ * @return a Synonym, or null
+ */
+ public Synonym getSynonymByNameAndCvTerm(String name, CvTerm type);
+
+ /**
+ * Return a list of FeatureSynonyms which link a given Feature and Synonym
+ *
+ * @param feature the test Feature
+ * @param synonym the test Synonym
+ * @return a (possibly empty) List<FeatureSynonym>
+ */
+ public List<FeatureSynonym> getFeatureSynonymsByFeatureAndSynonym(
+ Feature feature, Synonym synonym);
+
+ public FeatureDbXRef getFeatureDbXRefByFeatureAndDbXRef(final Feature feature, final DbXRef dbXRef);
+
+ /**
+ * Return all the FeatureDbXRefs for a given feature, <b>specified by name</b>, or all if
+ * <code>null</code> is passed
+ *
+ * @param uniqueName the uniquename of a Feature, or null for all FeatureDbXRefs
+ * @return a (possibly empty) List<FeatureDbXRefI>
+ */
+ public List<FeatureDbXRef> getFeatureDbXRefsByFeatureUniquename(final String uniqueName);
+
+ /**
+ * Return the list of FeatureSynonyms for a given Feature, <b>specified by name</b>, or all if
+ * <code>null</code> is passed
+ *
+ * @param uniqueName the uniquename of a Feature, or null for all
+ * @return a (possibly empty) List<FeatureSynonymI> of matching synonyms
+ */
+ public List<FeatureSynonym> getFeatureSynonymsByFeatureUniquename(final String uniqueName);
+
+ /**
+ * Return the list of all feature_synonyms as Feature.featureSynonyms
+ *
+ * @return a (possibly empty) List<Features> of matching synonyms
+ */
+ public List<Feature> getAllFeatureSynonymsAsFeature();
+
+ /**
+ * Return the list of Features for a given GO number
+ *
+ *
+ * @param go the GO number
+ * @return a (possibly empty) List<Feature> of matching genes
+ */
+ public List<List> getFeatureByGO(final String go);
+
+ /**
+ * Return a list of features that have this particular cvterm
+ *
+ *
+ * @param cvTermName the CvTerm name
+ * @return a (possibly empty) List<Feature> of children
+ */
+ public List<Feature> getFeaturesByCvTermName(String cvTermName);
+
+ /**
+ * Return a list of features that have this particular cvterm
+ *
+ *
+ * @param cvTermName the CvTerm name
+ * @return a (possibly empty) List<Feature> of children
+ */
+ public List<Feature> getFeaturesByCvTermNameAndCvName(String cvTermName, String cvName);
+
+ /**
+ * Return a list of top-level features
+ *
+ *
+ * @return a (possibly empty) List<Feature> of children
+ */
+ public List<Feature> getTopLevelFeatures();
+
+ public List<CountedName> getProducts();
+
+ /**
+ * Return a list of feature uniquename based on cvterm for auto-completion
+ *
+ * @param name the Feature uniquename
+ * @param cvTerm the CvTerm
+ * @param limit the number of maximum results to return
+ * @return a (possibly empty) List<String> of feature uniquename
+ */
+ public List<String> getPossibleMatches(String name,CvTerm cvTerm, int limit);
+
+ /**
+ * Return a list of feature uniquename based on cvterm for auto-completion
+ *
+ * @param name the Feature uniquename
+ * @param orgNames the comma seperated organism common names
+ * @param featureType the type of Features to return e.g gene
+ * @param limit the number of maximum results to return
+ * @return a (possibly empty) List<Feature> of Feature
+ */
+ public List<Feature> getFeaturesByAnyNameAndOrganism(String name,String orgNames,String featureType);
+
+ /**
+ * Return a list of feature based on organism
+ *
+ * @param organism the Organism
+ * @return a (possibly empty) List<String> of feature
+ */
+ public List<Feature> getFeaturesByOrganism(Organism org);
+
+ /**
+ * Return the features corresponding to uniquenames in the list
+ *
+ * @param names the list of uniquenames
+ * @return the list of Features, or null
+ */
+ public List<Feature> getFeaturesByUniqueNames(List<String> names);
+
+}
diff --git a/org/gmod/schema/general/Db.java b/org/gmod/schema/general/Db.java
new file mode 100644
index 0000000..eabd427
--- /dev/null
+++ b/org/gmod/schema/general/Db.java
@@ -0,0 +1,136 @@
+package org.gmod.schema.general;
+
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+
+
+
+
+
+
+
+
+
+public class Db implements Serializable {
+
+ // Fields
+
+
+
+ private int dbId;
+
+
+ private String name;
+
+
+ private String description;
+
+
+ private String urlPrefix;
+
+
+ private String url;
+
+
+ private Set<DbXRef> dbXRefs = new HashSet<DbXRef>(0);
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#getDbId()
+ */
+ public int getDbId() {
+ return this.dbId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#setDbId(int)
+ */
+ public void setDbId(int dbId) {
+ this.dbId = dbId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#getName()
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#setName(java.lang.String)
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#getDescription()
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#setDescription(java.lang.String)
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#getUrlPrefix()
+ */
+ public String getUrlPrefix() {
+ return this.urlPrefix;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#setUrlPrefix(java.lang.String)
+ */
+ public void setUrlPrefix(String urlPrefix) {
+ this.urlPrefix = urlPrefix;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#getUrl()
+ */
+ public String getUrl() {
+ return this.url;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#setUrl(java.lang.String)
+ */
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#getDbXRefs()
+ */
+ public Collection<DbXRef> getDbXRefs() {
+ return this.dbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbI#setDbXRefs(java.util.Set)
+ */
+ public void setDbXRefs(Set<DbXRef> dbXRefs) {
+ this.dbXRefs = dbXRefs;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/general/DbXRef.java b/org/gmod/schema/general/DbXRef.java
new file mode 100644
index 0000000..7945f1a
--- /dev/null
+++ b/org/gmod/schema/general/DbXRef.java
@@ -0,0 +1,292 @@
+package org.gmod.schema.general;
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.cv.CvTermDbXRef;
+import org.gmod.schema.cv.DbXRefProp;
+import org.gmod.schema.organism.OrganismDbXRef;
+import org.gmod.schema.phylogeny.PhylonodeDbXRef;
+import org.gmod.schema.phylogeny.Phylotree;
+import org.gmod.schema.pub.PubDbXRef;
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureCvTermDbXRef;
+import org.gmod.schema.sequence.FeatureDbXRef;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class DbXRef implements Serializable {
+
+ private Set<PhylonodeDbXRef> phylonodeDbXRefs = new HashSet<PhylonodeDbXRef>(0);
+ private Set<Phylotree> phylotrees = new HashSet<Phylotree>(0);
+
+ public Set<PhylonodeDbXRef> getPhylonodeDbXRefs() {
+ return this.phylonodeDbXRefs;
+ }
+
+ public void setPhylonodeDbXRefs(Set<PhylonodeDbXRef> phylonodeDbXRefs) {
+ this.phylonodeDbXRefs = phylonodeDbXRefs;
+ }
+
+
+ public Set<Phylotree> getPhylotrees() {
+ return this.phylotrees;
+ }
+
+ public void setPhylotrees(Set<Phylotree> phylotrees) {
+ this.phylotrees = phylotrees;
+ }
+
+
+
+ // Fields
+
+
+
+ private int dbXRefId;
+
+
+ private String version;
+
+
+
+ private Db db;
+
+
+ private String accession;
+
+
+ private String description;
+
+
+ private Set<DbXRefProp> dbXRefProps = new HashSet<DbXRefProp>(0);
+
+
+ private Set<FeatureCvTermDbXRef> featureCvTermDbXRefs = new HashSet<FeatureCvTermDbXRef>(0);
+
+
+ private Set<Feature> features = new HashSet<Feature>(0);
+
+
+ private Set<FeatureDbXRef> featureDbXRefs = new HashSet<FeatureDbXRef>(0);
+
+
+ private Set<CvTerm> cvTerms = new HashSet<CvTerm>(0);
+
+
+ private Set<PubDbXRef> pubDbXRefs = new HashSet<PubDbXRef>(0);
+
+
+ private Set<OrganismDbXRef> organismDbXRefs = new HashSet<OrganismDbXRef>(0);
+
+
+ private Set<CvTermDbXRef> cvTermDbXRefs = new HashSet<CvTermDbXRef>(0);
+
+ // Constructors
+
+ /** default constructor */
+ public DbXRef() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public DbXRef(Db db, String accession) {
+ this.version = "1";
+ this.db = db;
+ this.accession = accession;
+ }
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getDbXRefId()
+ */
+ public int getDbXRefId() {
+ return this.dbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setDbXRefId(int)
+ */
+ public void setDbXRefId(int dbXRefId) {
+ this.dbXRefId = dbXRefId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getVersion()
+ */
+ public String getVersion() {
+ return this.version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setVersion(java.lang.String)
+ */
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getDb()
+ */
+ public Db getDb() {
+ return this.db;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setDb(org.gmod.schema.general.DbI)
+ */
+ public void setDb(Db db) {
+ this.db = db;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getAccession()
+ */
+ public String getAccession() {
+ return this.accession;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setAccession(java.lang.String)
+ */
+ public void setAccession(String accession) {
+ this.accession = accession;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getDescription()
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setDescription(java.lang.String)
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getDbXRefProps()
+ */
+ private Collection<DbXRefProp> getDbXRefProps() {
+ return this.dbXRefProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setDbXRefProps(java.util.Set)
+ */
+ private void setDbXRefProps(Set<DbXRefProp> dbXRefProps) {
+ this.dbXRefProps = dbXRefProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getFeatureCvTermDbXRefs()
+ */
+ private Collection<FeatureCvTermDbXRef> getFeatureCvTermDbXRefs() {
+ return this.featureCvTermDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setFeatureCvTermDbXRefs(java.util.Set)
+ */
+ private void setFeatureCvTermDbXRefs(Set<FeatureCvTermDbXRef> featureCvTermDbXRefs) {
+ this.featureCvTermDbXRefs = featureCvTermDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getFeatures()
+ */
+ private Collection<Feature> getFeatures() {
+ return this.features;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setFeatures(java.util.Set)
+ */
+ private void setFeatures(Set<Feature> features) {
+ this.features = features;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getFeatureDbXRefs()
+ */
+ private Collection<FeatureDbXRef> getFeatureDbXRefs() {
+ return this.featureDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setFeatureDbXRefs(java.util.Set)
+ */
+ private void setFeatureDbXRefs(Set<FeatureDbXRef> featureDbXRefs) {
+ this.featureDbXRefs = featureDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getCvTerms()
+ */
+ public Collection<CvTerm> getCvTerms() {
+ return this.cvTerms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setCvTerms(java.util.Set)
+ */
+ private void setCvTerms(Set<CvTerm> cvTerms) {
+ this.cvTerms = cvTerms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getPubDbXRefs()
+ */
+ private Collection<PubDbXRef> getPubDbXRefs() {
+ return this.pubDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setPubDbXRefs(java.util.Set)
+ */
+ private void setPubDbXRefs(Set<PubDbXRef> pubDbXRefs) {
+ this.pubDbXRefs = pubDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getOrganismDbXRefs()
+ */
+ private Collection<OrganismDbXRef> getOrganismDbXRefs() {
+ return this.organismDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setOrganismDbXRefs(java.util.Set)
+ */
+ private void setOrganismDbXRefs(Set<OrganismDbXRef> organismDbXRefs) {
+ this.organismDbXRefs = organismDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#getCvTermDbXRefs()
+ */
+ private Collection<CvTermDbXRef> getCvTermDbXRefs() {
+ return this.cvTermDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.DbXRefI#setCvTermDbXRefs(java.util.Set)
+ */
+ private void setCvTermDbXRefs(Set<CvTermDbXRef> cvTermDbXRefs) {
+ this.cvTermDbXRefs = cvTermDbXRefs;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/general/Project.java b/org/gmod/schema/general/Project.java
new file mode 100644
index 0000000..f8bfd6a
--- /dev/null
+++ b/org/gmod/schema/general/Project.java
@@ -0,0 +1,59 @@
+package org.gmod.schema.general;
+
+
+import java.io.Serializable;
+
+public class Project implements Serializable {
+
+ // Fields
+ private int projectId;
+ private String name;
+ private String description;
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.ProjectI#getProjectId()
+ */
+ private int getProjectId() {
+ return this.projectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.ProjectI#setProjectId(int)
+ */
+ private void setProjectId(int projectId) {
+ this.projectId = projectId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.ProjectI#getName()
+ */
+ private String getName() {
+ return this.name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.ProjectI#setName(java.lang.String)
+ */
+ private void setName(String name) {
+ this.name = name;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.ProjectI#getDescription()
+ */
+ private String getDescription() {
+ return this.description;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.ProjectI#setDescription(java.lang.String)
+ */
+ private void setDescription(String description) {
+ this.description = description;
+ }
+
+}
+
+
diff --git a/org/gmod/schema/general/Tableinfo.java b/org/gmod/schema/general/Tableinfo.java
new file mode 100644
index 0000000..665d77f
--- /dev/null
+++ b/org/gmod/schema/general/Tableinfo.java
@@ -0,0 +1,148 @@
+package org.gmod.schema.general;
+
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Tableinfo implements Serializable {
+
+ // Fields
+
+ private int tableinfoId;
+ private String name;
+ private String primaryKeyColumn;
+ private int isView;
+ private Integer viewOnTableId;
+ private Integer superclassTableId;
+ private int isUpdateable;
+ private Date modificationDate;
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#getTableinfoId()
+ */
+
+
+
+ private int getTableinfoId() {
+ return this.tableinfoId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#setTableinfoId(int)
+ */
+ private void setTableinfoId(int tableinfoId) {
+ this.tableinfoId = tableinfoId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#getName()
+ */
+ private String getName() {
+ return this.name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#setName(java.lang.String)
+ */
+ private void setName(String name) {
+ this.name = name;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#getPrimaryKeyColumn()
+ */
+ private String getPrimaryKeyColumn() {
+ return this.primaryKeyColumn;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#setPrimaryKeyColumn(java.lang.String)
+ */
+ private void setPrimaryKeyColumn(String primaryKeyColumn) {
+ this.primaryKeyColumn = primaryKeyColumn;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#getIsView()
+ */
+ private int getIsView() {
+ return this.isView;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#setIsView(int)
+ */
+ private void setIsView(int isView) {
+ this.isView = isView;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#getViewOnTableId()
+ */
+ private Integer getViewOnTableId() {
+ return this.viewOnTableId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#setViewOnTableId(java.lang.Integer)
+ */
+ private void setViewOnTableId(Integer viewOnTableId) {
+ this.viewOnTableId = viewOnTableId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#getSuperclassTableId()
+ */
+ private Integer getSuperclassTableId() {
+ return this.superclassTableId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#setSuperclassTableId(java.lang.Integer)
+ */
+ private void setSuperclassTableId(Integer superclassTableId) {
+ this.superclassTableId = superclassTableId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#getIsUpdateable()
+ */
+ private int getIsUpdateable() {
+ return this.isUpdateable;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#setIsUpdateable(int)
+ */
+ private void setIsUpdateable(int isUpdateable) {
+ this.isUpdateable = isUpdateable;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#getModificationDate()
+ */
+ private Date getModificationDate() {
+ return this.modificationDate;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.TableInfoI#setModificationDate(java.util.Date)
+ */
+ private void setModificationDate(Date modificationDate) {
+ this.modificationDate = modificationDate;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/organism/Organism.java b/org/gmod/schema/organism/Organism.java
new file mode 100644
index 0000000..8a539e7
--- /dev/null
+++ b/org/gmod/schema/organism/Organism.java
@@ -0,0 +1,207 @@
+package org.gmod.schema.organism;
+
+
+
+import org.gmod.schema.phylogeny.PhylonodeOrganism;
+import org.gmod.schema.sequence.Feature;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+
+
+
+
+
+
+
+
+
+
+public class Organism implements Serializable {
+
+ private Set<PhylonodeOrganism> phylonodeOrganisms = new HashSet<PhylonodeOrganism>(0);
+
+ public Set<PhylonodeOrganism> getPhylonodeOrganisms() {
+ return this.phylonodeOrganisms;
+ }
+
+ public void setPhylonodeOrganisms(Set<PhylonodeOrganism> phylonodeOrganisms) {
+ this.phylonodeOrganisms = phylonodeOrganisms;
+ }
+
+ // Fields
+
+
+
+ private int organismId;
+
+
+ private String abbreviation;
+
+
+ private String genus;
+
+
+ private String species;
+
+
+ private String commonName;
+
+
+ private String comment;
+
+
+ private Set<OrganismProp> organismProps = new HashSet<OrganismProp>(0);
+
+
+ private Set<Feature> features = new HashSet<Feature>(0);
+
+
+ private Set<OrganismDbXRef> organismDbXRefs = new HashSet<OrganismDbXRef>(0);
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#getOrganismId()
+ */
+ public int getOrganismId() {
+ return this.organismId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#setOrganismId(int)
+ */
+ public void setOrganismId(int organismId) {
+ this.organismId = organismId;
+ }
+
+ public String getFullName() {
+ return getGenus()+' '+getSpecies();
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#getAbbreviation()
+ */
+ public String getAbbreviation() {
+ return this.abbreviation;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#setAbbreviation(java.lang.String)
+ */
+ public void setAbbreviation(String abbreviation) {
+ this.abbreviation = abbreviation;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#getGenus()
+ */
+ public String getGenus() {
+ return this.genus;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#setGenus(java.lang.String)
+ */
+ public void setGenus(String genus) {
+ this.genus = genus;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#getSpecies()
+ */
+ public String getSpecies() {
+ return this.species;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#setSpecies(java.lang.String)
+ */
+ public void setSpecies(String species) {
+ this.species = species;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#getCommonName()
+ */
+ public String getCommonName() {
+ return this.commonName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#setCommonName(java.lang.String)
+ */
+ public void setCommonName(String commonName) {
+ this.commonName = commonName;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#getComment()
+ */
+ public String getComment() {
+ return this.comment;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#setComment(java.lang.String)
+ */
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#getOrganismProps()
+ */
+ public Set<OrganismProp> getOrganismProps() {
+ return this.organismProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#setOrganismProps(java.util.Set)
+ */
+ private void setOrganismProps(Set<OrganismProp> organismProps) {
+ this.organismProps = organismProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#getFeatures()
+ */
+ private Collection<Feature> getFeatures() {
+ return this.features;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#setFeatures(java.util.Set)
+ */
+ private void setFeatures(Set<Feature> features) {
+ this.features = features;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#getOrganismDbXRefs()
+ */
+ private Collection<OrganismDbXRef> getOrganismDbXRefs() {
+ return this.organismDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismI#setOrganismDbXRefs(java.util.Set)
+ */
+ private void setOrganismDbXRefs(Set<OrganismDbXRef> organismDbXRefs) {
+ this.organismDbXRefs = organismDbXRefs;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/organism/OrganismDbXRef.java b/org/gmod/schema/organism/OrganismDbXRef.java
new file mode 100644
index 0000000..05ec076
--- /dev/null
+++ b/org/gmod/schema/organism/OrganismDbXRef.java
@@ -0,0 +1,85 @@
+package org.gmod.schema.organism;
+
+import org.gmod.schema.general.DbXRef;
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class OrganismDbXRef implements Serializable {
+
+ // Fields
+
+
+
+ private int organismDbXRefId;
+
+
+
+
+ private Organism organism;
+
+
+
+
+ private DbXRef dbXRef;
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismDbXRefI#getOrganismDbXRefId()
+ */
+ private int getOrganismDbXRefId() {
+ return this.organismDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismDbXRefI#setOrganismDbXRefId(int)
+ */
+ private void setOrganismDbXRefId(int organismDbXRefId) {
+ this.organismDbXRefId = organismDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismDbXRefI#getOrganism()
+ */
+ private Organism getOrganism() {
+ return this.organism;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismDbXRefI#setOrganism(org.gmod.schema.organism.OrganismI)
+ */
+ private void setOrganism(Organism organism) {
+ this.organism = organism;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismDbXRefI#getDbXRef()
+ */
+ private DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismDbXRefI#setDbXRef(org.gmod.schema.general.DbXRefI)
+ */
+ private void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/organism/OrganismProp.java b/org/gmod/schema/organism/OrganismProp.java
new file mode 100644
index 0000000..b8a1d5b
--- /dev/null
+++ b/org/gmod/schema/organism/OrganismProp.java
@@ -0,0 +1,124 @@
+package org.gmod.schema.organism;
+
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.utils.propinterface.PropertyI;
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class OrganismProp implements Serializable, PropertyI {
+
+ // Fields
+
+
+
+ private int organismPropId;
+
+
+
+
+ private Organism organism;
+
+
+
+
+ private CvTerm cvTerm;
+
+
+ private String value;
+
+
+ private int rank;
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#getOrganismPropId()
+ */
+ private int getOrganismPropId() {
+ return this.organismPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#setOrganismPropId(int)
+ */
+ private void setOrganismPropId(int organismPropId) {
+ this.organismPropId = organismPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#getOrganism()
+ */
+ private Organism getOrganism() {
+ return this.organism;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#setOrganism(org.gmod.schema.organism.OrganismI)
+ */
+ private void setOrganism(Organism organism) {
+ this.organism = organism;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#getCvTerm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#setCvTerm(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#getValue()
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#setValue(java.lang.String)
+ */
+ private void setValue(String value) {
+ this.value = value;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#getRank()
+ */
+ private int getRank() {
+ return this.rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.OrganismPropI#setRank(int)
+ */
+ private void setRank(int rank) {
+ this.rank = rank;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/phylogeny/Phylonode.java b/org/gmod/schema/phylogeny/Phylonode.java
new file mode 100644
index 0000000..8bb2b29
--- /dev/null
+++ b/org/gmod/schema/phylogeny/Phylonode.java
@@ -0,0 +1,230 @@
+package org.gmod.schema.phylogeny;
+// Generated Aug 31, 2006 4:02:18 PM by Hibernate Tools 3.2.0.beta7
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.sequence.Feature;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Phylonode generated by hbm2java
+ */
+
+
+public class Phylonode implements java.io.Serializable {
+
+ // Fields
+
+ private int phylonodeId;
+ private Phylotree phylotree;
+ private Phylonode parent;
+ private CvTerm cvTerm;
+ private Feature feature;
+ private int leftIdx;
+ private int rightIdx;
+ private String label;
+ private Double distance;
+ private Set<PhylonodeRelationship> phylonodeRelationshipsForObjectId = new HashSet<PhylonodeRelationship>(0);
+ private Set<PhylonodeOrganism> phylonodeOrganisms = new HashSet<PhylonodeOrganism>(0);
+ private Set<PhylonodePub> phylonodePubs = new HashSet<PhylonodePub>(0);
+ private Set<Phylonode> phylonodes = new HashSet<Phylonode>(0);
+ private Set<PhylonodeRelationship> phylonodeRelationshipsForSubjectId = new HashSet<PhylonodeRelationship>(0);
+ private Set<PhylonodeDbXRef> phylonodeDbXRefs = new HashSet<PhylonodeDbXRef>(0);
+ private Set<PhylonodeProp> phylonodeProps = new HashSet<PhylonodeProp>(0);
+
+ // Constructors
+
+ /** default constructor */
+ public Phylonode() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public Phylonode(int phylonodeId, Phylotree phylotree, int leftIdx, int rightIdx) {
+ this.phylonodeId = phylonodeId;
+ this.phylotree = phylotree;
+ this.leftIdx = leftIdx;
+ this.rightIdx = rightIdx;
+ }
+ /** full constructor */
+ public Phylonode(int phylonodeId, Phylotree phylotree, Phylonode parent, CvTerm cvTerm, Feature feature, int leftIdx, int rightIdx, String label, Double distance, Set<PhylonodeRelationship> phylonodeRelationshipsForObjectId, Set<PhylonodeOrganism> phylonodeOrganisms, Set<PhylonodePub> phylonodePubs, Set<Phylonode> phylonodes, Set<PhylonodeRelationship> phylonodeRelationshipsForSubjectId, Set<PhylonodeDbXRef> phylonodeDbXRefs, Set<PhylonodeProp> phylonodeProps) {
+ this.phylonodeId = phylonodeId;
+ this.phylotree = phylotree;
+ this.parent = parent;
+ this.cvTerm = cvTerm;
+ this.feature = feature;
+ this.leftIdx = leftIdx;
+ this.rightIdx = rightIdx;
+ this.label = label;
+ this.distance = distance;
+ this.phylonodeRelationshipsForObjectId = phylonodeRelationshipsForObjectId;
+ this.phylonodeOrganisms = phylonodeOrganisms;
+ this.phylonodePubs = phylonodePubs;
+ this.phylonodes = phylonodes;
+ this.phylonodeRelationshipsForSubjectId = phylonodeRelationshipsForSubjectId;
+ this.phylonodeDbXRefs = phylonodeDbXRefs;
+ this.phylonodeProps = phylonodeProps;
+ }
+
+ // Property accessors
+
+
+
+ public int getPhylonodeId() {
+ return this.phylonodeId;
+ }
+
+ public void setPhylonodeId(int phylonodeId) {
+ this.phylonodeId = phylonodeId;
+ }
+
+
+
+ public Phylotree getPhylotree() {
+ return this.phylotree;
+ }
+
+ public void setPhylotree(Phylotree phylotree) {
+ this.phylotree = phylotree;
+ }
+
+
+
+ public Phylonode getParent() {
+ return this.parent;
+ }
+
+ public void setParent(Phylonode parent) {
+ this.parent = parent;
+ }
+
+
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+
+ public Feature getFeature() {
+ return this.feature;
+ }
+
+ public void setFeature(Feature feature) {
+ this.feature = feature;
+ }
+
+
+ public int getLeftIdx() {
+ return this.leftIdx;
+ }
+
+ public void setLeftIdx(int leftIdx) {
+ this.leftIdx = leftIdx;
+ }
+
+
+ public int getRightIdx() {
+ return this.rightIdx;
+ }
+
+ public void setRightIdx(int rightIdx) {
+ this.rightIdx = rightIdx;
+ }
+
+
+ public String getLabel() {
+ return this.label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+
+ public Double getDistance() {
+ return this.distance;
+ }
+
+ public void setDistance(Double distance) {
+ this.distance = distance;
+ }
+
+ public Collection<PhylonodeRelationship> getPhylonodeRelationshipsForObjectId() {
+ return this.phylonodeRelationshipsForObjectId;
+ }
+
+ public void setPhylonodeRelationshipsForObjectId(Set<PhylonodeRelationship> phylonodeRelationshipsForObjectId) {
+ this.phylonodeRelationshipsForObjectId = phylonodeRelationshipsForObjectId;
+ }
+
+ public Collection<PhylonodeOrganism> getPhylonodeOrganisms() {
+ return this.phylonodeOrganisms;
+ }
+
+ public void setPhylonodeOrganisms(Set<PhylonodeOrganism> phylonodeOrganisms) {
+ this.phylonodeOrganisms = phylonodeOrganisms;
+ }
+
+ public Collection<PhylonodePub> getPhylonodePubs() {
+ return this.phylonodePubs;
+ }
+
+ public void setPhylonodePubs(Set<PhylonodePub> phylonodePubs) {
+ this.phylonodePubs = phylonodePubs;
+ }
+
+ public Collection<Phylonode> getPhylonodes() {
+ return this.phylonodes;
+ }
+
+ public void setPhylonodes(Set<Phylonode> phylonodes) {
+ this.phylonodes = phylonodes;
+ }
+
+ public Collection<PhylonodeRelationship> getPhylonodeRelationshipsForSubjectId() {
+ return this.phylonodeRelationshipsForSubjectId;
+ }
+
+ public void setPhylonodeRelationshipsForSubjectId(Set<PhylonodeRelationship> phylonodeRelationshipsForSubjectId) {
+ this.phylonodeRelationshipsForSubjectId = phylonodeRelationshipsForSubjectId;
+ }
+
+ public Collection<PhylonodeDbXRef> getPhylonodeDbXRefs() {
+ return this.phylonodeDbXRefs;
+ }
+
+ public void setPhylonodeDbXRefs(Set<PhylonodeDbXRef> phylonodeDbXRefs) {
+ this.phylonodeDbXRefs = phylonodeDbXRefs;
+ }
+
+ public Collection<PhylonodeProp> getPhylonodeProps() {
+ return this.phylonodeProps;
+ }
+
+ public void setPhylonodeProps(Set<PhylonodeProp> phylonodeProps) {
+ this.phylonodeProps = phylonodeProps;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/phylogeny/PhylonodeDbXRef.java b/org/gmod/schema/phylogeny/PhylonodeDbXRef.java
new file mode 100644
index 0000000..cf18f75
--- /dev/null
+++ b/org/gmod/schema/phylogeny/PhylonodeDbXRef.java
@@ -0,0 +1,80 @@
+package org.gmod.schema.phylogeny;
+// Generated Aug 31, 2006 4:02:18 PM by Hibernate Tools 3.2.0.beta7
+
+
+import org.gmod.schema.general.DbXRef;
+
+
+
+
+
+
+
+
+
+
+/**
+ * PhylonodeDbXRef generated by hbm2java
+ */
+
+
+public class PhylonodeDbXRef implements java.io.Serializable {
+
+ // Fields
+
+ private int phylonodeDbXRefId;
+ private DbXRef dbXRef;
+ private Phylonode phylonode;
+
+ // Constructors
+
+ /** default constructor */
+ public PhylonodeDbXRef() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public PhylonodeDbXRef(int phylonodeDbXRefId, DbXRef dbXRef, Phylonode phylonode) {
+ this.phylonodeDbXRefId = phylonodeDbXRefId;
+ this.dbXRef = dbXRef;
+ this.phylonode = phylonode;
+ }
+
+ // Property accessors
+
+
+
+ public int getPhylonodeDbXRefId() {
+ return this.phylonodeDbXRefId;
+ }
+
+ public void setPhylonodeDbXRefId(int phylonodeDbXRefId) {
+ this.phylonodeDbXRefId = phylonodeDbXRefId;
+ }
+
+
+
+
+ public DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ public void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+
+ public Phylonode getPhylonode() {
+ return this.phylonode;
+ }
+
+ public void setPhylonode(Phylonode phylonode) {
+ this.phylonode = phylonode;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/phylogeny/PhylonodeOrganism.java b/org/gmod/schema/phylogeny/PhylonodeOrganism.java
new file mode 100644
index 0000000..978e9b6
--- /dev/null
+++ b/org/gmod/schema/phylogeny/PhylonodeOrganism.java
@@ -0,0 +1,80 @@
+package org.gmod.schema.phylogeny;
+// Generated Aug 31, 2006 4:02:18 PM by Hibernate Tools 3.2.0.beta7
+
+
+import org.gmod.schema.organism.Organism;
+
+
+
+
+
+
+
+
+
+
+/**
+ * PhylonodeOrganism generated by hbm2java
+ */
+
+
+public class PhylonodeOrganism implements java.io.Serializable {
+
+ // Fields
+
+ private int phylonodeOrganismId;
+ private Organism organism;
+ private Phylonode phylonode;
+
+ // Constructors
+
+ /** default constructor */
+ public PhylonodeOrganism() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public PhylonodeOrganism(int phylonodeOrganismId, Organism organism, Phylonode phylonode) {
+ this.phylonodeOrganismId = phylonodeOrganismId;
+ this.organism = organism;
+ this.phylonode = phylonode;
+ }
+
+ // Property accessors
+
+
+
+ public int getPhylonodeOrganismId() {
+ return this.phylonodeOrganismId;
+ }
+
+ public void setPhylonodeOrganismId(int phylonodeOrganismId) {
+ this.phylonodeOrganismId = phylonodeOrganismId;
+ }
+
+
+
+ public Organism getOrganism() {
+ return this.organism;
+ }
+
+ public void setOrganism(Organism organism) {
+ this.organism = organism;
+ }
+
+
+
+ public Phylonode getPhylonode() {
+ return this.phylonode;
+ }
+
+ public void setPhylonode(Phylonode phylonode) {
+ this.phylonode = phylonode;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/phylogeny/PhylonodeProp.java b/org/gmod/schema/phylogeny/PhylonodeProp.java
new file mode 100644
index 0000000..8126743
--- /dev/null
+++ b/org/gmod/schema/phylogeny/PhylonodeProp.java
@@ -0,0 +1,104 @@
+package org.gmod.schema.phylogeny;
+// Generated Aug 31, 2006 4:02:18 PM by Hibernate Tools 3.2.0.beta7
+
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.utils.propinterface.PropertyI;
+
+
+
+
+
+
+
+
+
+
+/**
+ * PhylonodeProp generated by hbm2java
+ */
+
+
+public class PhylonodeProp implements java.io.Serializable, PropertyI {
+
+ // Fields
+
+ private int phylonodePropId;
+ private CvTerm cvTerm;
+ private Phylonode phylonode;
+ private String value;
+ private int rank;
+
+ // Constructors
+
+ /** default constructor */
+ public PhylonodeProp() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public PhylonodeProp(int phylonodePropId, CvTerm cvTerm, Phylonode phylonode, String value, int rank) {
+ this.phylonodePropId = phylonodePropId;
+ this.cvTerm = cvTerm;
+ this.phylonode = phylonode;
+ this.value = value;
+ this.rank = rank;
+ }
+
+ // Property accessors
+
+
+
+ public int getPhylonodePropId() {
+ return this.phylonodePropId;
+ }
+
+ public void setPhylonodePropId(int phylonodePropId) {
+ this.phylonodePropId = phylonodePropId;
+ }
+
+
+
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+
+
+ public Phylonode getPhylonode() {
+ return this.phylonode;
+ }
+
+ public void setPhylonode(Phylonode phylonode) {
+ this.phylonode = phylonode;
+ }
+
+
+ public String getValue() {
+ return this.value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+
+ public int getRank() {
+ return this.rank;
+ }
+
+ public void setRank(int rank) {
+ this.rank = rank;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/phylogeny/PhylonodePub.java b/org/gmod/schema/phylogeny/PhylonodePub.java
new file mode 100644
index 0000000..aabd38c
--- /dev/null
+++ b/org/gmod/schema/phylogeny/PhylonodePub.java
@@ -0,0 +1,80 @@
+package org.gmod.schema.phylogeny;
+// Generated Aug 31, 2006 4:02:18 PM by Hibernate Tools 3.2.0.beta7
+
+
+import org.gmod.schema.pub.Pub;
+
+
+
+
+
+
+
+
+
+
+/**
+ * PhylonodePub generated by hbm2java
+ */
+
+
+public class PhylonodePub implements java.io.Serializable {
+
+ // Fields
+
+ private int phylonodePubId;
+ private Pub pub;
+ private Phylonode phylonode;
+
+ // Constructors
+
+ /** default constructor */
+ public PhylonodePub() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public PhylonodePub(int phylonodePubId, Pub pub, Phylonode phylonode) {
+ this.phylonodePubId = phylonodePubId;
+ this.pub = pub;
+ this.phylonode = phylonode;
+ }
+
+ // Property accessors
+
+
+
+ public int getPhylonodePubId() {
+ return this.phylonodePubId;
+ }
+
+ public void setPhylonodePubId(int phylonodePubId) {
+ this.phylonodePubId = phylonodePubId;
+ }
+
+
+
+ public Pub getPub() {
+ return this.pub;
+ }
+
+ public void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+
+ public Phylonode getPhylonode() {
+ return this.phylonode;
+ }
+
+ public void setPhylonode(Phylonode phylonode) {
+ this.phylonode = phylonode;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/phylogeny/PhylonodeRelationship.java b/org/gmod/schema/phylogeny/PhylonodeRelationship.java
new file mode 100644
index 0000000..0330c9a
--- /dev/null
+++ b/org/gmod/schema/phylogeny/PhylonodeRelationship.java
@@ -0,0 +1,110 @@
+package org.gmod.schema.phylogeny;
+// Generated Aug 31, 2006 4:02:18 PM by Hibernate Tools 3.2.0.beta7
+
+
+import org.gmod.schema.cv.CvTerm;
+
+
+
+
+
+
+
+
+
+
+/**
+ * PhylonodeRelationship generated by hbm2java
+ */
+
+
+public class PhylonodeRelationship implements java.io.Serializable {
+
+ // Fields
+
+ private int phylonodeRelationshipId;
+ private Phylonode phylonodeBySubjectId;
+ private Phylonode phylonodeByObjectId;
+ private CvTerm cvTerm;
+ private Integer rank;
+
+ // Constructors
+
+ /** default constructor */
+ public PhylonodeRelationship() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public PhylonodeRelationship(int phylonodeRelationshipId, Phylonode phylonodeBySubjectId, Phylonode phylonodeByObjectId, CvTerm cvTerm) {
+ this.phylonodeRelationshipId = phylonodeRelationshipId;
+ this.phylonodeBySubjectId = phylonodeBySubjectId;
+ this.phylonodeByObjectId = phylonodeByObjectId;
+ this.cvTerm = cvTerm;
+ }
+ /** full constructor */
+ public PhylonodeRelationship(int phylonodeRelationshipId, Phylonode phylonodeBySubjectId, Phylonode phylonodeByObjectId, CvTerm cvTerm, Integer rank) {
+ this.phylonodeRelationshipId = phylonodeRelationshipId;
+ this.phylonodeBySubjectId = phylonodeBySubjectId;
+ this.phylonodeByObjectId = phylonodeByObjectId;
+ this.cvTerm = cvTerm;
+ this.rank = rank;
+ }
+
+ // Property accessors
+
+
+
+ public int getPhylonodeRelationshipId() {
+ return this.phylonodeRelationshipId;
+ }
+
+ public void setPhylonodeRelationshipId(int phylonodeRelationshipId) {
+ this.phylonodeRelationshipId = phylonodeRelationshipId;
+ }
+
+
+
+ public Phylonode getPhylonodeBySubjectId() {
+ return this.phylonodeBySubjectId;
+ }
+
+ public void setPhylonodeBySubjectId(Phylonode phylonodeBySubjectId) {
+ this.phylonodeBySubjectId = phylonodeBySubjectId;
+ }
+
+
+
+ public Phylonode getPhylonodeByObjectId() {
+ return this.phylonodeByObjectId;
+ }
+
+ public void setPhylonodeByObjectId(Phylonode phylonodeByObjectId) {
+ this.phylonodeByObjectId = phylonodeByObjectId;
+ }
+
+
+
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+
+ public Integer getRank() {
+ return this.rank;
+ }
+
+ public void setRank(Integer rank) {
+ this.rank = rank;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/phylogeny/Phylotree.java b/org/gmod/schema/phylogeny/Phylotree.java
new file mode 100644
index 0000000..e7d3ad6
--- /dev/null
+++ b/org/gmod/schema/phylogeny/Phylotree.java
@@ -0,0 +1,131 @@
+package org.gmod.schema.phylogeny;
+// Generated Aug 31, 2006 4:02:18 PM by Hibernate Tools 3.2.0.beta7
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.general.DbXRef;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Phylotree generated by hbm2java
+ */
+
+
+public class Phylotree implements java.io.Serializable {
+
+ // Fields
+
+ private int phylotreeId;
+ private CvTerm cvTerm;
+ private DbXRef dbXRef;
+ private String name;
+ private String comment;
+ private Set<Phylonode> phylonodes = new HashSet<Phylonode>(0);
+ private Set<PhylotreePub> phylotreePubs = new HashSet<PhylotreePub>(0);
+
+ // Constructors
+
+ /** default constructor */
+ public Phylotree() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public Phylotree(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+ /** full constructor */
+ public Phylotree(CvTerm cvTerm, DbXRef dbXRef, String name, String comment, Set<Phylonode> phylonodes, Set<PhylotreePub> phylotreePubs) {
+ this.cvTerm = cvTerm;
+ this.dbXRef = dbXRef;
+ this.name = name;
+ this.comment = comment;
+ this.phylonodes = phylonodes;
+ this.phylotreePubs = phylotreePubs;
+ }
+
+ // Property accessors
+
+
+ public int getPhylotreeId() {
+ return this.phylotreeId;
+ }
+
+ private void setPhylotreeId(int phylotreeId) {
+ this.phylotreeId = phylotreeId;
+ }
+
+
+
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+
+
+ public DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ public void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+
+ public String getComment() {
+ return this.comment;
+ }
+
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+
+ public Collection<Phylonode> getPhylonodes() {
+ return this.phylonodes;
+ }
+
+ public void setPhylonodes(Set<Phylonode> phylonodes) {
+ this.phylonodes = phylonodes;
+ }
+
+ public Collection<PhylotreePub> getPhylotreePubs() {
+ return this.phylotreePubs;
+ }
+
+ public void setPhylotreePubs(Set<PhylotreePub> phylotreePubs) {
+ this.phylotreePubs = phylotreePubs;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/phylogeny/PhylotreePub.java b/org/gmod/schema/phylogeny/PhylotreePub.java
new file mode 100644
index 0000000..e5f2dc2
--- /dev/null
+++ b/org/gmod/schema/phylogeny/PhylotreePub.java
@@ -0,0 +1,80 @@
+package org.gmod.schema.phylogeny;
+// Generated Aug 31, 2006 4:02:18 PM by Hibernate Tools 3.2.0.beta7
+
+
+import org.gmod.schema.pub.Pub;
+
+
+
+
+
+
+
+
+
+
+/**
+ * PhylotreePub generated by hbm2java
+ */
+
+
+public class PhylotreePub implements java.io.Serializable {
+
+ // Fields
+
+ private int phylotreePubId;
+ private Phylotree phylotree;
+ private Pub pub;
+
+ // Constructors
+
+ /** default constructor */
+ public PhylotreePub() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public PhylotreePub(int phylotreePubId, Phylotree phylotree, Pub pub) {
+ this.phylotreePubId = phylotreePubId;
+ this.phylotree = phylotree;
+ this.pub = pub;
+ }
+
+ // Property accessors
+
+
+
+ public int getPhylotreePubId() {
+ return this.phylotreePubId;
+ }
+
+ public void setPhylotreePubId(int phylotreePubId) {
+ this.phylotreePubId = phylotreePubId;
+ }
+
+
+
+ public Phylotree getPhylotree() {
+ return this.phylotree;
+ }
+
+ public void setPhylotree(Phylotree phylotree) {
+ this.phylotree = phylotree;
+ }
+
+
+
+ public Pub getPub() {
+ return this.pub;
+ }
+
+ public void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/pub/Pub.java b/org/gmod/schema/pub/Pub.java
new file mode 100644
index 0000000..a58817a
--- /dev/null
+++ b/org/gmod/schema/pub/Pub.java
@@ -0,0 +1,555 @@
+package org.gmod.schema.pub;
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.phylogeny.PhylonodePub;
+import org.gmod.schema.phylogeny.PhylotreePub;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureCvTermPub;
+import org.gmod.schema.sequence.FeatureLocPub;
+import org.gmod.schema.sequence.FeaturePropPub;
+import org.gmod.schema.sequence.FeaturePub;
+import org.gmod.schema.sequence.FeatureRelationshipPropPub;
+import org.gmod.schema.sequence.FeatureRelationshipPub;
+import org.gmod.schema.sequence.FeatureSynonym;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+
+
+
+
+
+
+
+
+
+
+
+
+public class Pub implements Serializable {
+
+
+ private Set<PhylotreePub> phylotreePubs = new HashSet<PhylotreePub>(0);
+ private Set<PhylonodePub> phylonodePubs = new HashSet<PhylonodePub>(0);
+
+
+ public Set<PhylotreePub> getPhylotreePubs() {
+ return this.phylotreePubs;
+ }
+
+ public void setPhylotreePubs(Set<PhylotreePub> phylotreePubs) {
+ this.phylotreePubs = phylotreePubs;
+ }
+
+
+ public Set<PhylonodePub> getPhylonodePubs() {
+ return this.phylonodePubs;
+ }
+
+ public void setPhylonodePubs(Set<PhylonodePub> phylonodePubs) {
+ this.phylonodePubs = phylonodePubs;
+ }
+
+ // Fields
+
+
+
+ private int pubId;
+
+
+
+
+ private CvTerm cvTerm;
+
+
+ private String title;
+
+
+ private String volumeTitle;
+
+
+ private String volume;
+
+
+ private String seriesName;
+
+
+ private String issue;
+
+
+ private String pyear;
+
+
+ private String pages;
+
+
+ private String miniRef;
+
+
+ private String uniqueName;
+
+
+ private Boolean obsolete;
+
+
+ private String publisher;
+
+
+ private String pubPlace;
+
+
+ private Set<PubAuthor> pubAuthors = new HashSet<PubAuthor>(0);
+
+
+ private Set<PubRelationship> pubRelationshipsForObjectId = new HashSet<PubRelationship>(0);
+
+
+ private Set<PubDbXRef> pubDbXRefs = new HashSet<PubDbXRef>(0);
+
+
+ private Set<FeatureCvTerm> featureCvTerms = new HashSet<FeatureCvTerm>(0);
+
+
+ private Set<FeatureRelationshipPub> featureRelationshipPubs = new HashSet<FeatureRelationshipPub>(0);
+
+
+ private Set<FeaturePub> featurePubs = new HashSet<FeaturePub>(0);
+
+
+ private Set<FeaturePropPub> featurePropPubs = new HashSet<FeaturePropPub>(0);
+
+
+ private Set<FeatureSynonym> featureSynonyms = new HashSet<FeatureSynonym>(0);
+
+
+ private Set<FeatureCvTermPub> featureCvTermPubs = new HashSet<FeatureCvTermPub>(0);
+
+
+ private Set<FeatureRelationshipPropPub> featureRelationshipPropPubs = new HashSet<FeatureRelationshipPropPub>(0);
+
+
+ private Set<PubProp> pubProps = new HashSet<PubProp>(0);
+
+
+ private Set<PubRelationship> pubRelationshipsForSubjectId = new HashSet<PubRelationship>(0);
+
+
+ private Set<FeatureLocPub> featureLocPubs = new HashSet<FeatureLocPub>(0);
+
+ // Constructors
+
+ /** default constructor */
+ public Pub() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public Pub(String uniqueName, CvTerm cvTerm) {
+ this.uniqueName = uniqueName;
+ this.cvTerm = cvTerm;
+ }
+
+ public Pub(String uniqueName) {
+ this.uniqueName = uniqueName;
+ }
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPubId()
+ */
+ public int getPubId() {
+ return this.pubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPubId(int)
+ */
+ public void setPubId(int pubId) {
+ this.pubId = pubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getCvTerm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setCvTerm(org.gmod.schema.cv.CvTermI)
+ */
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getTitle()
+ */
+ public String getTitle() {
+ return this.title;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setTitle(java.lang.String)
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getVolumeTitle()
+ */
+ public String getVolumeTitle() {
+ return this.volumeTitle;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setVolumeTitle(java.lang.String)
+ */
+ public void setVolumeTitle(String volumeTitle) {
+ this.volumeTitle = volumeTitle;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getVolume()
+ */
+ public String getVolume() {
+ return this.volume;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setVolume(java.lang.String)
+ */
+ public void setVolume(String volume) {
+ this.volume = volume;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getSeriesName()
+ */
+ public String getSeriesName() {
+ return this.seriesName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setSeriesName(java.lang.String)
+ */
+ public void setSeriesName(String seriesName) {
+ this.seriesName = seriesName;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getIssue()
+ */
+ public String getIssue() {
+ return this.issue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setIssue(java.lang.String)
+ */
+ public void setIssue(String issue) {
+ this.issue = issue;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPyear()
+ */
+ public String getPyear() {
+ return this.pyear;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPyear(java.lang.String)
+ */
+ public void setPyear(String pyear) {
+ this.pyear = pyear;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPages()
+ */
+ public String getPages() {
+ return this.pages;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPages(java.lang.String)
+ */
+ public void setPages(String pages) {
+ this.pages = pages;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getMiniRef()
+ */
+ public String getMiniRef() {
+ return this.miniRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setMiniRef(java.lang.String)
+ */
+ public void setMiniRef(String miniRef) {
+ this.miniRef = miniRef;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getUniqueName()
+ */
+ public String getUniqueName() {
+ return this.uniqueName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setUniqueName(java.lang.String)
+ */
+ public void setUniqueName(String uniqueName) {
+ this.uniqueName = uniqueName;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getobsolete()
+ */
+ public Boolean getObsolete() {
+ return this.obsolete;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setobsolete(java.lang.Boolean)
+ */
+ public void setObsolete(Boolean obsolete) {
+ this.obsolete = obsolete;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPublisher()
+ */
+ public String getPublisher() {
+ return this.publisher;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPublisher(java.lang.String)
+ */
+ public void setPublisher(String publisher) {
+ this.publisher = publisher;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPubPlace()
+ */
+ public String getPubPlace() {
+ return this.pubPlace;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPubPlace(java.lang.String)
+ */
+ public void setPubPlace(String pubPlace) {
+ this.pubPlace = pubPlace;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPubAuthors()
+ */
+ public Collection<PubAuthor> getPubAuthors() {
+ return this.pubAuthors;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPubAuthors(java.util.Set)
+ */
+ public void setPubAuthors(Set<PubAuthor> pubAuthors) {
+ this.pubAuthors = pubAuthors;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPubRelationshipsForObjectId()
+ */
+ public Collection<PubRelationship> getPubRelationshipsForObjectId() {
+ return this.pubRelationshipsForObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPubRelationshipsForObjectId(java.util.Set)
+ */
+ public void setPubRelationshipsForObjectId(Set<PubRelationship> pubRelationshipsForObjectId) {
+ this.pubRelationshipsForObjectId = pubRelationshipsForObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPubDbXRefs()
+ */
+ private Collection<PubDbXRef> getPubDbXRefs() {
+ return this.pubDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPubDbXRefs(java.util.Set)
+ */
+ private void setPubDbXRefs(Set<PubDbXRef> pubDbXRefs) {
+ this.pubDbXRefs = pubDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getFeatureCvTerms()
+ */
+ private Collection<FeatureCvTerm> getFeatureCvTerms() {
+ return this.featureCvTerms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setFeatureCvTerms(java.util.Set)
+ */
+ private void setFeatureCvTerms(Set<FeatureCvTerm> featureCvTerms) {
+ this.featureCvTerms = featureCvTerms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getFeatureRelationshipPubs()
+ */
+ private Collection<FeatureRelationshipPub> getFeatureRelationshipPubs() {
+ return this.featureRelationshipPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setFeatureRelationshipPubs(java.util.Set)
+ */
+ private void setFeatureRelationshipPubs(Set<FeatureRelationshipPub> featureRelationshipPubs) {
+ this.featureRelationshipPubs = featureRelationshipPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getFeaturePubs()
+ */
+ private Collection<FeaturePub> getFeaturePubs() {
+ return this.featurePubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setFeaturePubs(java.util.Set)
+ */
+ private void setFeaturePubs(Set<FeaturePub> featurePubs) {
+ this.featurePubs = featurePubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getFeaturePropPubs()
+ */
+ private Collection<FeaturePropPub> getFeaturePropPubs() {
+ return this.featurePropPubs;
+ }
+//
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setFeaturePropPubs(java.util.Set)
+ */
+ private void setFeaturePropPubs(Set<FeaturePropPub> featurePropPubs) {
+ this.featurePropPubs = featurePropPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getFeatureSynonyms()
+ */
+ private Collection<FeatureSynonym> getFeatureSynonyms() {
+ return this.featureSynonyms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setFeatureSynonyms(java.util.Set)
+ */
+ private void setFeatureSynonyms(Set<FeatureSynonym> featureSynonyms) {
+ this.featureSynonyms = featureSynonyms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getFeatureCvTermPubs()
+ */
+ private Collection<FeatureCvTermPub> getFeatureCvTermPubs() {
+ return this.featureCvTermPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setFeatureCvTermPubs(java.util.Set)
+ */
+ private void setFeatureCvTermPubs(Set<FeatureCvTermPub> featureCvTermPubs) {
+ this.featureCvTermPubs = featureCvTermPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getFeatureRelationshipPropPubs()
+ */
+ private Collection<FeatureRelationshipPropPub> getFeatureRelationshipPropPubs() {
+ return this.featureRelationshipPropPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setFeatureRelationshipPropPubs(java.util.Set)
+ */
+ private void setFeatureRelationshipPropPubs(Set<FeatureRelationshipPropPub> featureRelationshipPropPubs) {
+ this.featureRelationshipPropPubs = featureRelationshipPropPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPubProps()
+ */
+ private Collection<PubProp> getPubProps() {
+ return this.pubProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPubProps(java.util.Set)
+ */
+ private void setPubProps(Set<PubProp> pubProps) {
+ this.pubProps = pubProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getPubRelationshipsForSubjectId()
+ */
+ private Collection<PubRelationship> getPubRelationshipsForSubjectId() {
+ return this.pubRelationshipsForSubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setPubRelationshipsForSubjectId(java.util.Set)
+ */
+ private void setPubRelationshipsForSubjectId(Set<PubRelationship> pubRelationshipsForSubjectId) {
+ this.pubRelationshipsForSubjectId = pubRelationshipsForSubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#getFeatureLocPubs()
+ */
+ private Collection<FeatureLocPub> getFeatureLocPubs() {
+ return this.featureLocPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubI#setFeatureLocPubs(java.util.Set)
+ */
+ private void setFeatureLocPubs(Set<FeatureLocPub> featureLocPubs) {
+ this.featureLocPubs = featureLocPubs;
+ }
+
+}
+
+
diff --git a/org/gmod/schema/pub/PubAuthor.java b/org/gmod/schema/pub/PubAuthor.java
new file mode 100644
index 0000000..6edd160
--- /dev/null
+++ b/org/gmod/schema/pub/PubAuthor.java
@@ -0,0 +1,155 @@
+package org.gmod.schema.pub;
+
+
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class PubAuthor implements Serializable {
+
+ // Fields
+
+
+
+ private int pubAuthorId;
+
+
+
+ private Pub pub;
+
+
+ private int rank;
+
+
+ private Boolean editor;
+
+
+ private String surname;
+
+
+ private String givenNames;
+
+
+ private String suffix;
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#getPubAuthorId()
+ */
+ private int getPubAuthorId() {
+ return this.pubAuthorId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#setPubAuthorId(int)
+ */
+ private void setPubAuthorId(int pubAuthorId) {
+ this.pubAuthorId = pubAuthorId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#getPub()
+ */
+ private Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#setPub(org.gmod.schema.pub.PubI)
+ */
+ private void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#getRank()
+ */
+ private int getRank() {
+ return this.rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#setRank(int)
+ */
+ private void setRank(int rank) {
+ this.rank = rank;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#getEditor()
+ */
+ private Boolean getEditor() {
+ return this.editor;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#setEditor(java.lang.Boolean)
+ */
+ private void setEditor(Boolean editor) {
+ this.editor = editor;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#getSurname()
+ */
+ private String getSurname() {
+ return this.surname;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#setSurname(java.lang.String)
+ */
+ private void setSurname(String surname) {
+ this.surname = surname;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#getGivenNames()
+ */
+ private String getGivenNames() {
+ return this.givenNames;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#setGivenNames(java.lang.String)
+ */
+ private void setGivenNames(String givenNames) {
+ this.givenNames = givenNames;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#getSuffix()
+ */
+ private String getSuffix() {
+ return this.suffix;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubAuthorI#setSuffix(java.lang.String)
+ */
+ private void setSuffix(String suffix) {
+ this.suffix = suffix;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/pub/PubDbXRef.java b/org/gmod/schema/pub/PubDbXRef.java
new file mode 100644
index 0000000..1db4eda
--- /dev/null
+++ b/org/gmod/schema/pub/PubDbXRef.java
@@ -0,0 +1,117 @@
+package org.gmod.schema.pub;
+
+import org.gmod.schema.general.DbXRef;
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class PubDbXRef implements Serializable {
+
+ // Fields
+
+
+
+ private int pubDbXRefId;
+
+
+
+
+ private DbXRef dbXRef;
+
+
+
+
+ private Pub pub;
+
+
+ private boolean current;
+
+ // Constructors
+
+ /** default constructor */
+ public PubDbXRef() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public PubDbXRef(Pub pub, DbXRef dbXRef, boolean current) {
+ this.dbXRef = dbXRef;
+ this.pub = pub;
+ this.current = current;
+ }
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubDbXRefI#getPubDbXRefId()
+ */
+ private int getPubDbXRefId() {
+ return this.pubDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubDbXRefI#setPubDbXRefId(int)
+ */
+ private void setPubDbXRefId(int pubDbXRefId) {
+ this.pubDbXRefId = pubDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubDbXRefI#getDbXRef()
+ */
+ public DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubDbXRefI#setDbXRef(org.gmod.schema.general.DbXRefI)
+ */
+ public void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubDbXRefI#getPub()
+ */
+ public Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubDbXRefI#setPub(org.gmod.schema.pub.PubI)
+ */
+ public void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubDbXRefI#isCurrent()
+ */
+ public boolean isCurrent() {
+ return this.current;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubDbXRefI#setCurrent(boolean)
+ */
+ public void setCurrent(boolean current) {
+ this.current = current;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/pub/PubProp.java b/org/gmod/schema/pub/PubProp.java
new file mode 100644
index 0000000..3164d86
--- /dev/null
+++ b/org/gmod/schema/pub/PubProp.java
@@ -0,0 +1,145 @@
+package org.gmod.schema.pub;
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.utils.propinterface.PropertyI;
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+
+public class PubProp implements Serializable, PropertyI {
+
+ // Fields
+
+
+
+ private int pubPropId;
+
+
+
+
+ private CvTerm cvTerm;
+
+
+
+
+ private Pub pub;
+
+
+ private String value;
+
+
+ private Integer rank;
+
+ // Constructors
+
+ /** default constructor */
+ public PubProp() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public PubProp(CvTerm cvTerm, Pub pub, String value) {
+ this.cvTerm = cvTerm;
+ this.pub = pub;
+ this.value = value;
+ }
+ /** full constructor */
+ public PubProp(CvTerm cvTerm, Pub pub, String value, Integer rank) {
+ this.cvTerm = cvTerm;
+ this.pub = pub;
+ this.value = value;
+ this.rank = rank;
+ }
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#getPubPropId()
+ */
+ private int getPubPropId() {
+ return this.pubPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#setPubPropId(int)
+ */
+ private void setPubPropId(int pubPropId) {
+ this.pubPropId = pubPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#getCvTerm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#setCvTerm(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#getPub()
+ */
+ private Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#setPub(org.gmod.schema.pub.PubI)
+ */
+ private void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#getValue()
+ */
+ private String getValue() {
+ return this.value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#setValue(java.lang.String)
+ */
+ private void setValue(String value) {
+ this.value = value;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#getRank()
+ */
+ public Integer getRank() {
+ return this.rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubPropI#setRank(java.lang.Integer)
+ */
+ private void setRank(Integer rank) {
+ this.rank = rank;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/pub/PubRelationship.java b/org/gmod/schema/pub/PubRelationship.java
new file mode 100644
index 0000000..011a4db
--- /dev/null
+++ b/org/gmod/schema/pub/PubRelationship.java
@@ -0,0 +1,104 @@
+package org.gmod.schema.pub;
+
+import org.gmod.schema.cv.CvTerm;
+
+import java.io.Serializable;
+
+
+
+
+
+
+
+
+
+
+
+public class PubRelationship implements Serializable {
+
+ // Fields
+
+
+
+ private int pubRelationshipId;
+
+
+
+
+ private Pub pubBySubjectId;
+
+
+
+
+ private Pub pubByObjectId;
+
+
+
+
+ private CvTerm cvTerm;
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubRelationshipI#getPubRelationshipId()
+ */
+ private int getPubRelationshipId() {
+ return this.pubRelationshipId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubRelationshipI#setPubRelationshipId(int)
+ */
+ private void setPubRelationshipId(int pubRelationshipId) {
+ this.pubRelationshipId = pubRelationshipId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubRelationshipI#getPubBySubjectId()
+ */
+ private Pub getPubBySubjectId() {
+ return this.pubBySubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubRelationshipI#setPubBySubjectId(org.gmod.schema.pub.PubI)
+ */
+ private void setPubBySubjectId(Pub pubBySubjectId) {
+ this.pubBySubjectId = pubBySubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubRelationshipI#getPubByObjectId()
+ */
+ private Pub getPubByObjectId() {
+ return this.pubByObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubRelationshipI#setPubByObjectId(org.gmod.schema.pub.PubI)
+ */
+ private void setPubByObjectId(Pub pubByObjectId) {
+ this.pubByObjectId = pubByObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubRelationshipI#getCvTerm()
+ */
+ private CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.PubRelationshipI#setCvTerm(org.gmod.schema.cv.CvTermI)
+ */
+ private void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/Feature.java b/org/gmod/schema/sequence/Feature.java
new file mode 100644
index 0000000..ff4a8ed
--- /dev/null
+++ b/org/gmod/schema/sequence/Feature.java
@@ -0,0 +1,509 @@
+package org.gmod.schema.sequence;
+
+
+import org.gmod.schema.analysis.AnalysisFeature;
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.phylogeny.Phylonode;
+import org.gmod.schema.utils.CollectionUtils;
+
+
+
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
+import java.util.Date;
+import java.sql.Timestamp;
+
+public class Feature implements java.io.Serializable {
+
+ private int featureId;
+ private Organism organism;
+ private CvTerm cvTerm;
+ private String name;
+ private String uniqueName;
+ private Integer seqLen = -1;
+ private String md5Checksum;
+ private boolean analysis;
+ private boolean obsolete;
+ private Timestamp timeAccessioned;
+ private Timestamp timeLastModified;
+
+ // -------------------------------------------------------------------------------
+ // Unsorted properties below here
+
+
+ private Collection<Phylonode> phylonodes;
+ private DbXRef dbXRef;
+ private byte residues[];
+ private Collection<FeatureLoc> featureLocsForSrcFeatureId;
+ private Collection<FeatureRelationship> featureRelationshipsForObjectId;
+ private Collection<FeatureRelationship> featureRelationshipsForSubjectId;
+ private Collection<FeatureDbXRef> featureDbXRefs;
+ private Collection<FeatureLoc> featureLocsForFeatureId;
+ private Collection<FeatureCvTerm> featureCvTerms;
+
+ //@Cascade( {CascadeType.ALL, CascadeType.DELETE_ORPHAN} )
+ private Collection<FeatureProp> featureProps;
+ private Collection<FeaturePub> featurePubs;
+ private Collection<AnalysisFeature> analysisFeatures;
+ private Collection<FeatureSynonym> featureSynonyms;
+ private FeatureLoc featureLoc;
+
+ // Constructors
+
+ /** default constructor */
+ public Feature() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public Feature(Organism organism, CvTerm cvTerm, String uniqueName, boolean analysis, boolean obsolete, Timestamp timeAccessioned, Timestamp timeLastModified) {
+ this.organism = organism;
+ this.cvTerm = cvTerm;
+ this.uniqueName = uniqueName;
+ this.analysis = analysis;
+ this.obsolete = obsolete;
+ this.timeAccessioned = timeAccessioned;
+ this.timeLastModified = timeLastModified;
+ }
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeatureId()
+ */
+ public int getFeatureId() {
+ return this.featureId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setFeatureId(int)
+ */
+ public void setFeatureId(int featureId) {
+ this.featureId = featureId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getOrganism()
+ */
+ public Organism getOrganism() {
+ return this.organism;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setOrganism(org.gmod.schema.organism.OrganismI)
+ */
+ public void setOrganism(Organism organism) {
+ this.organism = organism;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getCvTerm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setCvTerm(org.gmod.schema.cv.CvTermI)
+ */
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getDbxref()
+ */
+ public DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setDbxref(org.gmod.schema.general.DbXRefI)
+ */
+ public void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+
+ /**
+ * Get the human-readable form of the feature eg the gene name
+ *
+ * @return the name, may be null
+ */
+ public String getName() {
+ return this.name;
+ }
+
+
+ /**
+ * Set the human-readable form of the feature eg the gene name
+ *
+ * @param name the human-readable name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+
+ /**
+ * Fetch the unique name (systematic id) for the feature
+ *
+ * @return the unique name, not null
+ */
+ /**
+ * @return
+ */
+ public String getUniqueName() {
+ return this.uniqueName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setUniquename(java.lang.String)
+ */
+ public void setUniqueName(String uniqueName) {
+ this.uniqueName = uniqueName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getResidues()
+ */
+ public byte[] getResidues() {
+ return this.residues;
+ }
+
+ /**
+ * Fetch a subset of the sequence (may be lazy)
+ *
+ * @param min the lower bound, in interbase coordinates
+ * @param max the upper bound, in interbase coordinates
+ * @return
+ */
+ public byte[] getResidues(int min, int max) {
+ byte[] results = new byte[max - min];
+ System.arraycopy(getResidues(), 0, results, 0, max);
+ return results;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setResidues(java.lang.String)
+ */
+ public void setResidues(byte[] residues) {
+ this.residues = residues;
+ if (residues == null) {
+ seqLen = 0;
+ md5Checksum = "";
+ return;
+ }
+ seqLen = residues.length;
+ this.md5Checksum = calcMD5(this.residues);
+ }
+
+
+ /**
+ * Fetch the length of the sequence. Find it from the parent if necessary
+ *
+ * @return the length
+ */
+ public int getSeqLen() {
+ if (this.seqLen.intValue() == -1 && residues != null) {
+ return getResidues().length;
+ }
+ return this.seqLen.intValue();
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setSeqlen(java.lang.Integer)
+ */
+ public void setSeqLen(Integer seqLen) {
+ if (seqLen == null) {
+ throw new IllegalArgumentException("Length of '"+uniqueName+"' attempted to be set to null");
+ }
+ this.seqLen = seqLen;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getMd5Checksum()
+ */
+ public String getMd5Checksum() {
+ return this.md5Checksum;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setMd5Checksum(java.lang.String)
+ */
+ public void setMd5Checksum(String md5Checksum) {
+ this.md5Checksum = md5Checksum;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#isAnalysis()
+ */
+ public boolean isAnalysis() {
+ return this.analysis;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setAnalysis(boolean)
+ */
+ public void setAnalysis(boolean analysis) {
+ this.analysis = analysis;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#isObsolete()
+ */
+ public boolean isObsolete() {
+ return this.obsolete;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setObsolete(boolean)
+ */
+ public void setObsolete(boolean obsolete) {
+ this.obsolete = obsolete;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getTimeAccessioned()
+ */
+ public Date getTimeAccessioned() {
+ return this.timeAccessioned;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setTimeAccessioned(java.util.Date)
+ */
+ public void setTimeAccessioned(Timestamp timeAccessioned) {
+ this.timeAccessioned = timeAccessioned;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getTimeLastModified()
+ */
+ public Timestamp getTimeLastModified() {
+ return this.timeLastModified;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setTimeLastModified(java.util.Date)
+ */
+ public void setTimeLastModified(Timestamp timeLastModified) {
+ this.timeLastModified = timeLastModified;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeaturelocsForSrcfeatureId()
+ */
+ public Collection<FeatureLoc> getFeatureLocsForSrcFeatureId() {
+ return (featureLocsForSrcFeatureId = CollectionUtils.safeGetter(featureLocsForSrcFeatureId));
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setFeaturelocsForSrcfeatureId(java.util.Set)
+ */
+ public void setFeatureLocsForSrcFeatureId(Collection<FeatureLoc> featureLocsForSrcFeatureId) {
+ this.featureLocsForSrcFeatureId = featureLocsForSrcFeatureId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeatureRelationshipsForObjectId()
+ */
+ public Collection<FeatureRelationship> getFeatureRelationshipsForObjectId() {
+ return (featureRelationshipsForObjectId = CollectionUtils.safeGetter(featureRelationshipsForObjectId));
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setFeatureRelationshipsForObjectId(java.util.Set)
+ */
+ public void setFeatureRelationshipsForObjectId(Collection<FeatureRelationship> featureRelationshipsForObjectId) {
+ this.featureRelationshipsForObjectId = featureRelationshipsForObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeatureRelationshipsForSubjectId()
+ */
+ public Collection<FeatureRelationship> getFeatureRelationshipsForSubjectId() {
+ return (featureRelationshipsForSubjectId = CollectionUtils.safeGetter(featureRelationshipsForSubjectId));
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setFeatureRelationshipsForSubjectId(java.util.Set)
+ */
+ public void setFeatureRelationshipsForSubjectId(Collection<FeatureRelationship> featureRelationshipsForSubjectId) {
+ this.featureRelationshipsForSubjectId = featureRelationshipsForSubjectId;
+ }
+
+ public void addFeatureRelationshipsForSubjectId(FeatureRelationship featureRelationshipForSubjectId) {
+ featureRelationshipForSubjectId.setFeatureBySubjectId(this);
+ this.featureRelationshipsForSubjectId.add(featureRelationshipForSubjectId);
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeatureDbxrefs()
+ */
+ public Collection<FeatureDbXRef> getFeatureDbXRefs() {
+ return this.featureDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setFeatureDbxrefs(java.util.Set)
+ */
+ public void setFeatureDbXRefs(Collection<FeatureDbXRef> featureDbXRefs) {
+ this.featureDbXRefs = featureDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeaturelocsForFeatureId()
+ */
+ public Collection<FeatureLoc> getFeatureLocsForFeatureId() {
+ return (featureLocsForFeatureId = CollectionUtils.safeGetter(featureLocsForFeatureId));
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setFeaturelocsForFeatureId(java.util.Set)
+ */
+ public void addFeatureLocsForFeatureId(FeatureLoc featureLocForFeatureId) {
+ featureLocForFeatureId.setFeatureByFeatureId(this);
+ getFeatureLocsForFeatureId().add(featureLocForFeatureId);
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeatureCvterms()
+ */
+ public Collection<FeatureCvTerm> getFeatureCvTerms() {
+ return this.featureCvTerms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setFeatureCvterms(java.util.Set)
+ */
+ public void setFeatureCvTerms(Collection<FeatureCvTerm> featureCvTerms) {
+ this.featureCvTerms = featureCvTerms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeatureProps()
+ */
+ public Collection<FeatureProp> getFeatureProps() {
+ return (featureProps = CollectionUtils.safeGetter(featureProps));
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setFeatureProps(java.util.Set)
+ */
+ public void addFeatureProp(FeatureProp featureProp) {
+ featureProp.setFeature(this);
+ getFeatureProps().add(featureProp);
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeaturePubs()
+ */
+ public Collection<FeaturePub> getFeaturePubs() {
+ return this.featurePubs;
+ }
+
+ public void setFeaturePubs(Collection<FeaturePub> featurePubs) {
+ this.featurePubs = featurePubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getAnalysisfeatures()
+ */
+ public Collection<AnalysisFeature> getAnalysisFeatures() {
+ return this.analysisFeatures;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setAnalysisfeatures(java.util.Set)
+ */
+ public void setAnalysisFeatures(Collection<AnalysisFeature> analysisFeatures) {
+ this.analysisFeatures = analysisFeatures;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#getFeatureSynonyms()
+ */
+ public Collection<FeatureSynonym> getFeatureSynonyms() {
+ return (featureSynonyms = CollectionUtils.safeGetter(featureSynonyms));
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureI#setFeatureSynonyms(java.util.Set)
+ */
+ public void setFeatureSynonyms(Collection<FeatureSynonym> featureSynonyms) {
+ this.featureSynonyms = featureSynonyms;
+ }
+
+ /**
+ * Get the display name for the gene, preferrably the name,
+ * otherwise the display name
+ *
+ * @return the preferred display name, never null
+ */
+ public String getDisplayName() {
+ return (getName() != null) ? getName() : getUniqueName();
+ }
+
+
+ public void setFeatureLocsForFeatureId(Collection<FeatureLoc> featureLocsForFeatureId) {
+ this.featureLocsForFeatureId = featureLocsForFeatureId;
+ }
+
+
+ public void setFeatureProps(Collection<FeatureProp> featureProps) {
+ this.featureProps = featureProps;
+ }
+
+
+ public Collection<Phylonode> getPhylonodes() {
+ return this.phylonodes;
+ }
+
+ public void setPhylonodes(Collection<Phylonode> phylonodes) {
+ this.phylonodes = phylonodes;
+ }
+
+ private String calcMD5(byte[] residue) {
+ try {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+// MessageDigest tc1 = md.clone();
+ byte[] md5Bytes = md5.digest(residue);
+
+ StringBuilder hexValue = new StringBuilder();
+ for (int i=0 ; i<md5Bytes.length ; i++) {
+ int val = md5Bytes[i] & 0xff;
+ if (val < 16) hexValue.append("0");
+ hexValue.append(Integer.toHexString(val));
+ }
+ return hexValue.toString();
+
+ }
+ catch (NoSuchAlgorithmException exp) {
+ exp.printStackTrace(); // Shouldn't happen - MD5 is supported algorithm
+ }
+ return null;
+ }
+
+ public FeatureLoc getFeatureLoc()
+ {
+ return featureLoc;
+ }
+
+ public void setFeatureLoc(FeatureLoc featureLoc)
+ {
+ this.featureLoc = featureLoc;
+ }
+
+}
diff --git a/org/gmod/schema/sequence/FeatureCvTerm.java b/org/gmod/schema/sequence/FeatureCvTerm.java
new file mode 100644
index 0000000..c4c3421
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureCvTerm.java
@@ -0,0 +1,169 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.pub.Pub;
+import org.gmod.schema.utils.Rankable;
+import org.gmod.schema.utils.propinterface.PropertyI;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+
+public class FeatureCvTerm implements Serializable, Rankable, PropertyI {
+
+ // Fields
+ private int featureCvTermId;
+ private CvTerm cvTerm;
+ private Feature feature;
+ private Pub pub;
+ private boolean not;
+ private int rank;
+ private Collection<FeatureCvTermProp> featureCvTermProps = new HashSet<FeatureCvTermProp>(0);
+ private Collection<FeatureCvTermPub> featureCvTermPubs = new HashSet<FeatureCvTermPub>(0);
+ private Collection<FeatureCvTermDbXRef> featureCvTermDbXRefs = new HashSet<FeatureCvTermDbXRef>(0);
+
+ // Constructors
+
+ /** default constructor */
+ public FeatureCvTerm() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public FeatureCvTerm(CvTerm cvTerm, Feature feature, Pub pub, boolean not,int rank) {
+ this.cvTerm = cvTerm;
+ this.feature = feature;
+ this.pub = pub;
+ this.not = not;
+ this.rank = rank;
+ }
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#getFeatureCvTermId()
+ */
+ public int getFeatureCvTermId() {
+ return this.featureCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#setFeatureCvTermId(int)
+ */
+ public void setFeatureCvTermId(int featureCvTermId) {
+ this.featureCvTermId = featureCvTermId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#getCvterm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#setCvterm(org.gmod.schema.cv.CvTermI)
+ */
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#getFeature()
+ */
+ public Feature getFeature() {
+ return this.feature;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#setFeature(org.genedb.db.jpa.Feature)
+ */
+ public void setFeature(Feature feature) {
+ this.feature = feature;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#getPub()
+ */
+ public Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#setPub(org.gmod.schema.pub.PubI)
+ */
+ public void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#isNot()
+ */
+ public boolean isNot() {
+ return this.not;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#setNot(boolean)
+ */
+ public void setNot(boolean not) {
+ this.not = not;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#getFeatureCvtermprops()
+ */
+ public Collection<FeatureCvTermProp> getFeatureCvTermProps() {
+ return this.featureCvTermProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#setFeatureCvtermprops(java.util.Set)
+ */
+ public void setFeatureCvTermProps(Collection<FeatureCvTermProp> featureCvTermProps) {
+ this.featureCvTermProps = featureCvTermProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#getFeatureCvtermPubs()
+ */
+ public Collection<FeatureCvTermPub> getFeatureCvTermPubs() {
+ return this.featureCvTermPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#setFeatureCvtermPubs(java.util.Set)
+ */
+ public void setFeatureCvTermPubs(Collection<FeatureCvTermPub> featureCvTermPubs) {
+ this.featureCvTermPubs = featureCvTermPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#getFeatureCvtermDbxrefs()
+ */
+ public Collection<FeatureCvTermDbXRef> getFeatureCvTermDbXRefs() {
+ return this.featureCvTermDbXRefs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermI#setFeatureCvtermDbxrefs(java.util.Set)
+ */
+ public void setFeatureCvTermDbXRefs(Collection<FeatureCvTermDbXRef> featureCvTermDbXRefs) {
+ this.featureCvTermDbXRefs = featureCvTermDbXRefs;
+ }
+
+ public int getRank() {
+ return rank;
+ }
+
+ public void setRank(int rank) {
+ this.rank = rank;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureCvTermDbXRef.java b/org/gmod/schema/sequence/FeatureCvTermDbXRef.java
new file mode 100644
index 0000000..14d1db9
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureCvTermDbXRef.java
@@ -0,0 +1,76 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.general.DbXRef;
+
+import java.io.Serializable;
+
+public class FeatureCvTermDbXRef implements Serializable {
+
+ // Fields
+ private int featureCvTermDbXRefId;
+ private DbXRef dbXRef;
+ private FeatureCvTerm featureCvTerm;
+
+ // Constructors
+
+ /** default constructor */
+ public FeatureCvTermDbXRef() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public FeatureCvTermDbXRef(DbXRef dbXRef, FeatureCvTerm featureCvTerm) {
+ this.dbXRef = dbXRef;
+ this.featureCvTerm = featureCvTerm;
+ }
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermDbXRefI#getFeatureCvTermDbXrefId()
+ */
+ private int getFeatureCvTermDbXRefId() {
+ return this.featureCvTermDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermDbXRefI#setFeatureCvTermDbXrefId(int)
+ */
+ private void setFeatureCvTermDbXRefId(int featureCvTermDbXRefId) {
+ this.featureCvTermDbXRefId = featureCvTermDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermDbXRefI#getDbxref()
+ */
+ public DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermDbXRefI#setDbxref(org.gmod.schema.general.DbXRefI)
+ */
+ public void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermDbXRefI#getFeatureCvterm()
+ */
+ public FeatureCvTerm getFeatureCvTerm() {
+ return this.featureCvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermDbXRefI#setFeatureCvterm(org.gmod.schema.sequence.FeatureCvTermI)
+ */
+ public void setFeatureCvTerm(FeatureCvTerm featureCvTerm) {
+ this.featureCvTerm = featureCvTerm;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureCvTermProp.java b/org/gmod/schema/sequence/FeatureCvTermProp.java
new file mode 100644
index 0000000..4af9838
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureCvTermProp.java
@@ -0,0 +1,175 @@
+package org.gmod.schema.sequence;
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.utils.propinterface.PropertyI;
+import org.gmod.schema.utils.Rankable;
+
+import java.io.Serializable;
+
+/**
+ * This represents a key/value pair attached to a FeatureCvTerm. The key is itself a CvTerm, with
+ * a free-text value
+ *
+ * Database constraints: feature_cvtermprop_c1 unique (feature_cvterm_id, type_id, rank)
+ *
+ * @author art
+ */
+/**
+ * @author art
+ *
+ */
+
+
+public class FeatureCvTermProp implements Serializable, PropertyI, Rankable {
+
+ // Fields
+
+
+ /**
+ * Database unique primary key
+ */
+ private int featureCvTermPropId;
+
+
+ /**
+ * The CvTerm that acts as the key in this map of properties
+ */
+ private CvTerm cvTerm;
+
+ /**
+ * The FeatureCvTerm to which this property is attached
+ */
+ private FeatureCvTerm featureCvTerm;
+
+ /**
+ * The value of this property
+ */
+ private String value;
+
+ /**
+ * The rank is used to distinguish multiple
+ * values for the same key eg /foo="value1", /foo="value2" in an EMBL file could be stored as two FeatureCvTerm with
+ * different ranks. The default is 0;
+ */
+ private int rank;
+
+ // Constructors
+
+ /** default constructor */
+ public FeatureCvTermProp() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public FeatureCvTermProp(CvTerm cvTerm, FeatureCvTerm featureCvTerm, int rank) {
+ this.cvTerm = cvTerm;
+ this.featureCvTerm = featureCvTerm;
+ this.rank = rank;
+ }
+ /** full constructor */
+ public FeatureCvTermProp(CvTerm cvTerm, FeatureCvTerm featureCvTerm, String value, int rank) {
+ this.cvTerm = cvTerm;
+ this.featureCvTerm = featureCvTerm;
+ this.value = value;
+ this.rank = rank;
+ }
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermPropI#getFeatureCvTermpropId()
+ */
+ private int getFeatureCvTermPropId() {
+ return this.featureCvTermPropId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermPropI#setFeatureCvTermpropId(int)
+ */
+ private void setFeatureCvTermPropId(int featureCvTermPropId) {
+ this.featureCvTermPropId = featureCvTermPropId;
+ }
+
+ /**
+ * Accessor for featureCvTerm
+ *
+ * @see featureCvTerm
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /**
+ * Accessor for cvTerm
+ *
+ * @see cvTerm
+ */
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+ /**
+ * Accessor for featureCvTerm
+ *
+ * @see featureCvTerm
+ */
+ public FeatureCvTerm getFeatureCvTerm() {
+ return this.featureCvTerm;
+ }
+
+
+ /**
+ * Accessor for featureCvTerm
+ *
+ * @see featureCvTerm
+ */
+ public void setFeatureCvTerm(FeatureCvTerm featureCvTerm) {
+ this.featureCvTerm = featureCvTerm;
+ }
+
+
+ /**
+ * Accessor for value
+ *
+ * @see value
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ /**
+ * Accessor for value
+ *
+ * @see value
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+
+ /**
+ * Accessor for rank
+ *
+ * @see rank
+ */
+ public int getRank() {
+ return this.rank;
+ }
+
+ /**
+ * Accessor for rank
+ *
+ * @see rank
+ */
+ public void setRank(int rank) {
+ this.rank = rank;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureCvTermPub.java b/org/gmod/schema/sequence/FeatureCvTermPub.java
new file mode 100644
index 0000000..3d81a59
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureCvTermPub.java
@@ -0,0 +1,78 @@
+package org.gmod.schema.sequence;
+
+
+
+import org.gmod.schema.pub.Pub;
+
+import java.io.Serializable;
+
+public class FeatureCvTermPub implements Serializable {
+
+ // Fields
+ private int featureCvTermPubId;
+ private Pub pub;
+ private FeatureCvTerm featureCvTerm;
+
+ // Constructors
+
+ /** default constructor */
+ public FeatureCvTermPub() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public FeatureCvTermPub(Pub pub, FeatureCvTerm featureCvTerm) {
+ this.pub = pub;
+ this.featureCvTerm = featureCvTerm;
+ }
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermPubI#getFeatureCvTermPubId()
+ */
+ public int getFeatureCvTermPubId() {
+ return this.featureCvTermPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermPubI#setFeatureCvTermPubId(int)
+ */
+ public void setFeatureCvTermPubId(int featureCvTermPubId) {
+ this.featureCvTermPubId = featureCvTermPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermPubI#getPub()
+ */
+ public Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermPubI#setPub(org.gmod.schema.pub.PubI)
+ */
+ public void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermPubI#getFeatureCvterm()
+ */
+ public FeatureCvTerm getFeatureCvTerm() {
+ return this.featureCvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureCvTermPubI#setFeatureCvterm(org.gmod.schema.sequence.FeatureCvTermI)
+ */
+ public void setFeatureCvTerm(FeatureCvTerm featureCvTerm) {
+ this.featureCvTerm = featureCvTerm;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureDbXRef.java b/org/gmod/schema/sequence/FeatureDbXRef.java
new file mode 100644
index 0000000..f43ae4c
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureDbXRef.java
@@ -0,0 +1,93 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.general.DbXRef;
+
+import java.io.Serializable;
+
+public class FeatureDbXRef implements Serializable {
+
+ // Fields
+ private int featureDbXRefId;
+ private DbXRef dbXRef;
+ private Feature feature;
+ private boolean current;
+
+ // Constructors
+
+ /** default constructor */
+ public FeatureDbXRef() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public FeatureDbXRef(DbXRef dbXRef, Feature feature, boolean current) {
+ this.dbXRef = dbXRef;
+ this.feature = feature;
+ this.current = current;
+ }
+
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureDbXRefI#getFeatureDbXRefId()
+ */
+ public int getFeatureDbXRefId() {
+ return this.featureDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureDbXRefI#setFeatureDbXRefId(int)
+ */
+ public void setFeatureDbXRefId(int featureDbXRefId) {
+ this.featureDbXRefId = featureDbXRefId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureDbXRefI#getDbxref()
+ */
+ public DbXRef getDbXRef() {
+ return this.dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureDbXRefI#setDbxref(org.gmod.schema.general.DbXRefI)
+ */
+ public void setDbXRef(DbXRef dbXRef) {
+ this.dbXRef = dbXRef;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureDbXRefI#getFeature()
+ */
+ public Feature getFeature() {
+ return this.feature;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureDbXRefI#setFeature(org.genedb.db.jpa.Feature)
+ */
+ public void setFeature(Feature feature) {
+ this.feature = feature;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureDbXRefI#isCurrent()
+ */
+ public boolean isCurrent() {
+ return this.current;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureDbXRefI#setCurrent(boolean)
+ */
+ public void setCurrent(boolean current) {
+ this.current = current;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureLoc.java b/org/gmod/schema/sequence/FeatureLoc.java
new file mode 100644
index 0000000..0669951
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureLoc.java
@@ -0,0 +1,251 @@
+package org.gmod.schema.sequence;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+public class FeatureLoc implements Serializable {
+
+ // Fields
+ private int featureLocId;
+ private Feature featureBySrcFeatureId;
+ private Feature featureByFeatureId;
+ private Integer fmin;
+ private boolean fminPartial;
+ private Integer fmax;
+ private boolean fmaxPartial;
+ private Short strand;
+ private Integer phase;
+ private String residueInfo;
+ private int locGroup;
+ private int rank;
+ private Set<FeatureLocPub> featureLocPubs = new HashSet<FeatureLocPub>(0);
+
+ // used by artemis
+ private int srcFeatureId;
+
+ // Constructors
+
+ /** default constructor */
+ public FeatureLoc() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+
+ private FeatureLoc(Feature featureBySrcFeatureId, boolean fminPartial, boolean fmaxPartial, int locGroup, int rank) {
+ this.featureBySrcFeatureId = featureBySrcFeatureId;
+ this.fminPartial = fminPartial;
+ this.fmaxPartial = fmaxPartial;
+ this.locGroup = locGroup;
+ this.rank = rank;
+ }
+ /** large constructor */
+ public FeatureLoc(Feature featureBySrcfeatureId, Feature featureByFeatureId, Integer fmin, boolean fminPartial, Integer fmax, boolean fmaxPartial, Short strand, Integer phase, int locGroup, int rank) {
+ this.featureBySrcFeatureId = featureBySrcfeatureId;
+ this.featureByFeatureId = featureByFeatureId;
+ this.fmin = fmin;
+ this.fminPartial = fminPartial;
+ this.fmax = fmax;
+ this.fmaxPartial = fmaxPartial;
+ this.strand = strand;
+ this.phase = phase;
+ this.locGroup = locGroup;
+ this.rank = rank;
+ }
+
+
+ // Property accessors
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getFeatureBySrcfeatureId()
+ */
+ public Feature getFeatureBySrcFeatureId() {
+ return this.featureBySrcFeatureId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setFeatureBySrcfeatureId(org.genedb.db.jpa.Feature)
+ */
+ public void setFeatureBySrcFeatureId(Feature featureBySrcFeatureId) {
+ this.featureBySrcFeatureId = featureBySrcFeatureId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getFeatureByFeatureId()
+ */
+ public Feature getFeatureByFeatureId() {
+ return this.featureByFeatureId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setFeatureByFeatureId(org.genedb.db.jpa.Feature)
+ */
+ public void setFeatureByFeatureId(Feature featureByFeatureId) {
+ this.featureByFeatureId = featureByFeatureId;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getFmin()
+ */
+ public Integer getFmin() {
+ return this.fmin;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setFmin(java.lang.Integer)
+ */
+ public void setFmin(Integer fmin) {
+ this.fmin = fmin;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#isFminPartial()
+ */
+ public boolean isFminPartial() {
+ return this.fminPartial;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setFminPartial(boolean)
+ */
+ public void setFminPartial(boolean fminPartial) {
+ this.fminPartial = fminPartial;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getFmax()
+ */
+ public Integer getFmax() {
+ return this.fmax;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setFmax(java.lang.Integer)
+ */
+ public void setFmax(Integer fmax) {
+ this.fmax = fmax;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#isFmaxPartial()
+ */
+ public boolean isFmaxPartial() {
+ return this.fmaxPartial;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setFmaxPartial(boolean)
+ */
+ public void setFmaxPartial(boolean fmaxPartial) {
+ this.fmaxPartial = fmaxPartial;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getStrand()
+ */
+ public Short getStrand() {
+ return this.strand;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setStrand(java.lang.Short)
+ */
+ public void setStrand(Short strand) {
+ this.strand = strand;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getPhase()
+ */
+ public Integer getPhase() {
+ return this.phase;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setPhase(java.lang.Integer)
+ */
+ public void setPhase(Integer phase) {
+ this.phase = phase;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getResidueInfo()
+ */
+ public String getResidueInfo() {
+ return this.residueInfo;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setResidueInfo(java.lang.String)
+ */
+ public void setResidueInfo(String residueInfo) {
+ this.residueInfo = residueInfo;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getLocgroup()
+ */
+ public int getLocGroup() {
+ return this.locGroup;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setLocgroup(int)
+ */
+ public void setLocGroup(int locGroup) {
+ this.locGroup = locGroup;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getRank()
+ */
+ public int getRank() {
+ return this.rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setRank(int)
+ */
+ public void setRank(int rank) {
+ this.rank = rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#getFeaturelocPubs()
+ */
+ public Set<FeatureLocPub> getFeatureLocPubs() {
+ return this.featureLocPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocI#setFeaturelocPubs(java.util.Set)
+ */
+ public void setFeaturelocPubs(Set<FeatureLocPub> featureLocPubs) {
+ this.featureLocPubs = featureLocPubs;
+ }
+
+
+ public int getFeatureLocId() {
+ return this.featureLocId;
+ }
+
+
+ public void setFeatureLocId(int featureLocId) {
+ this.featureLocId = featureLocId;
+ }
+
+ public int getSrcFeatureId() {
+ return srcFeatureId;
+ }
+
+ public void setSrcFeatureId(int srcFeatureId) {
+ this.srcFeatureId = srcFeatureId;
+ }
+
+}
diff --git a/org/gmod/schema/sequence/FeatureLocPub.java b/org/gmod/schema/sequence/FeatureLocPub.java
new file mode 100644
index 0000000..0b7bdcf
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureLocPub.java
@@ -0,0 +1,62 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.pub.Pub;
+
+import java.io.Serializable;
+
+public class FeatureLocPub implements Serializable {
+
+ // Fields
+ private int featureLocPubId;
+ private FeatureLoc featureLoc;
+ private Pub pub;
+
+ // Property accessors
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocPubI#getFeatureLocPubId()
+ */
+ private int getFeatureLocPubId() {
+ return this.featureLocPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocPubI#setFeatureLocPubId(int)
+ */
+ private void setFeatureLocPubId(int featureLocPubId) {
+ this.featureLocPubId = featureLocPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocPubI#getFeatureloc()
+ */
+ private FeatureLoc getFeatureloc() {
+ return this.featureLoc;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocPubI#setFeatureloc(org.gmod.schema.sequence.FeatureLocI)
+ */
+ private void setFeatureloc(FeatureLoc featureloc) {
+ this.featureLoc = featureloc;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocPubI#getPub()
+ */
+ private Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureLocPubI#setPub(org.gmod.schema.pub.PubI)
+ */
+ private void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureProp.java b/org/gmod/schema/sequence/FeatureProp.java
new file mode 100644
index 0000000..f87130d
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureProp.java
@@ -0,0 +1,122 @@
+package org.gmod.schema.sequence;
+
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.utils.propinterface.PropertyI;
+
+
+
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+public class FeatureProp implements Serializable, PropertyI {
+
+ // Fields
+ private int featurePropId;
+ public CvTerm cvTerm;
+ private Feature feature;
+ private String value;
+ private int rank;
+ private Set<FeaturePropPub> featurePropPubs = new HashSet<FeaturePropPub>(0);
+
+ // Constructors
+ /** default constructor */
+ public FeatureProp() {
+ // Deliberately empty default constructor
+ }
+
+ /** useful constructor ! */
+ public FeatureProp(Feature feature, CvTerm cvTerm, String value, int rank) {
+ this.cvTerm = cvTerm;
+ this.feature = feature;
+ this.value = value;
+ this.rank = rank;
+ }
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#getCvterm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#setCvterm(org.gmod.schema.cv.CvTermI)
+ */
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#getFeature()
+ */
+ public Feature getFeature() {
+ return this.feature;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#setFeature(org.genedb.db.jpa.Feature)
+ */
+ public void setFeature(Feature feature) {
+ this.feature = feature;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#getValue()
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#setValue(java.lang.String)
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#getRank()
+ */
+ public int getRank() {
+ return this.rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#setRank(int)
+ */
+ public void setRank(int rank) {
+ this.rank = rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#getFeaturepropPubs()
+ */
+ private Set<FeaturePropPub> getFeaturePropPubs() {
+ return this.featurePropPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropI#setFeaturepropPubs(java.util.Set)
+ */
+ public void setFeaturePropPubs(Set<FeaturePropPub> featurePropPubs) {
+ this.featurePropPubs = featurePropPubs;
+ }
+
+ public int getFeaturePropId() {
+ return this.featurePropId;
+ }
+
+ public void setFeaturePropId(final int featurePropId) {
+ this.featurePropId = featurePropId;
+ }
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeaturePropPub.java b/org/gmod/schema/sequence/FeaturePropPub.java
new file mode 100644
index 0000000..c0a3561
--- /dev/null
+++ b/org/gmod/schema/sequence/FeaturePropPub.java
@@ -0,0 +1,65 @@
+package org.gmod.schema.sequence;
+
+
+
+import org.gmod.schema.pub.Pub;
+
+import java.io.Serializable;
+
+public class FeaturePropPub implements Serializable {
+
+ // Fields
+ private int featurePropPubId;
+ private FeatureProp featureProp;
+ private Pub pub;
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropPubI#getFeaturePropPubId()
+ */
+ private int getFeaturePropPubId() {
+ return this.featurePropPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropPubI#setFeaturePropPubId(int)
+ */
+ private void setFeaturePropPubId(int featurePropPubId) {
+ this.featurePropPubId = featurePropPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropPubI#getFeatureprop()
+ */
+ private FeatureProp getFeatureProp() {
+ return this.featureProp;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropPubI#setFeatureprop(org.genedb.db.jpa.FeatureProp)
+ */
+ private void setFeatureProp(FeatureProp featureProp) {
+ this.featureProp = featureProp;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropPubI#getPub()
+ */
+ private Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePropPubI#setPub(org.gmod.schema.pub.PubI)
+ */
+ private void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeaturePub.java b/org/gmod/schema/sequence/FeaturePub.java
new file mode 100644
index 0000000..b99be41
--- /dev/null
+++ b/org/gmod/schema/sequence/FeaturePub.java
@@ -0,0 +1,77 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.pub.Pub;
+
+import java.io.Serializable;
+
+public class FeaturePub implements Serializable {
+
+ // Fields
+ private int featurePubId;
+ private Feature feature;
+ private Pub pub;
+
+ // Constructors
+
+ /** default constructor */
+ public FeaturePub() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public FeaturePub(Feature feature, Pub pub) {
+ this.feature = feature;
+ this.pub = pub;
+ }
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePubI#getFeaturePubId()
+ */
+ private int getFeaturePubId() {
+ return this.featurePubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePubI#setFeaturePubId(int)
+ */
+ private void setFeaturePubId(int featurePubId) {
+ this.featurePubId = featurePubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePubI#getFeature()
+ */
+ public Feature getFeature() {
+ return this.feature;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePubI#setFeature(org.genedb.db.jpa.Feature)
+ */
+ public void setFeature(Feature feature) {
+ this.feature = feature;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePubI#getPub()
+ */
+ public Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeaturePubI#setPub(org.gmod.schema.pub.PubI)
+ */
+ public void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureRelationship.java b/org/gmod/schema/sequence/FeatureRelationship.java
new file mode 100644
index 0000000..739bd78
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureRelationship.java
@@ -0,0 +1,160 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.cv.CvTerm;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class FeatureRelationship implements Serializable {
+
+ // Fields
+ private int featureRelationshipId;
+ private Feature featureBySubjectId;
+ private Feature featureByObjectId;
+ private CvTerm cvTerm;
+ private String value;
+ private int rank;
+ private Set<FeatureRelationshipProp> featureRelationshipProps = new HashSet<FeatureRelationshipProp>(0);
+ private Set<FeatureRelationshipPub> featureRelationshipPubs = new HashSet<FeatureRelationshipPub>(0);
+
+ // Constructors
+
+ /** default constructor */
+ public FeatureRelationship() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public FeatureRelationship(Feature featureBySubjectId, Feature featureByObjectId, CvTerm cvTerm, int rank) {
+ this.featureBySubjectId = featureBySubjectId;
+ this.featureByObjectId = featureByObjectId;
+ this.cvTerm = cvTerm;
+ this.rank = rank;
+ }
+
+
+ // Property accessors
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#getFeatureRelationshipId()
+ */
+ public int getFeatureRelationshipId() {
+ return this.featureRelationshipId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#setFeatureRelationshipId(int)
+ */
+ public void setFeatureRelationshipId(int featureRelationshipId) {
+ this.featureRelationshipId = featureRelationshipId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#getFeatureBySubjectId()
+ */
+ public Feature getFeatureBySubjectId() {
+ return this.featureBySubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#setFeatureBySubjectId(org.genedb.db.jpa.Feature)
+ */
+ public void setFeatureBySubjectId(Feature featureBySubjectId) {
+ this.featureBySubjectId = featureBySubjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#getFeatureByObjectId()
+ */
+ public Feature getFeatureByObjectId() {
+ return this.featureByObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#setFeatureByObjectId(org.genedb.db.jpa.Feature)
+ */
+ public void setFeatureByObjectId(Feature featureByObjectId) {
+ this.featureByObjectId = featureByObjectId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#getCvterm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#setCvterm(org.gmod.schema.cv.CvTermI)
+ */
+ public void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#getValue()
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#setValue(java.lang.String)
+ */
+ private void setValue(String value) {
+ this.value = value;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#getRank()
+ */
+ public int getRank() {
+ return this.rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#setRank(int)
+ */
+ public void setRank(int rank) {
+ this.rank = rank;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#getFeatureRelationshipprops()
+ */
+ private Collection<FeatureRelationshipProp> getFeatureRelationshipProps() {
+ return this.featureRelationshipProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#setFeatureRelationshipprops(java.util.Set)
+ */
+ private void setFeatureRelationshipProps(Set<FeatureRelationshipProp> featureRelationshipProps) {
+ this.featureRelationshipProps = featureRelationshipProps;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#getFeatureRelationshipPubs()
+ */
+ private Collection<FeatureRelationshipPub> getFeatureRelationshipPubs() {
+ return this.featureRelationshipPubs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipI#setFeatureRelationshipPubs(java.util.Set)
+ */
+ private void setFeatureRelationshipPubs(Set<FeatureRelationshipPub> featureRelationshipPubs) {
+ this.featureRelationshipPubs = featureRelationshipPubs;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureRelationshipProp.java b/org/gmod/schema/sequence/FeatureRelationshipProp.java
new file mode 100644
index 0000000..6aaeab9
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureRelationshipProp.java
@@ -0,0 +1,78 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.utils.propinterface.PropertyI;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class FeatureRelationshipProp implements Serializable, PropertyI {
+
+ // Fields
+ private int featureRelationshipPropId;
+ private CvTerm cvTerm;
+ private FeatureRelationship featureRelationship;
+ private String value;
+ private int rank;
+ private Set<FeatureRelationshipPropPub> featureRelationshipPropPubs = new HashSet<FeatureRelationshipPropPub>(0);
+
+ // Property accessors
+
+ private int getFeatureRelationshipPropId() {
+ return this.featureRelationshipPropId;
+ }
+
+ private void setFeatureRelationshipPropId(int featureRelationshipPropId) {
+ this.featureRelationshipPropId = featureRelationshipPropId;
+ }
+
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ private void setCvTerm(CvTerm cvTerm) {
+ this.cvTerm = cvTerm;
+ }
+
+ private FeatureRelationship getFeatureRelationship() {
+ return this.featureRelationship;
+ }
+
+ private void setFeatureRelationship(FeatureRelationship featureRelationship) {
+ this.featureRelationship = featureRelationship;
+ }
+
+
+ private String getValue() {
+ return this.value;
+ }
+
+ private void setValue(String value) {
+ this.value = value;
+ }
+
+
+ private int getRank() {
+ return this.rank;
+ }
+
+ private void setRank(int rank) {
+ this.rank = rank;
+ }
+
+ private Collection<FeatureRelationshipPropPub> getFeatureRelationshipPropPubs() {
+ return this.featureRelationshipPropPubs;
+ }
+
+ private void setFeatureRelationshipPropPubs(Set<FeatureRelationshipPropPub> featureRelationshipPropPubs) {
+ this.featureRelationshipPropPubs = featureRelationshipPropPubs;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureRelationshipPropPub.java b/org/gmod/schema/sequence/FeatureRelationshipPropPub.java
new file mode 100644
index 0000000..bac84e2
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureRelationshipPropPub.java
@@ -0,0 +1,63 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.pub.Pub;
+
+import java.io.Serializable;
+
+public class FeatureRelationshipPropPub implements Serializable {
+
+ // Fields
+ private int featureRelationshipPropPubId;
+ private FeatureRelationshipProp featureRelationshipProp;
+ private Pub pub;
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPropPubI#getFeatureRelationshipPropPubId()
+ */
+ private int getFeatureRelationshipPropPubId() {
+ return this.featureRelationshipPropPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPropPubI#setFeatureRelationshipPropPubId(int)
+ */
+ private void setFeatureRelationshipPropPubId(int featureRelationshipPropPubId) {
+ this.featureRelationshipPropPubId = featureRelationshipPropPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPropPubI#getFeatureRelationshipprop()
+ */
+ private FeatureRelationshipProp getFeatureRelationshipProp() {
+ return this.featureRelationshipProp;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPropPubI#setFeatureRelationshipprop(org.genedb.db.jpa.FeatureRelationshipProp)
+ */
+ private void setFeatureRelationshipProp(FeatureRelationshipProp featureRelationshipProp) {
+ this.featureRelationshipProp = featureRelationshipProp;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPropPubI#getPub()
+ */
+ private Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPropPubI#setPub(org.gmod.schema.pub.PubI)
+ */
+ private void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureRelationshipPub.java b/org/gmod/schema/sequence/FeatureRelationshipPub.java
new file mode 100644
index 0000000..0f420b7
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureRelationshipPub.java
@@ -0,0 +1,63 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.pub.Pub;
+
+import java.io.Serializable;
+
+public class FeatureRelationshipPub implements Serializable {
+
+ // Fields
+ private int featureRelationshipPubId;
+ private Pub pub;
+ private FeatureRelationship featureRelationship;
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPubI#getFeatureRelationshipPubId()
+ */
+ private int getFeatureRelationshipPubId() {
+ return this.featureRelationshipPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPubI#setFeatureRelationshipPubId(int)
+ */
+ private void setFeatureRelationshipPubId(int featureRelationshipPubId) {
+ this.featureRelationshipPubId = featureRelationshipPubId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPubI#getPub()
+ */
+ private Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPubI#setPub(org.gmod.schema.pub.PubI)
+ */
+ private void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPubI#getFeatureRelationship()
+ */
+ private FeatureRelationship getFeatureRelationship() {
+ return this.featureRelationship;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureRelationshipPubI#setFeatureRelationship(org.genedb.db.jpa.FeatureRelationship)
+ */
+ private void setFeatureRelationship(FeatureRelationship featureRelationship) {
+ this.featureRelationship = featureRelationship;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/FeatureSynonym.java b/org/gmod/schema/sequence/FeatureSynonym.java
new file mode 100644
index 0000000..4374656
--- /dev/null
+++ b/org/gmod/schema/sequence/FeatureSynonym.java
@@ -0,0 +1,127 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.pub.Pub;
+
+import java.io.Serializable;
+
+public class FeatureSynonym implements Serializable {
+
+ // Fields
+ private int featureSynonymId;
+ private Synonym synonym;
+ private Feature feature;
+ private Pub pub;
+ private boolean current;
+ private boolean internal;
+
+ // Constructors
+
+ /** default constructor */
+ public FeatureSynonym() {
+ // Deliberately empty default constructor
+ }
+
+ /** full constructor */
+ public FeatureSynonym(Synonym synonym, Feature feature, Pub pub, boolean current, boolean internal) {
+ this.synonym = synonym;
+ this.feature = feature;
+ this.pub = pub;
+ this.current = current;
+ this.internal = internal;
+ }
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#getFeatureSynonymId()
+ */
+ public int getFeatureSynonymId() {
+ return this.featureSynonymId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#setFeatureSynonymId(int)
+ */
+ public void setFeatureSynonymId(int featureSynonymId) {
+ this.featureSynonymId = featureSynonymId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#getSynonym()
+ */
+ public Synonym getSynonym() {
+ return this.synonym;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#setSynonym(org.gmod.schema.sequence.SynonymI)
+ */
+ public void setSynonym(Synonym synonym) {
+ this.synonym = synonym;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#getFeature()
+ */
+ public Feature getFeature() {
+ return this.feature;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#setFeature(org.genedb.db.jpa.Feature)
+ */
+ public void setFeature(Feature feature) {
+ this.feature = feature;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#getPub()
+ */
+ public Pub getPub() {
+ return this.pub;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#setPub(org.gmod.schema.pub.PubI)
+ */
+ public void setPub(Pub pub) {
+ this.pub = pub;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#isCurrent()
+ */
+ public boolean isCurrent() {
+ return this.current;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#setCurrent(boolean)
+ */
+ public void setCurrent(boolean current) {
+ this.current = current;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#isInternal()
+ */
+ public boolean isInternal() {
+ return this.internal;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.FeatureSynonymI#setInternal(boolean)
+ */
+ public void setInternal(boolean internal) {
+ this.internal = internal;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/sequence/Synonym.java b/org/gmod/schema/sequence/Synonym.java
new file mode 100644
index 0000000..b68d850
--- /dev/null
+++ b/org/gmod/schema/sequence/Synonym.java
@@ -0,0 +1,113 @@
+package org.gmod.schema.sequence;
+
+import org.gmod.schema.cv.CvTerm;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class Synonym implements Serializable {
+
+ // Fields
+ private int synonymId;
+ private CvTerm cvTerm;
+ private String name;
+ private String synonymSgml;
+ private Set<FeatureSynonym> featureSynonyms = new HashSet<FeatureSynonym>(0);
+
+ // Constructors
+
+ /** default constructor */
+ public Synonym() {
+ // Deliberately empty default constructor
+ }
+
+ /** minimal constructor */
+ public Synonym(CvTerm cvTerm, String name, String synonymSgml) {
+ this.cvTerm = cvTerm;
+ this.name = name;
+ this.synonymSgml = synonymSgml;
+ }
+
+
+ // Property accessors
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#getSynonymId()
+ */
+ public int getSynonymId() {
+ return this.synonymId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#setSynonymId(int)
+ */
+ public void setSynonymId(int synonymId) {
+ this.synonymId = synonymId;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#getCvterm()
+ */
+ public CvTerm getCvTerm() {
+ return this.cvTerm;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#setCvterm(org.gmod.schema.cv.CvTermI)
+ */
+ public void setCvTerm(CvTerm cvterm) {
+ this.cvTerm = cvterm;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#getName()
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#setName(java.lang.String)
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#getSynonymSgml()
+ */
+ private String getSynonymSgml() {
+ return this.synonymSgml;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#setSynonymSgml(java.lang.String)
+ */
+ public void setSynonymSgml(String synonymSgml) {
+ this.synonymSgml = synonymSgml;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#getFeatureSynonyms()
+ */
+ private Collection<FeatureSynonym> getFeatureSynonyms() {
+ return this.featureSynonyms;
+ }
+
+ /* (non-Javadoc)
+ * @see org.genedb.db.jpa.SynonymI#setFeatureSynonyms(java.util.Set)
+ */
+ private void setFeatureSynonyms(Set<FeatureSynonym> featureSynonyms) {
+ this.featureSynonyms = featureSynonyms;
+ }
+
+
+
+
+}
+
+
diff --git a/org/gmod/schema/utils/CollectionUtils.java b/org/gmod/schema/utils/CollectionUtils.java
new file mode 100644
index 0000000..7943080
--- /dev/null
+++ b/org/gmod/schema/utils/CollectionUtils.java
@@ -0,0 +1,27 @@
+package org.gmod.schema.utils;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * Static utility class for miscellaneous collection handling
+ *
+ * @author art
+ */
+public class CollectionUtils {
+
+ /**
+ *
+ *
+ * @param <T>
+ * @param collection
+ * @return the original collection, or an empty collection
+ */
+ public static <T> Collection<T> safeGetter(Collection<T> collection) {
+ if (collection != null) {
+ return collection;
+ }
+ return new HashSet<T>(0);
+ }
+
+}
diff --git a/org/gmod/schema/utils/CountedName.java b/org/gmod/schema/utils/CountedName.java
new file mode 100644
index 0000000..ed3477c
--- /dev/null
+++ b/org/gmod/schema/utils/CountedName.java
@@ -0,0 +1,55 @@
+package org.gmod.schema.utils;
+
+/**
+ * Class to store terms, eg products, and how many occurences there are
+ *
+ * @author cp2
+ */
+public class CountedName {
+
+ /**
+ *
+ */
+ private String name;
+
+ /**
+ *
+ */
+ private int count;
+
+
+
+ public CountedName(String name, int count) {
+ this.name = name;
+ this.count = count;
+ }
+
+ /**
+ * @return
+ */
+ public int getCount() {
+ return count;
+ }
+
+ /**
+ * @param count
+ */
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ /**
+ * @return
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/org/gmod/schema/utils/PeptideProperties.java b/org/gmod/schema/utils/PeptideProperties.java
new file mode 100644
index 0000000..5fffa5a
--- /dev/null
+++ b/org/gmod/schema/utils/PeptideProperties.java
@@ -0,0 +1,76 @@
+package org.gmod.schema.utils;
+
+public class PeptideProperties {
+
+ private String mass;
+
+ private String aminoAcids;
+
+ private String isoelectricPoint;
+
+ private String charge;
+
+ private String signalPeptide;
+
+ private String transmembraneDomain;
+
+ private String gpiAnchor;
+
+ public String getAminoAcids() {
+ return aminoAcids;
+ }
+
+ public void setAminoAcids(String aminoAcids) {
+ this.aminoAcids = aminoAcids;
+ }
+
+ public String getCharge() {
+ return charge;
+ }
+
+ public void setCharge(String charge) {
+ this.charge = charge;
+ }
+
+ public String getGpiAnchor() {
+ return gpiAnchor;
+ }
+
+ public void setGpiAnchor(String gpiAnchor) {
+ this.gpiAnchor = gpiAnchor;
+ }
+
+ public String getIsoelectricPoint() {
+ return isoelectricPoint;
+ }
+
+ public void setIsoelectricPoint(String isoelectricPoint) {
+ this.isoelectricPoint = isoelectricPoint;
+ }
+
+ public String getMass() {
+ return mass;
+ }
+
+ public void setMass(String mass) {
+ this.mass = mass;
+ }
+
+ public String getSignalPeptide() {
+ return signalPeptide;
+ }
+
+ public void setSignalPeptide(String signalPeptide) {
+ this.signalPeptide = signalPeptide;
+ }
+
+ public String getTransmembraneDomain() {
+ return transmembraneDomain;
+ }
+
+ public void setTransmembraneDomain(String transmembraneDomain) {
+ this.transmembraneDomain = transmembraneDomain;
+ }
+
+
+}
diff --git a/org/gmod/schema/utils/Rankable.java b/org/gmod/schema/utils/Rankable.java
new file mode 100644
index 0000000..ca36d2e
--- /dev/null
+++ b/org/gmod/schema/utils/Rankable.java
@@ -0,0 +1,16 @@
+package org.gmod.schema.utils;
+
+/**
+ * These domain objects use rank to define an order
+ *
+ * @author art
+ */
+public abstract interface Rankable {
+
+ /**
+ * Retrieve the rank, ie order, of this object. Ranks start at zero
+ *
+ * @return the rank
+ */
+ public abstract int getRank();
+}
diff --git a/org/gmod/schema/utils/propinterface/PropertyI.java b/org/gmod/schema/utils/propinterface/PropertyI.java
new file mode 100644
index 0000000..6b55fa2
--- /dev/null
+++ b/org/gmod/schema/utils/propinterface/PropertyI.java
@@ -0,0 +1,13 @@
+package org.gmod.schema.utils.propinterface;
+
+import org.gmod.schema.cv.CvTerm;
+
+/**
+ *
+ * @author cp2
+ */
+public interface PropertyI {
+
+ public abstract CvTerm getCvTerm();
+
+}
diff --git a/test/build-test.xml b/test/build-test.xml
new file mode 100644
index 0000000..639d259
--- /dev/null
+++ b/test/build-test.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0"?>
+
+<!-- Ant build file for Artemis Test Suite -->
+
+<project name="artemis.test" default="test" basedir=".">
+
+ <target name="init">
+ <tstamp />
+ <property name="name" value="artemis test suite" />
+ <property name="version" value="live" />
+ <property name="build.compiler" value="modern" />
+ <property name="classpath" value="." />
+ <property name="src.tests.dir" value="." />
+ <property name="src.lib.dir" value="lib" />
+ <property name="mainlib.dir" value="../lib" />
+
+ <property name="build.dir" value="./ant-build" />
+
+ <!-- Subdirectories for tests source and classes -->
+ <property name="build.src.tests" value="${build.dir}/src/tests" />
+ <property name="build.dest.tests" value="${build.dir}/classes/tests" />
+
+ <!-- Compile classpath -->
+ <path id="compile.classpath">
+ <!-- Main classes from build -->
+ <pathelement path="${classpath}" />
+ <pathelement path="../ant-build/classes/main/" />
+ <pathelement path="${build.dest.tests}" />
+ <!-- Dependency classes -->
+ <fileset dir="${src.lib.dir}">
+ <include name="**/*.jar" />
+ </fileset>
+ <fileset dir="${mainlib.dir}">
+ <include name="**/*.jar" />
+ </fileset>
+ <pathelement path=".." />
+ </path>
+ </target>
+
+ <!-- Prepares the build directory -->
+ <target name="prepare" depends="init">
+ <mkdir dir="${build.dir}" />
+ </target>
+
+ <!-- Prepares the source code -->
+ <target name="prepare-core" depends="init,prepare">
+ <!-- Creates directories -->
+ <mkdir dir="${build.src.tests}" />
+ <mkdir dir="${build.dest.tests}" />
+
+ <!-- Copies src files -->
+ <copy todir="${build.src.tests}">
+ <fileset dir="${src.tests.dir}">
+ <exclude name="**/CVS/**" />
+ <exclude name="**/data/**" />
+ <exclude name="**/lib/**" />
+ <exclude name="**/ant-build/**" />
+ </fileset>
+ </copy>
+
+ <!-- Copies jars -->
+ <copy todir="${build.dir}">
+ <fileset dir="${src.lib.dir}">
+ <include name="*.jar" />
+ </fileset>
+ </copy>
+ </target>
+
+ <!-- Compiles the source directory -->
+ <target name="compile" depends="init,prepare-core">
+ <javac
+ fork="true"
+ srcdir="${build.src.tests}"
+ destdir="${build.dest.tests}"
+ deprecation="false"
+ depend="no"
+ debug="true">
+ <classpath refid="compile.classpath"/>
+ </javac>
+ </target>
+
+ <target name="test" depends="compile">
+ <echo>Running test with EMBOSS_ROOT=${EMBOSS_ROOT}</echo>
+ <junit description="alltests" fork="yes">
+ <jvmarg value="-DEMBOSS_ROOT=${EMBOSS_ROOT}"/>
+ <batchtest todir="${build.dest.tests}">
+ <fileset dir="${build.src.tests}">
+ <include name="uk/ac/sanger/artemis/**/**Test.java"/>
+ <include name="uk/ac/sanger/artemis/**/**/**Test.java"/>
+ </fileset>
+ </batchtest>
+ <formatter type="plain" usefile="false"/>
+ <classpath refid="compile.classpath"/>
+ </junit>
+ </target>
+
+ <target name="test-headless" depends="compile">
+ <echo>Running test with EMBOSS_ROOT=${EMBOSS_ROOT}</echo>
+ <junit description="alltests" fork="yes">
+ <jvmarg value="-DEMBOSS_ROOT=${EMBOSS_ROOT}"/>
+ <jvmarg value="-Djava.awt.headless=true"/>
+ <batchtest todir="${build.dest.tests}">
+ <fileset dir="${build.src.tests}">
+ <include name="uk/ac/sanger/artemis/**/**Test.java"/>
+ <include name="uk/ac/sanger/artemis/**/**/**Test.java"/>
+ </fileset>
+ </batchtest>
+ <formatter type="plain" usefile="false"/>
+ <classpath refid="compile.classpath"/>
+ </junit>
+ </target>
+
+ <!-- Cleans everything -->
+ <target name="clean" depends="init">
+ <delete dir="${build.dir}" />
+ </target>
+</project>
diff --git a/test/data/MAL1.embl.gz b/test/data/MAL1.embl.gz
new file mode 100644
index 0000000..21a8ac2
Binary files /dev/null and b/test/data/MAL1.embl.gz differ
diff --git a/test/data/MAL1_8_16_24h.raw.bcf b/test/data/MAL1_8_16_24h.raw.bcf
new file mode 100644
index 0000000..0d8ace7
Binary files /dev/null and b/test/data/MAL1_8_16_24h.raw.bcf differ
diff --git a/test/data/MAL1_8_16_24h.raw.bcf.bci b/test/data/MAL1_8_16_24h.raw.bcf.bci
new file mode 100644
index 0000000..3c6b865
Binary files /dev/null and b/test/data/MAL1_8_16_24h.raw.bcf.bci differ
diff --git a/test/data/Pf3D7_01_02_v3.gff.gz b/test/data/Pf3D7_01_02_v3.gff.gz
new file mode 100644
index 0000000..8ca41fa
Binary files /dev/null and b/test/data/Pf3D7_01_02_v3.gff.gz differ
diff --git a/test/data/foo.restrict b/test/data/foo.restrict
new file mode 100644
index 0000000..e34c842
--- /dev/null
+++ b/test/data/foo.restrict
@@ -0,0 +1,41 @@
+########################################
+# Program: restrict
+# Rundate: Wed 18 Feb 2009 11:01:39
+# Commandline: restrict
+# [-sequence] /Users/tjc/foo.embl
+# -auto
+# -limit y
+# -enzymes HincII,hinfI,ppiI,hindiii
+# -outfile /private/tmp/circular_genome57650.txt
+# Report_format: table
+# Report_file: /private/tmp/circular_genome57650.txt
+########################################
+
+#=======================================
+#
+# Sequence: from: 1 to: 41243
+# HitCount: 158
+#
+# Minimum cuts per enzyme: 1
+# Maximum cuts per enzyme: 2000000000
+# Minimum length of recognition site: 4
+# Blunt ends allowed
+# Sticky ends allowed
+# DNA is linear
+# Ambiguities allowed
+#
+#=======================================
+
+ Start End Strand Enzyme_name Restriction_site 5prime 3prime 5primerev 3primerev
+ 81 86 + HindIII AAGCTT 81 85 . .
+ 140 144 + HinfI GANTC 140 143 . .
+ 40956 40960 + HinfI GANTC 40956 40959 . .
+ 41036 41041 + HindII GTYRAC 41038 41038 . .
+
+#---------------------------------------
+#---------------------------------------
+
+#---------------------------------------
+# Total_sequences: 1
+# Total_hitcount: 158
+#---------------------------------------
diff --git a/test/data/plot/base_position.plot.gz b/test/data/plot/base_position.plot.gz
new file mode 100644
index 0000000..c65d3e5
Binary files /dev/null and b/test/data/plot/base_position.plot.gz differ
diff --git a/test/data/plot/base_position_labels.plot.gz b/test/data/plot/base_position_labels.plot.gz
new file mode 100644
index 0000000..72340f6
Binary files /dev/null and b/test/data/plot/base_position_labels.plot.gz differ
diff --git a/test/data/plot/index_tab_sorted.plot.gz b/test/data/plot/index_tab_sorted.plot.gz
new file mode 100644
index 0000000..4ad67a3
Binary files /dev/null and b/test/data/plot/index_tab_sorted.plot.gz differ
diff --git a/test/data/plot/index_tab_sorted.plot.gz.tbi b/test/data/plot/index_tab_sorted.plot.gz.tbi
new file mode 100644
index 0000000..ca66deb
Binary files /dev/null and b/test/data/plot/index_tab_sorted.plot.gz.tbi differ
diff --git a/test/data/project.properties b/test/data/project.properties
new file mode 100644
index 0000000..5fb5c53
--- /dev/null
+++ b/test/data/project.properties
@@ -0,0 +1,20 @@
+
+project.Styphi.sequence=ftp://ftp.sanger.ac.uk/pub/pathogens/Salmonella/typhi/St.dna
+project.Styphi.annotation=ftp://ftp.sanger.ac.uk/pub/pathogens/Salmonella/typhi/St.art
+project.Styphi.title=Styphi
+
+#
+project.PF3D7.sequence = ftp://ftp.sanger.ac.uk/pub4/pathogens/Plasmodium/falciparum/3D7/3D7.latest_version/version3/August_2013/Pf3D7_01_v3.embl.gz
+project.PF3D7.bam = \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_0h.bam \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_8h.bam \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_16h.bam \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_24h.bam \
+ http://www.genedb.org/artemis/NAR/Malaria_RNASeq/version2.1.4/MAL_48h.bam
+project.PF3D7.title = Plasmodium\\ falciparum
+
+#
+project.Pberghei.sequence = Pberghei:berg01
+project.Pberghei.chado = genedb-db.sanger.ac.uk:5432/snapshot?genedb_ro
+project.Pberghei.title = Pberghei
+
diff --git a/test/data/test.embl.gz b/test/data/test.embl.gz
new file mode 100644
index 0000000..c628a8e
Binary files /dev/null and b/test/data/test.embl.gz differ
diff --git a/test/data/test.gff.gz b/test/data/test.gff.gz
new file mode 100644
index 0000000..eeb819f
Binary files /dev/null and b/test/data/test.gff.gz differ
diff --git a/test/data/test.vcf.gz b/test/data/test.vcf.gz
new file mode 100644
index 0000000..a6a9edd
Binary files /dev/null and b/test/data/test.vcf.gz differ
diff --git a/test/data/test.vcf.gz.tbi b/test/data/test.vcf.gz.tbi
new file mode 100644
index 0000000..7efad1a
Binary files /dev/null and b/test/data/test.vcf.gz.tbi differ
diff --git a/test/data/test1.vcf.gz b/test/data/test1.vcf.gz
new file mode 100644
index 0000000..ce2a384
Binary files /dev/null and b/test/data/test1.vcf.gz differ
diff --git a/test/data/test1.vcf.gz.tbi b/test/data/test1.vcf.gz.tbi
new file mode 100644
index 0000000..e162c4b
Binary files /dev/null and b/test/data/test1.vcf.gz.tbi differ
diff --git a/test/data/test2.vcf.gz b/test/data/test2.vcf.gz
new file mode 100644
index 0000000..0017e72
Binary files /dev/null and b/test/data/test2.vcf.gz differ
diff --git a/test/data/test2.vcf.gz.tbi b/test/data/test2.vcf.gz.tbi
new file mode 100644
index 0000000..d0e5c80
Binary files /dev/null and b/test/data/test2.vcf.gz.tbi differ
diff --git a/test/data/test3.vcf.gz b/test/data/test3.vcf.gz
new file mode 100644
index 0000000..e55c4ef
Binary files /dev/null and b/test/data/test3.vcf.gz differ
diff --git a/test/data/test3.vcf.gz.tbi b/test/data/test3.vcf.gz.tbi
new file mode 100644
index 0000000..7449cb3
Binary files /dev/null and b/test/data/test3.vcf.gz.tbi differ
diff --git a/test/data/test4.vcf.gz b/test/data/test4.vcf.gz
new file mode 100644
index 0000000..c478112
Binary files /dev/null and b/test/data/test4.vcf.gz differ
diff --git a/test/data/test4.vcf.gz.tbi b/test/data/test4.vcf.gz.tbi
new file mode 100644
index 0000000..83e260d
Binary files /dev/null and b/test/data/test4.vcf.gz.tbi differ
diff --git a/test/data/test_boundary.gff.gz b/test/data/test_boundary.gff.gz
new file mode 100644
index 0000000..d89f0ef
Binary files /dev/null and b/test/data/test_boundary.gff.gz differ
diff --git a/test/uk/ac/sanger/artemis/circular/digest/CircularGenomeControllerTest.java b/test/uk/ac/sanger/artemis/circular/digest/CircularGenomeControllerTest.java
new file mode 100644
index 0000000..7131629
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/circular/digest/CircularGenomeControllerTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import org.junit.Test;
+
+public class CircularGenomeControllerTest
+{
+ /**
+ * Test that EMBOSS_ROOT is set
+ */
+ @Test
+ public void testEmbossRoot()
+ {
+ assertNotNull("EMBOSS_ROOT not set", System.getProperty("EMBOSS_ROOT"));
+ }
+
+ /**
+ * Test that restrict can be found
+ */
+ @Test
+ public void testEmbossInstalled()
+ {
+ String restrictPath = System.getProperty("EMBOSS_ROOT") + "/bin/restrict";
+ File restrict = new File(restrictPath);
+ assertTrue("restrict not found at: " + restrictPath, restrict.exists());
+ }
+}
\ No newline at end of file
diff --git a/test/uk/ac/sanger/artemis/circular/digest/EmbossTableParserTest.java b/test/uk/ac/sanger/artemis/circular/digest/EmbossTableParserTest.java
new file mode 100644
index 0000000..4ffb1f4
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/circular/digest/EmbossTableParserTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Test;
+
+import uk.ac.sanger.artemis.circular.digest.CutSite;
+import uk.ac.sanger.artemis.circular.digest.EmbossTableParser;
+
+public class EmbossTableParserTest
+{
+ /**
+ * Test that restrict output is parsed
+ */
+ @Test
+ public void testParser() throws IOException
+ {
+ final InputStream inputStream = EmbossTableParserTest.class
+ .getResourceAsStream("/data/foo.restrict");
+
+ InputStreamReader reader = new InputStreamReader(inputStream);
+ EmbossTableParser etp = new EmbossTableParser();
+
+ List<CutSite> cutSites = etp.parse(new BufferedReader(reader));
+ CutSite firstCutSite = cutSites.get(0);
+
+ assertEquals("Number of cut sites", cutSites.size(), 4);
+ assertEquals("Enzyme name", firstCutSite.getEnzymeName(), "HindIII");
+ assertEquals("3prime", firstCutSite.getThreePrime(), 85);
+ assertEquals("5prime", firstCutSite.getFivePrime(), 81);
+ assertEquals("3prime-rev", firstCutSite.getThreePrimeRev(), 0);
+ assertEquals("5prime-rev", firstCutSite.getFivePrimeRev(), 0);
+ assertTrue("Cut site strand", firstCutSite.isForward());
+ }
+}
diff --git a/test/uk/ac/sanger/artemis/circular/digest/UtilsTest.java b/test/uk/ac/sanger/artemis/circular/digest/UtilsTest.java
new file mode 100644
index 0000000..34be14a
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/circular/digest/UtilsTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+import static org.junit.Assert.*;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import uk.ac.sanger.artemis.circular.digest.CutSite;
+
+public class UtilsTest
+{
+ /**
+ * Test that restrict output is parsed
+ */
+ @Ignore("Use EmbossTableParserTest")
+ @Test
+ public void testFindCutSitesFromEmbossReport()
+ {
+ final InputStream inputStream = UtilsTest.class
+ .getResourceAsStream("/data/foo.restrict");
+
+ InputStreamReader reader = new InputStreamReader(inputStream);
+ ReportDetails report = Utils.findCutSitesFromEmbossReport(reader);
+
+ assertNotNull("ReportDetails null", report);
+ assertEquals("Sequence length", report.length, 41243);
+
+ List<CutSite> cutSites = report.cutSites;
+ CutSite firstCutSite = cutSites.get(0);
+
+ assertEquals("Number of cut sites", cutSites.size(), 4);
+ assertEquals("Enzyme name", firstCutSite.getEnzymeName(), "HindIII");
+ assertEquals("3prime", firstCutSite.getThreePrime(), 85);
+ assertEquals("5prime", firstCutSite.getFivePrime(), 81);
+ assertEquals("3prime-rev", firstCutSite.getThreePrimeRev(), 0);
+ assertEquals("5prime-rev", firstCutSite.getFivePrimeRev(), 0);
+ assertTrue("Cut site strand", firstCutSite.isForward());
+ }
+
+}
diff --git a/test/uk/ac/sanger/artemis/components/EditMenuTest.java b/test/uk/ac/sanger/artemis/components/EditMenuTest.java
new file mode 100644
index 0000000..244ebef
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/components/EditMenuTest.java
@@ -0,0 +1,71 @@
+/*
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components;
+
+import static org.junit.Assert.assertTrue;
+
+import java.awt.GraphicsEnvironment;
+import javax.swing.JFrame;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.io.Entry;
+import uk.ac.sanger.artemis.io.Utils;
+import uk.ac.sanger.artemis.io.ValidateFeatureTest;
+
+
+public class EditMenuTest
+{
+ @Test
+ /**
+ * Split a gene model and undo
+ */
+ public void gffUnMergeAndUndo()
+ {
+ // ignore if in headless mode with no x11
+ if(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
+ return;
+
+ final EntryGroup egrp = Utils.getEntryGroup("/data/Pf3D7_01_02_v3.gff.gz");
+
+ FeatureVector features = egrp.getAllFeatures();
+ final Selection selection = new Selection(null);
+ Feature f = Utils.getCDSFeatureByIdPrefix("PF3D7_0103500.1", features);
+ final FeatureSegmentVector segments = f.getSegments();
+ selection.add(f);
+ selection.add(segments.elementAt(0));
+ selection.add(segments.elementAt(1));
+
+ // validate GFF features before split and undo and after
+ // to ensure the gene model structures remain consistent
+ ValidateFeatureTest.testAll(egrp);
+ EditMenu.unmergeFeature(null, selection, egrp);
+ EditMenu.undo(null, selection, egrp);
+ ValidateFeatureTest.testAll(egrp);
+ }
+}
\ No newline at end of file
diff --git a/test/uk/ac/sanger/artemis/components/ProjectPropertyTest.java b/test/uk/ac/sanger/artemis/components/ProjectPropertyTest.java
new file mode 100644
index 0000000..79c5f5a
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/components/ProjectPropertyTest.java
@@ -0,0 +1,168 @@
+/*
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.awt.GraphicsEnvironment;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Properties;
+
+/**
+ * Tests for loading and writing project properties for
+ * Artemis.
+ */
+public class ProjectPropertyTest
+{
+ private HashMap<String, HashMap<String, String>> projects;
+
+ /**
+ * Load the test properties
+ */
+ @Before
+ public void loadProperties()
+ {
+ InputStream ins =
+ this.getClass().getClassLoader().getResourceAsStream("data/project.properties");
+
+ try
+ {
+ final Properties projectProps = new Properties();
+ projectProps.load(ins);
+ ins.close();
+ projects = ProjectProperty.getProjectMap(projectProps);
+ }
+ catch (IOException e)
+ {
+ org.junit.Assert.fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test contents of hash for Styphi
+ */
+ @Test
+ public void projectContent1()
+ {
+ // ignore if in headless mode with no x11
+ if(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
+ return;
+
+ assertTrue("Styphi project", projects.containsKey("Styphi"));
+ if(projects.containsKey("Styphi"))
+ {
+ HashMap<String, String> thisProject = projects.get("Styphi");
+ assertTrue("Styphi project sequence", thisProject.containsKey("sequence"));
+ assertTrue("Styphi project annotation", thisProject.containsKey("annotation"));
+ }
+ }
+
+ /**
+ * Test contents of hash for PF3D7
+ */
+ @Test
+ public void projectContent2()
+ {
+ // ignore if in headless mode with no x11
+ if(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
+ return;
+
+ assertTrue("PF3D7 project", projects.containsKey("PF3D7"));
+ if(projects.containsKey("PF3D7"))
+ {
+ HashMap<String, String> thisProject = projects.get("PF3D7");
+ assertTrue("Styphi project sequence", thisProject.containsKey("sequence"));
+ assertTrue("Styphi project bam", thisProject.containsKey("bam"));
+ }
+ }
+
+ @Test
+ public void launch()
+ {
+ // ignore if in headless mode with no x11
+ if(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
+ return;
+
+ try
+ {
+ ProjectProperty pp = new ProjectProperty();
+ }
+ catch(Exception e)
+ {
+ org.junit.Assert.fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test writing and reading of the fields in the
+ * properties file
+ */
+ @Test
+ public void writeAndReadProperties()
+ {
+ // ignore if in headless mode with no x11
+ if(GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
+ return;
+
+ try
+ {
+ final File tmpFile = File.createTempFile("artemis.props", ".tmp");
+ ProjectProperty.writeProperties(tmpFile, projects);
+
+ final InputStream ins = new FileInputStream(tmpFile);
+ final Properties projectProps = new Properties();
+ projectProps.load(ins);
+ ins.close();
+ final HashMap<String, HashMap<String, String>>
+ tmpProjects = ProjectProperty.getProjectMap(projectProps);
+
+ for (String p: projects.keySet())
+ {
+ assertTrue("Contains "+p, tmpProjects.containsKey(p));
+
+ HashMap<String, String> props = projects.get(p);
+ HashMap<String, String> tmpProps = tmpProjects.get(p);
+ for(final String key: props.keySet())
+ {
+ // test property key (e.g. title, sequence, bam)
+ assertTrue("Contains property "+key, tmpProps.containsKey(key));
+
+ // test values
+ assertTrue("Contains property "+key+" = "+props.get(key),
+ props.get(key).equals(tmpProps.get(key)));
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ org.junit.Assert.fail(e.getMessage());
+ }
+
+ }
+
+}
diff --git a/test/uk/ac/sanger/artemis/components/TransferAnnotationToolTest.java b/test/uk/ac/sanger/artemis/components/TransferAnnotationToolTest.java
new file mode 100644
index 0000000..a36043e
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/components/TransferAnnotationToolTest.java
@@ -0,0 +1,243 @@
+/* TransferAnnotationToolTest.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.net.URL;
+import java.util.List;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import javax.swing.JCheckBox;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureKeyQualifierPredicate;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+
+import uk.ac.sanger.artemis.io.DocumentEntryFactory;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class TransferAnnotationToolTest
+{
+ private QualifierPanel qPanel;
+ private Vector genesToTransferTo = new Vector();
+ private String productStr;
+ private EntryGroup entryGroup;
+ private Feature originatingFeature;
+
+ /**
+ * Open a flat file and create the components in the TransferAnnotaionTool
+ * used to control the transfer of annotation.
+ */
+ @Before
+ public void setup()
+ {
+ entryGroup = readFile();
+ originatingFeature = getFeature("gene", "O", "CDS", entryGroup);
+
+ qPanel = new QualifierPanel(originatingFeature,
+ originatingFeature.getKey().getKeyString());
+ Hashtable qualifierHash = (Hashtable) qPanel.getQualifierCheckBoxes();
+
+ Enumeration enumQ = qualifierHash.keys();
+ productStr = null;
+ while (enumQ.hasMoreElements())
+ {
+ JCheckBox cbQualifierName = (JCheckBox) enumQ.nextElement();
+
+ if (cbQualifierName.getText().equals("product"))
+ {
+ cbQualifierName.setSelected(true);
+ Vector cbQualifierValues = (Vector) qualifierHash.get(cbQualifierName);
+ for (int i = 0; i < cbQualifierValues.size(); i++)
+ {
+ JCheckBox cbQualifierValue = (JCheckBox) cbQualifierValues.get(i);
+ cbQualifierValue.setSelected(true);
+ productStr = cbQualifierValue.getText();
+ }
+ }
+ else
+ cbQualifierName.setSelected(false);
+ }
+
+ JCheckBox cbNamesToTransferTo = new JCheckBox("N", true);
+ genesToTransferTo.add(cbNamesToTransferTo);
+ }
+
+
+ /**
+ * Test for appending qualifiers.
+ */
+ @Test
+ public void testAppend()
+ {
+ // append product
+ TransferAnnotationTool.transferAnnotation(qPanel.getQualifierCheckBoxes(),
+ genesToTransferTo, originatingFeature, entryGroup, true, false, false,
+ new StringBuffer(), new StringBuffer());
+
+ testTransferResult(entryGroup, 2, productStr);
+ }
+
+ /**
+ * Test for appending qualifiers does not create duplicates when run
+ * again.
+ */
+ @Test
+ public void testAppend2()
+ {
+ testAppend();
+ }
+
+ /**
+ * Test for overwriting qualifiers.
+ */
+ @Test
+ public void testOverwrite()
+ {
+ // check it overwrites product
+ TransferAnnotationTool.transferAnnotation(qPanel.getQualifierCheckBoxes(),
+ genesToTransferTo, originatingFeature, entryGroup, true, true, false,
+ new StringBuffer(), new StringBuffer());
+
+ testTransferResult(entryGroup, 1, productStr);
+ }
+
+ /**
+ * Check the result of the annotation transfer is correct.
+ * @param entryGroup
+ * @param nproducts
+ * @param productStr
+ */
+ private void testTransferResult(final EntryGroup entryGroup,
+ final int nproducts,
+ final String productStr)
+ {
+ final Feature featureN = getFeature("gene", "N", "CDS", entryGroup);
+ boolean hasTransferred = false;
+ StringVector values = null;
+ try
+ {
+ values = featureN.getQualifierByName("product").getValues();
+ for (int j = 0; j < values.size(); j++)
+ {
+ if (((String) values.get(j)).equals(productStr))
+ hasTransferred = true;
+ }
+ }
+ catch (Exception e)
+ {
+ Assert.fail(e.getMessage());
+ }
+
+ assertEquals("Annotation transfer (product), number of values", values.size(), nproducts);
+ assertTrue("Annotation transfer (product)", hasTransferred);
+ }
+
+ /**
+ * Return an Artemis EntryGroup by reading in an example file
+ * @return
+ */
+ private EntryGroup readFile()
+ {
+ URL url = TransferAnnotationToolTest.class.getResource("/etc/af063097.embl");
+
+ //assertEquals("Number", 4, 4);
+ final EntryInformation artemisEntryInformation =
+ Options.getArtemisEntryInformation();
+ final Document entryDocument =
+ DocumentFactory.makeDocument(url.getFile());
+
+ try
+ {
+ final uk.ac.sanger.artemis.io.Entry emblEntry =
+ DocumentEntryFactory.makeDocumentEntry(artemisEntryInformation,
+ entryDocument, null);
+ final Entry entry = new Entry(emblEntry);
+ final EntryGroup entryGroup = new SimpleEntryGroup(entry.getBases());
+ entryGroup.add(entry);
+ return entryGroup;
+ }
+ catch(uk.ac.sanger.artemis.util.OutOfRangeException oore)
+ {
+ Assert.fail(oore.getMessage());
+ }
+ catch(uk.ac.sanger.artemis.sequence.NoSequenceException nse)
+ {
+ Assert.fail(nse.getMessage());
+ }
+ catch(Exception e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Return a feature in an entry group by searching for the feature
+ * key and a qualifier with a given value.
+ * @param qualifierName
+ * @param qualifierValue
+ * @param key
+ * @param entryGroup
+ * @return
+ */
+ private Feature getFeature(final String qualifierName,
+ final String qualifierValue,
+ final String key,
+ final EntryGroup entryGroup)
+ {
+ FeatureVector features = entryGroup.getAllFeatures();
+ FeaturePredicate newPredicate = new FeatureKeyQualifierPredicate(
+ new Key(key), qualifierName, qualifierValue, false, false);
+
+ for (int i = 0; i < features.size(); i++)
+ {
+ Feature f = features.elementAt(i);
+ if (newPredicate.testPredicate(f))
+ return f;
+ }
+ return null;
+ }
+}
+
diff --git a/test/uk/ac/sanger/artemis/components/variant/WriteVCFTest.java b/test/uk/ac/sanger/artemis/components/variant/WriteVCFTest.java
new file mode 100644
index 0000000..5222ac9
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/components/variant/WriteVCFTest.java
@@ -0,0 +1,561 @@
+
+package uk.ac.sanger.artemis.components.variant;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+
+import junit.framework.Assert;
+
+import org.junit.BeforeClass;
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.components.variant.VCFview;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+
+/**
+ * Tests for writing out sequences based on VCF data.
+ */
+public class WriteVCFTest
+{
+ private static VCFview vcfView;
+ private static VCFview bcfView;
+
+ // TEST WRITING REGIONS
+
+ /**
+ * Write FASTA, from a range selection on the forward strand.
+ * Variation is a SNP.
+ * VCF line : test 120768 . C T
+ */
+ @Test
+ public void testRegionWriteFwdFastaSNP()
+ {
+ StringWriter writer = getRegionWriter(true, 120768, 120777);
+
+ StringBuffer buff = new StringBuffer(">test.embl.gz 120768:120777\n");
+ buff.append("cttgtcaagg\n".toUpperCase());
+ buff.append(">test.vcf.gz test.embl.gz 120768:120777\n");
+ buff.append("tttgtcaagg\n");
+ assertEquals("Export FASTA range ", writer.toString(), buff.toString());
+ }
+
+ /**
+ * Write FASTA, from a range selection on the reverse strand.
+ * Variation is a SNP.
+ * VCF line : test 120768 . C T
+ */
+ @Test
+ public void testRegionWriteRevFastaSNP()
+ {
+ StringWriter writer = getRegionWriter(false, 120768, 120777);
+
+ StringBuffer buff = new StringBuffer(">test.embl.gz 120768:120777 reverse\n");
+ buff.append("ccttgacaag\n".toUpperCase());
+ buff.append(">test.vcf.gz test.embl.gz 120768:120777 reverse\n");
+ buff.append("ccttgacaaa\n");
+ assertEquals("Export FASTA range ", writer.toString(), buff.toString());
+ }
+
+ /**
+ * Write FASTA, from a range selection on the forward strand.
+ * Variation is a deletion.
+ * test 396838 . tt t
+ */
+ @Test
+ public void testRegionWriteFwdFastaDeletion()
+ {
+ StringWriter writer = getRegionWriter(true, 396835, 396845);
+
+ StringBuffer buff = new StringBuffer(">test.embl.gz 396835:396845\n");
+ buff.append("tttttaggtat\n".toUpperCase());
+ buff.append(">test.vcf.gz test.embl.gz 396835:396845\n");
+ buff.append("tttt-aggtat\n");
+ assertEquals("Export FASTA range ", writer.toString(), buff.toString());
+ }
+
+ /**
+ * Write FASTA, from a range selection on the reverse strand.
+ * Variation is a deletion.
+ * test 396838 . tt t
+ */
+ @Test
+ public void testRegionWriteRevFastaDeletion()
+ {
+ StringWriter writer = getRegionWriter(false, 396835, 396845);
+
+ StringBuffer buff = new StringBuffer(">test.embl.gz 396835:396845 reverse\n");
+ buff.append("atacctaaaaa\n".toUpperCase());
+ buff.append(">test.vcf.gz test.embl.gz 396835:396845 reverse\n");
+ buff.append("ataccta-aaa\n");
+ assertEquals("Export FASTA range ", writer.toString(), buff.toString());
+ }
+
+ /**
+ * Write FASTA, from a range selection on the forward strand.
+ * Variation is a insertion.
+ * test 366787 . t tT
+ */
+ @Test
+ public void testRegionWriteFwdFastaInsertion()
+ {
+ StringWriter writer = getRegionWriter(true, 366785, 366795);
+
+ StringBuffer buff = new StringBuffer(">test.embl.gz 366785:366795\n");
+ buff.append("tttcgcttttt\n".toUpperCase());
+ buff.append(">test.vcf.gz test.embl.gz 366785:366795\n");
+ buff.append("tttTcgcttttt\n");
+ assertEquals("Export FASTA range ", writer.toString(), buff.toString());
+ }
+
+ /**
+ * Write FASTA, from a range selection on the reverse strand.
+ * Variation is a insertion.
+ * test 366787 . t tT
+ */
+ @Test
+ public void testRegionWriteRevFastaInsertion()
+ {
+ StringWriter writer = getRegionWriter(false, 366785, 366795);
+
+ StringBuffer buff = new StringBuffer(">test.embl.gz 366785:366795 reverse\n");
+ buff.append("aaaaagcgaaa\n".toUpperCase());
+ buff.append(">test.vcf.gz test.embl.gz 366785:366795 reverse\n");
+ buff.append("aaaaagcgaaaa\n");
+ assertEquals("Export FASTA range ", writer.toString(), buff.toString());
+ }
+
+
+ /**
+ * Write FASTA, from a range selection on the forward strand.
+ * Variation is a multiple allele, (PL number indicates either G or T):
+ * test 361978 . G T 77.76842 . DP=11;AF1=0.95;CI95=0.5,1;DP4=1,0,10,0;MQ=58;PV4=1,9.6e-06,0.37,1 PL:DP:SP:GT:GQ 106,0,0:11:0:1/1:10
+ */
+ @Test
+ public void testRegionWriteFwdFastaMultipleAllele()
+ {
+ StringWriter writer = getRegionWriter(true, 361975, 361985);
+
+ StringBuffer buff = new StringBuffer(">test.embl.gz 361975:361985\n");
+ buff.append("actgaaaaatt\n".toUpperCase());
+ buff.append(">test.vcf.gz test.embl.gz 361975:361985\n");
+ buff.append("actkaaaaatt\n");
+ assertEquals("Export FASTA range ", writer.toString(), buff.toString());
+ }
+
+ // TEST WRITING FEATURES
+ /**
+ * Write FASTA, from selection of features.
+ */
+ @Test
+ public void testFeatureWriteFastaSNP()
+ {
+ StringWriter writer = getFeatureWriter("SPN23F03630,SPN23F03800,SPN23F04290", vcfView);
+
+ StringBuffer fastaBuff = new StringBuffer(">test.embl.gz\n");
+ StringBuffer basesBuff = new StringBuffer();
+ //SPN23F03630
+
+ basesBuff.append("atgaagaaaactgtttataaaaaattgggtatttcaattattgcgagtactttattggct".toUpperCase());
+ basesBuff.append("agccagttatcgacagtatctgctttgagtgttatttctagtacaggtgaagaatatgag".toUpperCase());
+ basesBuff.append("gtaagtgagacactagaaaaaggtccagagtctaatgattcttcattatctgagatttca".toUpperCase());
+ basesBuff.append("ccaacgtatggttcatactaccaaaagcaatcagaagtattatcggtaatgatgatttga".toUpperCase());
+
+ //SPN23F03800
+ basesBuff.append("atggcaagtatcacactcacaccaagcgaaaaggatattcaggcttttcttgaacactat".toUpperCase());
+ basesBuff.append("caaaccagtctggctcctagcaagaatccctatatccgctactttttgaaactacctcaa".toUpperCase());
+ basesBuff.append("gcaacggtttctatctatacttctggaaaaatcttgcttcagggtgaaggggctgaaaaa".toUpperCase());
+ basesBuff.append("tacgccagtttctttggctatcaagctgtagagcaaaccagcggacaaaatcttccttta".toUpperCase());
+ basesBuff.append("attgggacagatgaggtgggaaatggttcctactttggtgggcttgcagttgtggctgcc".toUpperCase());
+ basesBuff.append("tttgtcacacctgaccagcacgactttttacgaaaactcggtgtgggggattctaagact".toUpperCase());
+ basesBuff.append("ctgaccgaccaaaagatccgtcagattgctcctattctcaaggaaaaaattcagcaccag".toUpperCase());
+ basesBuff.append("gcactccttctctcacccagcaagtacaacgaggtcatcggagaccgctacaatgctgtt".toUpperCase());
+ basesBuff.append("tcggttaaggttgccctccataatcaggctatctatctcctccttcaaaaaggtgttcag".toUpperCase());
+ basesBuff.append("cctgagaaaattgtgattgatgcctttaccagtgctaaaaattatgacaagtacttggca".toUpperCase());
+ basesBuff.append("caagagaccaatcgtttcagcaatcctatcagcttagaagaaaaggctgagggcaaatac".toUpperCase());
+ basesBuff.append("ttggctgtcgcagtttcttctgtcattgcgcgtgatctctttctggaaaatcttgaaaat".toUpperCase());
+ basesBuff.append("ttgggacgagaactgggttatcagcttccaagtggagctggaacggcttctgacaaggtg".toUpperCase());
+ basesBuff.append("gctagccagattttgcaagcctatggtatgcagggactcaacttctgcgccaaattgcac".toUpperCase());
+ basesBuff.append("tttaaaaatactgaaaaagcgaaaaacgcttag".toUpperCase());
+
+ //SPN23F04290
+ basesBuff.append("atgctttatgtgggcattgatatcgctaaaaataaacacgatgttacagccttgaatgtt".toUpperCase());
+ basesBuff.append("ccaggaaaaactgttcttaaaccactcactttttcaaataataaagctggttttgaactc".toUpperCase());
+ basesBuff.append("ttagatctgtctcttcgacagctcaaccaagactgtctcatcgctcttaaacttctttct".toUpperCase());
+ basesBuff.append("gatcccaatcgtgaacaatttcaacacgataatcggcaagtagacctaaaaatactggct".toUpperCase());
+ basesBuff.append("agacatattcatcgtctcaagaaaaaacagtctgattggaaagtacaatacactcgttgt".toUpperCase());
+ basesBuff.append("cttgatatcatctttcctgagttggataaaatcgttggaaagcattcagaatatacctac".toUpperCase());
+ basesBuff.append("caactcttgacgcgctaccctaatcctcagaaaaggattgaggcaggatttgataagctg".toUpperCase());
+ basesBuff.append("atagaaattaagcgattgaccgcttctaaaattcaggatatcctctcagtcgcacctcgt".toUpperCase());
+ basesBuff.append("tctatcgaaacaacatctcctgctcgtgaattcgaaatcatcgaaatcatcaaacattac".toUpperCase());
+ basesBuff.append("aagaggctcattgacaaggcggaaacatgtgtcaatgacttgatggctgagttcaactca".toUpperCase());
+ basesBuff.append("gtcatcacgacggttactgggattgggggtcgtttaggggcggtcattttagccgagatt".toUpperCase());
+ basesBuff.append("cgaaatattcatgcctttgataatcctgctcaattacaagctttcgctggactggattct".toUpperCase());
+ basesBuff.append("tctatttatcagtcaggtcagattgatttagctggaagaatgatcaaacggggttcccct".toUpperCase());
+ basesBuff.append("catctgcggtgggcactcatacaagctgccaaagcatgcgctcgcttttcacctgctttt".toUpperCase());
+ basesBuff.append("aaggcctatcttaagactaagttagaacaaggaaaacattacaatgtagccatcatccac".toUpperCase());
+ basesBuff.append("cttgcaaaaaaacttatccgaaccctgttttatatcctaaaaaagagctgccatttgacg".toUpperCase());
+ basesBuff.append("aacaaaaagtga".toUpperCase());
+
+ IOUtils.wrapString(basesBuff.toString(), fastaBuff);
+
+ fastaBuff.append(">test.vcf.gz \n");
+
+ basesBuff = new StringBuffer();
+ //SPN23F03630
+ basesBuff.append("atgaagaaaactatttataaaaaattgggtatttcaattattgcgagtactttattggct");
+ basesBuff.append("agccagttatcgacagtatctgctttgagtgttatttctagtacaggtgaagaatatgag");
+ basesBuff.append("gtaagtgagacaytagaaaaaggtccagagtctaatrattcttcattatctgagatttca");
+ basesBuff.append("ccaacgtatggttcatactaccaaaagcaatcagaagtattatcggtaatgatgatttga");
+
+ //SPN23F03800
+ basesBuff.append("atggcaagtatcacactcacaccaagcgaaaaggatattcaggcttttcttgaacactat");
+ basesBuff.append("caaaccagtctggctcctagcaagaatccctatatccgctactttttgaaactacctcaa");
+ basesBuff.append("gcaacggtttctatctatacttctggaaaaatcttgcttcagggtgaaggggctgaaaaa");
+ basesBuff.append("tacgccagtttctttggctatcaagctgtagagcaaaccagcggacaaaatcttccttta");
+ basesBuff.append("attgggacagatgaggtgggaaatggttcctactttggtgggcttgcagttgtggctgcc");
+ basesBuff.append("tttgtcacacctgaccagcacgactttttacgaaaactcggtgtgggggattctaagact");
+ basesBuff.append("ctgaccgaccaaaagatccgtcagattgctcctattctcaaggaaaaaatccagcaccag");
+ basesBuff.append("gcactccttctctcacccagcaagtacaacgaggtcatcggagaccgctacaatgctgtt");
+ basesBuff.append("tcggttaaggttgccctccataatcaggctatctatctcctccttcaaaaaggtgttcag");
+ basesBuff.append("cctgagaaaattgtgattgatgcctttaccagtgctaaaaattatgacaagtacttggca");
+ basesBuff.append("caagaggccaatcgtttcagcaatcctatcagcttagaaaaaaaggctgagggcaaatac");
+ basesBuff.append("ttggctgtcgcagtttcttctgtcattgcgcgtgatctctttctggaaaatcttgaaaat");
+ basesBuff.append("ctgggacgagaactgggttatcagctcccaagtggagctggaacagcttctgacaaggtg");
+ basesBuff.append("gctagccagattttgcaagcctatggtatgcagggactcagcttctgcgccaaattgcac");
+ basesBuff.append("tttaaaaacactgaaaaagcgaaaaaaacgcttag");
+
+ //SPN23F04290
+ basesBuff.append("atgctttatgtgggcattgatatcgctaaaaataaacacgatgttacagccttgaatgtt");
+ basesBuff.append("ccaggaaaaactgttcttaaaccactcactttttcaaataataaagctggttttgaactc");
+ basesBuff.append("ttagatctgtctcttcgacagctcaaccaagactgtctcatcgctcttaaacttctttct");
+ basesBuff.append("gaccccaatcgtgaacaatttcaacacgataatcggcaagtagaactaaaaatactggct");
+ basesBuff.append("agacatattcatcgtctcaagaaaaaacagtctgattggaaagtacaatacactcgttgt");
+ basesBuff.append("cttgatatcatctttcctgagttggataaaatcgttgaaaagcattcagaatatacctac");
+ basesBuff.append("caactcttgacgcgctaccctaatcctcagaaaaggcttgaggcaggatttgataagctg");
+ basesBuff.append("atagaaattaagcgattgaccgcttctaaaattcaggatatcctctcagttgcacctcgt");
+ basesBuff.append("tctatcgmaacaacatctcctgctcgtgaattcgaaatcatcgaaatcatcaaacattac");
+ basesBuff.append("aagaggctcattgacaaggcggaaacatgtgtcaatgacttgatggctgagttcaactcg");
+ basesBuff.append("gtcatcacgacggtcactgggattgggaatcgtttagaggcggtcattttagccgagatt");
+ basesBuff.append("cgaaatattcatgcctttgataatcctgctcaattacaagctttcgctggactggattct");
+ basesBuff.append("tctatttatcagtcaggtcagattgatttagctggaagaatgatcaaacggggttcccct");
+ basesBuff.append("catctgcggtgggcactcatacaagctgccaaagcatgccctcgcttttcacctgctttt");
+ basesBuff.append("aaggcctatcttaagactaagttagaataaggaaaacattacaatgtagccatcatccac");
+ basesBuff.append("cttgcaaaaaaacttatccgaaccctgttttatatcctaaaaaagagctgccatttgacg");
+ basesBuff.append("aacaaaaagtga");
+
+ IOUtils.wrapString(basesBuff.toString(), fastaBuff);
+
+ assertEquals("Export FASTA feature ", writer.toString(), fastaBuff.toString());
+ }
+
+
+ /**
+ * Write FASTA, from selection of features.
+ */
+ @Test
+ public void testBCFFeatureWriteFastaSNP()
+ {
+ StringWriter writer = getFeatureWriter("PFA0140c,PFA0475c,PFA0570w", bcfView);
+
+ StringBuffer fastaBuff = new StringBuffer(">MAL1.embl.gz\n");
+ StringBuffer basesBuff = new StringBuffer();
+ //PFA0140c
+ basesBuff.append("atgagcaatataaatgataataatattcaaaacagcgatgtaaaggaaataaaaaatgat".toUpperCase());
+ basesBuff.append("gaatatatgggagaagggtattataatacacataatgaatgtgagggttttgaacaaaac".toUpperCase());
+ basesBuff.append("aattatatgaataataatgaaatcaatatggtagatggaaaacatgataatagtgatgat".toUpperCase());
+ basesBuff.append("aatataaataattccatgggagggataacacatatgggacatgtgaatgagtataaaaaa".toUpperCase());
+ basesBuff.append("ataaaatcattttgtaagttaagaaggaaatttgtatatataaggagaacgggttataat".toUpperCase());
+ basesBuff.append("tcatacatatataatagaaagaaaaaaaaaaataaaaataaaatagagactgaaaggaat".toUpperCase());
+ basesBuff.append("gtagatgaaatgtataatatgaataataacaataataataatcataacaacaacaacaat".toUpperCase());
+ basesBuff.append("aataataataataataataataataataacagatataataataatattaattgtttggat".toUpperCase());
+ basesBuff.append("aattttagcgatttatttttaaaatttataaaacaattcattgatcatataaatgtacat".toUpperCase());
+ basesBuff.append("ttgaataatgtaaaaaattattatcgaaattatgtgtgtaatgataatatgaaatatgta".toUpperCase());
+ basesBuff.append("ttttatttatttattatatatatgttgttaaatattatgattatctttttttcgatgttt".toUpperCase());
+ basesBuff.append("gtatatttctttgtatatttttattttatcccacaaaataagtatgtatttcctatagat".toUpperCase());
+ basesBuff.append("ttttcattggtaaaaaatcctatagaagattatttacaaaacgaaaaaagatataacatt".toUpperCase());
+ basesBuff.append("atgaataaaatgaataatatggacaatatggataatatggataatatgaatcatataaat".toUpperCase());
+ basesBuff.append("tatatgaataacatgaatcataataataataataataataataattgtaatataaagaat".toUpperCase());
+ basesBuff.append("aattgtaataatattaatatgatgaaaaattttgataagtctaatttcaatgagtcagaa".toUpperCase());
+ basesBuff.append("gatttctatataaaatatttcgattctttaaaagaaagcaggaaaagaaatgattattta".toUpperCase());
+ basesBuff.append("tataataataaattaaaaggtttttataaagattttcaaaataatattttaagaggtcat".toUpperCase());
+ basesBuff.append("atactatttcaaaataaattatattattcagaaaaagaagagttttataagaatttcaat".toUpperCase());
+ basesBuff.append("ccttttaattttatatttttttttaagaaaagaacaggaaaagataatttacttaatata".toUpperCase());
+ basesBuff.append("aagaaaggatataaaattgatatatttttaaatttttcttatatgaataatgaatataat".toUpperCase());
+ basesBuff.append("gacaaatttaattttattcaattagttactgaaatttttaataaaaataataatgtaatg".toUpperCase());
+ basesBuff.append("tttagaaatgaaaaattatatatgaataataataattatgaatttataaagaaattacat".toUpperCase());
+ basesBuff.append("ttatttttaaatgctcctttttatttttttaatatgtttaataataaaacaaaagaaatt".toUpperCase());
+ basesBuff.append("cgattagtacatgattataaatatacgacgaatttttataaaattcaaatatatatatac".toUpperCase());
+ basesBuff.append("ccaccgatacaaatacataaagcttctatcgttattcttgtctacgttaattttatatat".toUpperCase());
+ basesBuff.append("tattacatgtataattatccttttgtgttcttttatatttttgtttttttcttgagcttc".toUpperCase());
+ basesBuff.append("atcttgatatttttcaacactatccttttttttatattgagttgttattattattttaaa".toUpperCase());
+ basesBuff.append("aacggattataa".toUpperCase());
+
+ // PFA0475c
+ basesBuff.append("atggaaggacagcataaagaaaaaaatacaaaaataaaaaaaagtaaaaaaccgttagtt".toUpperCase());
+ basesBuff.append("gttagtaatagaaaaccattcaacgtttttgaaaaaaaaaaaagtgcaaagccgctcgta".toUpperCase());
+ basesBuff.append("agagatcctcgtttttctgatttttcaggatcgttcaatgcgaacttttttagaaatgca".toUpperCase());
+ basesBuff.append("tacaagtttttatacgactcaagagaacaagaaaagaagattatagaaaaaaaattaaaa".toUpperCase());
+ basesBuff.append("agcaaaaatataacacaagaagaaaaggacgaattaaaaaaaaaatacaatgattataaa".toUpperCase());
+ basesBuff.append("agcactgatatattattaaaaaaaaaagaagaagaaaggaaattaaaagcagaattagta".toUpperCase());
+ basesBuff.append("aaacaggaaaaacaaaatattctaaccaaaaataaaaaaccttattattatagtgataga".toUpperCase());
+ basesBuff.append("aaaattaaaaaaatagttcaagaaaaattgtcgagttataagagtttaaagaaagttata".toUpperCase());
+ basesBuff.append("aaaaaagaaaaaaaaacattgcaaaaagagagaaagagaaacatcaaaccaacaaaaaaa".toUpperCase());
+ basesBuff.append("aaaatttttttaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".toUpperCase());
+ basesBuff.append("aaaaaaaaaaaaaaaacataa".toUpperCase());
+
+ // PFA0570w
+ basesBuff.append("atggaaggaaatgataagtgtttaaatgtcaccttggctgacatagaaaaagatacaatg".toUpperCase());
+ basesBuff.append("aaaaataatttaagcctaaatagtaaaggtaatgaacttttaaataataagaaaagtggt".toUpperCase());
+ basesBuff.append("tataaaaataatataaagaaaaaaaagaaaaaaagtataaaggaaaataatgggaatgaa".toUpperCase());
+ basesBuff.append("caaaataaaggtaatggtactcttaccctaaagaatgaagaaaatgatagtaaggtattc".toUpperCase());
+ basesBuff.append("aaaacatacagaaataaaagaaatagcaaaaatgataatatggaaaaacataaaaatgat".toUpperCase());
+ basesBuff.append("gtaacaaaaaatgaaaatgatataacaaaaaatgaaaatgatgagataataaaagaagat".toUpperCase());
+ basesBuff.append("aaaaatagcaatttaggaaaaaccaatggatataatataaaagatataagaagaaaaaaa".toUpperCase());
+ basesBuff.append("aaagacaataataaagaatacataagagaccatatgaaaaaaaaaaaagatataataatg".toUpperCase());
+ basesBuff.append("aataataaaggaaaaaaaaataatagtaataataataataatgacaataatgataataat".toUpperCase());
+ basesBuff.append("gataataataatagtaataatagcaataataataataataataataataataatagtaat".toUpperCase());
+ basesBuff.append("gaatatacaaaaagaaagaatacacataaaaaacatttaaatgaacactataaaaatgag".toUpperCase());
+ basesBuff.append("agtaataagaaaaaggttaatgaaaagaaatacaataatagtgtgtatgttaataataat".toUpperCase());
+ basesBuff.append("ataaagaaaaatcatgtcaacaaaaataaaaatgaaaattatttacaaaatgtgtggtta".toUpperCase());
+ basesBuff.append("tttttattcgataatgaagttagaaaagaaaatgaccaatgtgttggaaaaattatatca".toUpperCase());
+ basesBuff.append("ctggatactttcaataccatagaaaaattttacaagaactataaatatatgaaatcgcct".toUpperCase());
+ basesBuff.append("tcagccattaaggaaaaatacaacatttatctttttaaacaaaattttagacccctcttt".toUpperCase());
+ basesBuff.append("gacgaatatccaaatggttttatttgtaccgttaaaaatgccaatcattttaaaaatgac".toUpperCase());
+ basesBuff.append("agcgttgatataatatgggaaaaaatggttcttttggctataggagaagaatttagctta".toUpperCase());
+ basesBuff.append("atcgacttatgtggtttacaattatgcataagagataatgaaatgttttttaaaatatgg".toUpperCase());
+ basesBuff.append("atgaaaaattattcaaattatctaaaaaatatattgatgaaaaaattaagggacgcctac".toUpperCase());
+ basesBuff.append("aatgtatacaataacaaaaaaaatcaacaaggaaagggaaaaaatgaaaaggctaaaaag".toUpperCase());
+ basesBuff.append("aattacaataaaaataataagggcgcagaatttgtagctagtaaaaaggattctttaaaa".toUpperCase());
+ basesBuff.append("atgcataattatccaaacatagtaccaccaccaaattatttaggaaattacaatgtttac".toUpperCase());
+ basesBuff.append("aaatacaacctagatatgaatttgttttatttatataataatcaaaatatgcctaacccg".toUpperCase());
+ basesBuff.append("tatatatacattcctgtcaatgtacccaataatcaatataataatatttatccagattat".toUpperCase());
+ basesBuff.append("atgtacgacagtaatacgagctatcctatagatataattaataataatttattaagtaat".toUpperCase());
+ basesBuff.append("gatattaatgtaccaagcaattttgtaaataataaaatgaatggatcgataatagtagat".toUpperCase());
+ basesBuff.append("aaaaaaagtaaaattgattatggattaaagaatgaggattataaaaaaaaatctatgaat".toUpperCase());
+ basesBuff.append("tccttaaattcgaatgatatatatgaagatagtaaaagtactacatgtattaaatccgta".toUpperCase());
+ basesBuff.append("tataccgatgatgaatatgaatataataatagtagtaataataataataatatatcgtat".toUpperCase());
+ basesBuff.append("gcttgtcctggtgatcatgataaaacgttttgtgaattacgaaagaacccaaatgaatct".toUpperCase());
+ basesBuff.append("tccatccttgtaattataaatttaaaagaattttatacggaggtaagattagcatatgaa".toUpperCase());
+ basesBuff.append("ctatatattatatataataggagaatgaaaaaaaaaaacaataaaacaaattaa".toUpperCase());
+
+ IOUtils.wrapString(basesBuff.toString(), fastaBuff);
+
+ fastaBuff.append(">MAL1_8_16_24h.raw.bcf \n");
+ basesBuff = new StringBuffer();
+ //PFA0140c
+ basesBuff.append("atgagcaatataaatgataataatattcaaaacagcgatgtaaaggaaataaaaaatgat");
+ basesBuff.append("gaatatatgggagaagggtattataatacacataatgaatgtgagggttttgaacaaaac");
+ basesBuff.append("aattatatgaataataatgaaatcaatatggtagatggaaaacatgataatagtgatgat");
+ basesBuff.append("aatataaataattccatgggagggataacacatatgggacatgtgaatgagtataaaaaa");
+ basesBuff.append("ataaaatcattttgtaagttaagaaggaaatttgtatatataaggagaacgggttataat");
+ basesBuff.append("tcatacatatataatagaaagaaaaaaaaaaataaaaataaaatagagactgaaaggaat");
+ basesBuff.append("gtagatgaaatgtataatatgaataataacaataataataatcataacaacaacaacaat");
+ basesBuff.append("aataataataataataataataataataacagatataataataatattaattgtttggat");
+ basesBuff.append("aattttagcgatttatttttaaaatttataaaacaattcattgatcatataaatgtacat");
+ basesBuff.append("ttgaataatgtaaaaaattattatcgaaattatgtgtgtaatgataatatgaaatatgta");
+ basesBuff.append("ttttatttatttattatatatatgttgttaaatattatgattatctttttttcgatgttt");
+ basesBuff.append("gtatatttctttgtatatttttattttatcccacaaaataagtatgtatttcctatagat");
+ basesBuff.append("ttttcattggtaaaaaatcctatagaagattatttacaaaacgaaaaaagatataacatt");
+ basesBuff.append("atgaataaaatgaataatatggacaatatggataatatggataatatgaatcatataaat");
+ basesBuff.append("tatatgaataacatgaatcataataataataataataataataattgtaatataaagaat");
+ basesBuff.append("aattgtaataatattaatatgatgaaaaattttgataagtctaatttcaatgagtcagaa");
+ basesBuff.append("gatttctatataaaatatttcgattctttaaaagaaagcaggaaaagaaatgattattta");
+ basesBuff.append("tataataataaattaaaaggtttttataaagattttcaaaataatattttaagaggtcat");
+ basesBuff.append("atactatttcaaaataaattatattattcagaaaaagaagagttttataagaatttcaat");
+ basesBuff.append("ccttttaattttatatttttttttaagaaaagaacaggaaaagataatttacttaatata");
+ basesBuff.append("aagaaaggatataaaattgatatatttttaaatttttcttatatgaataatgaatataat");
+ basesBuff.append("gacaaatttaattttattcaattagttactgaaatttttaataaaaataataatgtaatg");
+ basesBuff.append("tttagaaatgaaaaattatatatgaataataataattatgaatttataaagaaattacat");
+ basesBuff.append("ttatttttaaatgctcctttttatttttttaatatgtttaataataakacaaaagaaatt"); // multi-allele
+ basesBuff.append("cgattagtacatgattataaatatacgacgaatttttataaaattcaaatatatatatac");
+ basesBuff.append("ccaccgatacaaatacataaagcttctatcgttattcttgtctacgttaattttatatat");
+ basesBuff.append("tattacatgtataattatccttttgtgttcttttatatttttgtttttttcttgagcttc");
+ basesBuff.append("atcttgatatttttcaacactatccttttttttatattgagttgttattattattttaaa");
+ basesBuff.append("aacggattataa");
+
+ // PFA0475c
+ basesBuff.append("atggaaggacagcataaagaaaaaaatacaaaaataaaaaaaagtaaaaaaccgttagtt");
+ basesBuff.append("gttagtaatagaaaaccattcaacgtttttgaaaaaaaaaaaagtgcaaagccgctcgta");
+ basesBuff.append("agagatcctcgtttttctgatttttcaggatcgttcaatgcgaacttttttagaaatgca");
+ basesBuff.append("tacaagtttttatacgactcaagagaacaagaaaagaagattatagaaaaaaaattaaaa");
+ basesBuff.append("agcaaaaatataacacaagaagaaaaggacgaattaaaaaaaaaatacaatgattataaa");
+ basesBuff.append("agcactgatatattattaaaaaaaaaagaagaagaaaggaaattaaaagcagaattagta");
+ basesBuff.append("aaacaggaaaaacaaaatattctaaccaaaaataaaaaaccttattattatagtgataga");
+ basesBuff.append("aaaattaaaaaaatagttcaagaaaaattgtcgagtaataagagtttaaagaaagttata");
+ basesBuff.append("aaaaaagaaaaaaaaacattgcaaaaagagagaaagagaaacatcataccaacaaaaaaa");
+ basesBuff.append("aaaatttttttaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+ basesBuff.append("aaaaaaaaaaaaaaaacataa");
+
+ // PFA0570w
+ basesBuff.append("atggaaggaaatgataagtgtttaaatgtcaccttggctgacatagaaaaagatacaatg");
+ basesBuff.append("aaaaataatttaagcctaaatagtaaaggtaatgaacttttaaataataagaaaagtggt");
+ basesBuff.append("tataaaaataatataaagaaaaaaaagaaaaaaagtataaaggaaaataatgggaatgaa");
+ basesBuff.append("caaaataaaggtaatggtactcttaccctaaagaatgaagaaaatgatagtaaggtattc");
+ basesBuff.append("aaaacatacagaaataaaagaaatagtaaaaatgataatatggaaaaacataaaaatgat");
+ basesBuff.append("gtaacaaaaaatgaaaatgatataacaaaaaatgaaaatgatgagataataaaagaagat");
+ basesBuff.append("aaaaatagcaatttaggaaaaaccaatggatataatataaaagatataagaagaaaaaaa");
+ basesBuff.append("aaagacaataataaagaatacataagagaccatatgaaaaaaaaaaaagatataataatg");
+ basesBuff.append("aataataaaggaaaaaaaaataatagtaataataataataatgacaataatgataataat");
+ basesBuff.append("gataataataatagtaataatagcaataataataataataataataataataatagtaat");
+ basesBuff.append("gaatatacaaaaagaaagaatacacataaaaaacatttaaatgaacactataaaaatgag");
+ basesBuff.append("agtaataagaaaaaggttaatgaaaagaaatacaataatagtgtgtatgttaataataat");
+ basesBuff.append("ataaagaaaaatcatgtmaacaaaaataaaaatgaaaattatttacaaaatgtgtggtta"); // multi-allele m = A or C
+ basesBuff.append("tttttattcgataatgaagttagaaaagaaaatgamcaatgtgttggaaaaattatatca"); // multi-allele
+ basesBuff.append("ctggatactttcaataccatagaaaaattttacaagaactataaatatatgaaatcgcct");
+ basesBuff.append("tcagccattaaggaawamtacaacatttatctttttaaacawaattttagacccctcttt"); // multi-allele
+ basesBuff.append("gacgaatatccaaatggttttatttgtaccgttaaaaatgccaatcattttaaaaatgac");
+ basesBuff.append("agcgttgatataatatgggaaaaaatggttcttttggctataggagaagaatttagctta");
+ basesBuff.append("atcgacttatgtggtttacaattatgcataagagataatgaaatgttttttaaaatatgg");
+ basesBuff.append("atgaaaaattattcaaattatctaaaaaatatattgatgaaaaaattaagggacgcctac");
+ basesBuff.append("aatgtatacaataacaaaaaaaatcaacaaggaaagggaaaaaatgaaaaggctaaaaag");
+ basesBuff.append("aattacaataaaaataataagggcgcagaatttgtagctagtaaaaaggattctttaaaa");
+ basesBuff.append("atgcataattatccaaacatagtaccaccaccaaattatttaggaaattacwatgtttac"); // multi-allele
+ basesBuff.append("aaatacaacctagatatgaatttgttttatttatataataatcaaaatatgcctaacccg");
+ basesBuff.append("tatatatacattcctgtcaatgtacccaataatcaatataataatatttatccagattat");
+ basesBuff.append("atgtacgacagtaatacgagctatcctatagatataattaataataatttattaagtaat");
+ basesBuff.append("gatattaatgtaccaagcaattttgtaaataataaaatgaatggatcgataatagtagat");
+ basesBuff.append("aaaaaaagtaaaattgattatggattaaagaatgaggattataaaaaaaaatctatgaat");
+ basesBuff.append("tccttaaattcgaatgatatatatgaagatagtaaaagtactacatgtattaaatccgta");
+ basesBuff.append("tataccgatgatgaatatgaatataataatagtagtaataataataataatatatcgtat");
+ basesBuff.append("gcttgtcctggtgatcatgataaaacgttttgtgaattacgaaagaacccaaatgaatct");
+ basesBuff.append("tccatccttgtaattataaatttaaaagaattttatacggaggtaagattagcatatgaa");
+ basesBuff.append("ctatatattatatataataggagaatgaaaaaaaaaaacaataaaacaaattaa");
+
+ IOUtils.wrapString(basesBuff.toString(), fastaBuff);
+
+ assertEquals("Export FASTA feature ", writer.toString(), fastaBuff.toString());
+ }
+
+ /**
+ * Get sequences for a selected region.
+ * @param isFwd
+ * @param start
+ * @param end
+ * @return
+ */
+ private StringWriter getRegionWriter(boolean isFwd, int start, int end)
+ {
+ StringWriter writer = new StringWriter();
+ Selection selection = new Selection(null);
+
+ FeatureVector features = vcfView.getEntryGroup().getAllFeatures();
+ Feature feature = null;
+ for(int i=0; i<features.size(); i++)
+ {
+ if(features.elementAt(i).isForwardFeature() && isFwd)
+ {
+ feature = features.elementAt(i);
+ break;
+ }
+ else if(!features.elementAt(i).isForwardFeature() && !isFwd)
+ {
+ feature = features.elementAt(i);
+
+ int length = vcfView.getEntryGroup().getSequenceEntry().getBases().getLength();
+ int tmp = start;
+ start = length - end + 1;
+ end = length - tmp + 1;
+
+ break;
+ }
+ }
+
+ try
+ {
+ selection.setMarkerRange(new MarkerRange(feature.getStrand(), start, end));
+ }
+ catch(uk.ac.sanger.artemis.util.OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ IOUtils.exportFastaByRange(vcfView, selection, false, writer);
+ return writer;
+ }
+
+ /**
+ * Get sequences for features.
+ * @param id comma separated list of feature id's
+ * @return
+ */
+ private StringWriter getFeatureWriter(String ids, final VCFview view)
+ {
+ StringWriter writer = new StringWriter();
+ String id[] = ids.split(",");
+
+ FeatureVector features = view.getEntryGroup().getAllFeatures();
+ FeatureVector selectedFeatures = new FeatureVector();
+ Feature feature = null;
+ for(int i=0; i<features.size(); i++)
+ {
+ for(int j=0; j<id.length; j++)
+ {
+ if(features.elementAt(i).getIDString().equals(id[j]))
+ {
+ feature = features.elementAt(i);
+ selectedFeatures.add(feature);
+ }
+ }
+ }
+
+ IOUtils.exportFasta(view, selectedFeatures, false, writer);
+ return writer;
+ }
+
+
+ /**
+ * Load in a VCF and BCF into separate VCFviews to be used
+ * separately in testing.
+ */
+ @BeforeClass
+ public static void oneTimeSetUp()
+ {
+ // load VCF
+ URL ref = WriteVCFTest.class.getResource("/data/test.embl.gz");
+ URL vcf = WriteVCFTest.class.getResource("/data/test.vcf.gz");
+
+ List<String> vcfFileList = new Vector<String>();
+ vcfFileList.add(vcf.getFile());
+
+ vcfView = new VCFview(null, new JPanel(),
+ vcfFileList,
+ 5000, 100000000, null,
+ ref.getFile(), null, null);
+
+ // load BCF
+ ref = WriteVCFTest.class.getResource("/data/MAL1.embl.gz");
+ vcf = WriteVCFTest.class.getResource("/data/MAL1_8_16_24h.raw.bcf");
+
+ List<String> bcfFileList = new Vector<String>();
+ bcfFileList.add(vcf.getFile());
+ bcfView = new VCFview(null, new JPanel(),
+ bcfFileList,
+ 5000, 100000000, null,
+ ref.getFile(), null, null);
+ }
+
+}
\ No newline at end of file
diff --git a/test/uk/ac/sanger/artemis/components/variant/WriteVariantSitesTest.java b/test/uk/ac/sanger/artemis/components/variant/WriteVariantSitesTest.java
new file mode 100644
index 0000000..447e669
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/components/variant/WriteVariantSitesTest.java
@@ -0,0 +1,166 @@
+package uk.ac.sanger.artemis.components.variant;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.components.variant.VCFview;
+import uk.ac.sanger.artemis.sequence.Bases;
+
+
+public class WriteVariantSitesTest
+{
+ /**
+ * Test SNP's
+ */
+ @Test
+ public void testSNPSites()
+ {
+ // load VCF
+ final URL ref = WriteVCFTest.class.getResource("/data/test.embl.gz");
+ final URL vcf = WriteVCFTest.class.getResource("/data/test1.vcf.gz");
+ VCFview vcfView = getVcfView(ref, vcf);
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ EntryGroup entryGroup = vcfView.getEntryGroup();
+
+ IOUtils.exportVariantFasta(vcfView, pw,
+ entryGroup.getSequenceEntry().getBases().getLength(),
+ entryGroup.getAllFeatures(),
+ entryGroup.getSequenceEntry().getBases());
+
+ StringBuilder fastaBuff = new StringBuilder(">1_sample\n");
+ fastaBuff.append("TTTTGAACTTATACA\n");
+ fastaBuff.append(">test.embl.gz\n");
+ fastaBuff.append("CCCCATGTGGGCGTG\n");
+
+ assertEquals("Export variants as FASTA ", sw.toString(), fastaBuff.toString());
+ }
+
+ /**
+ * Test insertions
+ */
+ @Test
+ public void testInsertionSites()
+ {
+ // load VCF
+ final URL ref = WriteVCFTest.class.getResource("/data/test.embl.gz");
+ final URL vcf = WriteVCFTest.class.getResource("/data/test2.vcf.gz");
+ VCFview vcfView = getVcfView(ref, vcf);
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ EntryGroup entryGroup = vcfView.getEntryGroup();
+
+ IOUtils.exportVariantFasta(vcfView, pw,
+ entryGroup.getSequenceEntry().getBases().getLength(),
+ entryGroup.getAllFeatures(),
+ entryGroup.getSequenceEntry().getBases());
+
+ StringBuilder fastaBuff = new StringBuilder(">1_sample\n");
+ fastaBuff.append("tGgTttttttAaaTttaAGAaAGTAGa\n");
+ fastaBuff.append(">test.embl.gz\n");
+ fastaBuff.append("t-gttttt-ta-att-a---aa-----\n");
+
+ assertEquals("Export variants as FASTA ", sw.toString(), fastaBuff.toString());
+ }
+
+ /**
+ * Test Multi-allele
+ */
+ @Test
+ public void testMultiAlleleSites()
+ {
+ // load VCF
+ final URL ref = WriteVCFTest.class.getResource("/data/test.embl.gz");
+ final URL vcf = WriteVCFTest.class.getResource("/data/test3.vcf.gz");
+ VCFview vcfView = getVcfView(ref, vcf);
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ EntryGroup entryGroup = vcfView.getEntryGroup();
+
+ IOUtils.exportVariantFasta(vcfView, pw,
+ entryGroup.getSequenceEntry().getBases().getLength(),
+ entryGroup.getAllFeatures(),
+ entryGroup.getSequenceEntry().getBases());
+
+ StringBuilder fastaBuff = new StringBuilder(">1_sample\n");
+ fastaBuff.append("wwmrwr\n");
+ fastaBuff.append(">test.embl.gz\n");
+ fastaBuff.append("ggtgcc\n");
+
+ assertEquals("Export variants as FASTA ", sw.toString(), fastaBuff.toString());
+ }
+
+
+ /**
+ * Test deletion. Note: do not write out if just deletion.
+ */
+ @Test
+ public void testDeletionSites()
+ {
+ // load VCF
+ final URL ref = WriteVCFTest.class.getResource("/data/test.embl.gz");
+ final URL vcf = WriteVCFTest.class.getResource("/data/test4.vcf.gz");
+ VCFview vcfView = getVcfView(ref, vcf);
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ EntryGroup entryGroup = vcfView.getEntryGroup();
+
+ IOUtils.exportVariantFasta(vcfView, pw,
+ entryGroup.getSequenceEntry().getBases().getLength(),
+ entryGroup.getAllFeatures(),
+ entryGroup.getSequenceEntry().getBases());
+
+ StringBuilder fastaBuff = new StringBuilder(">1_sample\n");
+ fastaBuff.append(">test.embl.gz\n");
+
+ assertEquals("Export variants as FASTA ", sw.toString(), fastaBuff.toString());
+ }
+
+ /**
+ * Load in a VCF and BCF into separate VCFviews to be used
+ * separately in testing.
+ */
+ private VCFview getVcfView(final URL ref, final URL vcf)
+ {
+ List<String> vcfFileList = new Vector<String>();
+ vcfFileList.add(vcf.getFile());
+
+ return new VCFview(null, new JPanel(),
+ vcfFileList,
+ 5000, 100000000, null,
+ ref.getFile(), null, null);
+
+ // load BCF
+/* ref = WriteVCFTest.class.getResource("/data/MAL1.embl.gz");
+ vcf = WriteVCFTest.class.getResource("/data/MAL1_8_16_24h.raw.bcf");
+
+ List<String> bcfFileList = new Vector<String>();
+ bcfFileList.add(vcf.getFile());
+ bcfView = new VCFview(null, new JPanel(),
+ bcfFileList,
+ 5000, 100000000, null,
+ ref.getFile(), null, null);*/
+ }
+}
\ No newline at end of file
diff --git a/test/uk/ac/sanger/artemis/io/GFFTest.java b/test/uk/ac/sanger/artemis/io/GFFTest.java
new file mode 100644
index 0000000..6d3c99d
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/io/GFFTest.java
@@ -0,0 +1,122 @@
+/*
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class GFFTest
+{
+ @Test
+ /**
+ * Test the segment ID is not null after changing the location of
+ * a CDS with a join.
+ */
+ public void testIdAfterLocationChange()
+ {
+ try
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ Feature f = Utils.getCDSFeatureByIdPrefix("PF3D7_0200200.1", entry.getAllFeatures());
+ Location oldLocation = f.getLocation();
+ Location newLocation = new Location("join(25234..29035,29837..31168)");
+ f.setLocation(newLocation);
+ GFFUtils.updateSegmentRangeStore((GFFStreamFeature)f, oldLocation, newLocation);
+
+ for(Range r: f.getLocation().getRanges())
+ assertTrue("CDS for PF3D7_0200100.1 not found ",
+ ((GFFStreamFeature)f).getSegmentID(r) != null);
+ }
+ catch (Exception e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ }
+
+ @Test
+ /**
+ * For a GFF with multiple sequences check the offset position
+ * of a gene is correctly set.
+ */
+ public void testGFFMultipleFastaOffset()
+ {
+ try
+ {
+ final Entry entry = Utils.getEntry("/data/Pf3D7_01_02_v3.gff.gz");
+ final EntryGroup egrp = new SimpleEntryGroup();
+ egrp.add(new uk.ac.sanger.artemis.Entry(entry));
+ final uk.ac.sanger.artemis.FeatureVector features = egrp.getAllFeatures();
+
+ // change the translation table did cause a problem
+ // with the offset that has now been fixed in GFFDocumentEntry
+ Utils.changeTranslationTable("11");
+
+ for (int i=0; i<features.size(); i++)
+ {
+ uk.ac.sanger.artemis.Feature f = features.elementAt(i);
+ try
+ {
+ final Qualifier q = f.getQualifierByName("ID");
+ if (q != null)
+ {
+ final String id = q.getValues().get(0);
+ if (id.equals("PF3D7_0200100"))
+ {
+ assertEquals("Offset check first base: " + id +
+ " " +f.getFirstBase() + " != 666083",
+ f.getFirstBase(), 666083);
+
+ assertEquals("Offset check location: " + id+
+ " " +f.getEmblFeature().getLocation().getFirstBase() + " != 666083",
+ f.getEmblFeature().getLocation().getFirstBase(), 666083);
+ }
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ }
+
+ GFFStreamFeature.contig_ranges = null;
+ }
+ catch (OutOfRangeException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ catch (NoSequenceException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ }
+}
diff --git a/test/uk/ac/sanger/artemis/io/GffToEMBLTest.java b/test/uk/ac/sanger/artemis/io/GffToEMBLTest.java
new file mode 100644
index 0000000..0ea4a2a
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/io/GffToEMBLTest.java
@@ -0,0 +1,67 @@
+/*
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import java.io.File;
+import java.net.URL;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+public class GffToEMBLTest
+{
+ @Test
+ /**
+ * Test writing EMBL format from GFF files.
+ */
+ public void testGffToEMBL()
+ {
+ testGffConversion("/data/Pf3D7_01_02_v3.gff.gz");
+ testGffConversion("/data/test.gff.gz");
+ testGffConversion("/data/test_boundary.gff.gz");
+ }
+
+ private void testGffConversion(String gff)
+ {
+ URL gffURL = Utils.class.getResource(gff);
+ File gffFile = new File(gffURL.getFile());
+ final File outDir = gffFile.getParentFile();
+ final boolean emblSubmission = false;
+ final boolean flatten = true;
+ final boolean gzip = true;
+
+ final File result = new File(outDir.getAbsolutePath()+File.separator+gffFile.getName()+".embl.gz");
+ assertFalse("EMBL file exists: "+result.getAbsolutePath(), result.exists());
+ if(result.exists())
+ return;
+
+ new GffToEMBL(gffURL.getFile(), outDir.getAbsolutePath(),
+ emblSubmission, flatten, gzip);
+
+ assertTrue("EMBL not created: "+result.getAbsolutePath(), result.exists());
+ if(result.exists() && result.isFile())
+ result.delete();
+ }
+}
diff --git a/test/uk/ac/sanger/artemis/io/Utils.java b/test/uk/ac/sanger/artemis/io/Utils.java
new file mode 100644
index 0000000..da216ac
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/io/Utils.java
@@ -0,0 +1,170 @@
+/*
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import java.io.IOException;
+import java.net.URL;
+
+import junit.framework.Assert;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class Utils
+{
+ public static Entry getEntry(final String fileName)
+ {
+ try
+ {
+ URL entryFile = Utils.class.getResource(fileName);
+ final Document doc = DocumentFactory.makeDocument(entryFile.getFile());
+ return DocumentEntryFactory.makeDocumentEntry(
+ Options.getArtemisEntryInformation(),doc,null);
+ }
+ catch(EntryInformationException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ catch(IOException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ return null;
+ }
+
+ public static EntryGroup getEntryGroup(final String fileName)
+ {
+ try
+ {
+ final Entry new_embl_entry = getEntry(fileName);
+ final uk.ac.sanger.artemis.Entry entry = new uk.ac.sanger.artemis.Entry(new_embl_entry);
+ final EntryGroup egrp = new SimpleEntryGroup(entry.getBases());
+ egrp.add(entry);
+ return egrp;
+ }
+ catch(OutOfRangeException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ catch(NoSequenceException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Get a CDS feature with the feature ID prefix
+ * @param featureName
+ * @param features
+ * @return
+ */
+ public static uk.ac.sanger.artemis.Feature getCDSFeatureByIdPrefix(
+ final String id,
+ final uk.ac.sanger.artemis.FeatureVector features)
+ {
+ try
+ {
+ for (int i=0; i<features.size(); i++)
+ {
+ uk.ac.sanger.artemis.Feature f = features.elementAt(i);
+ final Qualifier q = f.getQualifierByName("ID");
+ if (q != null)
+ {
+ final String thisId = q.getValues().get(0);
+ if (thisId.startsWith(id) && f.isCDS())
+ return f;
+ }
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ return null;
+ }
+
+ public static Feature getCDSFeatureByIdPrefix(
+ final String id,
+ final FeatureVector features)
+ {
+ try
+ {
+ for (Feature f: features)
+ {
+ final Qualifier q = f.getQualifierByName("ID");
+ if (q != null)
+ {
+ final String thisId = q.getValues().get(0);
+ if (thisId.startsWith(id) && f.getKey().getKeyString().equals("CDS"))
+ return f;
+ }
+ }
+ }
+ catch (InvalidRelationException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Method to change the translation table being used
+ * @param n - genetic code table number
+ */
+ protected static void changeTranslationTable(String n)
+ {
+ StringVector options_file_table =
+ Options.getOptions().getOptionValues("translation_table_"+n);
+ StringVector table = Options.getOptions().getOptionValues("translation_table_1");
+ for(String cod_plus_aa: options_file_table)
+ {
+ final int codon_index = Bases.getIndexOfBase(cod_plus_aa.charAt(0)) * 16 +
+ Bases.getIndexOfBase(cod_plus_aa.charAt(1)) * 4 +
+ Bases.getIndexOfBase(cod_plus_aa.charAt(2));
+ table.setElementAt(cod_plus_aa.substring(3), codon_index);
+ }
+
+ StringBuffer sbuff = new StringBuffer();
+ for(int i = 0; i < 64; ++i)
+ sbuff.append(table.elementAt(i)+" ");
+ Options.getOptions().setGeneticCode(sbuff.toString());
+
+ options_file_table =
+ Options.getOptions().getOptionValues("start_codons_"+n);
+ sbuff = new StringBuffer();
+ for(String start: options_file_table)
+ sbuff.append(start+" ");
+
+ Options.getOptions().setProperty("start_codons",sbuff.toString());
+ AminoAcidSequence.setGeneCode();
+ }
+}
\ No newline at end of file
diff --git a/test/uk/ac/sanger/artemis/io/ValidateFeatureTest.java b/test/uk/ac/sanger/artemis/io/ValidateFeatureTest.java
new file mode 100644
index 0000000..0d33434
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/io/ValidateFeatureTest.java
@@ -0,0 +1,333 @@
+/*
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.log4j.Level;
+import org.junit.Test;
+
+import junit.framework.Assert;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.io.DocumentEntryFactory;
+import uk.ac.sanger.artemis.io.Entry;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.FeatureVector;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.ValidateFeature;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+
+public class ValidateFeatureTest
+{
+ @Test
+ public void testGFF()
+ {
+ testAll(Utils.getEntryGroup("/data/test.gff.gz"));
+ }
+
+ public static void testAll(final EntryGroup egrp)
+ {
+ ValidateFeature validate = new ValidateFeature(egrp);
+ final uk.ac.sanger.artemis.FeatureVector features = egrp.getAllFeatures();
+
+ for(int i=0; i<features.size(); i++)
+ {
+ Feature artFeature = features.elementAt(i);
+ uk.ac.sanger.artemis.io.Feature f = artFeature.getEmblFeature();
+ if(ValidateFeature.isGFF(f, null))
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ String id = GeneUtils.getUniqueName(gffFeature);
+
+ assertTrue("Boundary "+id, ValidateFeature.isBoundaryOK(gffFeature) == 0);
+ assertTrue("Strand "+id, ValidateFeature.isStrandOK(gffFeature));
+ assertTrue("CDS phase "+id, ValidateFeature.isCDSPhaseOK(gffFeature));
+ assertTrue("Attribute "+id, ValidateFeature.isAttributesOK(gffFeature).length() == 0);
+ assertTrue("ID check "+id, ValidateFeature.isIdPrefixConsistent(gffFeature));
+ assertTrue("Start_range check "+id, ValidateFeature.isPartialConsistent(gffFeature, "Start_range"));
+ assertTrue("End_range check "+id, ValidateFeature.isPartialConsistent(gffFeature, "End_range"));
+
+ if(ValidateFeature.isPartOfGene(gffFeature))
+ assertTrue("Gene model not complete "+id, ValidateFeature.isCompleteGeneModelOK(gffFeature) == 0);
+ }
+
+ assertTrue("Stop codon "+artFeature.getIDString(), validate.hasValidStop(f));
+ assertTrue("Internal stop codon "+artFeature.getIDString(), !validate.isInternalStops(f));
+ }
+ }
+
+ /**
+ * Test the gene model boundary is consistent
+ */
+ @Test
+ public void testGFFBoundary()
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ final FeatureVector features = entry.getAllFeatures();
+
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ {
+ if(ValidateFeature.isGFF(f, null))
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ String id = GeneUtils.getUniqueName(gffFeature);
+ if(id.equals("PF3D7_0200100"))
+ assertTrue("Boundary check: "+id, ValidateFeature.isBoundaryOK(gffFeature) != 0); // boundary not OK
+ else
+ assertTrue("Boundary check: "+id, ValidateFeature.isBoundaryOK(gffFeature) == 0); // boundary OK
+ }
+ }
+ }
+
+ /**
+ * Test the gene model strand is consistent
+ */
+ @Test
+ public void testGFFStrand()
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ final FeatureVector features = entry.getAllFeatures();
+
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ {
+ if(ValidateFeature.isGFF(f, null))
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ String id = GeneUtils.getUniqueName(gffFeature);
+ if(id.equals("PF3D7_0200300"))
+ assertTrue("Strand check: "+id, !ValidateFeature.isStrandOK(gffFeature)); // strand not ok
+ else
+ assertTrue("Strand check: "+id, ValidateFeature.isStrandOK(gffFeature)); // strand ok
+ }
+ }
+ }
+
+ /**
+ * Test the CDS has a phase
+ */
+ @Test
+ public void testGFFPhase()
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ final FeatureVector features = entry.getAllFeatures();
+
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ {
+ if(ValidateFeature.isGFF(f, null))
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ String id = GeneUtils.getUniqueName(gffFeature);
+ if(id.equals("PF3D7_0200200.1:exon{2,1}"))
+ assertTrue("CDS phase check: "+id, !ValidateFeature.isCDSPhaseOK(gffFeature)); // phase not ok
+ else
+ assertTrue("CDS phase check: "+id, ValidateFeature.isCDSPhaseOK(gffFeature)); // phase ok
+ }
+ }
+ }
+
+ /**
+ * Test the gene models are complete
+ */
+ @Test
+ public void testGFFCompleteGeneModel()
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ final FeatureVector features = entry.getAllFeatures();
+
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ {
+ if(ValidateFeature.isGFF(f, null))
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ if(ValidateFeature.isPartOfGene(gffFeature))
+ {
+ String id = GeneUtils.getUniqueName(gffFeature);
+ if(id.startsWith("PF3D7_0200500"))
+ assertTrue("Complete gene model check: "+id, // gene model missing mRNA
+ ValidateFeature.isCompleteGeneModelOK(gffFeature) != 0);
+ else
+ assertTrue("Complete gene model check: "+id, // gene model complete
+ ValidateFeature.isCompleteGeneModelOK(gffFeature) == 0);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Test if the ID GFF3 attribute prefix is consistent within a gene model
+ */
+ @Test
+ public void testGFFId()
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ final FeatureVector features = entry.getAllFeatures();
+
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ {
+ if(ValidateFeature.isGFF(f, null))
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ if(ValidateFeature.isPartOfGene(gffFeature))
+ {
+ String id = GeneUtils.getUniqueName(gffFeature);
+ assertTrue("Complete gene model check: "+id,
+ ValidateFeature.isIdPrefixConsistent(gffFeature));
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Test if the Start_range and End_range are constistent within a gene model
+ */
+ @Test
+ public void testGFFPartials()
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ final FeatureVector features = entry.getAllFeatures();
+
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ {
+ if(ValidateFeature.isGFF(f, null))
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ if(ValidateFeature.isPartOfGene(gffFeature))
+ {
+ String id = GeneUtils.getUniqueName(gffFeature);
+ assertTrue("Start_range check: "+id,
+ ValidateFeature.isPartialConsistent(gffFeature, "Start_range"));
+ assertTrue("End_range check: "+id,
+ ValidateFeature.isPartialConsistent(gffFeature, "End_range"));
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Test if the Start_range and End_range are constistent within a gene model
+ */
+ @Test
+ public void testGFFAttributes()
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ final FeatureVector features = entry.getAllFeatures();
+
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ {
+ if(ValidateFeature.isGFF(f, null))
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ if(ValidateFeature.isPartOfGene(gffFeature))
+ {
+ String id = GeneUtils.getUniqueName(gffFeature);
+ assertTrue("Attributes : "+id,
+ ValidateFeature.isAttributesOK(gffFeature).length() == 0);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Test stop codons for genes
+ */
+ @Test
+ public void testGFFValidStop()
+ {
+ try
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ final FeatureVector features = entry.getAllFeatures();
+ final EntryGroup egrp = new SimpleEntryGroup();
+ egrp.add(new uk.ac.sanger.artemis.Entry(entry));
+ ValidateFeature validate = new ValidateFeature(egrp);
+
+ for (uk.ac.sanger.artemis.io.Feature f : features)
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ String id = GeneUtils.getUniqueName(gffFeature);
+ if( (id.startsWith("PF3D7_0200100") || id.startsWith("PF3D7_0200500.1:exon:2")) &&
+ f.getKey().getKeyString().equals("CDS"))
+ assertTrue("Stop codon "+id, !validate.hasValidStop(f)); // not valid
+ else
+ assertTrue("Stop codon "+id, validate.hasValidStop(f));
+ }
+ }
+ catch (OutOfRangeException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ catch (NoSequenceException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Test for internal stop codons
+ */
+ @Test
+ public void testGFFInternalStop()
+ {
+ try
+ {
+ final Entry entry = Utils.getEntry("/data/test_boundary.gff.gz");
+ final FeatureVector features = entry.getAllFeatures();
+ final EntryGroup egrp = new SimpleEntryGroup();
+ egrp.add(new uk.ac.sanger.artemis.Entry(entry));
+ ValidateFeature validate = new ValidateFeature(egrp);
+
+ for (uk.ac.sanger.artemis.io.Feature f : features)
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ String id = GeneUtils.getUniqueName(gffFeature);
+ assertTrue("Internal stop codon "+id, !validate.isInternalStops(f));
+ }
+ }
+ catch (OutOfRangeException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ catch (NoSequenceException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/test/uk/ac/sanger/artemis/plot/UserPlotTest.java b/test/uk/ac/sanger/artemis/plot/UserPlotTest.java
new file mode 100644
index 0000000..461b481
--- /dev/null
+++ b/test/uk/ac/sanger/artemis/plot/UserPlotTest.java
@@ -0,0 +1,129 @@
+/*
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.plot.LineAttributes;
+import uk.ac.sanger.artemis.plot.UserDataAlgorithm;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.io.Utils;
+
+import uk.ac.sanger.artemis.sequence.Strand;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import junit.framework.Assert;
+import org.junit.Test;
+
+public class UserPlotTest
+{
+ @Test
+ /**
+ * Base position user plot.
+ */
+ public void basePosition()
+ {
+ URL gffURL = UserPlotTest.class.getResource("/data/plot/base_position.plot.gz");
+ EntryGroup entryGrp = Utils.getEntryGroup("/data/MAL1.embl.gz");
+ final Strand fwdStrand = entryGrp.getBases ().getForwardStrand ();
+ final FileDocument doc = new FileDocument (new File(gffURL.getFile()));
+
+ try
+ {
+ final UserDataAlgorithm alg = new UserDataAlgorithm (fwdStrand, doc, false);
+ float [] values = new float [alg.getValueCount()];
+ int start = 278;
+ alg.getValues(start, start+10, values);
+ assertEquals("Number of plots",values.length,6);
+ assertTrue("Value of plot 1 at base position "+start,values[0]==804.99f);
+ }
+ catch(IOException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ }
+
+ @Test
+ /**
+ * Base position (with labels) user plot.
+ */
+ public void labelBasePosition()
+ {
+ URL gffURL = UserPlotTest.class.getResource("/data/plot/base_position_labels.plot.gz");
+ EntryGroup entryGrp = Utils.getEntryGroup("/data/MAL1.embl.gz");
+ final Strand fwdStrand = entryGrp.getBases ().getForwardStrand ();
+ final FileDocument doc = new FileDocument (new File(gffURL.getFile()));
+
+ try
+ {
+ final UserDataAlgorithm alg = new UserDataAlgorithm (fwdStrand, doc, false);
+ final LineAttributes lines[] = alg.getLineAttributes();
+ assertEquals("Number of lines",lines.length,6);
+ assertEquals("Number of plots",lines[0].getLabel(),"lab1");
+
+ float [] values = new float [alg.getValueCount()];
+ int start = 278;
+ alg.getValues(start, start+10, values);
+ assertEquals("Number of plots",values.length,6);
+ assertTrue("Value of plot 1 at base position "+start,values[0]==804.99f);
+ }
+ catch(IOException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ }
+
+ @Test
+ /**
+ * Indexed (tabix) user plot.
+ */
+ public void indexed()
+ {
+ URL gffURL = UserPlotTest.class.getResource("/data/plot/index_tab_sorted.plot.gz");
+ EntryGroup entryGrp = Utils.getEntryGroup("/data/MAL1.embl.gz");
+ final Strand fwdStrand = entryGrp.getBases ().getForwardStrand ();
+ final FileDocument doc = new FileDocument (new File(gffURL.getFile()));
+ try
+ {
+ final UserDataAlgorithm alg = new UserDataAlgorithm (fwdStrand, doc, false);
+
+ alg.readIndexValues(true, entryGrp.getSequenceEntry(), 1, 120);
+ float [] values = new float [alg.getValueCount()];
+
+ int start = 20;
+ alg.getValues(start, start+1, values);
+ assertEquals("Number of plots",values.length,6);
+ assertTrue("Value of plot 5 at base position "+start+"="+values[5],values[5]==(234.f/2.f));
+
+ alg.getValues(start, start+4, values);
+ assertTrue("Value of plot 5 at base position "+start+"="+values[5],values[5]==(234.f/5.f));
+ }
+ catch(IOException e)
+ {
+ Assert.fail(e.getMessage());
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/.project b/uk/ac/sanger/artemis/.project
new file mode 100644
index 0000000..6c6d444
--- /dev/null
+++ b/uk/ac/sanger/artemis/.project
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>artemis</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ </buildSpec>
+ <natures>
+ </natures>
+</projectDescription>
diff --git a/uk/ac/sanger/artemis/Action.java b/uk/ac/sanger/artemis/Action.java
new file mode 100644
index 0000000..d3a3635
--- /dev/null
+++ b/uk/ac/sanger/artemis/Action.java
@@ -0,0 +1,105 @@
+/* Action.java
+ *
+ * created: Tue Sep 17 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Action.java,v 1.2 2008-01-28 16:27:54 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+
+/**
+ * An Action is anything done by the user that causes the state of the data
+ * in Artemis to change. It is implemented as Vector of uk.ac.sanger.artemis.ChangeEvent
+ * objects. For example an Action might be the result of the user doing
+ * "Trim to Met". In that case there will be one Action that contains a
+ * FeatureChangeEvent for each Feature that was trimmed.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: Action.java,v 1.2 2008-01-28 16:27:54 tjc Exp $
+ **/
+
+public class Action
+{
+ /**
+ * Delegate.
+ **/
+ private final ChangeEventVector change_vector = new ChangeEventVector ();
+
+ /**
+ * Create a new, empty Action.
+ **/
+ public Action ()
+ {
+ }
+
+ /**
+ * Returns true if and only if nothing has happened during this action.
+ **/
+ public boolean isEmpty ()
+ {
+ if (change_vector.size () == 0)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Add a new ChangeEvent to this Action.
+ **/
+ public void addChangeEvent (final ChangeEvent change_event)
+ {
+ if (change_event instanceof FeatureChangeEvent)
+ addFeatureChangeEvent ((FeatureChangeEvent) change_event);
+ else if (change_event instanceof EntryChangeEvent)
+ addEntryChangeEvent ((EntryChangeEvent) change_event);
+ else
+ throw new Error ("internal error - unknown event type: " +
+ change_event);
+ }
+
+ /**
+ * Add a new FeatureChangeEvent to this Action.
+ **/
+ public void addFeatureChangeEvent (final FeatureChangeEvent
+ feature_change_event)
+ {
+ if (feature_change_event.featureHasChanged ())
+ change_vector.add (feature_change_event);
+ }
+
+ /**
+ * Add a new EntryChangeEvent to this Action.
+ **/
+ public void addEntryChangeEvent (final EntryChangeEvent entry_change_event)
+ {
+ change_vector.add (entry_change_event);
+ }
+
+ /**
+ * Return a ChangeEventVector containing all the ChangeEvents that occured
+ * during this Action.
+ **/
+ public ChangeEventVector getChangeEvents ()
+ {
+ return change_vector;
+ }
+}
diff --git a/uk/ac/sanger/artemis/ActionController.java b/uk/ac/sanger/artemis/ActionController.java
new file mode 100644
index 0000000..506f13b
--- /dev/null
+++ b/uk/ac/sanger/artemis/ActionController.java
@@ -0,0 +1,372 @@
+/* ActionController.java
+ *
+ * created: Tue Sep 17 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ActionController.java,v 1.6 2008-01-30 09:15:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JMenuItem;
+
+import uk.ac.sanger.artemis.sequence.SequenceChangeEvent;
+import uk.ac.sanger.artemis.sequence.SequenceChangeListener;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+import uk.ac.sanger.artemis.io.OutOfDateException;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+
+
+/**
+ * This class is maintains a Vector of Action objects to allow Undo.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ActionController.java,v 1.6 2008-01-30 09:15:31 tjc Exp $
+ **/
+
+public class ActionController
+ implements EntryGroupChangeListener, EntryChangeListener,
+ FeatureChangeListener, SequenceChangeListener
+{
+ /**
+ * The current Action. If null we aren't currently in an Action, if
+ * non-null we are in an Action.
+ **/
+ private Action current_action = null;
+
+ /* Storage for Actions. **/
+ private ActionVector undo_action_vector = new ActionVector ();
+ private ActionVector redo_action_vector = new ActionVector ();
+
+ private List<JMenuItem> undo;
+ private List<JMenuItem> redo;
+
+ /**
+ * Note the start of an action. Create a new Action and add all
+ * ChangeEvents to it until the next call to endAction().
+ **/
+ public void startAction ()
+ {
+ if (Options.getOptions ().getUndoLevels () == 0)
+ return; // undo disabled
+
+ if (current_action == null)
+ current_action = new Action ();
+ else
+ {
+ current_action = null;
+ throw new Error ("internal error - ActionController.startAction() " +
+ "called twice");
+ }
+ }
+
+ /**
+ * Finish an Action and make a note of the completed Action.
+ **/
+ public void endAction ()
+ {
+ if (Options.getOptions ().getUndoLevels () == 0)
+ return; // undo disabled
+
+ if (current_action == null) {
+//
+// believed to cause problems with FeatureList update if thrown...
+//
+// throw new Error ("internal error - in ActionController.endAction() " +
+// "no Action in progress");
+ }
+ else
+ {
+ if (!current_action.isEmpty ())
+ undo_action_vector.add (current_action);
+ current_action = null;
+ }
+ setEnabledMenuItems();
+ }
+
+ /**
+ * Discard all undo information.
+ **/
+ private void discardUndoRedo ()
+ {
+ undo_action_vector = new ActionVector();
+ redo_action_vector = new ActionVector();
+ setEnabledMenuItems();
+ }
+
+ /**
+ * Return true if and only if we can can undo() successfully at least once.
+ **/
+ public boolean canUndo ()
+ {
+ if (undo_action_vector.size () == 0)
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Undo the last Action then return true. If there was no last Action
+ * return false.
+ **/
+ public boolean undo ()
+ {
+ Action action = doLastAction(undo_action_vector, true);
+ if(action == null)
+ return false;
+
+ redo_action_vector.add(action);
+ setEnabledMenuItems();
+ return true;
+ }
+
+ /**
+ * Redo the last Action then return true. If there was no last Action
+ * return false.
+ **/
+ public boolean redo ()
+ {
+ Action action = doLastAction(redo_action_vector, false);
+ if(action == null)
+ return false;
+
+ undo_action_vector.add(action);
+ setEnabledMenuItems();
+ return true;
+ }
+
+ /**
+ * Enable undo/redo menu if they contain Action's
+ */
+ private void setEnabledMenuItems()
+ {
+ if(undo != null)
+ {
+ for(int i=0; i<undo.size(); i++)
+ {
+ JMenuItem undo_item = (JMenuItem) undo.get(i);
+ if(undo_action_vector.size()>0)
+ undo_item.setEnabled(true);
+ else
+ undo_item.setEnabled(false);
+ }
+ }
+
+ if(redo != null)
+ {
+ for(int i=0; i<redo.size(); i++)
+ {
+ JMenuItem redo_item = (JMenuItem) redo.get(i);
+ if(redo_action_vector.size()>0)
+ redo_item.setEnabled(true);
+ else
+ redo_item.setEnabled(false);
+ }
+ }
+ }
+
+ /**
+ * Undo / redo the last Action then return true. If there was no last Action
+ * return false.
+ * @param action_vector carry out last action from list
+ * @param isUndo if true this is an undo action, otherwise this is
+ * a redo action
+ * @return
+ */
+ private Action doLastAction(final ActionVector action_vector,
+ final boolean isUndo)
+ {
+ if (action_vector.size () == 0)
+ return null;
+
+ final Action temp_current_action = current_action;
+
+ // discard the in-progress Action (if any) and throw an exception below
+ current_action = null;
+
+ try
+ {
+ final Action last_action = action_vector.removeAndReturnLast ();
+ if(last_action.isEmpty())
+ return null;
+
+ final ChangeEventVector last_action_change_events =
+ last_action.getChangeEvents ();
+
+ for(int i = last_action_change_events.size() - 1 ; i >= 0 ; --i)
+ {
+ final int index;
+ if(isUndo)
+ index = i;
+ else
+ index = last_action_change_events.size() - 1 - i;
+
+ final ChangeEvent this_event =
+ last_action_change_events.elementAt (index);
+
+ if (this_event instanceof FeatureChangeEvent)
+ doFeatureChange ((FeatureChangeEvent) this_event, isUndo);
+ else if (this_event instanceof EntryChangeEvent)
+ doEntryChange ((EntryChangeEvent) this_event, isUndo);
+ else
+ throw new Error ("internal error - unknown event type: " +
+ this_event);
+ }
+
+ if (temp_current_action != null) {
+ // throw exception after undo so that undo still works.
+ throw new Error ("internal error - in ActionController.undo() " +
+ "Action in progress");
+ }
+
+ return last_action;
+ }
+ catch (ArrayIndexOutOfBoundsException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Undo the effect of one FeatureChangeEvent.
+ **/
+ private void doFeatureChange (final FeatureChangeEvent event, final boolean isUndo)
+ {
+ final Feature feature = event.getFeature ();
+
+ try {
+ if (feature.getEntry () != null)
+ {
+ if(isUndo)
+ feature.set (null,
+ event.getOldKey (),
+ event.getOldLocation (),
+ event.getOldQualifiers ());
+ else
+ feature.set (null,
+ event.getNewKey (),
+ event.getNewLocation (),
+ event.getNewQualifiers ());
+ }
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfDateException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Undo the effect of one EntryChangeEvent.
+ **/
+ private void doEntryChange (final EntryChangeEvent event, final boolean isUndo) {
+ try
+ {
+ if(isUndo)
+ {
+ if(event.getType () == EntryChangeEvent.FEATURE_DELETED)
+ event.getEntry ().add (event.getFeature (), true);
+ else if (event.getType () == EntryChangeEvent.FEATURE_ADDED)
+ event.getFeature ().removeFromEntry ();
+ }
+ else
+ {
+ if(event.getType () == EntryChangeEvent.FEATURE_DELETED)
+ event.getFeature ().removeFromEntry ();
+ else if (event.getType () == EntryChangeEvent.FEATURE_ADDED)
+ event.getEntry ().add (event.getFeature (), true);
+ }
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can update the display if entries
+ * are added or deleted.
+ **/
+ public void entryGroupChanged (final EntryGroupChangeEvent event)
+ {
+ switch (event.getType ())
+ {
+ case EntryGroupChangeEvent.ENTRY_DELETED:
+ discardUndoRedo ();
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen for
+ * FEATURE_ADDED and FEATURE_DELETED events so that we can undo them
+ **/
+ public void entryChanged (final EntryChangeEvent event)
+ {
+ if (current_action != null)
+ current_action.addChangeEvent (event);
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events from the Features in this object.
+ * @param event The change event.
+ **/
+ public void featureChanged (FeatureChangeEvent event)
+ {
+ if (current_action != null)
+ current_action.addChangeEvent (event);
+ }
+
+ /**
+ * If the sequence changes all bets are off - discard all Actions.
+ **/
+ public void sequenceChanged (final SequenceChangeEvent event)
+ {
+ discardUndoRedo ();
+ }
+
+ public void addUndoMenu(final JMenuItem undo_item)
+ {
+ if(undo == null)
+ undo = new Vector<JMenuItem>();
+ undo.add(undo_item);
+ setEnabledMenuItems();
+ }
+
+ public void addRedoMenu(final JMenuItem redo_item)
+ {
+ if(redo == null)
+ redo = new Vector<JMenuItem>();
+ redo.add(redo_item);
+ setEnabledMenuItems();
+ }
+}
diff --git a/uk/ac/sanger/artemis/ActionVector.java b/uk/ac/sanger/artemis/ActionVector.java
new file mode 100644
index 0000000..ee31094
--- /dev/null
+++ b/uk/ac/sanger/artemis/ActionVector.java
@@ -0,0 +1,87 @@
+/* ActionVector.java
+ *
+ * created: Tue Sep 17 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ActionVector.java,v 1.1 2004-06-09 09:44:05 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.util.Vector;
+
+/**
+ * A Vector of Action objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ActionVector.java,v 1.1 2004-06-09 09:44:05 tjc Exp $
+ **/
+
+public class ActionVector {
+ /**
+ * Create a new, empty ActionVector.
+ **/
+ public ActionVector () {
+
+ }
+
+ /**
+ * Appends the given Action object to the vector.
+ **/
+ public void add (Action item) {
+ if (vector.size () > 0 &&
+ vector.size () >= Options.getOptions ().getUndoLevels ()) {
+ vector.removeElementAt (0);
+ }
+ vector.addElement (item);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ **/
+ public Action elementAt (int index) {
+ return (Action) vector.elementAt (index);
+ }
+
+ /**
+ * Remove and return the last Action in the Vector.
+ **/
+ public Action removeAndReturnLast ()
+ throws ArrayIndexOutOfBoundsException
+ {
+ final Action return_action = (Action) vector.lastElement ();
+
+ vector.removeElementAt (vector.size() - 1);
+
+ return return_action;
+ }
+
+ /**
+ * Return the size of the Vector.
+ **/
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Delegate.
+ **/
+ final Vector vector = new Vector ();
+}
diff --git a/uk/ac/sanger/artemis/AlignMatch.java b/uk/ac/sanger/artemis/AlignMatch.java
new file mode 100644
index 0000000..a392ad4
--- /dev/null
+++ b/uk/ac/sanger/artemis/AlignMatch.java
@@ -0,0 +1,225 @@
+/* AlignMatch.java
+ *
+ * created: Wed Jul 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/AlignMatch.java,v 1.7 2005-12-13 10:33:29 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+/**
+ * Each object of this class represents a single match from an alignment.
+ *
+ * @author Kim Rutherford
+ * @version $Id: AlignMatch.java,v 1.7 2005-12-13 10:33:29 tjc Exp $
+ **/
+
+public class AlignMatch
+{
+ /** The range of the match in the subject sequence. */
+ private Range subject_sequence_range = null;
+
+ /** The range of the match in the query sequence. */
+ private Range query_sequence_range = null;
+
+ /** The score that was passed to the constructor. */
+ private int score = -1;
+
+ /**
+ * The percent identity that was passed to the constructor.
+ **/
+ private int percent_id = -1;
+
+ /**
+ * true if and only if the query hits the reverse complement of the subject
+ **/
+ private boolean rev_match;
+
+ private int match_length;
+
+ /**
+ * Create a new AlignMatch object.
+ * @param rev_match true if and only if the query hits the reverse
+ * complement of the subject.
+ * @param score the score for this match, which should be -1 if and only if
+ * this match has no score. The parameter must be >= -1.
+ **/
+ public AlignMatch(final Range subject_sequence_range,
+ final Range query_sequence_range,
+ final boolean rev_match,
+ final int score,
+ final int percent_id)
+ {
+ this.subject_sequence_range = subject_sequence_range;
+ this.query_sequence_range = query_sequence_range;
+ this.rev_match = rev_match;
+ this.score = score;
+ this.percent_id = percent_id;
+
+ match_length = Math.abs(getSubjectSequenceStart() -
+ getSubjectSequenceEnd());
+ }
+
+ public static AlignMatch copy(AlignMatch m)
+ {
+ return new AlignMatch(m.subject_sequence_range,
+ m.query_sequence_range,
+ m.rev_match,
+ m.score,
+ m.percent_id);
+ }
+
+ public int getLength()
+ {
+ return match_length;
+ }
+
+ /**
+ * Return the start(base) of the match in the subject sequence.
+ **/
+ public int getSubjectSequenceStart()
+ {
+ return subject_sequence_range.getStart();
+ }
+
+ /**
+ * Return the end(base) of the match in the subject sequence.
+ **/
+ public int getSubjectSequenceEnd()
+ {
+ return subject_sequence_range.getEnd();
+ }
+
+ /**
+ * Return the start(base) of the match in the query sequence.
+ **/
+ public int getQuerySequenceStart()
+ {
+ if(rev_match)
+ return query_sequence_range.getEnd();
+
+ return query_sequence_range.getStart();
+ }
+
+ /**
+ * Return the end(base) of the match in the query sequence.
+ **/
+ public int getQuerySequenceEnd()
+ {
+ if(rev_match)
+ return query_sequence_range.getStart();
+
+ return query_sequence_range.getEnd();
+ }
+
+ /**
+ * Return the Range of the match in the subject sequence.
+ **/
+ public Range getSubjectSequenceRange()
+ {
+ return subject_sequence_range;
+ }
+
+ /**
+ * Return the Range of the match in the query sequence.
+ **/
+ public Range getQuerySequenceRange()
+ {
+ return query_sequence_range;
+ }
+
+ /**
+ * Set the range for either the query or the subject match. This
+ * is used when flipping contigs round.
+ * @param start
+ * @param end
+ * @param subject
+ */
+ public void setRange(final int start, final int end,
+ boolean subject, boolean flip)
+ {
+ try
+ {
+ if(subject)
+ {
+ if(start < end)
+ this.subject_sequence_range = new Range(start, end);
+ else
+ this.subject_sequence_range = new Range(end, start);
+ }
+ else
+ {
+ if(start < end)
+ this.query_sequence_range = new Range(start, end);
+ else
+ this.query_sequence_range = new Range(end, start);
+ }
+ }
+ catch(OutOfRangeException ex)
+ {
+ ex.printStackTrace();
+ }
+
+ if(flip)
+ this.rev_match = !this.rev_match;
+ }
+
+ /**
+ * Returns true if and only if the query hits the reverse complement of the
+ * subject.
+ **/
+ public boolean isRevMatch()
+ {
+ return rev_match;
+ }
+
+ /**
+ * Return the score of this match.
+ **/
+ public int getScore()
+ {
+ return score;
+ }
+
+ /**
+ * Return the percent identity of this match.
+ **/
+ public int getPercentID()
+ {
+ return percent_id;
+ }
+
+ /**
+ * Return true if and only if this is a self match(ie query start ==
+ * subject start && query end == subject end)
+ **/
+ public boolean isSelfMatch()
+ {
+ if(getQuerySequenceStart() == getSubjectSequenceStart() &&
+ getQuerySequenceEnd() == getSubjectSequenceEnd())
+ return true;
+ else
+ return false;
+ }
+}
diff --git a/uk/ac/sanger/artemis/AlignMatchVector.java b/uk/ac/sanger/artemis/AlignMatchVector.java
new file mode 100644
index 0000000..86dc474
--- /dev/null
+++ b/uk/ac/sanger/artemis/AlignMatchVector.java
@@ -0,0 +1,126 @@
+/* AlignMatchVector.java
+ *
+ * created: Sat Jun 17 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/AlignMatchVector.java,v 1.3 2008-06-26 09:38:13 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.FastVector;
+
+import java.util.Comparator;
+
+/**
+ * This class is a Vector of AlignMatch objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: AlignMatchVector.java,v 1.3 2008-06-26 09:38:13 tjc Exp $
+ **/
+
+public class AlignMatchVector {
+ /**
+ * Create a new (empty) AlignMatchVector object.
+ **/
+ public AlignMatchVector () {
+
+ }
+
+ /**
+ * Appends the given AlignMatch object to the vector if and only if it
+ * isn't already in the vector.
+ **/
+ public void addElement (AlignMatch item) {
+ vector.add (item);
+ }
+
+ /**
+ * Appends the given AlignMatch object to the vector if and only if it
+ * isn't already in the vector. (same as addElement ()).
+ **/
+ public void add (AlignMatch item) {
+ addElement (item);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ **/
+ public AlignMatch elementAt (int index) {
+ return (AlignMatch) vector.get(index);
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public boolean remove (AlignMatch item) {
+ return vector.remove (item);
+ }
+
+ /**
+ * Return true if this object contains the given AlignMatch.
+ **/
+ public boolean contains (AlignMatch item) {
+ return vector.contains(item);
+ }
+
+ /**
+ * Performs the same function as Vector.removeAllElements ()
+ **/
+ public void removeAllElements () {
+ vector.clear ();
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public int indexOf (AlignMatch item) {
+ return vector.indexOf (item);
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ **/
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Sort this vector.
+ * @param cmp The returned vector will be sorted with this Comparator.
+ **/
+ public void sort (final Comparator cmp) {
+ vector = vector.sort (cmp);
+ }
+
+ /**
+ * Create a new AlignMatchVector with the same contents as this one.
+ **/
+ public Object clone () {
+ final AlignMatchVector return_vector = new AlignMatchVector ();
+ return_vector.vector = (FastVector) vector.clone ();
+ return return_vector;
+ }
+
+ /**
+ * Storage for AlignMatch objects.
+ **/
+ private FastVector vector = new FastVector ();
+}
diff --git a/uk/ac/sanger/artemis/AlignmentSelectionChangeEvent.java b/uk/ac/sanger/artemis/AlignmentSelectionChangeEvent.java
new file mode 100644
index 0000000..b2cf507
--- /dev/null
+++ b/uk/ac/sanger/artemis/AlignmentSelectionChangeEvent.java
@@ -0,0 +1,52 @@
+/* AlignmentSelectionChangeEvent.java
+ *
+ * created: Tue Feb 13 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/AlignmentSelectionChangeEvent.java,v 1.1 2004-06-09 09:44:08 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * Events of this type are send from AlignmentViewer objects whenever the list
+ * of selected/highlighted hits changes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: AlignmentSelectionChangeEvent.java,v 1.1 2004-06-09 09:44:08 tjc Exp $
+ **/
+
+public class AlignmentSelectionChangeEvent extends java.util.EventObject {
+ /**
+ * Create a new SelectionChangeEvent object.
+ * @param source The source of the event.
+ * @param selection The selected AlignMatch objects after the change.
+ **/
+ public AlignmentSelectionChangeEvent (final Object source,
+ final AlignMatchVector selection) {
+ super (source);
+ this.selected_matches = selection;
+ }
+
+ /**
+ * The selected matches.
+ **/
+ private AlignMatchVector selected_matches = null;
+}
diff --git a/uk/ac/sanger/artemis/AlignmentSelectionChangeListener.java b/uk/ac/sanger/artemis/AlignmentSelectionChangeListener.java
new file mode 100644
index 0000000..7ed651d
--- /dev/null
+++ b/uk/ac/sanger/artemis/AlignmentSelectionChangeListener.java
@@ -0,0 +1,39 @@
+/* AlignmentSelectionChangeListener.java
+ *
+ * created: Tue Feb 13 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/AlignmentSelectionChangeListener.java,v 1.1 2004-06-09 09:44:09 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * This interface is implemented by those classes that need to know when the
+ * AlignmentSelection changes. These events are generated by AlignmentViewer
+ * objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: AlignmentSelectionChangeListener.java,v 1.1 2004-06-09 09:44:09 tjc Exp $
+ **/
+
+public interface AlignmentSelectionChangeListener {
+ public void alignmentSelectionChanged (final AlignmentSelectionChangeEvent e);
+}
diff --git a/uk/ac/sanger/artemis/BlastM8ComparisonData.java b/uk/ac/sanger/artemis/BlastM8ComparisonData.java
new file mode 100644
index 0000000..9de9e3b
--- /dev/null
+++ b/uk/ac/sanger/artemis/BlastM8ComparisonData.java
@@ -0,0 +1,153 @@
+/* BlastM8ComparisonData.java
+ *
+ * created: Tue Apr 30 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/BlastM8ComparisonData.java,v 1.2 2007-02-27 10:24:32 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * This class implements the ComparisonData interface for blastall -m 8
+ * output.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: BlastM8ComparisonData.java,v 1.2 2007-02-27 10:24:32 tjc Exp $
+ **/
+
+public class BlastM8ComparisonData extends SimpleComparisonData {
+ /**
+ * Create a new BlastM8ComparisonData by reading from the given
+ * LinePushBackReader.
+ **/
+ public BlastM8ComparisonData (final LinePushBackReader stream)
+ throws IOException {
+ super (stream);
+ }
+
+ /**
+ * Create a new, empty instance of BlastM8ComparisonData.
+ **/
+ public BlastM8ComparisonData () {
+
+ }
+
+ /**
+ * Returns a new, empty instance of this type of object;
+ **/
+ protected SimpleComparisonData getNewSimpleComparisonData () {
+ return new BlastM8ComparisonData ();
+ }
+
+
+ /**
+ * Make an AlignMatch object from the given String.
+ **/
+ private static AlignMatch makeMatchFromStringStatic (String line)
+ throws IOException {
+ if (line.trim ().length () == 0 ||
+ line.startsWith ("#")) {
+ return null;
+ }
+
+ // allow empty columns
+ int index;
+ while((index = line.indexOf("\t\t")) > -1)
+ line = line.substring(0, index) + "\t \t" +
+ line.substring(index+2);
+
+ final StringTokenizer tokenizer = new StringTokenizer (line, "\t");
+
+ if (tokenizer.countTokens () != 12) {
+ final String message = "while reading blast -m 8 data: " +
+ "not enough fields in this line: " + line;
+ throw new ComparisonDataParseException (message);
+ }
+
+ // throw away the query name
+ tokenizer.nextToken ();
+ // throw away the subject name
+ tokenizer.nextToken ();
+
+ final String percent_ident_token = tokenizer.nextToken ();
+ final String score_token = tokenizer.nextToken ();
+
+ // dunno what it is - throw it away
+ tokenizer.nextToken ();
+ // see previous comment
+ tokenizer.nextToken ();
+ final String q_start_token = tokenizer.nextToken ();
+ final String q_end_token = tokenizer.nextToken ();
+ final String s_start_token = tokenizer.nextToken ();
+ final String s_end_token = tokenizer.nextToken ();
+
+
+ try {
+ final int score = Integer.valueOf (score_token).intValue ();
+ final int percent_ident =
+ (int)(Float.valueOf (percent_ident_token).floatValue ());
+ final int q_start = Integer.valueOf (q_start_token).intValue ();
+ final int q_end = Integer.valueOf (q_end_token).intValue ();
+ final int s_start = Integer.valueOf (s_start_token).intValue ();
+ final int s_end = Integer.valueOf (s_end_token).intValue ();
+
+ return makeAlignMatch (s_start, s_end, q_start, q_end, score,
+ percent_ident);
+ } catch (NumberFormatException e) {
+ throw new IOException ("while reading blast -m 8 data: " +
+ "failed to parse a number from this string: " +
+ e.getMessage ());
+ }
+ }
+
+ /**
+ * Make an AlignMatch object from the given String. The String must be in
+ * a format appropriate for this object.
+ **/
+ protected AlignMatch makeMatchFromString (final String line)
+ throws IOException {
+ return makeMatchFromStringStatic (line);
+ }
+
+ /**
+ * Returns true if and only if the given line is in the correct format for
+ * this type of ComparisonData. This should be as strict as possible.
+ **/
+ public static boolean formatCorrect (final String line) {
+ if (line.startsWith ("# BLASTN 2") || line.startsWith ("# TBLASTX 2")) {
+ return true;
+ } else {
+ try {
+ makeMatchFromStringStatic (line);
+ } catch (IOException e) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/ChangeEvent.java b/uk/ac/sanger/artemis/ChangeEvent.java
new file mode 100644
index 0000000..46fcd33
--- /dev/null
+++ b/uk/ac/sanger/artemis/ChangeEvent.java
@@ -0,0 +1,46 @@
+/* ChangeEvent.java
+ *
+ * created: Sat Oct 17 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ChangeEvent.java,v 1.1 2004-06-09 09:44:11 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+/**
+ * A "Change" event is delivered when the state of one of the objects in the
+ * uk.ac.sanger.artemis package changes state.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ChangeEvent.java,v 1.1 2004-06-09 09:44:11 tjc Exp $
+ **/
+
+abstract public class ChangeEvent extends java.util.EventObject
+{
+ /**
+ * Create a new ChangeEvent object.
+ * @param source The object that the ChangeEvent occurred upon.
+ **/
+ public ChangeEvent(Object source)
+ {
+ super(source);
+ }
+}
diff --git a/uk/ac/sanger/artemis/ChangeEventVector.java b/uk/ac/sanger/artemis/ChangeEventVector.java
new file mode 100644
index 0000000..981ba4d
--- /dev/null
+++ b/uk/ac/sanger/artemis/ChangeEventVector.java
@@ -0,0 +1,70 @@
+/* ChangeEventVector.java
+ *
+ * created: Tue Sep 17 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ChangeEventVector.java,v 1.1 2004-06-09 09:44:12 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.util.Vector;
+
+/**
+ * A Vector of ChangeEvent objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ChangeEventVector.java,v 1.1 2004-06-09 09:44:12 tjc Exp $
+ **/
+
+public class ChangeEventVector {
+ /**
+ * Create a new, empty ChangeEventVector.
+ **/
+ public ChangeEventVector () {
+
+ }
+
+ /**
+ * Appends the given ChangeEvent object to the vector.
+ **/
+ public void add (ChangeEvent item) {
+ vector.addElement (item);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ **/
+ public ChangeEvent elementAt (int index) {
+ return (ChangeEvent) vector.elementAt (index);
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ **/
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Delegate.
+ **/
+ final Vector vector = new Vector ();
+}
diff --git a/uk/ac/sanger/artemis/ChangeListener.java b/uk/ac/sanger/artemis/ChangeListener.java
new file mode 100644
index 0000000..f382de4
--- /dev/null
+++ b/uk/ac/sanger/artemis/ChangeListener.java
@@ -0,0 +1,39 @@
+/* ChangeListener.java
+ *
+ * created: Wed Oct 21 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ChangeListener.java,v 1.1 2004-06-09 09:44:13 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * The ChangeListener interface is used to listen for changes in this package.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ChangeListener.java,v 1.1 2004-06-09 09:44:13 tjc Exp $
+ *
+ **/
+
+public interface ChangeListener extends java.util.EventListener {
+
+}
+
diff --git a/uk/ac/sanger/artemis/ClipBoard.java b/uk/ac/sanger/artemis/ClipBoard.java
new file mode 100644
index 0000000..1d42f1f
--- /dev/null
+++ b/uk/ac/sanger/artemis/ClipBoard.java
@@ -0,0 +1,68 @@
+/* ClipBoard.java
+ *
+ * created: Mon Oct 26 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ClipBoard.java,v 1.1 2004-06-09 09:44:14 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+/**
+ * A simple clipboard for Diana which can interact with the system clipboard.
+ * Any Object can be stored in this clipboard but only some will be useful to
+ * other programs.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ClipBoard.java,v 1.1 2004-06-09 09:44:14 tjc Exp $
+ **/
+
+public class ClipBoard {
+ /**
+ * Create a new ClipBoard object.
+ **/
+ public ClipBoard () {
+
+ }
+
+ /**
+ * Set the contents of the clipboard.
+ * @param clip The new clipboard contents.
+ **/
+ public void setClip (Selection clip) {
+ this.clip = clip;
+ }
+
+
+ /**
+ * Return the current contents of the clipboard.
+ **/
+ public Selection getClip () {
+ return clip;
+ }
+
+
+ /**
+ * Holds whatever was last clipped.
+ **/
+ Selection clip;
+}
+
+
diff --git a/uk/ac/sanger/artemis/ComparisonData.java b/uk/ac/sanger/artemis/ComparisonData.java
new file mode 100644
index 0000000..b85239c
--- /dev/null
+++ b/uk/ac/sanger/artemis/ComparisonData.java
@@ -0,0 +1,81 @@
+/* ComparisonData.java
+ *
+ * created: Mon Jul 12 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ComparisonData.java,v 1.2 2004-12-14 10:41:42 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.io.Range;
+
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ * Objects that implement this interface provide data (AlignMatch objects)
+ * for the alignment of two sequences.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ComparisonData.java,v 1.2 2004-12-14 10:41:42 tjc Exp $
+ **/
+
+public interface ComparisonData
+{
+ /**
+ * Return an array containing all the AlignMatch objects for this
+ * comparison.
+ **/
+ public AlignMatch[] getMatches();
+
+ /**
+ * Return all the AlignMatch objects in this comparison which overlap
+ * first_seq_range on the first sequence or second_seq_range on the second
+ * sequence.
+ **/
+//public AlignMatch[] getMatchesInRange(final Range first_seq_range,
+// final Range second_seq_range);
+
+ /**
+ * If this object contain valid matches for a comparison between
+ * first_sequence and second_sequence return null (first_sequence is the
+ * subject of the comparison second_sequence is the query). If the
+ * comparison would be valid if the data for the ends of the matches were
+ * swapped, then return a copy of this object with all the matches flipped.
+ * (For now, valid means that none of the matches goes over the end of the
+ * sequence.)
+ * @exception OutOfRangeException Thrown if the data in this object is not
+ * valid for either orientation.
+ **/
+ public ComparisonData flipMatchesIfNeeded(final Bases first_sequence,
+ final Bases second_sequence)
+ throws OutOfRangeException;
+
+ /**
+ * Return the maximum score of all the AlignMatch objects in this object.
+ **/
+ public int getMaximumScore();
+
+ /**
+ * Return the minimum score of all the AlignMatch objects in this object.
+ **/
+ public int getMinimumScore();
+}
diff --git a/uk/ac/sanger/artemis/ComparisonDataFactory.java b/uk/ac/sanger/artemis/ComparisonDataFactory.java
new file mode 100644
index 0000000..2996f66
--- /dev/null
+++ b/uk/ac/sanger/artemis/ComparisonDataFactory.java
@@ -0,0 +1,85 @@
+/* ComparisonDataFactory.java
+ *
+ * created: Thu Jul 15 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999-2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ComparisonDataFactory.java,v 1.1 2004-06-09 09:44:16 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.*;
+
+/**
+ * This class contains the method readComparisonData (), which returns an
+ * appropriate ComparisonData object for a given Document.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ComparisonDataFactory.java,v 1.1 2004-06-09 09:44:16 tjc Exp $
+ **/
+
+public class ComparisonDataFactory {
+ /**
+ * This method creates an appropriate ComparisonData object from a Document.
+ **/
+ static public ComparisonData readComparisonData (Document data_document)
+ throws IOException {
+
+ final Reader in_file = data_document.getReader ();
+
+ final LinePushBackReader pushback_reader =
+ new LinePushBackReader (in_file);
+
+ final String line = pushback_reader.readLine ();
+
+ if (line == null) {
+ throw new IOException ("End of file while reading from: " +
+ data_document);
+ }
+
+ pushback_reader.pushBack (line);
+
+ if (MSPcrunchComparisonData.formatCorrect (line)) {
+ return new MSPcrunchComparisonData (pushback_reader);
+ } else {
+ if (SSAHAComparisonData.formatCorrect (line)) {
+ return new SSAHAComparisonData (pushback_reader);
+ } else {
+ if (BlastM8ComparisonData.formatCorrect (line)) {
+ return new BlastM8ComparisonData (pushback_reader);
+ } else {
+ if (MegaBlastComparisonData.formatCorrect (line)) {
+ return new MegaBlastComparisonData (pushback_reader);
+ } else {
+// if (tokenizer.countTokens () < 8) {
+// return new MUMmerComparisonData (pushback_reader);
+// } else {
+ throw new IOException ("cannot understand the comparison file format");
+// }
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/ComparisonDataParseException.java b/uk/ac/sanger/artemis/ComparisonDataParseException.java
new file mode 100644
index 0000000..ca202fb
--- /dev/null
+++ b/uk/ac/sanger/artemis/ComparisonDataParseException.java
@@ -0,0 +1,46 @@
+/* ComparisonDataParseException.java
+ *
+ * created: Mon Jul 19 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ComparisonDataParseException.java,v 1.1 2004-06-09 09:44:17 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.io.IOException;
+
+/**
+ * This exception is thrown if an input file containing comparison data can't
+ * be read.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ComparisonDataParseException.java,v 1.1 2004-06-09 09:44:17 tjc Exp $
+ **/
+
+public class ComparisonDataParseException extends IOException {
+ /**
+ * Create a new ComparisonDataParseException object with the given message.
+ **/
+ public ComparisonDataParseException (final String message) {
+ super (message);
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/Entry.java b/uk/ac/sanger/artemis/Entry.java
new file mode 100644
index 0000000..76ce2ae
--- /dev/null
+++ b/uk/ac/sanger/artemis/Entry.java
@@ -0,0 +1,1256 @@
+/* Entry.java
+ *
+ * created: Sun Oct 11 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Entry.java,v 1.11 2008-06-20 10:00:25 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.EmblStreamFeature;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.EmblDocumentEntry;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.IndexedGFFDocumentEntry;
+import uk.ac.sanger.artemis.io.PartialSequence;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.LocationParseException;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.DocumentEntryFactory;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+
+import java.util.NoSuchElementException;
+import java.util.Vector;
+import java.io.IOException;
+import java.io.File;
+
+
+/**
+ * This class is a wrapper for the io.Entry class which contains the state
+ * and handles the events needed for editing one embl entry in Diana. The
+ * state includes an EMBL.Entry object and a list of EntryChange listeners
+ * and FeatureChange listeners. Other objects can register as listeners for
+ * changes to the entry. For that reason, all changes to the embl.Entry
+ * objects should go through this class. (see ChangeEvent for details of the
+ * possible events.)
+ *
+ * @author Kim Rutherford
+ * @version $Id: Entry.java,v 1.11 2008-06-20 10:00:25 tjc Exp $
+ **/
+
+public class Entry implements FeatureChangeListener, Selectable
+{
+
+ /**
+ * The embl.Entry object that was passed to the constructor
+ **/
+ private uk.ac.sanger.artemis.io.Entry embl_entry;
+
+ /**
+ * A vector of those objects listening for entry change events.
+ **/
+ final private Vector<ChangeListener> entry_listener_list = new Vector<ChangeListener>();
+
+ /**
+ * A vector of those objects listening for feature change events.
+ **/
+ final private Vector<ChangeListener> feature_listener_list = new Vector<ChangeListener>();
+
+ /**
+ * This is the Bases reference that was passed to the constructor.
+ **/
+ /*final*/ private Bases bases;
+
+ /**
+ * Create a new Entry object.
+ * @param bases The Bases object which contains the Strand objects that will
+ * be used by the features of this Entry.
+ * @param embl_entry a reference to an embl.Entry object containing the
+ * underlying data for new object
+ * @exception OutOfRangeException Thrown if one of the features in
+ * embl_entry is out of range of the Bases object.
+ **/
+ public Entry(final Bases bases,
+ final uk.ac.sanger.artemis.io.Entry embl_entry)
+ throws OutOfRangeException
+ {
+ this.embl_entry = embl_entry;
+ this.bases = bases;
+
+ checkLocations();
+ createDianaFeatures();
+ }
+
+ /**
+ * Create a new Entry object from a uk.ac.sanger.artemis.io.Entry
+ * object. A new Bases object will be created for the new Entry.
+ * @param embl_entry a reference to an embl.Entry object containing the
+ * underlying data for new object
+ * @exception OutOfRangeException Thrown if one of the features in
+ * embl_entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if embl_entry contains no sequence.
+ **/
+ public Entry(final uk.ac.sanger.artemis.io.Entry embl_entry)
+ throws OutOfRangeException, NoSequenceException
+ {
+ this.embl_entry = embl_entry;
+
+ if(embl_entry.getSequence() == null ||
+ embl_entry.getSequence().length() == 0)
+ throw new NoSequenceException();
+ else
+ this.bases = new Bases(embl_entry.getSequence());
+
+ checkLocations();
+ createDianaFeatures();
+ }
+
+
+ /**
+ * Returns true if and only if this entry is read only.
+ **/
+ public boolean isReadOnly()
+ {
+ return getEMBLEntry().isReadOnly();
+ }
+
+ /**
+ * Save the changes to this Entry back to where the Entry came from. If
+ * this object is read only a ReadOnlyException will be thrown.
+ * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
+ * ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the
+ * same format it was created, otherwise it will be saved in the given
+ * format.
+ * @exception EntryInformationException Thrown if the destination type
+ * cannot contain the Key, Qualifier or Key/Qualifier combinations of one
+ * of the features in this Entry.
+ * @exception IOException Thrown if an IO error occurs while saving. One
+ * possibility is ReadOnlyException.
+ **/
+ public void save(final int destination_type)
+ throws IOException, EntryInformationException
+ {
+ if(destination_type == DocumentEntryFactory.ANY_FORMAT)
+ getEMBLEntry().save();
+ else
+ {
+ if(getEMBLEntry() instanceof DocumentEntry)
+ getEMBLEntryAsNewType(getEntryInformation(),
+ destination_type, false).save();
+ else
+ throw new ReadOnlyException("operation cannot be " +
+ "applied to this entry");
+ }
+ }
+
+ /**
+ * Save the changes to this Entry back to where the Entry came from(like
+ * save()) but without any uk.ac.sanger.artemis extensions. If this object is read only
+ * or doesn't support this a ReadOnlyException will be thrown.
+ * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
+ * ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the
+ * same format it was created, otherwise it will be saved in the given
+ * format.
+ * @exception EntryInformationException Thrown if the destination type
+ * cannot contain the Key, Qualifier or Key/Qualifier combinations of one
+ * of the features in this Entry.
+ **/
+ public void saveStandardOnly(final int destination_type)
+ throws IOException,EntryInformationException
+ {
+ if(destination_type == DocumentEntryFactory.ANY_FORMAT)
+ getEMBLEntry().save();
+ else
+ {
+ if(getEMBLEntry() instanceof DocumentEntry)
+ {
+ final EntryInformation entry_information =
+ Options.getDBEntryInformation();
+
+ getEMBLEntryAsNewType(entry_information,
+ destination_type, false).save();
+ }
+ else
+ throw new ReadOnlyException("operation cannot be " +
+ "applied to this entry");
+ }
+ }
+
+ /**
+ * Save the changes to this Entry to the file. If this object is read only
+ * a ReadOnlyException will be thrown. This method only works when the
+ * underlying embl.Entry object is a DocumentEntry.
+ * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT,
+ * GFF_FORMAT or ANY_FORMAT. If ANY_FORMAT then the Entry will
+ * be saved in the same format it was created, otherwise it will be saved
+ * in the given format.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys will be quietly thrown away when saving. "Invalid" means
+ * that the key/qualifier is not allowed to occur in the destination type
+ * (probably determined by the default EntryInformation object of the
+ * destination type). If false an EntryInformationException will be
+ * thrown for invalid keys or qualifiers.
+ * @exception EntryInformationException Thrown if force is false and if the
+ * destination type cannot contain the Key, Qualifier or Key/Qualifier
+ * combination of one of the features in this Entry.
+ **/
+ public void save(final File file, final int destination_type,
+ final boolean force)
+ throws IOException, EntryInformationException
+ {
+ final EntryInformation artemis_entry_information;
+
+ if((getEMBLEntry() instanceof DatabaseDocumentEntry ||
+ getEMBLEntry() instanceof GFFDocumentEntry) &&
+ destination_type == DocumentEntryFactory.EMBL_FORMAT)
+ artemis_entry_information = Options.getArtemisEntryInformation();
+ else
+ artemis_entry_information = getEntryInformation();
+
+ save(file, destination_type, force, artemis_entry_information);
+ }
+
+
+ /**
+ * Save the changes to this Entry to the file. If this object is read only
+ * a ReadOnlyException will be thrown. This method only works when the
+ * underlying embl.Entry object is a DocumentEntry.
+ * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT,
+ * GFF_FORMAT or ANY_FORMAT. If ANY_FORMAT then the Entry will
+ * be saved in the same format it was created, otherwise it will be saved
+ * in the given format.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys will be quietly thrown away when saving. "Invalid" means
+ * that the key/qualifier is not allowed to occur in the destination type
+ * (probably determined by the default EntryInformation object of the
+ * destination type). If false an EntryInformationException will be
+ * thrown for invalid keys or qualifiers.
+ * @exception EntryInformationException Thrown if force is false and if the
+ * destination type cannot contain the Key, Qualifier or Key/Qualifier
+ * combination of one of the features in this Entry.
+ **/
+ public void save(final File file, final int destination_type,
+ final boolean force, final EntryInformation artemis_entry_information)
+ throws IOException, EntryInformationException
+ {
+ final FileDocument file_document = new FileDocument(file);
+ final DocumentEntry document_entry =
+ (DocumentEntry)getEMBLEntryAsNewType(artemis_entry_information,
+ destination_type, force);
+ document_entry.save(file_document);
+ }
+
+ /**
+ * Save the changes to this Entry to the file(like save(Document)) but
+ * without any uk.ac.sanger.artemis extensions. If this object is read only a
+ * ReadOnlyException will be thrown. This method only works when the
+ * underlying embl.Entry object is a DocumentEntry.
+ * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
+ * ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the
+ * same format it was created, otherwise it will be saved in the given
+ * format.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys will be quietly thrown away when saving. "Invalid" means
+ * that the key/qualifier is not allowed to occur in the destination type
+ * (probably determined by the default EntryInformation object of the
+ * destination type). If false an EntryInformationException will be
+ * thrown for invalid keys or qualifiers.
+ * @exception EntryInformationException Thrown if force is false and if the
+ * destination type cannot contain the Key, Qualifier or Key/Qualifier
+ * combination of one of the features in this Entry.
+ **/
+ public void saveStandardOnly(final File file,
+ final int destination_type,
+ final boolean force)
+ throws IOException, EntryInformationException
+ {
+ final FileDocument file_document = new FileDocument(file);
+
+ final DocumentEntry document_entry =
+ (DocumentEntry) getEMBLEntryAsNewType(Options.getDBEntryInformation(),
+ destination_type, force);
+ document_entry.save(file_document);
+ }
+
+ /**
+ * Returns true if and only if there have been some changes to this Entry
+ * since the last save.
+ **/
+ public boolean hasUnsavedChanges()
+ {
+ return getEMBLEntry().hasUnsavedChanges();
+ }
+
+ /**
+ * Create a new Entry in the given EntryGroup.
+ * @return A reference to the new Entry.
+ **/
+ public static Entry newEntry(final Bases bases)
+ {
+ final uk.ac.sanger.artemis.io.Entry entry =
+ new EmblDocumentEntry(Options.getArtemisEntryInformation());
+
+ try
+ {
+ return new Entry(bases, entry);
+ }
+ catch(OutOfRangeException e)
+ {
+ // an empty Entry cannot have any out of range features
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return the name of this Entry or null if it has no name.
+ **/
+ public String getName()
+ {
+ return getEMBLEntry().getName();
+ }
+
+ /**
+ * Set the name of this Entry - if possible(the return value will let the
+ * caller know).
+ * @return true if and only if the name was successfully set. Not all
+ * Entry objects can change there name, so it is up to the calling
+ * function to check the return value.
+ **/
+ public boolean setName(final String name)
+ {
+ final String orig_entry_name = getName();
+
+ if(getEMBLEntry().setName(name))
+ {
+ if(orig_entry_name == null || ! name.equals(orig_entry_name))
+ {
+ // now inform the listeners that the name has changed
+ final EntryChangeEvent event =
+ new EntryChangeEvent(name, this, EntryChangeEvent.NAME_CHANGED);
+
+ fireAction(entry_listener_list, event);
+ }
+ return true;
+ }
+ else
+ return false;
+ }
+
+ /**
+ * Return the text of the EMBL header of this Entry or null if there is no
+ * header.
+ **/
+ public String getHeaderText()
+ {
+ return getEMBLEntry().getHeaderText();
+ }
+
+ /**
+ * Set the header of this Entry to be the given text.
+ * @return true if and only if the header was successfully set. Not all
+ * Entry objects can change their header, so it is up to the calling
+ * function to check the return value.
+ * @exception IOException thrown if there is a problem reading the header
+ * from the String - most likely ReadFormatException.
+ **/
+ public boolean setHeaderText(final String new_header)
+ throws IOException
+ {
+ if(getEMBLEntry().setHeaderText(new_header))
+ {
+ final EntryChangeEvent event =
+ new EntryChangeEvent(new_header,
+ this,
+ EntryChangeEvent.HEADER_CHANGED);
+
+ fireAction(entry_listener_list, event);
+
+ return true;
+ }
+ else
+ return false;
+ }
+
+ /**
+ * Return the path of the directory that this entry is in.
+ **/
+ public Document getRootDocument()
+ {
+ if(getEMBLEntry() instanceof DocumentEntry && // XXX FIXME:
+ ((DocumentEntry) getEMBLEntry()).getDocument() != null)
+ {
+ return((DocumentEntry)getEMBLEntry()).getDocument().getParent();
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The non-source key features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ public FeatureVector getFeaturesInRange(Range range)
+ throws OutOfRangeException
+ {
+ final FeatureVector return_features = new FeatureVector();
+ final uk.ac.sanger.artemis.io.FeatureVector embl_features =
+ getEMBLEntry().getFeaturesInRange(range);
+
+// System.err.println("starting getFeaturesInRange()");
+ Feature diana_feature;
+ for(int i = 0 ; i < embl_features.size() ; ++i)
+ {
+ diana_feature = getFeatureOf(embl_features.featureAt(i));
+
+// System.err.println("getFeaturesInRange() - diana_feature:" +
+// diana_feature + " " +
+// embl_features.elementAt(i));
+
+ return_features.add(diana_feature);
+ }
+
+// System.err.println("ending getFeaturesInRange()");
+
+ return return_features;
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects in this
+ * Entry.
+ * @return The non-source key features of this Entry. The
+ * returned object is a copy - changes will not effect the Entry
+ * object itself.
+ **/
+ public FeatureVector getAllFeatures()
+ {
+ final FeatureVector return_features = new FeatureVector();
+ final uk.ac.sanger.artemis.io.FeatureVector embl_features =
+ getEMBLEntry().getAllFeatures();
+
+ Feature diana_feature;
+ for(int i = 0; i < embl_features.size(); ++i)
+ {
+ diana_feature = getFeatureOf(embl_features.featureAt(i));
+ return_features.add(diana_feature);
+ }
+
+ return return_features;
+ }
+
+ public void remove(final Feature feature)
+ throws ReadOnlyException
+ {
+ remove(feature, false);
+ }
+
+ /**
+ * Delete a Feature from this object and the underlying embl.Entry object.
+ * @param feature The Feature to delete.
+ **/
+ public void remove(final Feature feature,
+ final boolean duplicate)
+ throws ReadOnlyException
+ {
+ synchronized(getBases())
+ {
+ // get the embl.Feature object out of the uk.ac.sanger.artemis.Feature object then
+ // remove it
+ if(!getEMBLEntry().remove(feature.getEmblFeature()))
+ throw new Error("internal error - remove failed");
+
+ // now inform the listeners that a deletion has occured
+ final EntryChangeEvent event =
+ new EntryChangeEvent(this, feature, duplicate, EntryChangeEvent.FEATURE_DELETED);
+
+ fireAction(entry_listener_list, event);
+ feature.setEntry(null);
+ }
+ }
+
+ /**
+ * Remove all the features in this Entry.
+ **/
+ public void removeAllFeatures()
+ throws ReadOnlyException
+ {
+ // remove the first feature
+ while(getFeatureCount() > 0)
+ remove(getFeature(0));
+ }
+
+ /**
+ * Add a Feature to the Entry. The new feature will be inserted in order
+ * in the feature vector. The features in the vector are ordered by the
+ * first base of each feature.
+ * @param new_feature The new feature to add. It should not be a member
+ * of the Entry object.
+ * @param force If true then invalid qualifiers will be quietly thrown away
+ * and a feature with invalid keys will not be added. "Invalid" means
+ * that the key/qualifier is non allowed to occur in an Entry of this type
+ * (probably determined by the EntryInformation object of this Entry).
+ * If false an EntryInformationException will be thrown for invalid keys
+ * or qualifiers.
+ * @exception EntryInformationException Thrown force is false if this Entry
+ * cannot contain the Key, Qualifier or Key/Qualifier combination of the
+ * given Feature
+ **/
+ public void add(final Feature new_feature,
+ final boolean force)
+ throws EntryInformationException, OutOfRangeException,
+ ReadOnlyException
+ {
+ add(new_feature, false, force);
+ }
+
+ /**
+ * Add a Feature to the Entry. The new feature will be inserted in order
+ * in the feature vector. The features in the vector are ordered by the
+ * first base of each feature.
+ * @param new_feature The new feature to add. It should not be a member
+ * of the Entry object.
+ * @param force If true then invalid qualifiers will be quietly thrown away
+ * and a feature with invalid keys will not be added. "Invalid" means
+ * that the key/qualifier is non allowed to occur in an Entry of this type
+ * (probably determined by the EntryInformation object of this Entry).
+ * If false an EntryInformationException will be thrown for invalid keys
+ * or qualifiers.
+ * @exception EntryInformationException Thrown force is false if this Entry
+ * cannot contain the Key, Qualifier or Key/Qualifier combination of the
+ * given Feature
+ **/
+ public void add(final Feature new_feature, final boolean duplicate,
+ final boolean force)
+ throws EntryInformationException, OutOfRangeException,
+ ReadOnlyException
+ {
+ try
+ {
+ if(new_feature.getEntry() != null)
+ throw new Error("internal error - Feature has a parent");
+
+ new_feature.setEntry(this);
+
+ final uk.ac.sanger.artemis.io.Feature old_embl_reference =
+ new_feature.getEmblFeature();
+
+ // this call will add the new_feature in the correct place in the
+ // embl.Feature vector of the FeatureTable object
+ final uk.ac.sanger.artemis.io.Feature new_embl_reference;
+
+ if(force)
+ {
+ new_embl_reference =
+ getEMBLEntry().forcedAdd(new_feature.getEmblFeature());
+ }
+ else
+ {
+ new_embl_reference =
+ getEMBLEntry().add(new_feature.getEmblFeature());
+ }
+
+ if(new_embl_reference != old_embl_reference)
+ {
+ new_feature.getEmblFeature().setUserData(null);
+ new_feature.setEmblFeature(new_embl_reference);
+ new_embl_reference.setUserData(new_feature);
+ }
+
+ // now inform the listeners that a addition has occured
+ final EntryChangeEvent event =
+ new EntryChangeEvent(this, new_feature, duplicate,
+ EntryChangeEvent.FEATURE_ADDED);
+
+ fireAction(entry_listener_list, event);
+
+ }
+ catch(EntryInformationException e)
+ {
+ new_feature.setEntry(null);
+ throw e;
+ }
+ }
+
+ /**
+ * Forget all the features in this entry and inform all the EntryChange
+ * listeners that all the features have gone away. The garbage collector
+ * will really dispose of the features later.
+ **/
+ public void dispose()
+ {
+ final FeatureEnumeration feature_enum = features();
+
+ while(feature_enum.hasMoreFeatures())
+ {
+ final Feature current_feature = feature_enum.nextFeature();
+
+ // now inform the listeners that a deletion has occured
+ final EntryChangeEvent event =
+ new EntryChangeEvent(this, current_feature,
+ EntryChangeEvent.FEATURE_DELETED);
+
+ fireAction(entry_listener_list, event);
+
+ current_feature.setEntry(null);
+ }
+ }
+
+ /**
+ * Returns the Feature at the given index in the vector of Features.
+ **/
+ public Feature getFeature(int i)
+ {
+ return getFeatureOf(getEMBLEntry().getFeatureAtIndex(i));
+ }
+
+ /**
+ * Create a new Feature in this Entry with no qualifiers, a default key and
+ * a location of "1..max_base"
+ **/
+ public Feature createFeature()
+ throws ReadOnlyException
+ {
+ final Key new_key;
+ final Location new_location;
+ final QualifierVector new_qualifiers = new QualifierVector();
+
+ try
+ {
+ new_key = getEntryInformation().getDefaultKey();
+ new_location = new Location("1" + ".." + getBases().getLength());
+
+ return createFeature(new_key, new_location, new_qualifiers);
+ }
+ catch(LocationParseException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(EntryInformationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Create and return a new Feature in this Entry with the given key,
+ * location and qualifiers. This method creates a new embl.Feature object
+ * and then wraps it in a uk.ac.sanger.artemis.Feature object and
+ * then inserts it in thisEntry object.
+ * @param new_key The Key to use for the new Feature.
+ * @param new_location The Location to use for the new Feature.
+ * @param new_qualifiers The a vector containing the Qualifier objects to
+ * use for the new Feature.
+ * @exception EntryInformationException Thrown force is false if this Entry
+ * cannot contain a feature with the given Key, Qualifier or
+ * Key/Qualifier combination.
+ **/
+ public Feature createFeature(Key new_key,
+ Location new_location,
+ QualifierVector new_qualifiers)
+ throws EntryInformationException, ReadOnlyException,
+ OutOfRangeException
+ {
+ final uk.ac.sanger.artemis.io.Feature new_embl_feature =
+ getEMBLEntry().createFeature(new_key, new_location, new_qualifiers);
+
+ final Feature new_feature = getFeatureOf(new_embl_feature);
+
+ // now inform the listeners that a addition has occured
+ final EntryChangeEvent event =
+ new EntryChangeEvent(this, new_feature, EntryChangeEvent.FEATURE_ADDED);
+
+ fireAction(entry_listener_list, event);
+ return new_feature;
+ }
+
+ /**
+ * Create and return a new Feature in this Entry with the given key,
+ * location and no qualifiers. This method creates a new embl.Feature
+ * object and then wraps it in a uk.ac.sanger.artemis.Feature object and then inserts it
+ * in this Entry object.
+ * @param new_key The Key to use for the new Feature.
+ * @param new_location The Location to use for the new Feature.
+ **/
+ public Feature createFeature(Key new_key,
+ Location new_location)
+ throws EntryInformationException, ReadOnlyException,
+ OutOfRangeException
+ {
+ final QualifierVector new_qualifiers = new QualifierVector();
+ return createFeature(new_key, new_location, new_qualifiers);
+ }
+
+ /**
+ * Return true if this Entry contains the given Feature.
+ **/
+ public boolean contains(Feature feature)
+ {
+ return getEMBLEntry().contains(feature.getEmblFeature());
+ }
+
+ /**
+ * Return the index of the given Feature in the feature table of this Entry
+ * or -1 if the feature isn't in this Entry.
+ **/
+ public int indexOf(Feature feature)
+ {
+ return getEMBLEntry().indexOf(feature.getEmblFeature());
+ }
+
+ /**
+ * Return the index of a feature in the embl feature table of this entry.
+ * The indices start at zero.
+ * @param feature The Feature to lookup.
+ * @return The index of the feature or -1 if the feature isn't in this
+ * Entry.
+ **/
+ public int getIndexOfFeature(Feature feature)
+ {
+ return getEMBLEntry().indexOf(feature.getEmblFeature());
+ }
+
+ /**
+ * Returns an enumeration of the Feature objects in this Entry. The
+ * returned FeatureEnumeration object will generate all features in this
+ * object in turn. The first item generated is the item at index 0, then
+ * the item at index 1, and so on.
+ **/
+ public FeatureEnumeration features()
+ {
+ return new FeatureEnumerator();
+ }
+
+ /**
+ * An Enumeration of Feature objects.
+ **/
+ public class FeatureEnumerator implements FeatureEnumeration
+ {
+ /**
+ * Create a new FeatureEnumeration that will enumerate the enclosing
+ * Entry object. The Entry object must not be changed while the
+ * enumeration is active.
+ **/
+ public FeatureEnumerator()
+ {
+ feature_enumerator = getEMBLEntry().features();
+ }
+
+ /**
+ * See the FeatureEnumeration interface for details.
+ **/
+ public boolean hasMoreFeatures()
+ {
+ return feature_enumerator.hasMoreFeatures();
+ }
+
+ /**
+ * See the FeatureEnumeration interface for details.
+ **/
+ public Feature nextFeature()
+ throws NoSuchElementException
+ {
+
+ final uk.ac.sanger.artemis.io.Feature this_feature =
+ feature_enumerator.nextFeature();
+
+ return getFeatureOf(this_feature);
+ }
+
+ /**
+ * The Enumeration for the current entry
+ **/
+ private uk.ac.sanger.artemis.io.FeatureEnumeration feature_enumerator;
+ }
+
+ /**
+ * Returns the number of features in this entry.
+ **/
+ public int getFeatureCount()
+ {
+ return getEMBLEntry().getFeatureCount();
+ }
+
+ /**
+ * Return the Bases object that was passed to the constructor.
+ **/
+ public Bases getBases()
+ {
+ return bases;
+ }
+
+ /**
+ * This method translates the start and end of every each Range in every
+ * Location into another coordinate system. The Ranges will be truncated
+ * if necessary.
+ * @param constraint This contains the start and end base of the new
+ * coordinate system. The position given by constraint.getStart() will
+ * be at postion/base 1 in the new coordinate system.
+ * @return a copy of the Entry which has been translated into the new
+ * coordinate system.
+ **/
+ public Entry truncate(final Bases new_bases, final Range constraint)
+ {
+ final uk.ac.sanger.artemis.io.Entry new_embl_entry;
+
+ if(getEMBLEntry() instanceof GFFDocumentEntry ||
+ getEMBLEntry() instanceof IndexedGFFDocumentEntry)
+ new_embl_entry = new GFFDocumentEntry(getEntryInformation());
+ else
+ new_embl_entry = new EmblDocumentEntry(getEntryInformation());
+
+ final Entry new_entry;
+
+ try
+ {
+ new_entry = new Entry(new_bases, new_embl_entry);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ if(getEMBLEntry() instanceof IndexedGFFDocumentEntry)
+ {
+ ((IndexedGFFDocumentEntry)getEMBLEntry()).truncate(constraint, new_entry);
+ return new_entry;
+ }
+
+ final FeatureEnumeration feature_enum = features();
+
+ while(feature_enum.hasMoreFeatures())
+ {
+ final Feature current_feature = feature_enum.nextFeature();
+ final Location current_feature_location = current_feature.getLocation();
+
+ final Location new_location =
+ current_feature_location.truncate(constraint);
+
+ if(new_location != null)
+ {
+ final Key current_key = current_feature.getKey();
+ final QualifierVector current_qualifiers =
+ current_feature.getQualifiers();
+ try
+ {
+ final uk.ac.sanger.artemis.io.Feature new_feature;
+ if(current_feature.getEmblFeature() instanceof GFFStreamFeature)
+ new_feature = new GFFStreamFeature(current_key, new_location,
+ current_qualifiers);
+ else
+ new_feature = new EmblStreamFeature(current_key, new_location,
+ current_qualifiers);
+
+ new_entry.add(new Feature(new_feature), true);
+ }
+ catch(EntryInformationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(ReadOnlyException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+
+ return new_entry;
+ }
+
+ /**
+ * Return a Vector containing those features that don't have valid EMBL
+ * key.
+ **/
+ public FeatureVector checkForNonEMBLKeys()
+ {
+ final FeatureVector non_embl_features = new FeatureVector();
+ final FeatureEnumeration feature_enum = features();
+
+ while(feature_enum.hasMoreFeatures())
+ {
+ final Feature current_feature = feature_enum.nextFeature();
+
+ if(!current_feature.hasValidEMBLKey())
+ non_embl_features.add(current_feature);
+ }
+
+ return non_embl_features;
+ }
+
+ /**
+ * Returns a vector containing those CDS features that have no /pseudo
+ * qualifier and do not have a valid start codon. The returned features
+ * may be illegal in EMBL submissions.
+ **/
+ public FeatureVector checkFeatureStartCodons()
+ {
+ // get all the CDS features that do not have a /pseudo or /pseudogene qualifier
+ final FeaturePredicateConjunction predicate = new FeaturePredicateConjunction(
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
+ FeaturePredicateConjunction.AND);
+
+ final FeatureVector non_embl_features = new FeatureVector();
+ final FeatureEnumeration feature_enum = features();
+
+ while(feature_enum.hasMoreFeatures())
+ {
+ final Feature current_feature = feature_enum.nextFeature();
+ if(predicate.testPredicate(current_feature))
+ {
+ if(!current_feature.hasValidStartCodon())
+ non_embl_features.add(current_feature);
+ }
+ }
+ return non_embl_features;
+ }
+
+ /**
+ * Returns a vector containing those CDS features that have no /pseudo
+ * qualifier and do not have a valid stop codon. The returned features may
+ * be illegal in EMBL submissions.
+ **/
+ public FeatureVector checkFeatureStopCodons()
+ {
+ // get all the CDS features that do not have a /pseudo or /pseudogene qualifier
+ final FeaturePredicateConjunction predicate = new FeaturePredicateConjunction(
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
+ FeaturePredicateConjunction.AND);
+
+ final FeatureVector non_embl_features = new FeatureVector();
+
+ final FeatureEnumeration feature_enum = features();
+
+ while(feature_enum.hasMoreFeatures())
+ {
+ final Feature current_feature = feature_enum.nextFeature();
+
+ if(predicate.testPredicate(current_feature))
+ {
+ if(!current_feature.hasValidStopCodon())
+ non_embl_features.add(current_feature);
+ }
+ }
+
+ return non_embl_features;
+ }
+
+ /**
+ * Returns a vector containing those features that have the same key and
+ * location as one or more other features. These features are not allowed
+ * in EMBL submissions.
+ **/
+ public FeatureVector checkForEMBLDuplicates()
+ {
+ final FeatureVector non_embl_features = new FeatureVector();
+ final FeatureEnumeration feature_enum = features();
+ Feature last_feature = null;
+
+ while(feature_enum.hasMoreFeatures())
+ {
+ final Feature current_feature = feature_enum.nextFeature();
+ final Key current_key = current_feature.getKey();
+ final Location current_location = current_feature.getLocation();
+
+ if(last_feature != null &&
+ last_feature.getKey().equals(current_key) &&
+ last_feature.getLocation().equals(current_location))
+ {
+ if(! non_embl_features.contains(last_feature))
+ non_embl_features.add(last_feature);
+
+ non_embl_features.add(current_feature);
+ continue;
+ }
+ last_feature = current_feature;
+ }
+
+ return non_embl_features;
+ }
+
+ /**
+ * Returns a vector containing those features that have the same key and
+ * location as one or more other features. These features are not allowed
+ * in EMBL submissions.
+ **/
+ public FeatureVector checkForOverlappingCDSs()
+ {
+ final FeatureVector cds_features = new FeatureVector();
+ final FeatureEnumeration feature_enum = features();
+
+ while(feature_enum.hasMoreFeatures())
+ {
+ final Feature current_feature = feature_enum.nextFeature();
+
+ if(current_feature.isCDS())
+ cds_features.add(current_feature);
+ }
+
+ final FeatureVector return_features = new FeatureVector();
+
+ for(int i = 0 ; i + 1 < cds_features.size() ; ++i)
+ {
+ final Feature this_feature = cds_features.elementAt(i);
+ final Feature next_feature = cds_features.elementAt(i + 1);
+
+ final int this_feature_end = this_feature.getRawLastBase();
+ final int next_feature_start = next_feature.getRawFirstBase();
+
+ if(this_feature_end >= next_feature_start)
+ return_features.add(this_feature);
+ }
+
+ return return_features;
+ }
+
+ /**
+ * Returns a vector containing those features that have a feature that is
+ * missing a required qualifier. These features are not allowed in EMBL
+ * submissions.
+ **/
+ public FeatureVector checkForMissingQualifiers()
+ {
+ final FeatureVector non_embl_features = new FeatureVector();
+
+ final FeatureEnumeration feature_enum = features();
+
+ while(feature_enum.hasMoreFeatures())
+ {
+ final Feature current_feature = feature_enum.nextFeature();
+
+ if(!current_feature.hasRequiredQualifiers())
+ non_embl_features.add(current_feature);
+ }
+
+ return non_embl_features;
+ }
+
+ /**
+ * Adds the specified event listener to receive entry change events from
+ * this object.
+ * @param l the event change listener.
+ **/
+ public void addEntryChangeListener(EntryChangeListener l)
+ {
+ entry_listener_list.addElement(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * entry change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeEntryChangeListener(EntryChangeListener l)
+ {
+ entry_listener_list.removeElement(l);
+ }
+
+ /**
+ * Adds the specified event listener to receive feature change events from
+ * this object.
+ * @param l the event change listener.
+ **/
+ public void addFeatureChangeListener(FeatureChangeListener l)
+ {
+ feature_listener_list.addElement(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * feature change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeFeatureChangeListener(FeatureChangeListener l)
+ {
+ feature_listener_list.removeElement(l);
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events from the Features in this object.
+ * @param event The change event.
+ **/
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ // pass the action straight through
+ fireAction(feature_listener_list, event);
+ }
+
+ /**
+ * Return the EntryInformation object of this Entry.
+ **/
+ public EntryInformation getEntryInformation()
+ {
+ return getEMBLEntry().getEntryInformation();
+ }
+
+ /**
+ * Send an event to those object listening for it.
+ * @param listeners A Vector of the objects that the event should be sent
+ * to.
+ * @param event The event to send
+ **/
+ private void fireAction(Vector<ChangeListener> listeners, ChangeEvent event)
+ {
+ final Vector<ChangeListener> targets;
+ // copied from a book - synchronising the whole method might cause a
+ // deadlock
+ synchronized(this)
+ {
+ targets = (Vector) listeners.clone();
+ }
+
+ for(ChangeListener target: targets)
+ {
+ if(event instanceof EntryChangeEvent)
+ {
+ final EntryChangeListener entry_change_listener =
+ (EntryChangeListener) target;
+ entry_change_listener.entryChanged((EntryChangeEvent) event);
+ }
+ else
+ {
+ final FeatureChangeListener feature_change_listener =
+ (FeatureChangeListener) target;
+ feature_change_listener.featureChanged((FeatureChangeEvent) event);
+ }
+ }
+ }
+
+ /**
+ * Return the uk.ac.sanger.artemis.Feature object of the given embl.Feature object. This
+ * method will create an appropriate uk.ac.sanger.artemis.Feature if none exists.
+ **/
+ private Feature
+ getFeatureOf(uk.ac.sanger.artemis.io.Feature embl_feature)
+ {
+ final Feature test_feature =(Feature) embl_feature.getUserData();
+
+ if(test_feature == null)
+ {
+ final Feature new_feature = new Feature(embl_feature);
+ new_feature.setEntry(this);
+ // hack to create a FeatureSegment for each exon, which has to be done
+ // aftre setting the Entry of the Feature
+ new_feature.getSegments();
+ return new_feature;
+ }
+ else
+ return test_feature;
+ }
+
+ /**
+ * Check that all features in the embl.Entry object are in range for the
+ * Bases object that was passed to the constructor.
+ **/
+ private void checkLocations() throws OutOfRangeException
+ {
+ final uk.ac.sanger.artemis.io.FeatureEnumeration feature_enumerator =
+ getEMBLEntry().features();
+
+ while(feature_enumerator.hasMoreFeatures())
+ {
+ final uk.ac.sanger.artemis.io.Feature feature =
+ feature_enumerator.nextFeature();
+
+ final Location location = feature.getLocation();
+ final RangeVector ranges = location.getRanges();
+
+ for(int i = 0 ; i < ranges.size() ; ++i)
+ {
+ if(((Range)ranges.elementAt(i)).getEnd() > getBases().getLength())
+ {
+ if(!(getBases().getSequence() instanceof PartialSequence))
+ throw new OutOfRangeException(location.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a uk.ac.sanger.artemis.Feature object for each embl.Feature in embl_entry.
+ **/
+ private void createDianaFeatures()
+ {
+ // enumerating the features will call getFeatureOf() for each
+ // embl.Feature, which will in turn create a uk.ac.sanger.artemis.Feature
+
+ final FeatureEnumeration feature_enumerator = features();
+ while(feature_enumerator.hasMoreFeatures())
+ feature_enumerator.nextFeature();
+ }
+
+ /**
+ * Return the embl.Entry object that was passed to the constructor.
+ **/
+ public uk.ac.sanger.artemis.io.Entry getEMBLEntry()
+ {
+ return embl_entry;
+ }
+
+ /**
+ * Return the embl.Entry object that was passed to the constructor.
+ * @param entry_information The EntryInformation object for the new Entry.
+ * @param new_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
+ * ANY_FORMAT. If ANY_FORMAT then the Entry will be returned as is.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys in the new Entry will be quietly thrown away. "Invalid"
+ * means that the key/qualifier is not allowed to occur in an Entry of
+ * this type(probably determined by the EntryInformation object of this
+ * Entry). If false an EntryInformationException will be thrown for
+ * invalid keys or qualifiers.
+ * @exception EntryInformationException Thrown if an Entry using the given
+ * EntryInformation object cannot contain the Key, Qualifier or
+ * Key/Qualifier combination of one of the features in the Document.
+ **/
+ private uk.ac.sanger.artemis.io.Entry
+ getEMBLEntryAsNewType(final EntryInformation entry_information,
+ final int new_type, final boolean force)
+ throws EntryInformationException
+ {
+ return DocumentEntryFactory.makeDocumentEntry(entry_information,
+ embl_entry, new_type,
+ force);
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/EntryChangeEvent.java b/uk/ac/sanger/artemis/EntryChangeEvent.java
new file mode 100644
index 0000000..416b10f
--- /dev/null
+++ b/uk/ac/sanger/artemis/EntryChangeEvent.java
@@ -0,0 +1,161 @@
+/* EntryChangeEvent.java
+ *
+ * created: Sat Oct 17 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/EntryChangeEvent.java,v 1.2 2006-05-31 10:38:48 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * This event is sent when a change occurs in an entry. eg. a feature is
+ * deleted.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryChangeEvent.java,v 1.2 2006-05-31 10:38:48 tjc Exp $
+ *
+ */
+
+public class EntryChangeEvent extends ChangeEvent {
+ /**
+ * Event type - feature removed.
+ **/
+ public static final int FEATURE_DELETED = 1;
+
+ /**
+ * Event type - feature added.
+ **/
+ public static final int FEATURE_ADDED = 2;
+
+ /**
+ * Event type - The name of an entry has changed.
+ **/
+ public static final int NAME_CHANGED = 3;
+
+ /**
+ * Event type - The header of an entry has changed.
+ **/
+ public static final int HEADER_CHANGED = 4;
+
+ /**
+ * The Entry that was passed to the constructor (if any).
+ **/
+ private Entry entry;
+
+ /**
+ * The Feature object that was passed to the constructor.
+ **/
+ private Feature feature;
+
+ /**
+ *
+ **/
+ private boolean duplicate;
+
+ /**
+ * This is the type of this event (eg FEATURE_DELETED, FEATURE_ADDED, etc),
+ * as passed to the constructor
+ **/
+ private int type;
+
+ /**
+ * Create a new EntryChangeEvent object.
+ * @param entry This Entry object that this event refers to.
+ * @param feature This Feature object that this event refers to.
+ * @param type This type of the event.
+ **/
+ public EntryChangeEvent (Entry entry,
+ Feature feature,
+ int type)
+ {
+ super (entry);
+ this.entry = entry;
+ this.feature = feature;
+ this.type = type;
+ }
+
+ /**
+ * Create a new EntryChangeEvent object.
+ * @param entry This Entry object that this event refers to.
+ * @param feature This Feature object that this event refers to.
+ * @param type This type of the event.
+ **/
+ public EntryChangeEvent (Entry entry,
+ Feature feature,
+ boolean duplicate,
+ int type)
+ {
+ super (entry);
+ this.entry = entry;
+ this.feature = feature;
+ this.duplicate = duplicate;
+ this.type = type;
+ }
+
+ /**
+ * Create a new EntryChangeEvent object. This constructor is used for
+ * HEADER_CHANGED and NAME_CHANGED events.
+ * @param source The source of this event.
+ * @param entry This Entry object that this event refers to.
+ * @param type This type of the event.
+ **/
+ public EntryChangeEvent (Object source,
+ Entry entry,
+ int type)
+ {
+ super (source);
+ this.entry = entry;
+ this.feature = null;
+ this.type = type;
+ }
+
+ /**
+ * Return the type of this event, ie the type passed to the
+ * constructor.
+ **/
+ public int getType ()
+ {
+ return type;
+ }
+
+ /**
+ * Return the target Feature object for this event.
+ **/
+ public Feature getFeature ()
+ {
+ return feature;
+ }
+
+
+ public boolean isDuplicate()
+ {
+ return duplicate;
+ }
+
+ /**
+ * This is a convenience method that returns the Entry the generated this
+ * event.
+ **/
+ public Entry getEntry ()
+ {
+ return entry;
+ }
+}
diff --git a/uk/ac/sanger/artemis/EntryChangeListener.java b/uk/ac/sanger/artemis/EntryChangeListener.java
new file mode 100644
index 0000000..6352add
--- /dev/null
+++ b/uk/ac/sanger/artemis/EntryChangeListener.java
@@ -0,0 +1,43 @@
+/* EntryChangeListener.java
+ *
+ * created: Wed Oct 21 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/EntryChangeListener.java,v 1.1 2004-06-09 09:44:20 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * The EntryChangeListener interface is implemented by those classes that
+ * need to listen for changes to Entry objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryChangeListener.java,v 1.1 2004-06-09 09:44:20 tjc Exp $
+ **/
+
+public interface EntryChangeListener extends ChangeListener {
+ /**
+ * Invoked when an Entry is changed.
+ **/
+ void entryChanged (EntryChangeEvent event);
+}
+
+
diff --git a/uk/ac/sanger/artemis/EntryGroup.java b/uk/ac/sanger/artemis/EntryGroup.java
new file mode 100644
index 0000000..980606f
--- /dev/null
+++ b/uk/ac/sanger/artemis/EntryGroup.java
@@ -0,0 +1,344 @@
+/* EntryGroup.java
+ *
+ * created: Wed Nov 11 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/EntryGroup.java,v 1.1 2004-06-09 09:44:21 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+/**
+ * Objects that implement this interface contain a vector of Entry object,
+ * with additional methods for querying and changing the feature tables of
+ * all the entries at once.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryGroup.java,v 1.1 2004-06-09 09:44:21 tjc Exp $
+ **/
+
+public interface EntryGroup
+ extends FeatureChangeListener, EntryChangeListener
+{
+ /**
+ * Return the default Entry for this EntryGroup. The "default" is the
+ * Entry where new features are created.
+ **/
+ Entry getDefaultEntry();
+
+ /**
+ * Set the default Entry. The "default" is the Entry where new features
+ * are created.
+ * @param entry The new default entry. If this Entry is not active this
+ * method will return immediately.
+ **/
+ void setDefaultEntry(final Entry entry);
+
+ /**
+ * Returns true if and only if there are any unsaved changes in any of the
+ * Entry objects in this EntryGroup.
+ **/
+ boolean hasUnsavedChanges();
+
+ /**
+ * Return the index of a feature within this object. This method treats
+ * all the features in all the active entries as if they were in one big
+ * array. The first feature of the first entry will have index 1, the
+ * first from the second entry will have index 1 +(the number of features
+ * in the first entry), etc.
+ * @param feature The feature to find the index of.
+ * @return The index of the feature or -1 if the feature isn't in any of
+ * the entries. The first index is 0 the last is the total number of
+ * features in all the entries of this object minus one.
+ **/
+ int indexOf(final Feature feature);
+
+ /**
+ * Return the index of an Entry within this object.
+ * @return The index of the Entry or -1 if the Entry isn't in this Object.
+ **/
+ int indexOf(final Entry entry);
+
+ /**
+ * Return true if any of the active entries in the group contains the given
+ * feature.
+ **/
+ boolean contains(final Feature feature);
+
+ /**
+ * Return true if the given Entry is active(visible). The Feature objects
+ * in an Entry that is not active will be ignored by the methods that deal
+ * will features: featureAt(), indexOf(), contains(), features(), etc.
+ **/
+ boolean isActive(final Entry entry);
+
+ /**
+ * Set the "active" setting of the Entry at the given index. If the index
+ * refers to the default entry and new_active is false, the default entry
+ * will be set to the active entry or null if there are no active entries.
+ * @param index The index of the Entry to change.
+ * @param new_active The new active setting.
+ **/
+ void setIsActive(final int index, final boolean new_active);
+
+ /**
+ * Set the "active" setting of the given Entry. The Entry is the default
+ * entry and new_active is false, the default entry will be set to the
+ * active entry or null if there are no active entries.
+ * @param entry The Entry to activate or deactivate.
+ * @param new_active The new active setting.
+ **/
+ void setIsActive(final Entry entry, final boolean new_active);
+
+ /**
+ * Return the Entry from this EntryGroup that contains the sequence to view
+ * or return null if none of the entries contains a sequence.
+ **/
+ Entry getSequenceEntry();
+
+ /**
+ * Returns the base length of the sequence of the first Entry in this group
+ * or 0 if this group is empty.
+ **/
+ int getSequenceLength();
+
+ /**
+ * Returns the Bases object of the first Entry in this group or null if
+ * this group is empty.
+ **/
+ Bases getBases();
+
+ /**
+ * Reverse and complement the sequence and all features in every Entry in
+ * this EntryGroup.
+ **/
+ void reverseComplement()
+ throws ReadOnlyException;
+
+ /**
+ * Return the number of entries in this EntryGroup.
+ **/
+ int size();
+
+ /**
+ * Return the ith Entry in this EntryGroup.
+ **/
+ Entry elementAt(final int i);
+
+ /**
+ * Return the Feature at the given index. This method treats all the
+ * features in all the entries as if they were in one big array. See
+ * the comment on indexOf().
+ * @param index The index of the required Feature.
+ * @return The Feature at the given index. The first index is 0 the last
+ * is the total number of features in all the entries of this object minus
+ * one. If the index is out of range then null will be returned.
+ **/
+ Feature featureAt(final int index);
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range of indices.
+ * @param
+ **/
+ FeatureVector getFeaturesInIndexRange(final int start_index,
+ final int end_index);
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range for all the active entries in the EntryGroup.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The non-source key features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ FeatureVector getFeaturesInRange(final Range range)
+ throws OutOfRangeException;
+
+ /**
+ * Return a vector containing the references of the Feature objects from
+ * all the active entries in the EntryGroup.
+ * @return The non-source key features in active entries of this
+ * EntryGroup. The returned object is a copy - changes will not effect
+ * the EntryGroup object itself.
+ **/
+ FeatureVector getAllFeatures();
+
+ /**
+ * Return a count of the number of Feature objects from all the active
+ * entries in the EntryGroup.
+ * @return A count of the non-source key features in active entries of this
+ * EntryGroup.
+ **/
+ int getAllFeaturesCount();
+
+ /**
+ * Add an Entry to this object and then emit the appropriate EntryChange
+ * events.
+ **/
+ void addElement(final Entry entry);
+
+ /**
+ * A convenience method that does the same as addElement(Entry).
+ **/
+ void add(final Entry entry);
+
+ /**
+ * Remove an Entry from this object and then emit the appropriate
+ * EntryGroupChange events. The first entry in the group can only be
+ * removed if it is the only Entry because the first Entry contains the
+ * sequence.
+ * @return true if the removal succeeded, false if it fails(which can
+ * if the given Entry isn't in this EntryGroup or if the user tries to
+ * remove the first Entry).
+ **/
+ boolean removeElement(final Entry entry);
+
+ /**
+ * A convenience method that does the same as removeElement(Entry).
+ **/
+ boolean remove(final Entry entry);
+
+ /**
+ * Create a new(blank) Feature in the default Entry of this EntryGroup.
+ * See getDefaultEntry() and Entry.createFeature().
+ * @return The new Feature.
+ **/
+ Feature createFeature()
+ throws ReadOnlyException;
+
+ /**
+ * Create a new(empty) Entry in this EntryGroup. See Entry.newEntry().
+ * @return The reference of the new Entry.
+ **/
+ Entry createEntry();
+
+ /**
+ * Create a new(empty) Entry in this EntryGroup. See Entry.newEntry().
+ * @param name The(file) name of the new Entry.
+ * @return The reference of the new Entry.
+ **/
+ Entry createEntry(final String name);
+
+ /**
+ * Returns an enumeration of the Feature objects in this EntryGroup. The
+ * returned FeatureEnumeration object will generate all features in this
+ * object in turn. The first item generated is the item at index 0, then
+ * the item at index 1, and so on.
+ **/
+ FeatureEnumeration features();
+
+ /**
+ * Adds the specified event listener to receive entry group change events
+ * from this object.
+ * @param l the event change listener.
+ **/
+ void addEntryGroupChangeListener(EntryGroupChangeListener l);
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * entry group change events from this object.
+ * @param l the event change listener.
+ **/
+ void removeEntryGroupChangeListener(EntryGroupChangeListener l);
+
+ /**
+ * Adds the specified event listener to receive entry change events from
+ * this object.
+ * @param l the event change listener.
+ **/
+ void addEntryChangeListener(EntryChangeListener l);
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * entry change events from this object.
+ * @param l the event change listener.
+ **/
+ void removeEntryChangeListener(EntryChangeListener l);
+
+ /**
+ * Adds the specified event listener to receive feature change events from
+ * this object.
+ * @param l the event change listener.
+ **/
+ void addFeatureChangeListener(FeatureChangeListener l);
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * feature change events from this object.
+ * @param l the event change listener.
+ **/
+ void removeFeatureChangeListener(FeatureChangeListener l);
+
+ /**
+ * Return the reference of an EntryVector containing the active entries of
+ * this EntryGroup.
+ **/
+ EntryVector getActiveEntries();
+
+ /**
+ * This method translates the start and end of every each Range in every
+ * Location into another coordinate system. The Ranges will be truncated
+ * if necessary.
+ * @param constraint This contains the start and end base of the new
+ * coordinate system. The position given by constraint.getStart() will
+ * be at postion/base 1 in the new coordinate system.
+ * @return a copy of the EntryGroup which has been translated into the new
+ * coordinate system.
+ **/
+ EntryGroup truncate(final Range constraint);
+
+ /**
+ * Return the ActionController for this EntryGroup(for undo).
+ **/
+ ActionController getActionController();
+
+ /**
+ * Return true if and only if one or more of the entries or features in
+ * this SimpleEntryGroup are read-only.
+ **/
+ boolean isReadOnly();
+
+ /**
+ * Increment the reference count for this EntryGroup.
+ **/
+ void ref();
+
+ /**
+ * Decrement the reference count for this EntryGroup. When the reference
+ * count goes to zero a EntryGroupChangeEvent is sent to all
+ * EntryGroupChangeListeners with type EntryGroupChangeEvent.DONE_GONE.
+ * The listeners should then stop using the EntryGroup and release any
+ * associated resources.
+ **/
+ void unref();
+
+ /**
+ * Return the current reference count.
+ **/
+ int refCount();
+}
diff --git a/uk/ac/sanger/artemis/EntryGroupChangeEvent.java b/uk/ac/sanger/artemis/EntryGroupChangeEvent.java
new file mode 100644
index 0000000..3a46ce1
--- /dev/null
+++ b/uk/ac/sanger/artemis/EntryGroupChangeEvent.java
@@ -0,0 +1,115 @@
+/* EntryGroupChangeEvent.java
+ *
+ * created: Mon Dec 7 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/EntryGroupChangeEvent.java,v 1.1 2004-06-09 09:44:22 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * This event is sent when a change occurs in an EntryGroup. eg. an Entry
+ * is deleted or added.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryGroupChangeEvent.java,v 1.1 2004-06-09 09:44:22 tjc Exp $
+ **/
+
+public class EntryGroupChangeEvent extends ChangeEvent {
+ /**
+ * Event type - Entry removed.
+ **/
+ public static final int ENTRY_DELETED = 1;
+
+ /**
+ * Event type - Entry added.
+ **/
+ public static final int ENTRY_ADDED = 2;
+
+ /**
+ * Event type - Entry has been made active.
+ **/
+ public static final int ENTRY_ACTIVE = 3;
+
+ /**
+ * Event type - Entry has been made inactive.
+ **/
+ public static final int ENTRY_INACTIVE = 4;
+
+ /**
+ * Event type - There is now a different default Entry.
+ **/
+ public static final int NEW_DEFAULT_ENTRY = 5;
+
+ /**
+ * Event type - This event means that the entry group is now not used by
+ * anything important.
+ **/
+ public static final int DONE_GONE = 6;
+
+ /**
+ * Create a new EntryChangeEvent object.
+ * @param entry_group This EntryGroup object that this event refers to.
+ * @param entry This Entry object that this event refers to (if any).
+ * @param type This type of the event.
+ **/
+ public EntryGroupChangeEvent (final EntryGroup entry_group,
+ final Entry entry,
+ final int type) {
+ super (entry_group);
+ this.entry = entry;
+ this.type = type;
+ }
+
+ /**
+ * Return the type of this event ie. the type passed to the constructor.
+ **/
+ public int getType () {
+ return type;
+ }
+
+ /**
+ * Return the target Entry object for this event. Will return null for
+ * DONE_GONE events.
+ **/
+ public Entry getEntry () {
+ return entry;
+ }
+
+ /**
+ * This is a convenience method that returns the EntryGroup the generated
+ * this event.
+ **/
+ public EntryGroup getEntryGroup () {
+ return (EntryGroup) getSource ();
+ }
+
+ /**
+ * The Entry object that was passed to the constructor.
+ **/
+ private Entry entry;
+
+ /**
+ * This is the type of this event (eg ENTRY_ADDED, ENTRY_DELETED, etc), as
+ * passed to the constructor
+ **/
+ private int type;
+}
diff --git a/uk/ac/sanger/artemis/EntryGroupChangeListener.java b/uk/ac/sanger/artemis/EntryGroupChangeListener.java
new file mode 100644
index 0000000..b245b64
--- /dev/null
+++ b/uk/ac/sanger/artemis/EntryGroupChangeListener.java
@@ -0,0 +1,43 @@
+/* EntryGroupChangeListener.java
+ *
+ * created: Mon Dec 7 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/EntryGroupChangeListener.java,v 1.1 2004-06-09 09:44:23 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * The EntryGroupChangeListener interface is implemented by those classes
+ * that need to listen for additions/deleltions to EntryGroup objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryGroupChangeListener.java,v 1.1 2004-06-09 09:44:23 tjc Exp $
+ **/
+
+public interface EntryGroupChangeListener extends ChangeListener {
+ /**
+ * Invoked when an EntryGroup is changed.
+ **/
+ void entryGroupChanged (EntryGroupChangeEvent event);
+}
+
+
diff --git a/uk/ac/sanger/artemis/EntrySource.java b/uk/ac/sanger/artemis/EntrySource.java
new file mode 100644
index 0000000..4631924
--- /dev/null
+++ b/uk/ac/sanger/artemis/EntrySource.java
@@ -0,0 +1,98 @@
+/* EntrySource.java
+ *
+ * created: Wed Jun 7 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/EntrySource.java,v 1.2 2004-12-03 17:47:04 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+
+/**
+ * This interface is implemented by those objects that can produce an Entry
+ * object. Examples include objects that can read an Entry from a file or
+ * from a CORBA server.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: EntrySource.java,v 1.2 2004-12-03 17:47:04 tjc Exp $
+ **/
+
+public interface EntrySource
+{
+ /**
+ * Get an Entry object from this source (by reading from a file, reading
+ * from a CORBA server, or whatever).
+ * @param bases The Bases object to pass to the Entry constructor.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @param show_progress If true show a Dialog showing the progress while
+ * loading (may be ignored).
+ * @return null if and only if the read is cancelled by the user or if the
+ * read fails.
+ **/
+ Entry getEntry(final Bases bases,
+ final boolean show_progress)
+ throws OutOfRangeException, IOException;
+
+ /**
+ * Get an Entry object from this source (by reading from a file, reading
+ * from a CORBA server, or whatever). A Bases object will be created for
+ * the sequence of the new Entry.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if the entry that we read has no
+ * sequence.
+ * @param show_progress If true show a Dialog showing the progress while
+ * loading (may be ignored).
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ Entry getEntry(final boolean show_progress)
+ throws OutOfRangeException, NoSequenceException, IOException;
+
+ /**
+ * Returns true if and only if this EntrySource always returns "full"
+ * entries. ie. entries that contain features and sequence.
+ **/
+ boolean isFullEntrySource();
+
+// /**
+// * Get an Entry object from this source by name (by reading from a file,
+// * reading from a CORBA server, or whatever).
+// * @param entry_name The name (or id/accession number) of the Entry to read.
+// * @exception OutOfRangeException Thrown if one of the features in
+// * embl_entry is out of range of the Bases object.
+// * @exception NoSequenceException Thrown if the entry that we read has no
+// * sequence.
+// * @return null if and only if there is no Entry with that name.
+// **/
+// public Entry getEntryByName (final String entry_name)
+// throws OutOfRangeException, NoSequenceException, IOException;
+
+ /**
+ * Return the name of this source (to display to the user in menus).
+ **/
+ String getSourceName ();
+}
diff --git a/uk/ac/sanger/artemis/EntrySourceVector.java b/uk/ac/sanger/artemis/EntrySourceVector.java
new file mode 100644
index 0000000..e16cea2
--- /dev/null
+++ b/uk/ac/sanger/artemis/EntrySourceVector.java
@@ -0,0 +1,123 @@
+/* EntrySourceVector.java
+ *
+ * created: Wed Jun 7 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/EntrySourceVector.java,v 1.1 2004-06-09 09:44:26 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.util.Vector;
+
+/**
+ * This class is a Vector of EntrySource objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntrySourceVector.java,v 1.1 2004-06-09 09:44:26 tjc Exp $
+ *
+ **/
+
+public class EntrySourceVector {
+ /**
+ * Create a new (empty) EntrySourceVector object.
+ **/
+ public EntrySourceVector () {
+
+ }
+
+ /**
+ * Appends the given EntrySource object to the vector if and only if it
+ * isn't already in the vector.
+ **/
+ public void addElement (EntrySource entry_source) {
+ if (indexOf (entry_source) == -1) {
+ vector.addElement (entry_source);
+ }
+ }
+
+ /**
+ * Appends the given EntrySource object to the vector if and only if it
+ * isn't already in the vector. (same as addElement ()).
+ **/
+ public void add (EntrySource entry_source) {
+ addElement (entry_source);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ **/
+ public EntrySource elementAt (int index) {
+ return (EntrySource) vector.elementAt (index);
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public boolean removeElement (EntrySource entry_source) {
+ return vector.removeElement (entry_source);
+ }
+
+ /**
+ * Return true if this object contains the given EntrySource.
+ **/
+ public boolean contains (EntrySource entry_source) {
+ if (indexOf (entry_source) == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Performs the same function as Vector.removeAllElements ()
+ **/
+ public void removeAllElements () {
+ vector.removeAllElements ();
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public int indexOf (EntrySource entry_source) {
+ return vector.indexOf (entry_source);
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ **/
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Create a new EntrySourceVector with the same contents as this one.
+ **/
+ public Object clone () {
+ final EntrySourceVector return_vector = new EntrySourceVector ();
+ return_vector.vector = (Vector) vector.clone ();
+ return return_vector;
+ }
+
+ /**
+ * Storage for EntrySource objects.
+ **/
+ private Vector vector = new Vector ();
+}
diff --git a/uk/ac/sanger/artemis/EntryVector.java b/uk/ac/sanger/artemis/EntryVector.java
new file mode 100644
index 0000000..78d0aa0
--- /dev/null
+++ b/uk/ac/sanger/artemis/EntryVector.java
@@ -0,0 +1,131 @@
+/* EntryVector.java
+ *
+ * created: Sat Oct 17 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/EntryVector.java,v 1.1 2004-06-09 09:44:27 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.util.Vector;
+
+/**
+ * This class is a Vector of Entry objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryVector.java,v 1.1 2004-06-09 09:44:27 tjc Exp $
+ *
+ **/
+
+public class EntryVector
+{
+
+ /** Storage for Entry objects. */
+ private Vector vector = new Vector();
+
+ /**
+ * Create a new(empty) EntryVector object.
+ **/
+ public EntryVector()
+ {
+ }
+
+ /**
+ * Appends the given Entry object to the vector if and only if it isn't
+ * already in the vector.
+ **/
+ protected void addElement(Entry entry)
+ {
+ if(indexOf(entry) == -1)
+ vector.addElement(entry);
+ }
+
+ /**
+ * Appends the given Entry object to the vector if and only if it isn't
+ * already in the vector. (same as addElement()).
+ **/
+ protected void add(Entry entry)
+ {
+ addElement(entry);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt()
+ **/
+ public Entry elementAt(int index)
+ {
+ return(Entry) vector.elementAt(index);
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement()
+ **/
+ protected boolean removeElement(Entry entry)
+ {
+ return vector.removeElement(entry);
+ }
+
+ /**
+ * Return true if this object contains the given Entry.
+ **/
+ public boolean contains(Entry entry)
+ {
+ if(indexOf(entry) == -1)
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Performs the same function as Vector.removeAllElements()
+ **/
+ public void removeAllElements()
+ {
+ vector.removeAllElements();
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement()
+ **/
+ public int indexOf(Entry entry)
+ {
+ return vector.indexOf(entry);
+ }
+
+ /**
+ * Performs the same function as Vector.size()
+ **/
+ public int size()
+ {
+ return vector.size();
+ }
+
+ /**
+ * Create a new EntryVector with the same contents as this one.
+ **/
+ public Object clone()
+ {
+ final EntryVector return_vector = new EntryVector();
+ return_vector.vector = (Vector)vector.clone();
+ return return_vector;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/ExternalProgram.java b/uk/ac/sanger/artemis/ExternalProgram.java
new file mode 100644
index 0000000..d46859e
--- /dev/null
+++ b/uk/ac/sanger/artemis/ExternalProgram.java
@@ -0,0 +1,863 @@
+/* ExternalProgram.java
+ *
+ * created: Tue Jan 26 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998-2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ExternalProgram.java,v 1.21 2009-08-17 15:49:25 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.components.ProgressBarFrame;
+import uk.ac.sanger.artemis.components.filetree.RemoteFileNode;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.j2ssh.FileTransferProgressMonitor;
+import uk.ac.sanger.artemis.j2ssh.FTProgress;
+
+import java.io.*;
+import java.text.*;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+/**
+ * Each object of this class represents one external executable or script,
+ * and contains methods for invoking it.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ExternalProgram.java,v 1.21 2009-08-17 15:49:25 tjc Exp $
+ **/
+
+public class ExternalProgram
+{
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(ExternalProgram.class);
+
+ final public static int AA_PROGRAM = 0;
+ final public static int DNA_PROGRAM = 1;
+ final public static int APPLICATION = 2;
+
+ /**
+ * The string to put at the end of the file that stores the counter used
+ * when creating the protein files to run the search on. The file contains
+ * a comment line and then a line with a single integer on it. This file
+ * is updated by setFileNumber() and read with getFileNumber().
+ **/
+ final private static String file_counter_filename = "file_number_counter";
+
+ /**
+ * The name of the external program that was passed to the constructor.
+ **/
+ private String name;
+
+ /** The default options to pass to this program. */
+ private String program_options;
+
+ /** One of AA_PROGRAM, DNA_PROGRAM, APPLICATION */
+ private int program_type;
+
+ /**
+ * Create a new ExternalProgram object for the program with given name.
+ * @param name The name of the program.
+ * @param search_options The default options to use when running the
+ * program.
+ * @param program_type The type of external program to run(eg. AA_PROGRAM)
+ **/
+ public ExternalProgram(final String name,
+ final String program_options,
+ final int program_type)
+ {
+ this.name = name;
+ this.program_options = program_options;
+ this.program_type = program_type;
+ }
+
+ /**
+ * Return the program_type that was passed to the constructor.
+ **/
+ public int getType()
+ {
+ return program_type;
+ }
+
+ /**
+ * Run this program with the options that were set with setOptions().
+ * @param features The program will be run for each of these features.
+ * @param logger The log for errors, STDOUT and STDERR of the external
+ * program.
+ * @return An ExternalProgramThread object that can be used to monitor the
+ * external program after it starts.
+ * @exception IOException Thrown if an IO error occur while trying to run
+ * the program(eg the program could not be found).
+ * @exception ExternalProgramException Thrown if there is a non-IO error
+ * while attempting to run the program.
+ * @exception ReadOnlyException Thrown if the one features is in a
+ * read-only entry.
+ * @exception EntryInformationException Thrown if there is no qualifier
+ * that can be used to store the output filename
+ **/
+ public ExternalProgramMonitor run(final FeatureVector features,
+ final Logger logger)
+ throws IOException, ExternalProgramException, EntryInformationException,
+ ReadOnlyException
+ {
+ final StringVector sequence_file_names = new StringVector();
+
+ // sequence_file_names will be set by prepareRun()
+ final File file_of_filenames = prepareRun(features, sequence_file_names);
+
+ try
+ {
+ if( System.getProperty("j2ssh") != null &&
+ !System.getProperty("j2ssh").equals("false") &&
+ (getRealName().indexOf("blast") > -1 || getRealName().startsWith("fast")))
+ {
+ logger4j.debug("GET READY TO CALL SSH CLIENT " + getRealName());
+
+ final Feature this_feature = features.elementAt(0);
+ Entry entry = this_feature.getEntry();
+ String[] args;
+
+ if (((DocumentEntry) entry.getEMBLEntry()).getDocument() instanceof RemoteFileDocument)
+ {
+ RemoteFileDocument nodeDoc = (RemoteFileDocument) (((DocumentEntry) entry
+ .getEMBLEntry()).getDocument());
+ RemoteFileNode node = nodeDoc.getRemoteFileNode();
+
+ String wdir = node.getRootDir() + "/" + node.getFullName();
+ int index = wdir.lastIndexOf("/");
+ wdir = wdir.substring(0, index);
+
+ args = new String[9];
+
+ args[0] = "-f";
+ args[1] = file_of_filenames.getPath();
+ args[2] = "-cmd";
+ args[3] = getRealName();
+ args[4] = "-wdir";
+ args[5] = wdir;
+ args[6] = "-d";
+ args[7] = getProgramOptions();
+ args[8] = "-keep";
+ }
+ else
+ {
+ args = new String[6];
+
+ args[0] = "-f";
+ args[1] = file_of_filenames.getPath();
+ args[2] = "-cmd";
+ args[3] = getRealName();
+ args[4] = "-d";
+ args[5] = getProgramOptions();
+ }
+
+ logger4j.debug("CALL SSH CLIENT " + getRealName());
+ uk.ac.sanger.artemis.j2ssh.SshPSUClient ssh = new uk.ac.sanger.artemis.j2ssh.SshPSUClient(
+ args);
+ ssh.start();
+ new ProgressBarFrame(1, getName());
+ return null;
+ }
+
+ final String[] arguments;
+ switch (program_type)
+ {
+ case DNA_PROGRAM:
+ // fall through
+ case AA_PROGRAM:
+ arguments = new String[]
+ { file_of_filenames.getPath(), getProgramOptions() };
+ break;
+ case APPLICATION:
+ arguments = new String[]
+ { file_of_filenames.getPath(), };
+ break;
+ default:
+ throw new Error("internal error - unknown program type");
+ }
+
+ final Process process = startProgram("run_" + getRealName(), arguments);
+
+ //
+ new ProgressBarFrame(1, getName());
+ return new ProcessMonitor(process, getName(), logger);
+ }
+ catch (SecurityException e)
+ {
+ // re-throw as an ExternalProgramException
+ throw new ExternalProgramException("SecurityException while running "
+ + getName() + ": " + e.getMessage());
+ }
+ }
+
+
+ /**
+ * Write sequence files for each of the given features and add a
+ * /something_file qualifier
+ * @param features Files will be written for each of these features.
+ * @param sequence_file_names The names of each sequence files will be
+ * returned in this Vector.
+ * @return a File representing a file that contains the names of each of
+ * the newly created files.
+ **/
+ private File prepareRun(final FeatureVector features,
+ final StringVector sequence_file_names)
+ throws IOException, ExternalProgramException, EntryInformationException,
+ ReadOnlyException
+ {
+ final String new_qualifier_name = getName() + "_file";
+
+ if(getType() != APPLICATION)
+ {
+ // do this first so that we don't get an exception half way through the
+ // list
+ for(int i = 0 ; i < features.size() ; ++i)
+ {
+ final Feature this_feature = features.elementAt(i);
+
+ if(this_feature.getEntry().isReadOnly())
+ throw new ReadOnlyException();
+
+ final EntryInformation entry_information =
+ this_feature.getEntry().getEntryInformation();
+
+ if(!entry_information.isValidQualifier(this_feature.getKey(),
+ new_qualifier_name))
+ {
+ final String message = this_feature.getKey() + " cannot have " +
+ new_qualifier_name + " as a qualifier";
+ throw new EntryInformationException(message);
+ }
+ }
+ }
+
+ if(features.size() == 0)
+ return null;
+
+ final NumberFormat number_format = NumberFormat.getNumberInstance();
+
+ number_format.setMaximumIntegerDigits(5);
+ number_format.setMinimumIntegerDigits(5);
+ number_format.setGroupingUsed(false);
+
+ // save the directory of the first feature we see - used for writing the
+ // file of filenames
+ File first_directory = null;
+
+ // stores the number of features in each directory
+ final Hashtable<File, Long> feature_count_hash = new Hashtable<File, Long>();
+
+ for(int i = 0 ; i < features.size() ; ++i)
+ {
+ final Feature this_feature = features.elementAt(i);
+ final Entry this_feature_entry = this_feature.getEntry();
+
+ File base_directory;
+
+ base_directory = getBaseDirectoryFromEntry(this_feature_entry);
+
+ if(base_directory == null)
+ base_directory = new File(".");
+
+ if(first_directory == null)
+ first_directory = base_directory;
+
+ if(feature_count_hash.containsKey(base_directory))
+ {
+ final long new_number =
+ ((Long) feature_count_hash.get(base_directory)).longValue() + 1;
+
+ feature_count_hash.put(base_directory, new Long(new_number));
+ }
+ else
+ feature_count_hash.put(base_directory, new Long(1));
+ }
+
+ // store the file number to use for the next sequence file - the key is
+ // a File containing the directory of there Entry that contains the
+ // Feature and the value is the next file number to use
+
+ Entry entry = features.elementAt(0).getEntry();
+ RemoteFileNode node = null;
+
+ if(((DocumentEntry)entry.getEMBLEntry()).getDocument()
+ instanceof RemoteFileDocument)
+ {
+ RemoteFileDocument nodeDoc =
+ (RemoteFileDocument)(((DocumentEntry)entry.getEMBLEntry()).getDocument());
+ node = nodeDoc.getRemoteFileNode();
+ }
+
+ final Hashtable<File, Long> file_number_hash = new Hashtable<File, Long>();
+
+ for(final Enumeration<File> e = feature_count_hash.keys() ;
+ e.hasMoreElements() ;)
+ {
+ final File directory = e.nextElement();
+
+ final long old_file_number = getFileNumber(directory, node);
+
+ final long feature_count =
+ ((Long) feature_count_hash.get(directory)).longValue();
+
+ file_number_hash.put(directory, new Long(old_file_number));
+
+ setFileNumber(directory, old_file_number + feature_count, node);
+ }
+
+ // write the sequences out
+ for(int i = 0 ; i < features.size() ; ++i)
+ {
+ final Feature this_feature = features.elementAt(i);
+ final Entry this_feature_entry = this_feature.getEntry();
+
+ // the directory where we will write the program results. defaults to
+ // the current directory
+ File base_directory;
+
+ base_directory = getBaseDirectoryFromEntry(this_feature_entry);
+
+ if(base_directory == null)
+ base_directory = new File(".");
+
+ final File program_directory = new File(base_directory, getName());
+
+ makeDirectory(program_directory);
+
+ final String entry_name;
+
+ {
+ final String test_name = this_feature_entry.getName();
+ if(test_name == null)
+ entry_name = "no_name";
+ else
+ entry_name = test_name;
+ }
+
+
+ final long old_file_number =
+ ((Long) file_number_hash.get(base_directory)).longValue();
+
+ file_number_hash.put(base_directory, new Long(old_file_number + 1));
+
+ final String new_file_name =
+ getName() + File.separatorChar + entry_name + ".seq." +
+ number_format.format(old_file_number);
+
+ final File write_file;
+
+ final String new_file_name_full =
+ new File(base_directory,
+ File.separatorChar + new_file_name).getPath();
+
+ sequence_file_names.add(new_file_name_full);
+
+ write_file = new File(new_file_name_full);
+
+ final Writer writer = new FileWriter(write_file);
+
+ switch(program_type)
+ {
+ case DNA_PROGRAM:
+ this_feature.writeBasesOfFeature(writer);
+ break;
+ case AA_PROGRAM:
+ this_feature.writeAminoAcidsOfFeature(writer);
+ break;
+ case APPLICATION:
+ this_feature.writeNative(writer);
+ break;
+ default:
+ throw new Error("internal error - unknown program type");
+ }
+
+ writer.close();
+
+ if(program_type != APPLICATION)
+ {
+ setFeatureQualifier(this_feature, new_qualifier_name, new_file_name);
+
+ if( ((DocumentEntry)entry.getEMBLEntry()).getDocument()
+ instanceof DatabaseDocument )
+ {
+ setProteinFeatureQualifier(this_feature, new_qualifier_name, new_file_name);
+ }
+ }
+ }
+
+ // write the sequence file names to a file and then run the program with
+ // that file as an argument
+ final File file_of_filenames =
+ new File(new File(first_directory, getName()),
+ getName() + "_" +
+ "file_of_filenames." +
+ (getFileNumber(first_directory, node) - 1));
+
+ final Writer filenames_writer = new FileWriter(file_of_filenames);
+
+ final PrintWriter filenames_printwriter =
+ new PrintWriter(filenames_writer);
+
+ for(int i = 0 ; i < sequence_file_names.size() ; ++i)
+ filenames_printwriter.println(sequence_file_names.elementAt(i));
+
+ filenames_printwriter.close();
+ filenames_writer.close();
+
+ logger4j.debug("WRITTEN "+file_of_filenames.getCanonicalPath());
+
+
+ return file_of_filenames;
+ }
+
+ /**
+ * Add a qualifier link to the program results
+ * @param this_feature
+ * @param new_qualifier_name
+ * @param new_file_name
+ * @throws ReadOnlyException
+ * @throws EntryInformationException
+ */
+ private void setFeatureQualifier(final Feature this_feature,
+ final String new_qualifier_name,
+ final String new_file_name)
+ throws ReadOnlyException, EntryInformationException
+ {
+ uk.ac.sanger.artemis.io.Qualifier new_qualifier;
+
+ if(getName().startsWith("fast") || getName().startsWith("blast"))
+ {
+ new_qualifier = this_feature.getQualifierByName(new_qualifier_name);
+ String db = getProgramOptions().trim();
+
+ int ind;
+ if((ind = db.indexOf(' ')) > -1)
+ db.substring(0, ind);
+
+ if(new_qualifier == null)
+ new_qualifier = new uk.ac.sanger.artemis.io.Qualifier(
+ new_qualifier_name, db + ":" + new_file_name + ".out");
+ else
+ {
+ // search for existing 'db:' values
+ final StringVector values = new_qualifier.getValues();
+ for(int j = 0; j < values.size(); j++)
+ {
+ String value = (String) values.get(j);
+ if(value.startsWith(db + ":"))
+ values.remove(value);
+ }
+
+ new_qualifier.addValues(values);
+ values.add(db + ":" + new_file_name + ".out");
+ new_qualifier = new uk.ac.sanger.artemis.io.Qualifier(
+ new_qualifier_name, values);
+ }
+ }
+ else
+ new_qualifier = new uk.ac.sanger.artemis.io.Qualifier(
+ new_qualifier_name, new_file_name + ".out");
+
+ this_feature.setQualifier(new_qualifier);
+ }
+
+ /**
+ * In database mode add a link on the polypeptide to the results file.
+ * @param this_feature
+ * @param new_qualifier_name
+ * @param new_file_name
+ */
+ private void setProteinFeatureQualifier(final Feature this_feature,
+ final String new_qualifier_name,
+ final String new_file_name)
+ {
+ try
+ {
+ if(this_feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL)
+ && this_feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ GFFStreamFeature gffFeature = (GFFStreamFeature)(this_feature.getEmblFeature());
+ ChadoCanonicalGene chado_gene = gffFeature.getChadoGene();
+ String transcriptName = chado_gene.getTranscriptFromName(GeneUtils.getUniqueName(gffFeature));
+ Feature protein =
+ (Feature)chado_gene.getProteinOfTranscript(transcriptName).getUserData();
+ setFeatureQualifier(protein, new_qualifier_name, new_file_name);
+ }
+ }
+ catch(Exception e)
+ {
+ logger4j.debug(e.getMessage());
+ }
+ }
+
+ /**
+ * Return the name of this ExternalProgram, as passed to the constructor.
+ **/
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Start a new external program with exec.
+ * @param name the name of the program to start.
+ * @param arguments the arguments to pass to the new program. (can be null)
+ * @return A Process object for the new program.
+ **/
+ public static Process startProgram(final String name,
+ final String [] arguments)
+ throws SecurityException, ExternalProgramException, IOException
+ {
+
+ // try to get the program from the artemis directory/jar file
+ InputStream code_stream = ExternalProgram.class.getResourceAsStream(name);
+
+ // try the etc directory
+ if(code_stream == null)
+ code_stream = ExternalProgram.class.getResourceAsStream("/etc/" + name);
+
+ if(code_stream == null)
+ {
+ // the code isn't in the artemis directory/jar file so just call exec()
+
+ final String [] real_arguments;
+
+ if(arguments == null)
+ real_arguments = new String [1];
+ else
+ {
+ real_arguments = new String [arguments.length + 1];
+
+ for(int i = 0 ; i < arguments.length ; ++i)
+ real_arguments[i + 1] = arguments[i];
+ }
+
+ real_arguments[0] = name;
+
+ return Runtime.getRuntime().exec(real_arguments);
+ }
+ else
+ {
+ final String [] sh_arguments;
+
+ if(arguments == null)
+ sh_arguments = new String [2];
+ else
+ {
+ sh_arguments = new String [arguments.length + 2];
+
+ for(int i = 0 ; i < arguments.length ; ++i)
+ sh_arguments[i + 2] = arguments[i];
+ }
+
+ sh_arguments[0] = "/bin/sh";
+ sh_arguments[1] = "-s";
+
+ final Process process = Runtime.getRuntime().exec(sh_arguments);
+ final OutputStream out_stream = process.getOutputStream();
+
+ final int BUFFER_SIZE = 10000;
+ final byte [] buffer = new byte [BUFFER_SIZE];
+
+ int read_size = 0;
+
+ while((read_size = code_stream.read(buffer)) != -1)
+ out_stream.write(buffer, 0, read_size);
+
+ code_stream.close();
+ out_stream.close();
+
+ return process;
+ }
+ }
+
+
+ /**
+ * Read the number to be used when creating the next output file. This
+ * number is appended to file names to attempt to make them unique. The
+ * number is read from directory + "/" + prog_name + "/" +
+ * file_counter_filename. If the file doesn't exist, it is created and
+ * initialised.
+ **/
+ private long getFileNumber(final File directory,
+ final RemoteFileNode node)
+ throws IOException
+ {
+ try
+ {
+ final Reader file_reader;
+ if(node == null)
+ file_reader =
+ new FileReader(new File(directory, File.separatorChar +
+ getName() + File.separatorChar +
+ file_counter_filename));
+ else
+ {
+ String fs = "/"; // assume ssh to unix server
+
+ String dir = node.getRootDir()+ fs +
+ node.getFullName();
+ int index = dir.lastIndexOf(fs);
+ dir = dir.substring(0,index) + fs + getName();
+
+ FileTransferProgressMonitor monitor =
+ new FileTransferProgressMonitor(null);
+ FTProgress progress = monitor.add(node.getFile());
+ byte[] contents = node.getFileContents(progress, dir+fs+
+ file_counter_filename);
+ monitor.close();
+
+ if(contents == null)
+ {
+ logger4j.debug("getFileNumber() creating "+dir+
+ fs+file_counter_filename);
+
+ node.mkdir(dir);
+ return setFileNumber(directory, 1, node);
+ }
+
+ file_reader = new StringReader(new String(contents));
+
+ logger4j.debug("getFileNumber()\n"+new String(contents));
+ }
+
+ final BufferedReader reader = new BufferedReader(file_reader);
+
+ // the first line should be a comment
+ final String comment_line = reader.readLine();
+
+ if(comment_line == null || comment_line.length() == 0)
+ return setFileNumber(directory, guessNumber(directory), node);
+
+ if(!comment_line.startsWith("#"))
+ return setFileNumber(directory, guessNumber(directory), node);
+
+ final String number_line = reader.readLine();
+
+ try
+ {
+ return Integer.parseInt(number_line);
+ }
+ catch(NumberFormatException e)
+ {
+ return setFileNumber(directory, guessNumber(directory), node);
+ }
+ finally
+ {
+ file_reader.close();
+ reader.close();
+ }
+ }
+ catch(FileNotFoundException e)
+ {
+ // create a new file_number_counter file
+ return setFileNumber(directory, guessNumber(directory), node);
+ }
+ catch(IOException e)
+ {
+ return setFileNumber(directory, guessNumber(directory), node);
+ }
+ }
+
+ /**
+ * Write the given number to the file_number file in the given directory
+ * (eg. directory + "/blastp/" + new_file_number).
+ **/
+ protected long setFileNumber(final File directory,
+ final long new_file_number, final RemoteFileNode node)
+ throws IOException
+ {
+ makeDirectory(new File(directory, getName()));
+
+ File local_file = new File(directory, File.separatorChar +
+ getName() + File.separatorChar +
+ file_counter_filename);
+
+ final FileWriter file_writer = new FileWriter(local_file);
+ final PrintWriter print_writer = new PrintWriter(file_writer);
+
+ print_writer.println("# the file is machine generated - do not edit");
+ print_writer.println(new_file_number);
+ print_writer.close();
+ file_writer.close();
+
+ if(node != null)
+ {
+ final String fs = "/"; // assume unix ssh server
+ String dir = node.getRootDir()+ fs +
+ node.getFullName();
+ int index = dir.lastIndexOf(fs);
+ dir = dir.substring(0,index) + fs +
+ getName() + fs;
+
+ logger4j.debug("setFileNumber() "+
+ local_file.getCanonicalPath()+" --> "+dir);
+
+ node.put(dir, local_file, null, true);
+ }
+
+ return new_file_number;
+ }
+
+ /**
+ * Create a directory path from the given directory(if it does not
+ * exist).
+ **/
+ protected void makeDirectory(final File directory)
+ throws IOException
+ {
+ if(directory.exists())
+ {
+ if(directory.isDirectory())
+ {
+ // all is ok
+ if(directory.canWrite())
+ return;
+ else
+ throw new IOException("Cannot write to: " +
+ directory.getAbsolutePath());
+ }
+ else
+ throw new IOException(directory.getAbsolutePath() +
+ " is not a directory");
+ }
+ else
+ directory.mkdirs();
+ }
+
+ /**
+ * Try to read the current file names in the results directory and guess
+ * the number we are up to. This can be slow.
+ **/
+ private long guessNumber(final File directory)
+ {
+ try
+ {
+ long biggest_so_far = -1;
+
+ final String[] file_names = new File(directory, getName()).list();
+
+ if(file_names == null)
+ return 1;
+
+ for(int i = 0 ; i < file_names.length ; ++i)
+ {
+ final int last_dot = file_names[i].lastIndexOf('.');
+
+ final String number_string;
+
+ if(last_dot == -1)
+ number_string = file_names[i];
+ else
+ number_string = file_names[i].substring(last_dot + 1);
+
+ try
+ {
+ final long number = Long.parseLong(number_string);
+
+ if(number > biggest_so_far)
+ biggest_so_far = number;
+ }
+ catch(NumberFormatException e) {}
+ }
+
+ if(biggest_so_far == -1)
+ return 1;
+ else
+ return biggest_so_far;
+ }
+ catch(SecurityException e)
+ {
+ // give up
+ return 0;
+ }
+ }
+
+ /**
+ * Return the options that will be used when the program is run.
+ **/
+ public String getProgramOptions()
+ {
+ if(program_options.equals("-"))
+ return "";
+ else
+ return program_options;
+ }
+
+ /**
+ * Set the options that will be used when the program is run.
+ **/
+ public void setProgramOptions(final String program_options)
+ {
+ this.program_options = program_options;
+ }
+
+ /**
+ * Return the name of this program without modifiers(the bits after the
+ * "+" in the name). eg. if the name is "blastp+go" this method will be
+ * "blastp"
+ **/
+ private String getRealName()
+ {
+ final int plus_index = getName().indexOf('+');
+
+ if(plus_index > 0)
+ return getName().substring(0, plus_index);
+ else
+ return getName();
+ }
+
+ /**
+ * Return the dirtectory that the given entry was read from.
+ **/
+ private File getBaseDirectoryFromEntry(final Entry entry)
+ {
+ final uk.ac.sanger.artemis.io.Entry embl_entry = entry.getEMBLEntry();
+
+ if(embl_entry instanceof DocumentEntry)
+ {
+ final DocumentEntry document_entry =(DocumentEntry) embl_entry;
+
+ if(document_entry.getDocument() instanceof FileDocument)
+ {
+ final FileDocument file_document =
+ (FileDocument) document_entry.getDocument();
+
+ if(file_document.getFile().getParent() != null)
+ return new File(file_document.getFile().getParent());
+ }
+ }
+ if(((DocumentEntry)entry.getEMBLEntry()).getDocument()
+ instanceof RemoteFileDocument ||
+ ((DocumentEntry)entry.getEMBLEntry()).getDocument()
+ instanceof DatabaseDocument)
+ return new File(System.getProperty("user.dir"));
+
+ return null;
+ }
+
+
+}
diff --git a/uk/ac/sanger/artemis/ExternalProgramEvent.java b/uk/ac/sanger/artemis/ExternalProgramEvent.java
new file mode 100644
index 0000000..fe94243
--- /dev/null
+++ b/uk/ac/sanger/artemis/ExternalProgramEvent.java
@@ -0,0 +1,125 @@
+/* ExternalProgramEvent.java
+ *
+ * created: Wed Aug 6 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ExternalProgramEvent.java,v 1.2 2007-11-09 10:36:30 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+//import uk.ac.sanger.jcon.job.*;
+
+/**
+ * An event that represents a change in the state of an ExternalProgram.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ExternalProgramEvent.java,v 1.2 2007-11-09 10:36:30 tjc Exp $
+ **/
+
+public class ExternalProgramEvent {
+ /**
+ * Create a new ExternalProgramEvent object of the given type.
+ * @param message A summary of the event.
+ * @param task The Task that caused this event.
+ **/
+ /*
+ public ExternalProgramEvent (final int type, final String message,
+ final Task task) {
+ this.type = type;
+ this.message = message;
+ this.task = task;
+ }
+ */
+
+ /**
+ * Create a new ExternalProgramEvent object of the given type.
+ * @param message A summary of the event.
+ * @param process The Process that caused this event.
+ **/
+ public ExternalProgramEvent (final int type, final String message,
+ final Process process) {
+ this.type = type;
+ this.message = message;
+ this.process = process;
+ }
+
+ /**
+ * The type of event sent when the ExternalProgram finishes.
+ **/
+ final public static int FINISHED = 1;
+
+ /**
+ * The type of event sent when the ExternalProgram starts.
+ **/
+ final public static int STARTED = 1;
+
+ /**
+ * Return the type of this event.
+ **/
+ public int getType () {
+ return type;
+ }
+
+ /**
+ * Return the message that was passed to the constructor.
+ **/
+ public String getMessage () {
+ return message;
+ }
+
+ /**
+ * Return the Task that was passed to the constructor or null if a Process
+ * was passed to the constructor.
+ **/
+ /*public Task getTask () {
+ return task;
+ }*/
+
+ /**
+ * Return the Process that was passed to the constructor or null if a
+ * Task was passed to the constructor.
+ **/
+ public Process getProcess () {
+ return process;
+ }
+
+ /**
+ * The type that was passed to the constructor.
+ **/
+ final private int type;
+
+ /**
+ * The message that was passed to the constructor.
+ **/
+ final private String message;
+
+ /**
+ * The Task that was passed to the constructor or null if a Process was
+ * passed to the constructor.
+ **/
+ //private Task task = null;
+
+ /**
+ * The Process that was passed to the constructor or null if a Task was
+ * passed to the constructor.
+ **/
+ private Process process = null;
+}
diff --git a/uk/ac/sanger/artemis/ExternalProgramException.java b/uk/ac/sanger/artemis/ExternalProgramException.java
new file mode 100644
index 0000000..dafc9ac
--- /dev/null
+++ b/uk/ac/sanger/artemis/ExternalProgramException.java
@@ -0,0 +1,45 @@
+/* ExternalProgramException.java
+ *
+ * created: Tue Jan 26 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ExternalProgramException.java,v 1.1 2004-06-09 09:44:30 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+/**
+ * This exception is thrown by ExternalProgram objects if there is an error
+ * while trying to execute.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ExternalProgramException.java,v 1.1 2004-06-09 09:44:30 tjc Exp $
+ **/
+
+public class ExternalProgramException extends Exception {
+ /**
+ * Create a new ExternalProgramException with the given message.
+ **/
+ public ExternalProgramException (final String message) {
+ super (message);
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/ExternalProgramListener.java b/uk/ac/sanger/artemis/ExternalProgramListener.java
new file mode 100644
index 0000000..aacd97a
--- /dev/null
+++ b/uk/ac/sanger/artemis/ExternalProgramListener.java
@@ -0,0 +1,41 @@
+/* ExternalProgramListener.java
+ *
+ * created: Wed Aug 6 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ExternalProgramListener.java,v 1.1 2004-06-09 09:44:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * This interface is implemented by those classes that need to listen for
+ * changes to ExternalProgram objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ExternalProgramListener.java,v 1.1 2004-06-09 09:44:31 tjc Exp $
+ **/
+
+public interface ExternalProgramListener {
+ /**
+ * Called when the status of an ExternalProgram changes.
+ **/
+ void statusChanged (final ExternalProgramEvent event);
+}
diff --git a/uk/ac/sanger/artemis/ExternalProgramMonitor.java b/uk/ac/sanger/artemis/ExternalProgramMonitor.java
new file mode 100644
index 0000000..a92eedb
--- /dev/null
+++ b/uk/ac/sanger/artemis/ExternalProgramMonitor.java
@@ -0,0 +1,42 @@
+/* ExternalProgramMonitor.java
+ *
+ * created: Wed Aug 6 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ExternalProgramMonitor.java,v 1.1 2004-06-09 09:44:32 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * A Runnable that monitors the state of an ExternalProgram.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ExternalProgramMonitor.java,v 1.1 2004-06-09 09:44:32 tjc Exp $
+ **/
+
+public interface ExternalProgramMonitor extends Runnable
+{
+ /**
+ * Add a listener for ExternalProgramEvents.
+ **/
+ void addExternalProgramListener (ExternalProgramListener l);
+}
+
diff --git a/uk/ac/sanger/artemis/ExternalProgramThread.java b/uk/ac/sanger/artemis/ExternalProgramThread.java
new file mode 100644
index 0000000..1ba564e
--- /dev/null
+++ b/uk/ac/sanger/artemis/ExternalProgramThread.java
@@ -0,0 +1,42 @@
+/* ExternalProgramThread.java
+ *
+ * created: Wed Aug 6 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ExternalProgramThread.java,v 1.1 2004-06-09 09:44:33 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * ExternalProgramThread class
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ExternalProgramThread.java,v 1.1 2004-06-09 09:44:33 tjc Exp $
+ **/
+
+public class ExternalProgramThread {
+ /**
+ *
+ **/
+ public ExternalProgramThread () {
+
+ }
+}
diff --git a/uk/ac/sanger/artemis/ExternalProgramVector.java b/uk/ac/sanger/artemis/ExternalProgramVector.java
new file mode 100644
index 0000000..4f7d962
--- /dev/null
+++ b/uk/ac/sanger/artemis/ExternalProgramVector.java
@@ -0,0 +1,90 @@
+/* ExternalProgramVector.java
+ *
+ * created: Tue Jan 26 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ExternalProgramVector.java,v 1.1 2004-06-09 09:44:35 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+import java.util.Vector;
+
+/**
+ * This class implements a Vector of ExternalProgram objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ExternalProgramVector.java,v 1.1 2004-06-09 09:44:35 tjc Exp $
+ **/
+
+public class ExternalProgramVector {
+ /**
+ * Create a new (empty) ExternalProgramVector.
+ **/
+ public ExternalProgramVector () {
+
+ }
+
+ /**
+ * Performs the same function as Vector.addElement ()
+ */
+ public void add (final ExternalProgram program) {
+ vector.addElement (program);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ */
+ public ExternalProgram elementAt (final int index) {
+ return (ExternalProgram) vector.elementAt (index);
+ }
+
+ /**
+ * Return true if this object contains the given ExternalProgram.
+ **/
+ public boolean contains (final ExternalProgram program) {
+ if (indexOf (program) == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public int indexOf (final ExternalProgram program) {
+ return vector.indexOf (program);
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ */
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Storage for ExternalProgram objects.
+ */
+ private Vector vector = new Vector ();
+}
+
+
diff --git a/uk/ac/sanger/artemis/Feature.java b/uk/ac/sanger/artemis/Feature.java
new file mode 100644
index 0000000..2a68472
--- /dev/null
+++ b/uk/ac/sanger/artemis/Feature.java
@@ -0,0 +1,3372 @@
+/* Feature.java
+ *
+ * created: Sun Oct 11 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Feature.java,v 1.35 2009-02-03 11:36:39 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.io.OutOfDateException;
+import uk.ac.sanger.artemis.io.LocationParseException;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.PartialSequence;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.DateStampFeature;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.EmblStreamFeature;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.FastaStreamSequence;
+import uk.ac.sanger.artemis.io.StreamFeature;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.plot.*;
+
+import java.awt.Color;
+import java.util.Vector;
+import java.io.*;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+/**
+ * This class extends an embl.Feature with the other information needed by
+ * Diana. It also able to send events to other objects that are interested
+ * in changes to this object. (see FeatureChangeEvent details of the
+ * possible change events.) To make changes to the feature it calls methods
+ * in the Entry class. Changes to this object will update the underlying
+ * embl.Feature and embl.Entry objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Feature.java,v 1.35 2009-02-03 11:36:39 tjc Exp $
+ **/
+
+public class Feature
+ implements EntryChangeListener, Selectable, SequenceChangeListener,
+ MarkerChangeListener, OptionChangeListener
+{
+
+ /**
+ * The Entry that controls/contains this feature. This is the Entry object
+ * that was passed to the constructor.
+ **/
+ private Entry entry;
+
+ /**
+ * This is a reference to the low level Feature object that this class is
+ * providing a wrapper for. This is the embl.Feature object that was
+ * passed to constructor.
+ **/
+ private uk.ac.sanger.artemis.io.Feature embl_feature;
+
+ /**
+ * The segment of this Feature. All features have at least one segment.
+ **/
+ private FeatureSegmentVector segments = null;
+
+ /**
+ * A vector of those objects listening for feature change events.
+ **/
+ private final Vector feature_listener_list = new Vector();
+
+ /**
+ * The translation of the bases of this feature.
+ **/
+ private AminoAcidSequence amino_acids = null;
+
+ /**
+ * The bases of this feature.
+ **/
+ private String bases = null;
+
+ /**
+ * The count of the number of amino acids in this feature. (set by
+ * getAACount() and resetCache()).
+ **/
+ private int aa_count = -1;
+
+ /**
+ * The count of the bases in this feature (set by getBaseCount() and
+ * resetCache()).
+ **/
+ private int base_count = -1;
+
+ /**
+ * This array contains counts of the occurrences of each three bases. The
+ * first index is the first base, the second index is the second base, etc.
+ * The indices are the same as those for Bases.letter_index.
+ * (set by resetCache()).
+ **/
+ private int [][][] codon_counts = null;
+
+ /**
+ * This array contains counts of the occurrences of each amino acids in the
+ * translation of the bases of the current feature.
+ * (set by resetCache()).
+ **/
+ private int [] residue_counts = null;
+
+ /**
+ * This array contains counts of number of each base that appear in each
+ * codon position. The first index is the codon position and the second is
+ * the base (indexed in the same way as Bases.letter_index).
+ * (set by resetCache()).
+ **/
+ private int [][] positional_base_counts = null;
+
+ /**
+ * This array contains the counts of the total number of each base in the
+ * feature.
+ * The indices are the same as those for Bases.letter_index.
+ * (set by resetCache()).
+ **/
+ private int [] base_counts = null;
+
+ /**
+ * The current Location reference is saved each time setLocation() is
+ * called so that if the reference changes resetCache() can
+ * be called. This is needed to handle RWCorbaFeature objects which can
+ * change their Location at arbitrary times.
+ **/
+ private Location old_location = null;
+
+ /**
+ * Incremented when startListening() is called - decremented when
+ * stopListening() is called.
+ **/
+ private int listen_count = 0;
+
+ private Color colour = null;
+
+ /**
+ * Create a new Feature object.
+ * @param entry The uk.ac.sanger.artemis.Entry object that contains this Feature.
+ * @param embl_feature The embl.Feature object that this class is
+ * providing a wrapper for.
+ **/
+ public Feature(uk.ac.sanger.artemis.io.Feature embl_feature)
+ {
+ this.embl_feature = embl_feature;
+ embl_feature.setUserData(this);
+ old_location = embl_feature.getLocation();
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so that if the feature is deleted we can destroy any
+ * objects that use it.
+ *
+ * NOTE: not active currently
+ **/
+ public void entryChanged(EntryChangeEvent event)
+ {
+ }
+
+ /**
+ * This method fixes up the location of this Feature when a Marker changes.
+ **/
+ public void markerChanged(final MarkerChangeEvent event)
+ {
+ try
+ {
+ final Location old_location = getLocation();
+ updateEMBLFeatureLocation();
+ locationChanged(old_location);
+ }
+ catch(ReadOnlyException e) {}
+ }
+
+
+ /**
+ * This method fixes up the location of this Feature when a sequence
+ * changes.
+ **/
+ public void sequenceChanged(final SequenceChangeEvent event)
+ {
+ try
+ {
+ // we don't send a FeatureChangeEvent because the logical location
+ // hasn't changed
+
+ // all the Markers for the ranges of this feature will have changed
+ // before this method is called because the markers are added as
+ // SequenceChangeListener with a higher priority than this Feature
+
+ if(event.getType() == SequenceChangeEvent.REVERSE_COMPLEMENT)
+ reverseComplement(getEntry().getBases().getLength());
+ else if(event.getType() == SequenceChangeEvent.CONTIG_REVERSE_COMPLEMENT)
+ {
+ final Location old_location = getLocation();
+
+ // if the event is contained within this feature then the feature
+ // sequence may have changed
+ final Range this_feature_range = getMaxRawRange();
+
+ boolean feature_changed = false;
+ Range eventRange = event.getRange();
+
+ // reverse complement feature if within contig region
+ if(eventRange.getStart() <= this_feature_range.getStart() &&
+ eventRange.getEnd() >= this_feature_range.getEnd())
+ {
+ try
+ {
+ final Location new_location =
+ getLocation().reverseComplement(event.getLength(), eventRange.getStart());
+
+ setLocationInternal(new_location);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - inconsistent location: " + e);
+ }
+ }
+ }
+ else if(event.getType() == SequenceChangeEvent.CONTIG_REORDER)
+ {
+ final Location old_location = getLocation();
+
+ final int new_base_pos = event.getPosition();
+ final int range_start = event.getRange().getStart();
+ final int range_end = event.getRange().getEnd();
+ final Range this_feature_range = getMaxRawRange();
+
+ // check if feature is effected
+ if( (this_feature_range.getStart() >= new_base_pos ||
+ this_feature_range.getStart() >= range_start) &&
+ (this_feature_range.getStart() < new_base_pos ||
+ this_feature_range.getStart() < range_end) )
+ {
+ try
+ {
+ final int diff;
+
+ if(range_start <= this_feature_range.getStart() &&
+ range_end >= this_feature_range.getEnd())
+ {
+ if(new_base_pos < range_start)
+ diff = new_base_pos-range_start;
+ else
+ diff = new_base_pos-range_end-1;
+ }
+ else
+ {
+ if(this_feature_range.getStart() < range_start)
+ diff = range_end-range_start+1;
+ else
+ diff = range_start-range_end-1;
+ }
+
+ Location new_location = moveSegments(diff);
+ setLocationInternal(new_location);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - inconsistent location: " + e);
+ }
+ }
+ }
+ else
+ {
+ final Location old_location = getLocation();
+
+ // if the event is contained within this feature then the feature
+ // sequence may have changed
+ final Range this_feature_range = getMaxRawRange();
+
+ boolean feature_changed = false;
+ int eventPosition = event.getPosition();
+
+ if(eventPosition >= this_feature_range.getStart() &&
+ eventPosition <= this_feature_range.getEnd() + 1)
+ {
+ // check each segment
+ final FeatureSegmentVector segments = getSegments();
+ int seg_size = segments.size();
+
+ for(int i = 0; i < seg_size; ++i)
+ {
+ final FeatureSegment this_segment = segments.elementAt(i);
+
+ final Range this_segment_range = this_segment.getRawRange();
+
+ if(eventPosition >= this_segment_range.getStart() &&
+ eventPosition <= this_segment_range.getEnd() + 1)
+ feature_changed = true;
+ }
+ }
+
+ updateEMBLFeatureLocation();
+
+ if(feature_changed)
+ {
+ resetCache();
+ locationChanged(old_location);
+ }
+ }
+ }
+ catch(ReadOnlyException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Invoked when an Option is changed.
+ **/
+ public void optionChanged(OptionChangeEvent event)
+ {
+ // if the eukaryotic mode option changes the sequence may change (ie. the
+ // start codon may need to be translated differently) - see
+ // getTranslation()
+ locationChanged(getLocation());
+ }
+
+ /**
+ * Returns the embl feature that was passed to the constructor.
+ **/
+ public uk.ac.sanger.artemis.io.Feature getEmblFeature()
+ {
+ return embl_feature;
+ }
+
+ /**
+ * Write this Feature to the given stream in the native format of this
+ * Feature. The output will be in Genbank format if this Feature is a
+ * Genbank feature and EMBL format otherwise.
+ * @param writer The record is written to this Writer.
+ **/
+ public void writeNative(final Writer writer)
+ throws IOException
+ {
+ if(getEmblFeature() instanceof StreamFeature)
+ ((StreamFeature)getEmblFeature()).writeToStream(writer);
+ else
+ {
+ final EntryInformation entry_info = getEntry().getEntryInformation();
+
+ // this is a hack to make the correct EntryInformation object available
+ // to the feature writing code.
+ final uk.ac.sanger.artemis.io.EmblDocumentEntry document_entry =
+ new uk.ac.sanger.artemis.io.EmblDocumentEntry(entry_info);
+
+ final uk.ac.sanger.artemis.io.Feature returned_feature =
+ document_entry.forcedAdd(new EmblStreamFeature(getEmblFeature()));
+
+ ((EmblStreamFeature)returned_feature).writeToStream(writer);
+ }
+ }
+
+ /**
+ * Write a PIR database record of this feature to the given Writer.
+ * @param writer The record is written to this Writer.
+ **/
+ public void writePIROfFeature(final Writer writer)
+ {
+ final String gene_name = getGeneName();
+
+ final String pir_name;
+
+ if(gene_name == null)
+ {
+ if(getLabel() == null)
+ pir_name = getKey().toString();
+ else
+ pir_name = getLabel();
+ }
+ else
+ pir_name = gene_name;
+
+ final String header_line =
+ ">BL;" + pir_name + ", " +
+ getEntry().getName() + " " +
+ getWriteRange() +
+ " MW:" + (int) getMolecularWeight();
+
+ final PrintWriter print_writer = new PrintWriter(writer);
+
+ print_writer.println(header_line);
+
+ final String translation_string =
+ getTranslation().toString().toUpperCase();
+
+ wrapAndWrite(print_writer, translation_string, 80);
+ print_writer.println("*");
+ print_writer.flush();
+ }
+
+ /**
+ * Write the bases of this feature to the given Writer.
+ * @param writer The bases are written to this Writer.
+ **/
+ public void writeBasesOfFeature(final Writer writer)
+ throws IOException
+ {
+ final FastaStreamSequence stream_sequence =
+ new FastaStreamSequence(getBases(),
+ getIDString() + ", " +
+ getWriteRange());
+
+ stream_sequence.writeToStream(writer);
+ }
+
+ /**
+ * Write the amino acid symbols of this feature to the given Writer.
+ * @param writer The amino acids are written to this Writer.
+ **/
+ public void writeAminoAcidsOfFeature(final Writer writer)
+ throws IOException
+ {
+ final StringBuffer header_buffer = new StringBuffer(">");
+
+ header_buffer.append(getSystematicName());
+ header_buffer.append(" ");
+ header_buffer.append(getIDString());
+ header_buffer.append(" ");
+
+ final String product = getProductString();
+
+ if(product == null)
+ header_buffer.append("undefined product");
+ else
+ header_buffer.append(product);
+
+ header_buffer.append(" ").append(getWriteRange()).append(" MW:");
+ header_buffer.append((int) getMolecularWeight());
+
+ final PrintWriter print_writer = new PrintWriter(writer);
+
+ print_writer.println(header_buffer);
+
+ final String translation_string =
+ getTranslation().toString().toUpperCase();
+
+ wrapAndWrite(print_writer, translation_string, 60);
+
+ print_writer.flush();
+ }
+
+ /**
+ * Return a String containing the bases upstream of this feature.
+ * @param count The number of (immediately) upstream bases to return.
+ **/
+ public String getUpstreamBases(final int count)
+ {
+ final int feature_start_base = getFirstBase();
+ final int start_base;
+ final int end_base;
+
+ if(feature_start_base == 1)
+ {
+ // there are no bases before this feature
+ return "";
+ }
+ else
+ {
+ end_base = feature_start_base - 1;
+
+ if(feature_start_base > count)
+ start_base = feature_start_base - count;
+ else
+ start_base = 1;
+ }
+
+ final String bases_string;
+
+ try
+ {
+ bases_string =
+ getStrand().getSubSequence(new Range(start_base, end_base));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ return bases_string;
+ }
+
+ /**
+ * Return a String containing the bases downstream of this feature.
+ * @param count The number of (immediately) downstream bases to return.
+ **/
+ public String getDownstreamBases(final int count)
+ {
+ final int feature_end_base = getLastBase();
+ final int start_base;
+ final int end_base;
+ final int sequenceLength = getSequenceLength();
+
+ if(feature_end_base == sequenceLength)
+ {
+ // there are no bases after this feature
+ return "";
+ }
+ else
+ {
+ start_base = feature_end_base + 1;
+
+ if(sequenceLength - feature_end_base > count)
+ end_base = feature_end_base + count;
+ else
+ end_base = sequenceLength;
+ }
+
+ final String bases_string;
+
+ try
+ {
+ bases_string =
+ getStrand().getSubSequence(new Range(start_base, end_base));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ return bases_string;
+ }
+
+ /**
+ * Helper method for writePIROfFeature().
+ * @return A string of the form "100:200 reverse" or "1:2222 forward".
+ **/
+ public String getWriteRange()
+ {
+ String partial = " ";
+ if(isPartial(true))
+ partial += "partial 5' ";
+ if(isPartial(false))
+ partial += "partial 3' ";
+
+ return (isForwardFeature() ?
+ getFirstCodingBaseMarker().getRawPosition() + ":" +
+ getLastBaseMarker().getRawPosition() + partial + "forward" :
+ getLastBaseMarker().getRawPosition() + ":" +
+ getFirstCodingBaseMarker().getRawPosition() + partial + "reverse");
+ }
+
+ /**
+ * If lookAt5prime is set to true then only return true if the 5' end is
+ * partial otherwise only return true if the 3' end is partial.
+ * @param lookAt5prime
+ * @return
+ */
+ private boolean isPartial(final boolean lookAt5prime)
+ {
+ try
+ {
+ boolean isDatabaseFeature = GeneUtils.isDatabaseEntry(getEmblFeature());
+ if(isDatabaseFeature)
+ {
+ if(lookAt5prime)
+ {
+ if(isForwardFeature())
+ {
+ if(getQualifierByName("Start_range") != null)
+ return true;
+ }
+ else if(getQualifierByName("End_range") != null)
+ return true;
+ }
+ else
+ {
+ if(isForwardFeature())
+ {
+ if(getQualifierByName("End_range") != null)
+ return true;
+ }
+ else if(getQualifierByName("Start_range") != null)
+ return true;
+ }
+ }
+ } catch (Exception e) {}
+ return getLocation().isPartial(lookAt5prime);
+ }
+
+ /**
+ * Write a String object to a PrintWriter object, wrapping it a the given
+ * coloumn.
+ * @param writer The String will be written to this object.
+ * @param string The String to write.
+ * @param wrap_column The lines of output will be no longer than this many
+ * characters.
+ **/
+ private void wrapAndWrite(final PrintWriter writer,
+ final String string,
+ final int wrap_column)
+ {
+ String remaining_string = string;
+
+ while(remaining_string.length() > 0)
+ {
+ int last_index = wrap_column;
+
+ if(wrap_column > remaining_string.length())
+ last_index = remaining_string.length();
+
+ final String write_string = remaining_string.substring(0, last_index);
+ writer.println(write_string);
+ remaining_string = remaining_string.substring(last_index);
+ }
+ }
+
+ /**
+ * Return this Feature as a EMBL, Genbank or GFF formatted String
+ * (depending on the type of this Feature).
+ **/
+ public String toString()
+ {
+ final StringWriter string_writer = new StringWriter();
+
+ try
+ {
+ writeNative(string_writer);
+ }
+ catch(IOException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ return string_writer.toString() ;
+ }
+
+ /**
+ * Return a Reader object that gives an EMBL format version of this
+ * Feature when read.
+ **/
+ public Reader toReader()
+ {
+ return new StringReader(toString());
+ }
+
+ /**
+ * Return true if and only if this Feature is on the forward strand.
+ **/
+ public boolean isForwardFeature()
+ {
+ if(getLocation().isComplement())
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Return the key of this Feature.
+ **/
+ public Key getKey()
+ {
+ return getEmblFeature().getKey();
+ }
+
+ /**
+ * Return true if and only if the key of this feature is protein feature -
+ * one that should be displayed on the translation lines of the
+ * FeatureDisplay component rather than on the forward or backward strand.
+ **/
+ public boolean isProteinFeature()
+ {
+ if(getKey().toString().startsWith("CDS") ||
+ getKey().equals(DatabaseDocument.EXONMODEL) ||
+ getKey().equals("exon") ||
+ getKey().equals("BLASTCDS") ||
+ getKey().equals("polypeptide"))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if the key of this feature is CDS feature.
+ **/
+ public boolean isCDS()
+ {
+ if(getKey().equals("CDS"))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if the key of this feature is CDS feature and
+ * the feature has a /partial qualifier.
+ **/
+ private boolean isPartialCDS()
+ {
+ try
+ {
+ if(getKey().equals("CDS") && getQualifierByName("partial") != null)
+ return true;
+ else
+ return false;
+ }
+ catch(InvalidRelationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return the Location of this Feature.
+ **/
+ public Location getLocation()
+ {
+ final Location current_location = getEmblFeature().getLocation();
+ return current_location;
+ }
+
+ /**
+ * Return a Vector containing the qualifiers of this Feature.
+ * XXX - FIXME - should return a copy or be private
+ **/
+ public QualifierVector getQualifiers()
+ {
+ // return the embl.QualifierVector from the underlying embl.Feature object
+ return getEmblFeature().getQualifiers();
+ }
+
+ /**
+ * Return the Entry that owns this object.
+ **/
+ public Entry getEntry()
+ {
+ return entry;
+ }
+
+ /**
+ * Return the value of the first /codon_start qualifier as an integer or
+ * returns 1 if codon_start doesn't exist or if it makes no sense.
+ **/
+ public int getCodonStart()
+ {
+ try
+ {
+ final String codon_start_string = getValueOfQualifier("codon_start");
+
+ if(codon_start_string == null)
+ {
+ // default value
+ return 1;
+ }
+
+ if(codon_start_string.equals("2"))
+ return 2;
+ else
+ {
+ if(codon_start_string.equals("3"))
+ return 3;
+ else
+ return 1; // default value
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ return 1;
+ }
+ }
+
+ /**
+ * Return the value of the first /score qualifier of this feature as an
+ * integer.
+ * @return The score or -1 if the feature has no /score qualifier.
+ **/
+ public int getScore()
+ {
+ try
+ {
+ final String score_string = getValueOfQualifier("score");
+
+ if(score_string == null)
+ {
+ // default value
+ return -1;
+ }
+
+ try
+ {
+ final int score_int = Float.valueOf(score_string).intValue();
+
+ if(score_int > 100)
+ return 100;
+
+ if(score_int < 0)
+ return 0;
+
+ return score_int;
+ }
+ catch(NumberFormatException e)
+ {
+ // assume there is no /score
+
+ return -1;
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Set the Entry that owns this object.
+ **/
+ public void setEntry(final Entry entry)
+ {
+ if(this.entry != null)
+ stopListening();
+
+ final Entry old_entry = this.entry;
+
+ this.entry = entry;
+
+ if(old_entry == entry)
+ return;
+ else
+ {
+ if(old_entry != null)
+ removeFeatureChangeListener(old_entry);
+
+ if(entry != null)
+ {
+ // the Entry object acts as a proxy for FeatureChange events, other
+ // objects can can addFeatureChangeListener() once on the Entry object
+ // instead of calling it for every Feature.
+ addFeatureChangeListener(getEntry());
+ }
+ }
+
+ if(this.entry != null)
+ startListening();
+ }
+
+ /**
+ * Change this Feature and it's underlying embl Feature object to the given
+ * key, location and qualifiers.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ * @exception OutOfRangeException Thrown if the location is out of
+ * range for this Entry.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ **/
+ public void set(Key new_key,
+ Location new_location,
+ QualifierVector new_qualifiers)
+ throws EntryInformationException, OutOfRangeException,
+ ReadOnlyException
+ {
+ try
+ {
+ set((Date)null, new_key, new_location, new_qualifiers);
+ }
+ catch(OutOfDateException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Change this Feature and it's underlying embl Feature object to the given
+ * key, location and qualifiers.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ * @exception OutOfRangeException Thrown if the location is out of
+ * range for this Entry.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfDateException If the key has changed in the server since
+ * the time given by datestamp. If datestamp argument is null then this
+ * exception will never be thrown.
+ **/
+ public void set(final Date datestamp,
+ final Key new_key,
+ final Location new_location,
+ final QualifierVector new_qualifiers)
+ throws EntryInformationException, OutOfRangeException,
+ ReadOnlyException, OutOfDateException
+ {
+ final Key old_key = getKey();
+ old_location = getLocation();
+ final QualifierVector old_qualifiers = getQualifiers().copy();
+
+ final int sequence_length;
+
+ if(!(getEntry().getBases().getSequence() instanceof PartialSequence))
+ {
+ sequence_length = getEntry().getBases().getLength();
+
+ if(new_location != null)
+ {
+ final Range span = new_location.getTotalRange();
+
+ if(span.getEnd() > sequence_length || span.getStart() < 1)
+ {
+ throw new OutOfRangeException(new_location.toString());
+ }
+ }
+ }
+
+ if(datestamp == null ||
+ !(getEmblFeature() instanceof DateStampFeature))
+ {
+ getEmblFeature().set(new_key, new_location, new_qualifiers);
+ }
+ else
+ {
+ ((DateStampFeature)getEmblFeature()).set(datestamp,
+ new_key,
+ new_location,
+ new_qualifiers);
+ }
+
+ resetCache();
+
+ if(new_location != old_location && segments != null)
+ reexamineSegments();
+
+ // now inform the listeners that a change has occured
+ final FeatureChangeEvent event =
+ new FeatureChangeEvent(this,
+ this,
+ old_key,
+ old_location,
+ old_qualifiers,
+ FeatureChangeEvent.ALL_CHANGED);
+
+ fireAction(feature_listener_list, event);
+ }
+
+ /**
+ * This method will send a FeatureChangeEvent with type LOCATION_CHANGED to
+ * all the FeatureChangeEvent listeners when a marker changes. It also
+ * calls resetCache(), because changing the location will change the
+ * translation and bases of the feature.
+ **/
+ private void locationChanged(final Location old_location,
+ final QualifierVector qualifiers,
+ int type)
+ {
+ resetCache();
+
+ // now inform the listeners that a change has occured
+ final FeatureChangeEvent feature_change_event =
+ new FeatureChangeEvent(this,
+ this,
+ null,
+ old_location,
+ qualifiers,
+ type);
+
+ this.old_location = getLocation();
+
+ fireAction(feature_listener_list, feature_change_event);
+ }
+
+ private void locationChanged(final Location old_location)
+ {
+ locationChanged(old_location, null,
+ FeatureChangeEvent.LOCATION_CHANGED);
+ }
+
+ /**
+ * Add the values from the given qualifier to the Qualifier object with the
+ * same name in this Feature or if there is no Qualifier with that name
+ * just add a copy of the argument.
+ * @param qualifier This object contians name and values to add.
+ * @return The Qualifier that was changed or created.
+ **/
+ public Qualifier addQualifierValues(Qualifier qualifier)
+ throws EntryInformationException, ReadOnlyException
+ {
+
+ final QualifierVector old_qualifiers = getQualifiers().copy();
+ final Qualifier return_qualifier;
+
+ final Qualifier current_qualifier =
+ getEmblFeature().getQualifierByName(qualifier.getName());
+
+ if(current_qualifier == null)
+ return_qualifier = qualifier.copy();
+ else
+ {
+ return_qualifier = current_qualifier.copy();
+ return_qualifier.addValues(qualifier.getValues());
+ }
+
+ setQualifier(return_qualifier);
+
+ // now inform the listeners that a change has occured
+ // THIS IS ALREADY DONE IN setQualifier()
+/* final FeatureChangeEvent event =
+ new FeatureChangeEvent(qualifier,
+ this,
+ null,
+ null,
+ old_qualifiers,
+ FeatureChangeEvent.QUALIFIER_CHANGED);
+
+ fireAction(feature_listener_list, event);
+*/
+ return return_qualifier;
+ }
+
+ /**
+ * Add the given Qualifier to this Feature, replacing any exisiting
+ * Qualifier that have the same name.
+ * @param qualifier The Qualifier to add.
+ **/
+ public void setQualifier(final Qualifier qualifier)
+ throws EntryInformationException, ReadOnlyException
+ {
+ final QualifierVector old_qualifiers = getQualifiers().copy();
+
+ getEmblFeature().setQualifier(qualifier);
+
+ // now inform the listeners that a change has occured
+ final FeatureChangeEvent event =
+ new FeatureChangeEvent(qualifier,
+ this,
+ null,
+ null,
+ old_qualifiers,
+ FeatureChangeEvent.QUALIFIER_CHANGED);
+
+ if(qualifier.getName().equals("transl_except"))
+ {
+ // discard cache
+ amino_acids = null;
+ }
+
+ fireAction(feature_listener_list, event);
+ }
+
+ /**
+ * Remove the Qualifier with the given name. If there is no Qualifier with
+ * that name then return immediately.
+ * @param name The qualifier name to look for.
+ **/
+ public void removeQualifierByName(final String name)
+ throws EntryInformationException, ReadOnlyException, OutOfDateException
+ {
+ if(getEmblFeature().getQualifierByName(name) == null)
+ {
+ // nothing to remove
+ return;
+ }
+
+ final QualifierVector old_qualifiers = getQualifiers().copy();
+
+ getEmblFeature().removeQualifierByName(name);
+
+ // now inform the listeners that a change has occured
+ final FeatureChangeEvent event =
+ new FeatureChangeEvent(this,
+ this,
+ null,
+ null,
+ old_qualifiers,
+ FeatureChangeEvent.QUALIFIER_CHANGED);
+
+ if(name.equals("transl_except"))
+ {
+ // discard cache
+ amino_acids = null;
+ }
+
+ fireAction(feature_listener_list, event);
+ }
+
+ /**
+ * Return the time when this feature last changed or null if this Feature
+ * doesn't support datestamps.
+ **/
+ public Date getDatestamp()
+ {
+ if(getEmblFeature() instanceof DateStampFeature)
+ return ((DateStampFeature)getEmblFeature()).getDatestamp();
+ else
+ return null;
+ }
+
+
+ /**
+ * Return true if and only if any qualifier in this feature contains the
+ * given text string.
+ * @param search_text The text to search for.
+ * @param fold_case If true then the text comparisons will ignore case.
+ * @param match_substring If true then matches to substrings are allowed.
+ * @param qualifier_names If null search all qualifiers, otherwise just
+ * search these names,
+ **/
+ public boolean containsText(final String search_text,
+ final boolean fold_case,
+ final boolean match_substring,
+ final StringVector qualifier_names)
+ {
+ return findOrReplaceText(search_text, fold_case, match_substring, false,
+ qualifier_names, null);
+ }
+
+
+ /**
+ * Return true if and only if any qualifier in this feature contains the
+ * given text string.
+ * @param search_text The text to search for.
+ * @param fold_case If true then the text comparisons will ignore case.
+ * @param match_substring If true then matches to substrings are allowed.
+ * @param qualifier_names If null search all qualifiers, otherwise just
+ * search these names,
+ * @param replaceText text to replace all qualifier value matches. If null
+ * then returns true if text found.
+ **/
+ public boolean findOrReplaceText(final String search_text,
+ final boolean fold_case,
+ final boolean match_substring,
+ final boolean deleteQualifier,
+ final StringVector qualifier_names,
+ final String replaceText)
+ {
+ final String real_search_text;
+
+ if(fold_case)
+ real_search_text = search_text.toLowerCase();
+ else
+ real_search_text = search_text;
+
+ final QualifierVector qualifiers = getQualifiers();
+ QualifierVector newQualifiers = null;
+ Vector<String> qualifiersToDelete = null;
+ int qual_size = qualifiers.size();
+
+ boolean hasReplacedOrDeletedText = false;
+
+ for(int i = 0; i < qual_size; ++i)
+ {
+ final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(i);
+
+ if(qualifier_names != null &&
+ !qualifier_names.contains(this_qualifier.getName()))
+ continue;
+
+ final StringVector values = this_qualifier.getValues();
+
+ if(values != null)
+ {
+ StringVector newValues = null;
+ StringVector deleteValues = null;
+ int val_size = values.size();
+
+ for(int values_index = 0; values_index < val_size;
+ ++values_index)
+ {
+ String this_value_string = (String)values.elementAt(values_index);
+
+ if(this_value_string == null)
+ continue;
+
+ if(fold_case)
+ this_value_string = this_value_string.toLowerCase();
+
+ if(! match_substring &&
+ this_value_string.equals(real_search_text) ||
+ match_substring &&
+ this_value_string.indexOf(real_search_text) != -1)
+ {
+ if(deleteQualifier)
+ {
+ if(deleteValues == null)
+ deleteValues = new StringVector();
+ deleteValues.add(this_value_string);
+ continue;
+ }
+ // found the match & return true if replace function off
+ if(replaceText == null)
+ return true;
+
+ String new_text = replaceText;
+ if(match_substring)
+ {
+ final String value_string = (String)values.elementAt(values_index);
+ new_text =
+ Pattern.compile(real_search_text, Pattern.CASE_INSENSITIVE)
+ .matcher(value_string).replaceAll(replaceText);
+ }
+ if(newValues == null)
+ newValues = this_qualifier.getValues();
+
+ newValues.setElementAt(new_text, values_index);
+ }
+ }
+
+ if(newValues != null)
+ {
+ if(newQualifiers == null)
+ newQualifiers = new QualifierVector();
+ newQualifiers.setQualifier(
+ new Qualifier(this_qualifier.getName(),newValues));
+ hasReplacedOrDeletedText = true;
+ }
+
+ if(deleteQualifier && deleteValues != null)
+ {
+ newValues = this_qualifier.getValues();
+ newValues.removeAll(deleteValues);
+
+ if(newValues.size() < 1)
+ {
+ if(qualifiersToDelete == null)
+ qualifiersToDelete = new Vector<String>();
+ qualifiersToDelete.add(this_qualifier.getName());
+ }
+ else
+ {
+ newQualifiers = new QualifierVector();
+ newQualifiers.setQualifier(
+ new Qualifier(this_qualifier.getName(),newValues));
+ }
+ hasReplacedOrDeletedText = true;
+ }
+ }
+ }
+
+ // delete qualifiers
+ if(qualifiersToDelete != null)
+ {
+ try
+ {
+ for (int i = 0; i < qualifiersToDelete.size(); i++)
+ removeQualifierByName(qualifiersToDelete.get(i));
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ if(newQualifiers != null)
+ {
+ for(int i=0; i<newQualifiers.size(); i++)
+ {
+ try
+ {
+ setQualifier((Qualifier) newQualifiers.elementAt(i));
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return hasReplacedOrDeletedText;
+ }
+
+ public boolean hasValidStartCodon()
+ {
+ return hasValidStartCodon(false);
+ }
+
+ /**
+ * Return true if and only if this feature ends in a valid stop codon.
+ **/
+ public boolean hasValidStartCodon(final boolean force)
+ {
+ if(!isCDS() && !force)
+ return true;
+
+ try
+ {
+ if(getQualifierByName("codon_start") != null &&
+ isPartialCDS() &&
+ getFirstBase() == 1)
+ return true;
+ }
+ catch(InvalidRelationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final AminoAcidSequence translation = getTranslation();
+
+ if(translation.length() < 1)
+ return false;
+
+ final String first_codon = getTranslationBases().substring(0, 3);
+
+ final StringVector start_codons = Options.getOptions().getStartCodons();
+
+// if(Options.getOptions().isEukaryoticMode())
+// start_codons = Options.getOptions().getEukaryoticStartCodons();
+// else
+// start_codons = Options.getOptions().getProkaryoticStartCodons();
+
+ if(start_codons.contains(first_codon))
+ return true;
+ else
+ return false;
+ }
+
+ public boolean hasValidStopCodon()
+ {
+ return hasValidStopCodon(false);
+ }
+
+ /**
+ * Return true if and only if this feature ends in a valid stop codon.
+ **/
+ public boolean hasValidStopCodon(final boolean force)
+ {
+ if(!isCDS() && !force)
+ return true;
+
+ if(isPartialCDS() &&
+ getLastBase() == getEntry().getBases().getLength())
+ return true;
+
+ final int bases_length = getBaseCount() - getCodonStart() + 1;
+
+ if(bases_length % 3 != 0)
+ return false;
+
+ if(bases_length < 3)
+ return false;
+
+ final String codon_string = getBases().substring(getBaseCount() - 3);
+
+ final char last_codon_translation =
+ AminoAcidSequence.getCodonTranslation(codon_string);
+
+ if(AminoAcidSequence.isStopCodon(last_codon_translation))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if this feature has a valid EMBL key (rather
+ * than an Artemis extension).
+ **/
+ public boolean hasValidEMBLKey()
+ {
+ if(getEntry().getEntryInformation().isValidKey(getKey()))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if this feature has all the required EMBL
+ * qualifiers (for submission to EMBL).
+ **/
+ public boolean hasRequiredQualifiers()
+ {
+ final StringVector required_qualifiers =
+ getEntry().getEntryInformation().getRequiredQualifiers(getKey());
+
+ if(required_qualifiers == null)
+ return true;
+
+ try
+ {
+ int reqd_size = required_qualifiers.size();
+ for(int i = 0; i < reqd_size; ++i)
+ {
+ if(getQualifierByName((String)required_qualifiers.elementAt(i)) == null)
+ return false;
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ return true;
+ }
+
+ /**
+ * This method will fix the end location of the feature so that the feature
+ * ends with a stop codon.
+ * @return true if and only if the feature has a stop codon or if a stop
+ * codon was successfully added (by moving the end by three bases).
+ **/
+ public boolean fixStopCodon()
+ throws ReadOnlyException
+ {
+ final Location old_location = getLocation();
+ final int codon_start = getCodonStart();
+
+ // there is no possible way to fix this feature
+ if((getBaseCount() - codon_start + 1) % 3 != 0)
+ return false;
+
+ final FeatureSegment last_segment = getSegments().lastElement();
+ final Marker last_base_marker = last_segment.getEnd();
+ final Marker last_codon_marker;
+
+ try
+ {
+ last_codon_marker = last_base_marker.moveBy(-2);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final Range last_codon_range;
+
+ try
+ {
+ last_codon_range = new Range(last_codon_marker.getPosition(),
+ last_codon_marker.getPosition() + 2);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final String last_codon_string =
+ last_codon_marker.getStrand().getSubSequence(last_codon_range);
+
+ final char last_amino_acid_char =
+ AminoAcidSequence.getCodonTranslation(last_codon_string);
+
+ if(AminoAcidSequence.isStopCodon(last_amino_acid_char))
+ return true;
+
+ final Range next_codon_range;
+
+ try
+ {
+ next_codon_range =
+ new Range(last_codon_marker.getPosition() + 3,
+ last_codon_marker.getPosition() + 5);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final String next_codon_string =
+ last_codon_marker.getStrand().getSubSequence(next_codon_range);
+
+ final char next_amino_acid_char =
+ AminoAcidSequence.getCodonTranslation(next_codon_string);
+
+ if(AminoAcidSequence.isStopCodon(next_amino_acid_char))
+ {
+ try
+ {
+ // move the end marker to the next codon
+ final Marker new_end_marker = last_base_marker.moveBy(3);
+
+ last_segment.setEndPosition(new_end_marker.getPosition());
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ updateEMBLFeatureLocation();
+ locationChanged(old_location);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * This method will move the start of the first segment of this feature so
+ * that the first codon is a start codon. If the segment already starts at
+ * a start codon it will return true immediately.
+ * @param trim_to_any If true then the features will be trimmed to the next
+ * codon with the base pattern: ATG, GTG or TTG, otherwise the features
+ * will be trimmed to ATG (Met) only.
+ * @param trim_to_next If true then the features will be trimmed to the
+ * next start codon (dependent on trim_to_any) regardless of whether the
+ * feature currently start on a start codon. If false then the feature
+ * will only be trimmed if the feature doesn't start on a start codon.
+ * @return true if and only if the trim worked. It will fail if the first
+ * segment is less than 6 bases long or if there is no start codon in the
+ * first segment or in the first 30% of the feature.
+ **/
+ public boolean trimStart(final boolean trim_to_any,
+ final boolean trim_to_next)
+ throws ReadOnlyException
+ {
+ final Location old_location = getLocation();
+ final FeatureSegment first_segment = getSegments().elementAt(0);
+
+ // too short to trim
+ if(first_segment.getBaseCount() < 6)
+ return false;
+
+ final BasePattern search_pattern;
+
+ try
+ {
+ if(trim_to_any)
+ search_pattern = new BasePattern("dtg");
+ else
+ search_pattern = new BasePattern("atg");
+ }
+ catch(BasePatternFormatException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final String first_codon_bases = getTranslationBases().substring(0, 3);
+
+ Marker current_marker = first_segment.getStart();
+
+ try
+ {
+ current_marker = current_marker.moveBy(getCodonStart() - 1);
+ }
+ catch(OutOfRangeException e)
+ {
+ return false;
+ }
+
+ if(search_pattern.matches(first_codon_bases))
+ {
+ // the segment already starts on a start codon
+ if(trim_to_next)
+ {
+ // move passed the current start codon
+ try
+ {
+ current_marker = current_marker.moveBy(3);
+ }
+ catch(OutOfRangeException e)
+ {
+ return false;
+ }
+ }
+ else
+ return true;
+ }
+
+ final int end_of_segment_position =
+ first_segment.getEnd().getPosition();
+
+ final int start_of_feature_position =
+ first_segment.getStart().getPosition();
+
+ while(true)
+ {
+ try
+ {
+ final String current_codon = Strand.getCodonAtMarker(current_marker);
+
+ if(search_pattern.matches(current_codon))
+ break;
+
+ current_marker = current_marker.moveBy(3);
+
+ final int current_marker_position = current_marker.getPosition();
+
+ if(current_marker_position > end_of_segment_position - 2 ||
+ (!trim_to_next &&
+ 1.0 * (current_marker_position - start_of_feature_position) /
+ getTranslationBasesLength() > 0.3))
+ {
+ // the current_marker is past the end of the first segment or is
+ // more than 30% into the feature
+ return false;
+ }
+ }
+ catch(OutOfRangeException e)
+ {
+ return false;
+ }
+ }
+
+ try
+ {
+ first_segment.setStartPosition(current_marker.getPosition());
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ try
+ {
+ // remove /codon_start (if it exists) since the new location will be in
+ // the correct frame
+ removeQualifierByName("codon_start");
+ }
+ catch(OutOfDateException _)
+ {
+ // do nothing
+ }
+ catch(EntryInformationException _)
+ {
+ // do nothing
+ }
+
+ updateEMBLFeatureLocation();
+ locationChanged(old_location);
+
+ return true;
+ }
+
+ /**
+ * Return the label (the value of the first /label qualifier) of this
+ * Feature, but return null if there is no label or if the label is "*".
+ **/
+ public String getLabel()
+ {
+ try
+ {
+ return getValueOfQualifier("label");
+ }
+ catch(InvalidRelationException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Return the gene name (the value of the first /gene qualifier) of this
+ * Feature or null if there it has no gene name.
+ **/
+ public String getGeneName()
+ {
+ try
+ {
+ return getValueOfQualifier("gene");
+ }
+ catch(InvalidRelationException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Return the name of this Feature to use in the display. The search order
+ * is /primary_name, /synonym, /systematic_id, /temporary_systematic_id,
+ * /gene, /locus_tag, /label. If none of the qualifiers exists return
+ * the feature key of this feature.
+ **/
+ public String getIDString()
+ {
+ final String picked_name =
+ pickName(Options.getOptions().getDisplayQualifierNames());
+
+ if(picked_name == null)
+ return getKey().toString();
+ else
+ return picked_name;
+ }
+
+ /**
+ * Return the systematic name of this feature.
+ **/
+ public String getSystematicName()
+ {
+ final String picked_name =
+ pickName(Options.getOptions().getSystematicQualifierNames());
+
+ if(picked_name == null)
+ return getIDString();
+ else
+ return picked_name;
+ }
+
+
+ /**
+ * Look at the qualifier_names one-by-one and return the first value of the
+ * first qualifier found.
+ **/
+ private String pickName(final StringVector qualifier_names)
+ {
+ int qn_size = qualifier_names.size();
+ for(int i = 0; i < qn_size; ++i)
+ {
+ try
+ {
+ final Qualifier qualifier =
+ getQualifierByName((String)qualifier_names.elementAt(i));
+
+ if(qualifier != null)
+ {
+ final StringVector values = qualifier.getValues();
+
+ if(values != null && values.size() > 0)
+ {
+ for(int j=0; j<values.size(); j++)
+ {
+ final String value = (String)values.elementAt(j);
+ if(value != null && !value.endsWith("current=false") && !value.equals(""))
+ return value;
+ }
+ }
+ }
+ }
+ catch(InvalidRelationException e){}
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the note (the value of the first /note qualifier) of this
+ * Feature or null if there is no /note.
+ **/
+ public String getNote()
+ {
+ try
+ {
+ return getValueOfQualifier("note");
+ }
+ catch(InvalidRelationException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Return the product (the value of the first /product qualifier) of this
+ * Feature or null if there is no /product.
+ **/
+ public String getProductString()
+ {
+ try
+ {
+ final String product = getValueOfQualifier("product");
+ if( product != null &&
+ getEmblFeature() instanceof GFFStreamFeature &&
+ product.startsWith("term=") )
+ return product.substring(5);
+
+ return product;
+ }
+ catch(InvalidRelationException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Return the number of bases in this feature not inclding introns (total of
+ * all segments).
+ **/
+ public int getBaseCount()
+ {
+ if(base_count == -1)
+ {
+ int new_base_count = 0;
+ int seg_size = getSegments().size();
+
+ for(int i = 0; i < seg_size; ++i)
+ new_base_count += getSegments().elementAt(i).getBaseCount();
+
+ base_count = new_base_count;
+ }
+
+ return base_count;
+ }
+
+ /**
+ * Return the bases in this feature (all bases in all segments).
+ **/
+ public String getBases()
+ {
+ if(bases == null)
+ {
+ final StringBuffer buffer = new StringBuffer();
+ int seg_size = getSegments().size();
+
+ for(int i = 0; i < seg_size; ++i)
+ buffer.append(getSegments().elementAt(i).getBases());
+
+ bases = buffer.toString();
+ }
+
+ return bases;
+ }
+
+ /**
+ * Return the number of coding bases in this feature, that is, the number
+ * of codons divided by three.
+ **/
+ public int getTranslationBasesLength()
+ {
+ final int codon_start = getCodonStart();
+ final int start_index = codon_start - 1;
+
+ int end_index = getBases().length();
+
+ final int mod_value = (end_index - start_index) % 3;
+
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ end_index -= mod_value;
+
+ final int new_length = end_index - start_index;
+
+ if(new_length >= 3)
+ {
+ // remove the stop codon (if present)
+
+ final String last_codon = getBases().substring(end_index - 3);
+
+ final char amino_acid_char =
+ AminoAcidSequence.getCodonTranslation(last_codon);
+
+ if(AminoAcidSequence.isStopCodon(amino_acid_char))
+ return new_length - 3;
+ }
+
+ return new_length;
+ }
+
+ /**
+ * Return the bases in this feature (all bases in all segments), taking
+ * the /start_codon qualifier into consideration.
+ **/
+ public String getTranslationBases()
+ {
+ final int codon_start = getCodonStart();
+ final int start_index = codon_start - 1;
+ final int end_index = getTranslationBasesLength() + start_index;
+
+ return getBases().substring(start_index, end_index);
+ }
+
+ /**
+ * Return an AminoAcidSequence containing the translated sequence of the
+ * feature with changes from /transl_except qualifiers added. Return null
+ * if there are no valid /transl_except qualifiers (ie. parsable and with
+ * coordinates in range).
+ **/
+ private AminoAcidSequence fixTranslationExceptions()
+ {
+ final String amino_acids_string = amino_acids.toString();
+ String new_amino_acids_string = amino_acids_string;
+
+ try
+ {
+ final Qualifier except_qualifier =
+ getQualifierByName("transl_except");
+
+ if(except_qualifier != null)
+ {
+ final StringVector values = except_qualifier.getValues();
+
+ if(values != null)
+ {
+ for(int i = 0; i < values.size(); ++i)
+ {
+ final String value = (String)values.elementAt(i);
+
+ final String START_STRING = "(pos:";
+ final String COMMA_STRING = ",aa:";
+
+ if(value.startsWith(START_STRING) && value.endsWith(")"))
+ {
+ final int comma_pos = value.lastIndexOf(COMMA_STRING);
+
+ if(comma_pos >= 0)
+ {
+ final String location_part =
+ value.substring(START_STRING.length(), comma_pos);
+
+ final String aa_part =
+ value.substring(comma_pos + COMMA_STRING.length(),
+ value.length() - 1);
+
+ char aa_part_one_letter_code =
+ AminoAcidSequence.getOneLetterCode(aa_part);
+
+ // aa_part is probably "OTHER"
+ if(aa_part_one_letter_code == (char)-1)
+ aa_part_one_letter_code = '.';
+
+ final Location location = new Location(location_part);
+
+ int start_base_in_feature;
+
+ if(isForwardFeature())
+ {
+ start_base_in_feature =
+ location.getFirstBase() - getRawFirstBase() -
+ (getCodonStart() - 1);
+ }
+ else
+ {
+ start_base_in_feature =
+ getRawLastBase() - location.getLastBase() -
+ (getCodonStart() - 1);
+ }
+
+ if(start_base_in_feature >= 0 &&
+ start_base_in_feature <
+ getBaseCount() - getCodonStart() + 1)
+ {
+ final int start_aa_in_feature = start_base_in_feature / 3;
+
+ new_amino_acids_string =
+ new_amino_acids_string.substring(0, start_aa_in_feature) +
+ aa_part_one_letter_code +
+ new_amino_acids_string.substring(start_aa_in_feature + 1);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch(InvalidRelationException e) {}
+ catch(LocationParseException e) {}
+
+ if(new_amino_acids_string == amino_acids_string)
+ return null;
+ else
+ return new AminoAcidSequence(new_amino_acids_string);
+ }
+
+ /**
+ * Return the translation of the bases in this feature.
+ **/
+ public AminoAcidSequence getTranslation()
+ {
+ if(amino_acids == null)
+ {
+ amino_acids =
+ AminoAcidSequence.getTranslation(getTranslationBases(), true);
+
+ // a very short feature
+ if(amino_acids.length() == 0)
+ return amino_acids;
+
+ final AminoAcidSequence fixed_amino_acids =
+ fixTranslationExceptions();
+
+ if(fixed_amino_acids != null)
+ amino_acids = fixed_amino_acids;
+
+ if(isCDS() && !isPartialCDS() && hasValidStartCodon())
+ {
+ if(amino_acids.elementAt(0) != 'm')
+ {
+ // translation should always start with M
+ final String amino_acids_string = amino_acids.toString();
+
+ final String new_amino_acids_string =
+ 'M' + amino_acids_string.substring(1);
+
+ amino_acids = new AminoAcidSequence(new_amino_acids_string);
+ }
+ }
+ }
+ return amino_acids;
+ }
+
+ /**
+ * Return the number of amino acids in this feature (total of all segments).
+ **/
+ public int getAACount()
+ {
+ if(aa_count == -1)
+ aa_count = getTranslationBasesLength() / 3;
+
+ return aa_count;
+ }
+
+ /**
+ * Return the total molecular weight of the amino acid translation of the
+ * bases of this feature.
+ **/
+ public float getMolecularWeight()
+ {
+ return getTranslation().getMolecularWeight();
+ }
+
+ /**
+ * Return the number of occurrences of the given codon in the frame
+ * starting at the correct frame for this feature (see the
+ * getTranslationBases() method). The first index is the first base, the
+ * second index is the second base, etc. The indices are the same as those
+ * for Bases.letter_index. An example: calling getCodonCount(0, 0, 1)
+ * will return the count of the codon with the sequence t,t,c.
+ **/
+ public int getCodonCount(final int first,
+ final int second,
+ final int third)
+ {
+ if(codon_counts == null)
+ setArrays();
+
+ return codon_counts[first][second][third];
+ }
+
+ /**
+ * Return a count of the occurrences of each amino acids in the translation
+ * of the bases of the current feature. The index should be the same as
+ * the index that is returned by AminoAcidSequence.getSymbolIndex().
+ **/
+ public int getResidueCount(final int amino_acid_index)
+ {
+ if(residue_counts == null)
+ setArrays();
+
+ return residue_counts[amino_acid_index];
+ }
+
+ /**
+ * Return the count of each base that appears in each codon position (1, 2
+ * or 3). The first index is the codon position and the second is the base
+ * (indexed in the same way as Bases.letter_index). (set by resetCache ()).
+ **/
+ public int getPositionalBaseCount(final int codon_base_position,
+ final int base_index)
+ {
+ if(positional_base_counts == null)
+ setArrays();
+
+ return positional_base_counts[codon_base_position][base_index];
+ }
+
+ /**
+ * Return the count each base in the feature. The indices are the same as
+ * those for Bases.letter_index. (set by resetCache()).
+ **/
+ public int getBaseCount(final int base_index)
+ {
+ if(base_counts == null)
+ setArrays();
+
+ return base_counts[base_index];
+ }
+
+ /**
+ * Return the codon position 1 and 2 correlation score for the bases of
+ * this feature.
+ **/
+ public double get12CorrelationScore()
+ {
+ final int t1_count =
+ getPositionalBaseCount(0, Bases.getIndexOfBase('t'));
+ final int c1_count =
+ getPositionalBaseCount(0, Bases.getIndexOfBase('c'));
+ final int a1_count =
+ getPositionalBaseCount(0, Bases.getIndexOfBase('a'));
+ final int g1_count =
+ getPositionalBaseCount(0, Bases.getIndexOfBase('g'));
+
+ final int t2_count =
+ getPositionalBaseCount(1, Bases.getIndexOfBase('t'));
+ final int c2_count =
+ getPositionalBaseCount(1, Bases.getIndexOfBase('c'));
+ final int a2_count =
+ getPositionalBaseCount(1, Bases.getIndexOfBase('a'));
+ final int g2_count =
+ getPositionalBaseCount(1, Bases.getIndexOfBase('g'));
+
+ final int c3_count =
+ getPositionalBaseCount(2, Bases.getIndexOfBase('c'));
+ final int g3_count =
+ getPositionalBaseCount(2, Bases.getIndexOfBase('g'));
+
+ final int base_total = getTranslationBases().length();
+
+ final double cor1_2_score =
+ 3.0 * t1_count/base_total *
+ Codon12CorrelationAlgorithm.correlation_score_factors_1[0] +
+ 3.0 * c1_count/base_total *
+ Codon12CorrelationAlgorithm.correlation_score_factors_1[1] +
+ 3.0 * a1_count/base_total *
+ Codon12CorrelationAlgorithm.correlation_score_factors_1[2] +
+ 3.0 * g1_count/base_total *
+ Codon12CorrelationAlgorithm.correlation_score_factors_1[3] +
+ 3.0 * t2_count/base_total *
+ Codon12CorrelationAlgorithm.correlation_score_factors_2[0] +
+ 3.0 * c2_count/base_total *
+ Codon12CorrelationAlgorithm.correlation_score_factors_2[1] +
+ 3.0 * a2_count/base_total *
+ Codon12CorrelationAlgorithm.correlation_score_factors_2[2] +
+ 3.0 * g2_count/base_total *
+ Codon12CorrelationAlgorithm.correlation_score_factors_2[3] +
+ 0.5; // add 0.5 because that is what the old uk.ac.sanger.artemis did
+
+ return cor1_2_score;
+ }
+
+ /**
+ * Return the percentage GC content of the bases of this feature.
+ **/
+ public double getPercentGC()
+ {
+ final String bases = getBases();
+
+ if(bases.length() > 0)
+ {
+ int gc_count = 0;
+
+ final char[] sequence_chars = new char[bases.length()];
+
+ bases.getChars(0, bases.length(), sequence_chars, 0);
+
+ for(int i = 0 ; i < bases.length() ; ++i)
+ {
+ final char this_char = sequence_chars[i];
+ if(this_char == 'g' || this_char == 'c')
+ ++gc_count;
+ }
+
+ return 100.0 * gc_count / bases.length();
+ }
+ else
+ return 0.0;
+ }
+
+ /**
+ * Return the first (lowest) base number of this Feature. The lowest base
+ * of the Feature is the minimum (with respect to the Feature's Strand) of
+ * the lowest bases of all the segments of this Feature.
+ * @return The first base of this feature or 0 if this feature doesn't know
+ * where it is.
+ **/
+ public int getFirstBase()
+ {
+ final Marker first_base_marker = getFirstBaseMarker();
+
+ if(first_base_marker == null)
+ return 0;
+ else
+ return first_base_marker.getPosition ();
+ }
+
+ /**
+ * Return the last (highest) base number of this Feature. The highest base
+ * of the Feature is the maximum (with respect to the Feature's Strand) of
+ * the highest bases of all the segments of this Feature.
+ * @return The last base of this feature or 0 if this feature doesn't know
+ * where it is.
+ **/
+ public int getLastBase()
+ {
+ final Marker last_base_marker = getLastBaseMarker();
+
+ if(last_base_marker == null)
+ return 0;
+ else
+ return last_base_marker.getPosition ();
+ }
+
+ /**
+ * Return true if and only if the first base of this Feature is less than
+ * the argument Feature with respect to the Feature's Strand.
+ **/
+ public boolean lessThan(Feature other_feature)
+ {
+ if(getFirstBase() < other_feature.getFirstBase())
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if the first base of this Feature is greater
+ * than the argument Feature with respect to the Feature's Strand.
+ **/
+ public boolean greaterThan(Feature other_feature)
+ {
+ if(getFirstBase() > other_feature.getFirstBase())
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Return the first (lowest) base number of this Feature. The lowest base
+ * of the Feature is the minimum (with respect to the raw sequence) of the
+ * lowest bases of all the segments of this Feature.
+ * @return The first base of this feature or 0 if this feature doesn't know
+ * where it is.
+ **/
+ public int getRawFirstBase()
+ {
+ final int A_BIG_NUMBER = 0x7fffffff; // largest integer
+ int minimum = A_BIG_NUMBER;
+
+ for(int i = 0; i < getSegments().size(); ++i)
+ {
+ final int current_minimum;
+ if(isForwardFeature())
+ {
+ current_minimum =
+ getSegments().elementAt(i).getStart().getRawPosition();
+ }
+ else
+ {
+ current_minimum =
+ getSegments().elementAt(i).getEnd().getRawPosition();
+ }
+
+ if(current_minimum < minimum)
+ minimum = current_minimum;
+ }
+
+ if(minimum == A_BIG_NUMBER)
+ return 0;
+ else
+ return minimum;
+ }
+
+ /**
+ * Return the last (highest) base number of this Feature. The highest base
+ * of the Feature is the maximum (with respect to the raw sequence) of the
+ * highest bases of all the segments of this Feature.
+ * @return The last base of this feature or 0 if this feature doesn't know
+ * where it is.
+ **/
+ public int getRawLastBase()
+ {
+ final int A_SMALL_NUMBER = -1;
+ int maximum = A_SMALL_NUMBER;
+ int seg_size = getSegments().size();
+
+ for(int i = 0; i < seg_size; ++i)
+ {
+ final int current_maximum;
+ if(isForwardFeature())
+ {
+ current_maximum =
+ getSegments().elementAt(i).getEnd().getRawPosition();
+ }
+ else
+ {
+ current_maximum =
+ getSegments().elementAt(i).getStart().getRawPosition();
+ }
+
+ if(current_maximum > maximum)
+ maximum = current_maximum;
+ }
+
+ if(maximum == A_SMALL_NUMBER)
+ return 0;
+ else
+ return maximum;
+ }
+
+ /**
+ * Return the maximum extent of this feature. The range returned starts at
+ * getRawFirstBase() and ends at getRawLastBase().
+ **/
+ public Range getMaxRawRange()
+ {
+ try
+ {
+ return new Range(getRawFirstBase(), getRawLastBase());
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+
+ /**
+ * Return true if and only if the first base of this Feature is less than
+ * the argument Feature with to the raw sequence.
+ **/
+ public boolean rawLessThan(Feature other_feature)
+ {
+ if(getRawFirstBase() < other_feature.getRawFirstBase())
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if the first base of this Feature is greater
+ * than the argument Feature with to the raw sequence.
+ **/
+ public boolean rawGreaterThan(Feature other_feature)
+ {
+ if(getRawFirstBase() > other_feature.getRawFirstBase())
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return the Marker of the first (lowest) base number of this Feature.
+ * The lowest base of the Feature is the minimum of the lowest bases of all
+ * the segments of this Feature.
+ * @return The Marker of the first base of this feature or null if this
+ * feature doesn't know where it is.
+ **/
+ public Marker getFirstBaseMarker()
+ {
+ final int A_BIG_NUMBER = 0x7fffffff; // largest integer
+ int minimum = A_BIG_NUMBER;
+ Marker minimum_marker = null;
+
+ for(int i = 0 ; i < getSegments().size() ; ++i)
+ {
+ final Marker current_marker = getSegments().elementAt(i).getStart();
+ final int current_minimum = current_marker.getPosition();
+
+ if(current_minimum < minimum)
+ {
+ minimum = current_minimum;
+ minimum_marker = current_marker;
+ }
+ }
+
+ return minimum_marker;
+ }
+
+ /**
+ * Return the Marker of the last (highest) base number of this Feature.
+ * The highest base of the Feature is the maximum of the highest bases of
+ * all the segments of this Feature.
+ * @return The Marker of the last base of this feature or null if this
+ * feature doesn't know where it is.
+ **/
+ public Marker getLastBaseMarker()
+ {
+ final long A_SMALL_NUMBER = -1;
+ long maximum = A_SMALL_NUMBER;
+ Marker maximum_marker = null;
+
+ for(int i = 0; i < getSegments().size(); ++i)
+ {
+ final Marker current_marker = getSegments().elementAt(i).getEnd();
+ final int current_maximum = current_marker.getPosition();
+
+ if(current_maximum > maximum)
+ {
+ maximum = current_maximum;
+ maximum_marker = current_marker;
+ }
+ }
+
+ return maximum_marker;
+ }
+
+ /**
+ * Return the Marker of the first base of the first (coding) codon of this
+ * feature. This is the minimum of the lowest bases of all the segments of
+ * this Feature plus codon_start minus one.
+ * @return The Marker of the first coding base of this feature or null if
+ * this feature doesn't know where it is.
+ **/
+ public Marker getFirstCodingBaseMarker()
+ {
+ final Marker first_base_marker = getFirstBaseMarker();
+
+ try
+ {
+ return first_base_marker.moveBy(getCodonStart() - 1);
+ }
+ catch(OutOfRangeException e)
+ {
+ // if we are at the end of the sequence then just return the first base
+ // marker
+ return first_base_marker;
+ }
+ }
+
+ /**
+ * Given a base position in this feature, return the Marker of the
+ * corresponding base on the Strand object.
+ * @param position The position within the feature - not counting introns.
+ * @exception OutOfRangeException Thrown if the new position is less than 1
+ * or greater than the length of the sequence.
+ **/
+ public Marker getPositionInSequence (final int position)
+ throws OutOfRangeException
+ {
+ // the position is outside of the feature
+ if(position < 1)
+ throw new OutOfRangeException("position: " + position);
+
+ // subtract one because positions are numbered from 1 not 0
+ int bases_remaining = position - 1;
+
+ for(int i = 0; i < segments.size(); ++i)
+ {
+ final FeatureSegment this_segment = segments.elementAt(i);
+
+ if(bases_remaining < this_segment.getBaseCount())
+ return this_segment.getStart().moveBy(bases_remaining);
+ else
+ bases_remaining -= this_segment.getBaseCount();
+ }
+
+ // the position is outside of the feature
+ throw new OutOfRangeException("position: " + position);
+ }
+
+ /**
+ * Return the (base) position in this feature of the given Marker or -1 if
+ * the given Marker does not correspond to any bases in this Feature.
+ **/
+ public int getFeaturePositionFromMarker(final Marker base_marker)
+ {
+ // counts the bases in the previous segments as we loop over them
+ int bases_so_far = 0;
+ final int marker_position = base_marker.getPosition();
+ int seg_size = segments.size();
+
+ for(int i = 0; i < seg_size; ++i)
+ {
+ final FeatureSegment this_segment = segments.elementAt(i);
+ final int this_segment_start = this_segment.getStart().getPosition();
+
+ final int marker_start_diff = marker_position - this_segment_start;
+
+ if(marker_start_diff >= 0 &&
+ marker_start_diff < this_segment.getBaseCount())
+ {
+ return bases_so_far + marker_start_diff;
+ }
+
+ bases_so_far += this_segment.getBaseCount();
+ }
+
+ return -1;
+ }
+
+ public void resetColour()
+ {
+ colour = null;
+ }
+
+ /**
+ * Return the colour (the value of the /colour qualifier) of this Feature
+ * or null if there is no colour qualifier and no default colour.
+ **/
+ public Color getColour()
+ {
+ if(colour != null)
+ return colour;
+
+ String colour_qualifier;
+ try
+ {
+ colour_qualifier = getValueOfQualifier("colour");
+
+ // it's international "be nice to Americans day":
+ if(colour_qualifier == null)
+ colour_qualifier = getValueOfQualifier("color");
+ }
+ catch(InvalidRelationException e)
+ {
+ colour_qualifier = null;
+ }
+
+ // use default colour for this type of feature
+ if(colour_qualifier == null)
+ {
+ colour = Options.getOptions().getDefaultFeatureColour(getKey());
+ return colour;
+ }
+
+ final StringVector colours = StringVector.getStrings(colour_qualifier);
+
+ if(colours.size() < 1)
+ {
+ colour = Options.getOptions().getDefaultFeatureColour(getKey());
+ return colour;
+ }
+
+ try
+ {
+ if(colours.size() == 3)
+ {
+ int red = Integer.parseInt((String)colours.elementAt(0));
+ int green = Integer.parseInt((String)colours.elementAt(1));
+ int blue = Integer.parseInt((String)colours.elementAt(2));
+
+ if(red < 0)
+ red = 0;
+
+ if(red > 255)
+ red = 255;
+
+ if(green < 0)
+ green = 0;
+
+ if(green > 255)
+ green = 255;
+
+ if(blue < 0)
+ blue = 0;
+
+ if(blue > 255)
+ blue = 255;
+
+ colour = new Color(red, green, blue);
+ return colour;
+ }
+ else
+ {
+ final String colour_string = (String)colours.elementAt(0);
+
+ final int colour_number;
+
+ colour_number = Integer.parseInt(colour_string);
+ colour = Options.getOptions().getColorFromColourNumber(colour_number);
+ return colour;
+ }
+ }
+ catch(NumberFormatException e)
+ {
+ // use default colour for this type of feature
+ colour = Options.getOptions().getDefaultFeatureColour(getKey());
+ return colour;
+ }
+ }
+
+ /**
+ * Return a vector of String objects containing all the values of the
+ * qualifier with the given name. If the Feature has two qualifiers:
+ * /db_xref="PID:e1256466" /db_xref="SPTREMBL:O43016"
+ * then this method will return a vector containing "PID:e1256466" and
+ * "SPTREMBL:O43016".
+ * Returns null if the is no Qualifier with the given name.
+ **/
+ public StringVector getValuesOfQualifier(String qualifier_name)
+ throws InvalidRelationException
+ {
+ final Qualifier this_qualifier = getQualifierByName(qualifier_name);
+
+ if(this_qualifier == null)
+ return null;
+ else
+ return this_qualifier.getValues();
+ }
+
+ /**
+ * Return the Qualifier in this Feature with the given name or null if
+ * there no such Qualifier.
+ * @param name The qualifier name to look for.
+ **/
+ public Qualifier getQualifierByName(final String name)
+ throws InvalidRelationException
+ {
+ return getEmblFeature().getQualifierByName(name);
+ }
+
+ /**
+ * Return the value of the first occurrence of the given qualifier in this
+ * Feature or null if this feature doesn't have this qualifier. If this
+ * feature has the given qualifier but the qualifier doesn't have a value
+ * then it will return "" - a zero length String.
+ * @param qualifier_name The name of the qualifier to search for.
+ **/
+ public String getValueOfQualifier(String qualifier_name)
+ throws InvalidRelationException
+ {
+ final StringVector values = getValuesOfQualifier(qualifier_name);
+
+ if(values == null)
+ return null;
+ else
+ {
+ if(values.size() == 0)
+ return "";
+ else
+ {
+ final String first_element = (String)values.elementAt(0);
+
+ if(first_element == null)
+ return "";
+ else
+ return first_element;
+ }
+ }
+ }
+
+ /**
+ * Remove this Feature from the Entry object that contains it. It will
+ * also be removed from the underlying embl.Entry object.
+ **/
+ public void removeFromEntry()
+ throws ReadOnlyException
+ {
+ getEntry().remove(this);
+ }
+
+ /**
+ * Return the vector containing the references of the FeatureSegment
+ * objects of this Feature.
+ **/
+ public FeatureSegmentVector getSegments()
+ {
+ final Location current_location = getEmblFeature().getLocation();
+
+ if(segments == null)
+ createSegments();
+ else
+ {
+ // see comment on old_location
+ if(current_location != old_location)
+ {
+ reexamineSegments();
+ if(!old_location.equals(current_location))
+ {
+ locationChanged(old_location);
+ old_location = current_location;
+ }
+ }
+ }
+
+ return segments;
+ }
+
+ /**
+ * Examine each Range in the Location of this Feature. Remove
+ * FeatureSegments that don't correspond to Ranges in the Location. Add
+ * a FeatureSegment for each extra Range that has appeared. This is
+ * necessary because the feature location may change at any time in a
+ * client/server environment.
+ **/
+ private void reexamineSegments()
+ {
+ stopSegmentsListening();
+
+ final Location current_location = getEmblFeature().getLocation();
+ final RangeVector ranges = current_location.getRanges();
+ final Vector new_segments = new Vector();
+
+ int ranges_size = ranges.size();
+ new_segments.setSize(ranges_size);
+
+ FeatureSegmentVector old_segments =
+ (FeatureSegmentVector)segments.clone();
+
+ // if the feature has changed strand, give up and recreate all
+ // segments
+ if(current_location.isComplement() != old_location.isComplement())
+ old_segments.removeAllElements();
+
+ // first find exact matches
+EXACT_SEGMENTS:
+ for(int old_segment_index = old_segments.size() - 1; old_segment_index >= 0;
+ --old_segment_index)
+ {
+ final FeatureSegment old_segment =
+ old_segments.elementAt(old_segment_index);
+
+ for(int range_index = 0; range_index < ranges_size;
+ ++range_index)
+ {
+ final Range new_range = (Range)ranges.elementAt(range_index);
+
+ // there is a Range in the new Location that exactly matches a Range
+ // in an old FeatureSegment so reuse the old FeatureSegment
+ if(old_segment.getRawRange().equals(new_range))
+ {
+ old_segment.setRange(new_range);
+ new_segments.setElementAt(old_segment, range_index);
+ old_segments.removeElementAt(old_segment_index);
+ continue EXACT_SEGMENTS;
+ }
+ }
+ }
+
+ // find matches where the start or end has changed
+CHANGED_END:
+ for(int old_segment_index = old_segments.size() - 1; old_segment_index >= 0;
+ --old_segment_index)
+ {
+ final FeatureSegment old_segment =
+ old_segments.elementAt(old_segment_index);
+
+ for(int range_index = 0; range_index < ranges_size;
+ ++range_index)
+ {
+ final Range new_range = (Range)ranges.elementAt(range_index);
+
+ if(old_segment.getRawRange().getStart() ==
+ new_range.getStart() ||
+ old_segment.getRawRange().getEnd() ==
+ new_range.getEnd())
+ {
+ // there is a Range in the new Location that partly matches a Range
+ // in an old FeatureSegment so reuse the old FeatureSegment
+ old_segment.setRange(new_range);
+ new_segments.setElementAt(old_segment, range_index);
+ old_segments.removeElementAt(old_segment_index);
+ continue CHANGED_END;
+ }
+ }
+ }
+
+ // create a segment for each range that we don't have a segment for
+ for(int new_segment_index = 0; new_segment_index < ranges_size;
+ ++new_segment_index)
+ {
+ final Range missing_range = (Range)ranges.elementAt(new_segment_index);
+
+ if(new_segments.elementAt(new_segment_index) == null)
+ {
+ // new Range -> create a segment
+ new_segments.setElementAt(makeSegment(missing_range),
+ new_segment_index);
+ }
+ }
+
+ segments = new FeatureSegmentVector();
+
+ for(int i = 0; i < ranges.size(); ++i)
+ segments.addElementAtEnd((FeatureSegment) new_segments.elementAt(i));
+
+ startSegmentsListening();
+ }
+
+ /**
+ * Return the reference of the Strand that this Feature and it's
+ * FeatureSegment objects is associated with.
+ **/
+ public Strand getStrand()
+ {
+ // get the strand of the first entry in the entry group
+ if(isForwardFeature())
+ return entry.getBases().getForwardStrand();
+ else
+ return entry.getBases().getReverseStrand();
+ }
+
+ /**
+ * Return the reference of a new copy of this Feature. This method will
+ * update the underlying embl.Entry and the new Feature will be installed
+ * in the same Entry object as this one.
+ * @return The reference of the new feature.
+ **/
+ public Feature duplicate()
+ throws ReadOnlyException
+ {
+ return duplicate(false);
+ }
+
+ /**
+ * Return the reference of a new copy of this Feature. This method will
+ * update the underlying embl.Entry and the new Feature will be installed
+ * in the same Entry object as this one.
+ * @param isDuplicatedInChado if true then create new ID and update in chado
+ * @return
+ * @throws ReadOnlyException
+ */
+ public Feature duplicate(final boolean isDuplicatedInChado)
+ throws ReadOnlyException
+ {
+ uk.ac.sanger.artemis.io.Feature new_embl_feature;
+
+ if(getEmblFeature() instanceof GFFStreamFeature)
+ new_embl_feature = new GFFStreamFeature(getEmblFeature(), isDuplicatedInChado);
+ else
+ new_embl_feature = new EmblStreamFeature(getEmblFeature());
+
+ final Feature return_feature = new Feature(new_embl_feature);
+
+ try
+ {
+ getEntry().add(return_feature, !isDuplicatedInChado, false);
+ }
+ catch(EntryInformationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ return return_feature;
+ }
+
+ /**
+ * Move this Feature to the given Entry. The move is achieved by removing
+ * the feature from it's current Entry and adding to the the
+ * destination_entry.
+ * @param destination_entry The Feature will be moved to this Entry.
+ * @param force If true then invalid qualifiers will be quietly thrown away
+ * when saving. Features with invalid keys won't be moved. "Invalid"
+ * means that the key/qualifier is not allowed to occur in the
+ * destination type (probably determined by the default EntryInformation
+ * object of the destination type). If false an
+ * EntryInformationException will be thrown for invalid keys or
+ * qualifiers.
+ * @exception EntryInformationException Thrown if force is false and if the
+ * destination type cannot contain the Key, Qualifier or Key/Qualifier
+ * combination of the given feature.
+ **/
+ public void moveTo(final Entry destination_entry, final boolean force)
+ throws EntryInformationException, OutOfRangeException,
+ ReadOnlyException
+ {
+ if(destination_entry.isReadOnly())
+ throw new ReadOnlyException();
+ else
+ {
+ // this (hack) causes the calls to stopListening() and startListening()
+ // in setEntry() to return immediately which improves the speed a lot
+ // when many features are loaded
+ startListening();
+
+ try
+ {
+ final Entry old_entry = getEntry();
+
+ getEntry().remove(this);
+
+ try
+ {
+ destination_entry.add(this, force);
+ }
+ catch(EntryInformationException e)
+ {
+ // put the feature back where it was
+ old_entry.add(this, true);
+
+ // re-throw
+ throw e;
+ }
+ }
+ finally
+ {
+ // see note above on startListening()
+ stopListening();
+ }
+ }
+ }
+
+ /**
+ * Make a copy of the this Feature and add it to the given Entry.
+ * @return The reference of the new feature.
+ * @exception EntryInformationException Thrown if the destination Entry
+ * cannot contain a Feature with this Key, Qualifier or Key/Qualifier
+ * combination.
+ **/
+ public Feature copyTo(final Entry destination_entry)
+ throws EntryInformationException, OutOfRangeException,
+ ReadOnlyException
+ {
+ if(destination_entry.isReadOnly())
+ throw new ReadOnlyException();
+
+ uk.ac.sanger.artemis.io.Feature new_embl_feature =
+ new EmblStreamFeature(getEmblFeature());
+
+ final Feature return_feature = new Feature(new_embl_feature);
+
+ destination_entry.add(return_feature, false);
+
+ return return_feature;
+ }
+
+ /**
+ * Adds the specified event listener to receive feature change events from
+ * this object.
+ * @param l the change event listener.
+ **/
+ public void addFeatureChangeListener(FeatureChangeListener l)
+ {
+ feature_listener_list.addElement(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * feature change events from this object.
+ * @param l the change event listener.
+ **/
+ public void removeFeatureChangeListener(FeatureChangeListener l)
+ {
+ feature_listener_list.removeElement(l);
+ }
+
+ /**
+ * Returns true if and only if this feature is read only or is in a read
+ * only entry.
+ **/
+ public boolean isReadOnly()
+ {
+ return getEmblFeature().isReadOnly();
+ }
+
+ /**
+ * This method examines each FeatureSegment and uses that information to
+ * set the location of the underlying embl.Feature object. This must be
+ * called any time a segment position changes, or when there is a change to
+ * the sequence (the sequence as represented by the Strand and Bases
+ * objects). This method does not fire off any FeatureChange events.
+ **/
+ private void updateEMBLFeatureLocation()
+ throws ReadOnlyException
+ {
+ final boolean complement = getLocation().isComplement();
+ final RangeVector ranges = new RangeVector();
+
+ for(int i = 0; i < segments.size(); ++i)
+ ranges.addElement(segments.elementAt (i).getRawRange ());
+
+ try
+ {
+ final Location new_location = new Location(ranges, complement);
+
+ getEmblFeature().setLocation(new_location);
+ old_location = new_location;
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - inconsistent location information: " +
+ e.getMessage());
+ }
+ }
+
+
+ /**
+ * Send an event to those object listening for it.
+ * @param listeners A Vector of the objects that the event should be sent
+ * to.
+ * @param event The event to send
+ **/
+ private void fireAction(Vector listeners, ChangeEvent event)
+ {
+ final Vector targets;
+ // copied from a book - synchronising the whole method might cause a
+ // deadlock
+ synchronized(this)
+ {
+ targets = (Vector)listeners.clone();
+ }
+
+ for(int i = 0 ; i < targets.size() ; ++i)
+ {
+ ChangeListener target = (ChangeListener)targets.elementAt(i);
+
+ final FeatureChangeListener feature_change_listener =
+ (FeatureChangeListener)target;
+ feature_change_listener.featureChanged((FeatureChangeEvent) event);
+ }
+ }
+
+ /**
+ * Reset these arrays: segments, amino_acids, bases, codon_counts,
+ * residue_counts, positional_base_counts and base_counts.
+ **/
+ private void resetCache()
+ {
+ amino_acids = null;
+ bases = null;
+ codon_counts = null;
+ residue_counts = null;
+ positional_base_counts = null;
+ base_counts = null;
+ aa_count = -1;
+ base_count = -1;
+ }
+
+ /**
+ * Update the values stored in residue_counts, codon_counts, base_counts
+ * and positional_base_counts.
+ **/
+ private void setArrays()
+ {
+ final String translation_bases = getTranslationBases();
+ final AminoAcidSequence translation = getTranslation();
+
+ final String translation_string = translation.toString();
+
+ codon_counts = new int[4][4][4];
+ residue_counts = new int[AminoAcidSequence.symbol_count];
+ base_counts = new int[4];
+ positional_base_counts = new int[3][4];
+
+ // zero the residue_counts array
+ for(int i = 0; i < residue_counts.length; ++i)
+ residue_counts[i] = 0;
+
+ int trans_len = translation_string.length();
+ for(int i = 0; i < trans_len; ++i)
+ {
+ final int symbol_index =
+ AminoAcidSequence.getSymbolIndex(translation_string.charAt(i));
+ ++residue_counts[symbol_index];
+ }
+
+ // zero the codon_counts array
+ for(int first = 0 ; first < 4 ; ++first)
+ {
+ for(int second = 0 ; second < 4 ; ++second)
+ {
+ for(int third = 0 ; third < 4 ; ++third)
+ codon_counts[first][second][third] = 0;
+ }
+ }
+
+ int base_len = translation_bases.length();
+ for(int base_index = 0; base_index < base_len;
+ ++base_index)
+ {
+ // this is 0, 1, 2 or 3
+ final int index_of_base =
+ Bases.getIndexOfBase(translation_bases.charAt(base_index));
+
+ if(index_of_base < 4)
+ ++base_counts[index_of_base];
+ }
+
+ for(int i = 0; i < translation_bases.length() / 3; ++i)
+ {
+ final int first_base_index =
+ Bases.getIndexOfBase(translation_bases.charAt(i * 3));
+ final int second_base_index =
+ Bases.getIndexOfBase(translation_bases.charAt(i * 3 + 1));
+ final int third_base_index =
+ Bases.getIndexOfBase(translation_bases.charAt(i * 3 + 2));
+
+ if(first_base_index < 4)
+ ++positional_base_counts[0][first_base_index];
+
+ if(second_base_index < 4)
+ ++positional_base_counts[1][second_base_index];
+
+ if(third_base_index < 4)
+ ++positional_base_counts[2][third_base_index];
+
+ if(first_base_index < 4 && second_base_index < 4 && third_base_index < 4)
+ ++codon_counts[first_base_index][second_base_index][third_base_index];
+ }
+ }
+
+ /**
+ * Remove all FeatureSegments from all listener lists.
+ **/
+ private void stopSegmentsListening()
+ {
+ if(segments != null)
+ {
+ for(int i = 0 ; i < segments.size(); ++i)
+ {
+ segments.elementAt(i).stopListening();
+ segments.elementAt(i).removeMarkerChangeListener(this);
+ }
+ }
+ }
+
+ /**
+ * Remove this Feature and its FeatureSegments from all listener lists.
+ **/
+ private void stopListening()
+ {
+ if(listen_count == 1)
+ {
+ if(getEntry() != null && getEntry().getBases() != null)
+ {
+ final Bases bases = getEntry().getBases();
+ bases.removeSequenceChangeListener(this);
+ }
+
+ stopSegmentsListening();
+
+ Options.getOptions().removeOptionChangeListener(this);
+ }
+
+ --listen_count;
+
+ if(listen_count < 0)
+ throw new Error("Feature.listen_count < 0");
+ }
+
+ /**
+ * Add all FeatureSegments to all appropriate listener lists.
+ **/
+ private void startSegmentsListening()
+ {
+ if(segments != null)
+ {
+ int seg_size = segments.size();
+ for (int i = 0; i < seg_size; ++i)
+ {
+ segments.elementAt(i).startListening();
+ segments.elementAt(i).addMarkerChangeListener(this);
+ }
+ }
+ }
+
+ /**
+ * Add this Feature and its FeatureSegments to the all appropriate listener
+ * lists.
+ **/
+ private void startListening()
+ {
+ if(listen_count == 0)
+ {
+ if(getEntry() != null && getEntry().getBases() != null)
+ {
+ final Bases bases = getEntry().getBases();
+ startSegmentsListening();
+
+ // it doesn't matter what the priority is as long as it is lower than
+ // the FeatureSegment priority, so that all FeatureSegment objects get
+ // updated before we try to update the location of this feature
+ final int PRIORITY = Marker.LISTENER_PRIORITY - 2;
+ bases.addSequenceChangeListener(this, PRIORITY);
+ }
+
+ Options.getOptions().addOptionChangeListener(this);
+ }
+
+ ++listen_count;
+ }
+
+ /**
+ * Create one FeatureSegment object for each segment/exon of this Feature.
+ **/
+ private void createSegments()
+ {
+ // this call will remove each segment from the MarkerChangeListener list
+ // of the start and end Marker objects of the segment. If we don't do
+ // this the segments will not be garbage collected.
+ stopSegmentsListening();
+
+ segments = new FeatureSegmentVector();
+ final Location location = getLocation();
+ final RangeVector ranges = location.getRanges();
+
+ int ranges_size = ranges.size();
+ for(int i = 0; i < ranges_size; ++i)
+ {
+ final Range this_range = (Range)ranges.elementAt(i);
+ segments.add(makeSegment(this_range));
+ }
+
+ startSegmentsListening();
+ }
+
+ /**
+ * Make a single FeatureSegment.
+ * @param range Provides the start and end positions of the segment.
+ **/
+ private FeatureSegment makeSegment(final Range range)
+ {
+ return new FeatureSegment(this, range);
+ }
+
+ /**
+ * Set the Location of this feature and reset old_location and
+ * segments. Don't send any FeatureChangeEvents.
+ **/
+ private void setLocationInternal(final Location new_location)
+ throws ReadOnlyException, OutOfRangeException
+ {
+ getEmblFeature().setLocation(new_location, getEntry().getEMBLEntry());
+
+ if(new_location != old_location)
+ reexamineSegments();
+
+ old_location = new_location;
+ resetCache();
+ }
+
+ /**
+ * Set the Location of this feature and notify any FeatureChangeListeners.
+ **/
+ public void setLocation(final Location new_location)
+ throws ReadOnlyException, OutOfRangeException
+ {
+ final Location old_location = getLocation();
+ setLocationInternal(new_location);
+ locationChanged(old_location);
+ }
+
+ public void addSegment(final Range range)
+ throws ReadOnlyException
+ {
+ final QualifierVector old_qualifiers = getQualifiers().copy();
+ addSegment(range, old_qualifiers);
+ }
+
+ /**
+ * Add a FeatureSegment to this Feature.
+ * @param range Provides the start and end positions of the segment.
+ **/
+ public void addSegment(final Range range,
+ final QualifierVector old_qualifiers)
+ throws ReadOnlyException
+ {
+ final Location old_location = getLocation();
+ final Location new_location = old_location.addRange(range);
+
+ try
+ {
+ setLocationInternal(new_location);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - inconsistent location " +
+ "information: " + e);
+ }
+
+ reexamineSegments();
+ locationChanged(old_location, old_qualifiers, FeatureChangeEvent.SEGMENT_CHANGED);
+ }
+
+ private Location moveSegments(final int shift)
+ throws OutOfRangeException, ReadOnlyException
+ {
+ Location location = getLocation();
+ RangeVector ranges = old_location.getRanges();
+ for(int i=0; i<ranges.size(); i++)
+ {
+ old_location = location;
+ final Range seg_range = (Range)ranges.elementAt(i);
+ final Range seg_new_range = new Range(seg_range.getStart() + shift,
+ seg_range.getEnd() + shift);
+ location = location.changeRange(seg_range, seg_new_range);
+
+// System.out.println("DIFF "+ shift+" "+seg_new_range.getStart()+".."+
+// seg_new_range.getEnd()+" was "+
+// seg_range.getStart()+".."+
+// seg_range.getEnd());
+ }
+
+ return location;
+ }
+
+ /**
+ * Delete a FeatureSegment from this object and update the location
+ * underlying embl.Feature object.
+ * @param segment The FeatureSegment to delete.
+ * @exception LastSegmentException thrown if an attempt is made to remove
+ * the last segment.
+ **/
+ public void removeSegment(FeatureSegment segment)
+ throws ReadOnlyException, LastSegmentException
+ {
+ if(getSegments().size() > 1)
+ {
+ QualifierVector qualifiers = getQualifiers().copy();
+ final Location old_location = getLocation();
+
+ final Location new_location =
+ old_location.removeRange(segment.getRawRange());
+
+ try
+ {
+ setLocationInternal(new_location);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - inconsistent location " +
+ "information: " + e);
+ }
+
+ reexamineSegments();
+
+ locationChanged(old_location, qualifiers, FeatureChangeEvent.SEGMENT_CHANGED);
+ }
+ else
+ throw new LastSegmentException();
+ }
+
+ /**
+ * Return a list of all the names of the qualifiers in the given features.
+ **/
+ public static StringVector getAllQualifierNames(final FeatureVector features)
+ {
+ final StringVector qualifier_names = new StringVector();
+ int feat_size = features.size();
+
+ for(int i = 0; i < feat_size; ++i)
+ {
+ final Feature feature = features.elementAt(i);
+ final QualifierVector qualifiers = feature.getQualifiers();
+
+ int qualifiers_size = qualifiers.size();
+ for(int qualifier_index = 0; qualifier_index < qualifiers_size;
+ ++qualifier_index)
+ {
+ final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(qualifier_index);
+ final String name = this_qualifier.getName();
+
+ if(!qualifier_names.contains(name))
+ qualifier_names.add(name);
+ }
+ }
+
+ qualifier_names.sort();
+
+ return qualifier_names;
+ }
+
+ /**
+ * Reverse and complement the location of this feature.
+ * @param sequence_length The length of the sequence that this Feature is
+ * associated with.
+ **/
+ private void reverseComplement(final int sequence_length)
+ throws ReadOnlyException
+ {
+ try
+ {
+ final Location new_location =
+ getLocation().reverseComplement(sequence_length);
+
+ setLocationInternal(new_location);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - inconsistent location: " + e);
+ }
+ }
+
+ /**
+ * Return the sequence length of the Strand that this feature is attached
+ * to.
+ **/
+ private int getSequenceLength()
+ {
+ FeatureSegment first_seg = getSegments().elementAt(0);
+ return first_seg.getStart().getStrand().getSequenceLength();
+ }
+
+ /**
+ * Set the embl.Feature reference of this Feature to be the given reference.
+ **/
+ void setEmblFeature(final uk.ac.sanger.artemis.io.Feature new_embl_feature)
+ {
+ embl_feature = new_embl_feature;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/FeatureChangeEvent.java b/uk/ac/sanger/artemis/FeatureChangeEvent.java
new file mode 100644
index 0000000..f94b77d
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeatureChangeEvent.java
@@ -0,0 +1,234 @@
+/* FeatureChangeEvent.java
+ *
+ * created: Sat Oct 17 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeatureChangeEvent.java,v 1.3 2006-07-19 16:10:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.QualifierVector;
+
+import java.io.*;
+
+/**
+ * This event is sent when a change occurs within a feature. eg the location
+ * or qualifier changes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureChangeEvent.java,v 1.3 2006-07-19 16:10:31 tjc Exp $
+ */
+
+public class FeatureChangeEvent extends ChangeEvent
+{
+ /** Event type - feature location changed. */
+ public static final int LOCATION_CHANGED = 1;
+
+ /** Event type - feature qualifier changed. */
+ public static final int QUALIFIER_CHANGED = 2;
+
+ /** Event type - feature key changed. */
+ public static final int KEY_CHANGED = 3;
+
+ /** Event type - feature has been re-read. */
+ public static final int ALL_CHANGED = 4;
+
+ /** feature segment added or deleted */
+ public static final int SEGMENT_CHANGED = 5;
+
+ /**
+ * The feature that this event was generated for.
+ **/
+ private Feature feature;
+
+ /**
+ * This is the type of this event (eg LOCATION_CHANGED, QUALIFIER_CHANGED,
+ * etc), as passed to the constructor
+ **/
+ private int type;
+
+ /**
+ * See getOldKey().
+ **/
+ private Key old_key;
+
+ /**
+ * See getOldLocation().
+ **/
+ private Location old_location;
+
+ /**
+ * See getOldQualifiers().
+ **/
+ private QualifierVector old_qualifiers;
+
+ /**
+ * See getNewKey().
+ **/
+ private Key new_key;
+
+ /**
+ * See getNewLocation().
+ **/
+ private Location new_location;
+
+ /**
+ * See getNewQualifiers().
+ **/
+ private QualifierVector new_qualifiers;
+
+ /**
+ * Create a new FeatureChangeEvent object.
+ * @param source The object that generated the event.
+ * @param feature This Feature object that this event refers to.
+ * @param key The key before the Feature changed or null if the key hasn't
+ * changed.
+ * @param location The Location before the Feature changed or null if the
+ * location hasn't changed.
+ * @param key The Qualifiers before the Feature changed or null if the
+ * qualifiers have't changed.
+ * @param type This type of the event.
+ **/
+ public FeatureChangeEvent (Object source,
+ Feature feature,
+ Key old_key,
+ Location old_location,
+ QualifierVector old_qualifiers,
+ int type)
+ {
+ super (source);
+
+ this.feature = feature;
+ this.type = type;
+
+ this.old_key = old_key;
+ this.old_location = old_location;
+ this.old_qualifiers = old_qualifiers;
+
+ this.new_key = feature.getKey ();
+ this.new_location = feature.getLocation ();
+ this.new_qualifiers = feature.getQualifiers ();
+ }
+
+ /**
+ * Return the Feature of this event.
+ **/
+ public Feature getFeature ()
+ {
+ return feature;
+ }
+
+ /**
+ * Return the type of this event as passed to the constructor.
+ **/
+ public int getType ()
+ {
+ return type;
+ }
+
+ /**
+ * Return the old Key from before the event. Could return null.
+ **/
+ public Key getOldKey ()
+ {
+ return old_key;
+ }
+
+ /**
+ * Return the old Location from before the event. Could return null.
+ **/
+ public Location getOldLocation ()
+ {
+ return old_location;
+ }
+
+ /**
+ * Return the old qualifiers from before the event. Could return null.
+ **/
+ public QualifierVector getOldQualifiers ()
+ {
+ return old_qualifiers;
+ }
+
+ /**
+ * Return the Key after the event (extracted from the Feature was is
+ * passed to the constructor).
+ **/
+ public Key getNewKey ()
+ {
+ return new_key;
+ }
+
+ /**
+ * Return the Location after the event (extracted from the Feature was is
+ * passed to the constructor).
+ **/
+ public Location getNewLocation ()
+ {
+ return new_location;
+ }
+
+ /**
+ * Return the qualifiers after the event (extracted from the Feature was is
+ * passed to the constructor).
+ **/
+ public QualifierVector getNewQualifiers ()
+ {
+ return new_qualifiers;
+ }
+
+ /**
+ * Return true if and only if this event caused an actual change in the
+ * Feature (rather than someone just pressing OK in a FeatureEdit for
+ * example).
+ **/
+ public boolean featureHasChanged ()
+ {
+ if(getOldKey() != null)
+ {
+ if(!getOldKey().equals(getNewKey()))
+ {
+ return true;
+ }
+ }
+
+ if(getOldLocation() != null)
+ {
+ if(!getOldLocation().equals(getNewLocation()))
+ {
+ return true;
+ }
+ }
+
+ if(getOldQualifiers() != null)
+ {
+ if(!getOldQualifiers().equals(getNewQualifiers()))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/FeatureChangeListener.java b/uk/ac/sanger/artemis/FeatureChangeListener.java
new file mode 100644
index 0000000..d714631
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeatureChangeListener.java
@@ -0,0 +1,44 @@
+/* FeatureChangeListener.java
+ *
+ * created: Wed Oct 21 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeatureChangeListener.java,v 1.1 2004-06-09 09:44:38 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * The FeatureChangeListener interface is implemented by those classes that
+ * need to listen for changes to Feature objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureChangeListener.java,v 1.1 2004-06-09 09:44:38 tjc Exp $
+ *
+ **/
+
+public interface FeatureChangeListener extends ChangeListener {
+ /**
+ * Invoked when a Feature object is changed.
+ **/
+ void featureChanged (FeatureChangeEvent event);
+}
+
+
diff --git a/uk/ac/sanger/artemis/FeatureEnumeration.java b/uk/ac/sanger/artemis/FeatureEnumeration.java
new file mode 100644
index 0000000..61b2ca9
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeatureEnumeration.java
@@ -0,0 +1,58 @@
+/* FeatureEnumeration.java
+ *
+ * created: Thu Nov 12 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeatureEnumeration.java,v 1.1 2004-06-09 09:44:39 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.util.NoSuchElementException;
+
+/**
+ * An object that implements the FeatureEnumeration interface generates a
+ * series of Feature objects, one at a time. Successive calls to the
+ * nextFeature method return successive elements of the series.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureEnumeration.java,v 1.1 2004-06-09 09:44:39 tjc Exp $
+ *
+ **/
+
+public interface FeatureEnumeration {
+ /**
+ * Tests if this enumeration contains more features.
+ * @return true if and only if this enumeration object contains at least
+ * one more feature to provide; false otherwise.
+ **/
+ boolean hasMoreFeatures ();
+
+ /**
+ * Returns the next Feature object of this enumeration if this enumeration
+ * object has at least one more element to provide.
+ * @return The next Feature of this enumeration.
+ * @exception NoSuchElementException If no more elements exist.
+ **/
+ Feature nextFeature ()
+ throws NoSuchElementException;
+}
+
+
diff --git a/uk/ac/sanger/artemis/FeatureFromVectorPredicate.java b/uk/ac/sanger/artemis/FeatureFromVectorPredicate.java
new file mode 100644
index 0000000..c84c9c2
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeatureFromVectorPredicate.java
@@ -0,0 +1,63 @@
+/* FeatureFromVectorPredicate.java
+ *
+ * created: Wed Sep 8 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeatureFromVectorPredicate.java,v 1.1 2004-06-09 09:44:40 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * Each object of this class can be used to test Feature objects to see if
+ * they are in the FeatureVector that is passed to the constructor. See
+ * FeaturePredicate.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureFromVectorPredicate.java,v 1.1 2004-06-09 09:44:40 tjc Exp $
+ **/
+
+public class FeatureFromVectorPredicate
+ implements FeaturePredicate {
+ /**
+ * Create a new FeatureFromVectorPredicate that tests as true (with
+ * testPredicate ()) only for those features in the given FeatureVector.
+ **/
+ public FeatureFromVectorPredicate (final FeatureVector feature_vector) {
+ this.feature_vector = feature_vector;
+ }
+
+ /**
+ * Test a Feature against this FeatureKeyPredicate.
+ * @param feature The Feature to test the predicate against.
+ * @return Return true if and only if the given Feature is in the
+ * FeatureVector that was passed to the constructor.
+ **/
+ public boolean testPredicate (final Feature feature) {
+ return feature_vector.contains (feature);
+ }
+
+ /**
+ * The FeatureVector that was passed to the constructor.
+ **/
+ private FeatureVector feature_vector;
+}
+
+
diff --git a/uk/ac/sanger/artemis/FeatureKeyPredicate.java b/uk/ac/sanger/artemis/FeatureKeyPredicate.java
new file mode 100644
index 0000000..deed248
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeatureKeyPredicate.java
@@ -0,0 +1,62 @@
+/* FeatureKeyPredicate.java
+ *
+ * created: Tue Mar 30 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeatureKeyPredicate.java,v 1.1 2004-06-09 09:44:41 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.io.Key;
+
+/**
+ * Each object of this class can be used to test Feature objects to see if
+ * they have the given Key. See FeaturePredicate.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureKeyPredicate.java,v 1.1 2004-06-09 09:44:41 tjc Exp $
+ **/
+
+public class FeatureKeyPredicate
+ implements FeaturePredicate {
+ /**
+ * Create a new FeatureKeyPredicate object.
+ * @param key The Key to test the Feature against.
+ **/
+ public FeatureKeyPredicate (final Key key) {
+ this.key = key;
+ }
+
+ /**
+ * Test the given Feature against this FeatureKeyPredicate.
+ * @param feature The Feature to test the predicate against.
+ * @return Return true if and only if the given Feature has the same key
+ * as the one the was passed to the constructor.
+ **/
+ public boolean testPredicate (final Feature feature) {
+ return feature.getKey ().equals (key);
+ }
+
+ /**
+ * The Key that was passed to the constructor.
+ **/
+ private Key key;
+}
diff --git a/uk/ac/sanger/artemis/FeatureKeyQualifierPredicate.java b/uk/ac/sanger/artemis/FeatureKeyQualifierPredicate.java
new file mode 100644
index 0000000..6541114
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeatureKeyQualifierPredicate.java
@@ -0,0 +1,237 @@
+/* FeatureKeyQualifierPredicate.java
+ *
+ * created: Tue Mar 30 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeatureKeyQualifierPredicate.java,v 1.1 2004-06-09 09:44:42 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+/**
+ * Each object of this class can be used to test Feature objects to see if
+ * they have a given Key and a given Qualifier.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureKeyQualifierPredicate.java,v 1.1 2004-06-09 09:44:42 tjc Exp $
+ **/
+public class FeatureKeyQualifierPredicate
+ implements FeaturePredicate {
+ /**
+ * Create a new FeatureKeyPredicate object.
+ * @param key testPredicate () will return false if this Key isn't the
+ * same as the Key of the test Feature.
+ * @param qualifier_name testPredicate () will return false if the test
+ * Feature does not contain a Qualifier with this name. If null then
+ * match any qualifier name.
+ * @param qualifier_value testPredicate () will return false if the test
+ * Feature does not contain a Qualifier named qualifier_name with this
+ * value.
+ **/
+ public FeatureKeyQualifierPredicate (final Key key,
+ final String qualifier_name,
+ final String qualifier_value) {
+ this.key = key;
+ this.qualifier_name = qualifier_name;
+ this.qualifier_value = qualifier_value;
+ this.qualifier_must_exist = false;
+ }
+
+ /**
+ * Create a new FeatureKeyPredicate object.
+ * @param key testPredicate () will return false if this Key isn't the
+ * same as the Key of the test Feature.
+ * @param qualifier_name testPredicate () will return false if the test
+ * Feature does not contain a Qualifier with this name. If null then
+ * match any qualifier.
+ * @param qualifier_value testPredicate () will return false if the test
+ * Feature does not contain a Qualifier named qualifier_name with this
+ * value.
+ * @param sub_string_match if true then qualifier_value need only match a
+ * substring for the predicate to be true. If false then qualifier_value
+ * must match the full length of the target for the predicate to be true.
+ * @param ignore_case If true then case will be ignored when searching for
+ * qualifier_value.
+ **/
+ public FeatureKeyQualifierPredicate (final Key key,
+ final String qualifier_name,
+ final String qualifier_value,
+ final boolean sub_string_match,
+ final boolean ignore_case) {
+ this(key, qualifier_name, qualifier_value, sub_string_match, ignore_case, false);
+ }
+
+ /**
+ * Create a new FeatureKeyPredicate object.
+ * @param key testPredicate () will return false if this Key isn't the
+ * same as the Key of the test Feature.
+ * @param qualifier_name testPredicate () will return false if the test
+ * Feature does not contain a Qualifier with this name. If null then
+ * match any qualifier.
+ * @param qualifier_value testPredicate () will return false if the test
+ * Feature does not contain a Qualifier named qualifier_name with this
+ * value.
+ * @param sub_string_match if true then qualifier_value need only match a
+ * substring for the predicate to be true. If false then qualifier_value
+ * must match the full length of the target for the predicate to be true.
+ * @param ignore_case If true then case will be ignored when searching for
+ * qualifier_value.
+ **/
+ public FeatureKeyQualifierPredicate (final Key key,
+ final String qualifier_name,
+ final String qualifier_value,
+ final boolean sub_string_match,
+ final boolean ignore_case,
+ final boolean deleteQualifier) {
+ this.key = key;
+ this.qualifier_name = qualifier_name;
+ this.qualifier_must_exist = false;
+ this.sub_string_match = sub_string_match;
+ this.ignore_case = ignore_case;
+ this.deleteQualifier = deleteQualifier;
+
+ if (ignore_case) {
+ this.qualifier_value = qualifier_value.toLowerCase ();
+ } else {
+ this.qualifier_value = qualifier_value;
+ }
+ }
+
+ /**
+ * Create a new FeatureKeyPredicate object.
+ * @param key testPredicate () will return false if this Key isn't the
+ * same as the Key of the test Feature.
+ * @param qualifier_name testPredicate () will return false if the test
+ * Feature does not contain a Qualifier with this name. If null then
+ * match any qualifier.
+ **/
+ public FeatureKeyQualifierPredicate (final Key key,
+ final String qualifier_name) {
+ this (key, qualifier_name, true);
+ }
+
+ /**
+ * Create a new FeatureKeyPredicate object.
+ * @param key testPredicate () will return false if this Key isn't the
+ * same as the Key of the test Feature.
+ * @param qualifier_name testPredicate () will return false if the test
+ * Feature does not contain a Qualifier with this name.
+ * @param qualifier_must_exist If false then the predicate is true only if
+ * there is NO Qualifier with the given value in the Feature. If true
+ * then the predicate is true only if there IS a Qualifier with the
+ * given value in the Feature.
+ **/
+ public FeatureKeyQualifierPredicate (final Key key,
+ final String qualifier_name,
+ final boolean qualifier_must_exist) {
+ this.key = key;
+ this.qualifier_name = qualifier_name;
+ this.qualifier_value = null;
+ this.qualifier_must_exist = qualifier_must_exist;
+ }
+
+ /**
+ * Test the given Feature against this FeatureKeyPredicate.
+ * @param feature The Feature to test the predicate against.
+ * @return Return true if and only if the given Feature has the same key
+ * as the one the was passed to the constructor and contains a
+ * Qualifier with the name and value that was passed to the
+ * constructor.
+ **/
+ public boolean testPredicate (final Feature feature) {
+ if (key != null && !feature.getKey ().equals (key)) {
+ return false;
+ }
+
+ if (qualifier_value == null) {
+ // we are only testing for existence in this case
+ Qualifier qualifier = null;
+ try {
+ qualifier = feature.getQualifierByName (qualifier_name);
+ } catch (InvalidRelationException e) {
+ // ignore - qualifier is null
+ }
+
+ final boolean has_qualifier = qualifier != null;
+
+ if (qualifier_must_exist && has_qualifier ||
+ !qualifier_must_exist && !has_qualifier) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ final StringVector qualifier_names_to_search;
+
+ if (qualifier_name == null) {
+ qualifier_names_to_search = null;
+ } else {
+ qualifier_names_to_search = new StringVector (qualifier_name);
+ }
+
+ return feature.findOrReplaceText (qualifier_value, ignore_case,
+ sub_string_match, deleteQualifier,
+ qualifier_names_to_search, null);
+ }
+ }
+
+ /**
+ * The Key that was passed to the constructor.
+ **/
+ private Key key = null;
+
+ /**
+ * The Qualifier name that was passed to the constructor.
+ **/
+ private String qualifier_name = null;
+
+ /**
+ * The Qualifier value that was passed to the constructor.
+ **/
+ private String qualifier_value = null;
+
+ /**
+ * If false then the predicate is true only if there is NO Qualifier with
+ * the given value in the Feature. If true then the predicate is true
+ * only if there IS a Qualifier with the given value in the Feature.
+ **/
+ private boolean qualifier_must_exist = true;
+
+ /**
+ * If true then qualifier_value need only match a substring for the
+ * predicate to be true. If false then qualifier_value must match the full
+ * length of the target for the predicate to be true. (set by the
+ * constructor).
+ **/
+ private boolean sub_string_match = false;
+
+ /**
+ * If true then case will be ignored when searching for qualifier_value.
+ **/
+ private boolean ignore_case = false;
+
+ /** If true delete the qualifier value */
+ private boolean deleteQualifier = false;
+}
diff --git a/uk/ac/sanger/artemis/FeaturePatternPredicate.java b/uk/ac/sanger/artemis/FeaturePatternPredicate.java
new file mode 100644
index 0000000..60d6f8d
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeaturePatternPredicate.java
@@ -0,0 +1,61 @@
+/* FeaturePatternPredicate.java
+ *
+ * created: Tue Aug 6 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeaturePatternPredicate.java,v 1.1 2004-06-09 09:44:43 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
+
+/**
+ * Each object of this class can be used to test Feature objects to see if
+ * they contain a given sequence pattern. See FeaturePredicate.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: FeaturePatternPredicate.java,v 1.1 2004-06-09 09:44:43 tjc Exp $
+ **/
+
+public class FeaturePatternPredicate implements FeaturePredicate {
+ /**
+ * Creata a new FeaturePatternPredicate object which will test for the
+ * presence of the given AminoAcidSequence in the translation of the Feature
+ **/
+ public FeaturePatternPredicate (final AminoAcidSequence motif_pattern) {
+ this.motif_pattern = motif_pattern;
+ }
+
+ /**
+ * Test the given Feature against this FeaturePredicate
+ * @param feature The Feature to test the predicate against.
+ * @return Return true if and only if the given Feature contains the given
+ * sequence pattern.
+ **/
+ public boolean testPredicate (final Feature feature) {
+ return motif_pattern.checkForMatch (feature.getTranslation ());
+ }
+
+ /**
+ * The AminoAcidSequence that was passed to the constructor.
+ **/
+ private AminoAcidSequence motif_pattern;
+}
diff --git a/uk/ac/sanger/artemis/FeaturePredicate.java b/uk/ac/sanger/artemis/FeaturePredicate.java
new file mode 100644
index 0000000..0650524
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeaturePredicate.java
@@ -0,0 +1,46 @@
+/* FeaturePredicate.java
+ *
+ * created: Thu Feb 4 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeaturePredicate.java,v 1.1 2004-06-09 09:44:44 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+/**
+ * Each object that implements this interface represents a predicate that can
+ * be tested with a Feature reference.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeaturePredicate.java,v 1.1 2004-06-09 09:44:44 tjc Exp $
+ **/
+
+public interface FeaturePredicate {
+ /**
+ * Test the given Feature against this predicate.
+ * @param feature The Feature to test the predicate against.
+ * @return Return true if and only if this predicate is true for the given
+ * Feature.
+ **/
+ boolean testPredicate (final Feature feature);
+}
+
+
diff --git a/uk/ac/sanger/artemis/FeaturePredicateConjunction.java b/uk/ac/sanger/artemis/FeaturePredicateConjunction.java
new file mode 100644
index 0000000..4d31cdd
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeaturePredicateConjunction.java
@@ -0,0 +1,122 @@
+/* FeaturePredicateConjunction.java
+ *
+ * created: Tue Aug 6 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeaturePredicateConjunction.java,v 1.1 2004-06-09 09:44:45 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * A class for combining two predicates.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: FeaturePredicateConjunction.java,v 1.1 2004-06-09 09:44:45 tjc Exp $
+ **/
+
+public class FeaturePredicateConjunction implements FeaturePredicate {
+ /**
+ * Create a new FeaturePredicateConjunction object.
+ * @param type the type of conjunction to use when testPredicate() is
+ * called.
+ **/
+ public FeaturePredicateConjunction (final FeaturePredicate predicate1,
+ final FeaturePredicate predicate2,
+ final int type) {
+ this.type = type;
+
+ predicates.add (predicate1);
+ predicates.add (predicate2);
+
+ if (!(type == OR || type == AND)) {
+ throw new Error ("internal error - illegal type given to " +
+ "FeaturePredicateConjunction constructor");
+ }
+ }
+
+ /**
+ * Create a new FeaturePredicateConjunction object.
+ * @param type the type of conjunction to use when testPredicate() is
+ * called.
+ **/
+ public FeaturePredicateConjunction (final FeaturePredicateVector predicates,
+ final int type) {
+ this.predicates = predicates.copy ();
+ this.type = type;
+
+ if (!(type == OR || type == AND)) {
+ throw new Error ("internal error - illegal type given to " +
+ "FeaturePredicateConjunction constructor");
+ }
+
+ if (predicates.size () == 0) {
+ throw new Error ("internal error - no predicates given to " +
+ "FeaturePredicateConjunction constructor");
+ }
+ }
+
+ /**
+ * The && predicate type.
+ **/
+ final public static int AND = 1;
+
+ /**
+ * The || predicate type.
+ **/
+ final public static int OR = 0;
+
+ /**
+ * Test the given Feature against this predicate.
+ * @param feature The Feature to test the predicate against.
+ * @return predicate1 && predicate2 if AND was passed as a type to the
+ * constructor. Returns predicate1 || predicate2 if OR was passed to the
+ * constructor.
+ **/
+ public boolean testPredicate (final Feature feature) {
+ for (int i = 0 ; i < predicates.size () ; ++i) {
+ if (type == AND) {
+ if (!predicates.elementAt (i).testPredicate (feature)) {
+ return false;
+ }
+ } else {
+ if (predicates.elementAt (i).testPredicate (feature)) {
+ return true;
+ }
+ }
+ }
+
+ if (type == AND) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * The FeaturePredicates passed to the constructor.
+ **/
+ private FeaturePredicateVector predicates = new FeaturePredicateVector ();
+
+ /**
+ * The type of conjunction passed to the constructor.
+ **/
+ private int type;
+}
diff --git a/uk/ac/sanger/artemis/FeaturePredicateVector.java b/uk/ac/sanger/artemis/FeaturePredicateVector.java
new file mode 100644
index 0000000..03d988d
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeaturePredicateVector.java
@@ -0,0 +1,74 @@
+/* FeaturePredicateVector.java
+ *
+ * created: Mon Oct 13 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeaturePredicateVector.java,v 1.1 2004-06-09 09:44:46 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.util.Vector;
+
+/**
+ * A Vector of FeaturePredicate objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: FeaturePredicateVector.java,v 1.1 2004-06-09 09:44:46 tjc Exp $
+ **/
+
+public class FeaturePredicateVector {
+ /**
+ * Appends the given FeaturePredicate object to the vector.
+ **/
+ public void add (FeaturePredicate item) {
+ vector.addElement (item);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ **/
+ public FeaturePredicate elementAt (int index) {
+ return (FeaturePredicate) vector.elementAt (index);
+ }
+
+ /**
+ * Return the size of this Vector.
+ **/
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Return a new copy of this object.
+ **/
+ public FeaturePredicateVector copy () {
+ final FeaturePredicateVector new_vector = new FeaturePredicateVector ();
+
+ new_vector.vector = (Vector) vector.clone ();
+
+ return new_vector;
+ }
+
+ /**
+ * Delegate.
+ **/
+ private Vector vector = new Vector ();
+}
diff --git a/uk/ac/sanger/artemis/FeatureSegment.java b/uk/ac/sanger/artemis/FeatureSegment.java
new file mode 100644
index 0000000..cdfad14
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeatureSegment.java
@@ -0,0 +1,506 @@
+/* FeatureSegment.java
+ *
+ * created: Sun Oct 11 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeatureSegment.java,v 1.4 2005-11-28 16:46:38 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.FuzzyRange;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.sequence.*;
+
+import java.util.Vector;
+
+/**
+ * Objects of this class will generally represent one exon. We use the name
+ * "segment" because EMBL does not specify that a feature with a join or order
+ * must be a coding sequence.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureSegment.java,v 1.4 2005-11-28 16:46:38 tjc Exp $
+ *
+ **/
+
+public class FeatureSegment
+ implements Selectable, SequenceChangeListener, MarkerChangeListener
+{
+
+ private boolean complement;
+
+ /**
+ * Create a new FeatureSegment object.
+ * @param feature The reference of the Feature that contains this segment.
+ * @param range The Range of this segment.
+ **/
+ public FeatureSegment (final Feature feature,
+ final Range range)
+ {
+ this.feature = feature;
+
+ if(feature == null)
+ throw new Error();
+
+ setRange(range);
+
+ // check which strand this is on (used to test for trans spliced)
+ final Location location = feature.getLocation();
+ this.complement = location.isComplement(range);
+ }
+
+ /**
+ * Set the Range of this FeatureSegment. Updates all listeners and
+ * Markers.
+ **/
+ public void setRange (final Range range) {
+ this.range = range;
+
+ final int start_position;
+ final int end_position;
+
+ if (getFeature ().isForwardFeature ()) {
+ start_position = range.getStart ();
+ end_position = range.getEnd ();
+ } else {
+ // markers are reversed on the reverse strand
+ start_position = range.getEnd ();
+ end_position = range.getStart ();
+ }
+
+ final Strand strand = getFeature ().getStrand ();
+
+ try {
+ if (start == null) {
+ start = strand.makeMarkerFromRawPosition (start_position);
+ } else {
+ start.removeMarkerChangeListener (this);
+ start.setRawPosition (start_position);
+ }
+ if (end == null) {
+ end = strand.makeMarkerFromRawPosition (end_position);
+ } else {
+ end.removeMarkerChangeListener (this);
+ end.setRawPosition (end_position);
+ }
+ } catch (OutOfRangeException e) {
+ throw new Error (getFeature().getIDString()+ " internal error - " +
+ "unexpected OutOfRangeException for position: " +
+ start_position);
+ }
+
+ start.addMarkerChangeListener (this);
+ end.addMarkerChangeListener (this);
+ }
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int NO_FRAME = -1;
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int FORWARD_FRAME_1 = 0;
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int FORWARD_FRAME_2 = 1;
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int FORWARD_FRAME_3 = 2;
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int FORWARD_STRAND = 3;
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int REVERSE_STRAND = 4;
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int REVERSE_FRAME_3 = 5;
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int REVERSE_FRAME_2 = 6;
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int REVERSE_FRAME_1 = 7;
+
+ /**
+ * Return value of getFrameID ().
+ **/
+ public final static int SCALE_LINE = 8;
+
+ /**
+ * Return the owning Feature of this segment (as passed to the constructor).
+ **/
+ public Feature getFeature () {
+ return feature;
+ }
+
+ /**
+ * Return the start position Marker of this object.
+ **/
+ public Marker getStart () {
+ return start;
+ }
+
+ /**
+ * Return the end position Marker of this object.
+ **/
+ public Marker getEnd () {
+ return end;
+ }
+
+ /**
+ * Set the position of start marker of this FeatureSegment.
+ **/
+ void setStartPosition (final int position)
+ throws OutOfRangeException {
+ getStart ().setPosition (position);
+ updateRange ();
+ }
+
+ /**
+ * Set the position of end marker of this FeatureSegment.
+ **/
+ void setEndPosition (final int position)
+ throws OutOfRangeException {
+ getEnd ().setPosition (position);
+ updateRange ();
+ }
+
+ /**
+ * Return the range (in bases of this segment) that was passed to the
+ * constructor.
+ **/
+ public Range getRawRange () {
+ return range;
+ }
+
+ /**
+ * Return a MarkerRange that exactly covers this segment.
+ **/
+ public MarkerRange getMarkerRange () {
+ try {
+ return new MarkerRange (getFeature ().getStrand (),
+ getStart ().getPosition (),
+ getEnd ().getPosition ());
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - " + e);
+ }
+ }
+
+ /**
+ * Remove this FeatureSegment from the Feature that contains it.
+ **/
+ public void removeFromFeature ()
+ throws ReadOnlyException, LastSegmentException{
+ getFeature ().removeSegment (this);
+ feature = null;
+ }
+
+ /**
+ * Return the number of bases in this feature (total of all segments).
+ **/
+ public int getBaseCount () {
+ return getEnd ().getPosition () - getStart ().getPosition () + 1;
+ }
+
+ /**
+ * Return the bases in this FeatureSegment.
+ **/
+ public String getBases () {
+ final Strand strand = getFeature ().getStrand ();
+ try {
+ return strand.getSubSequence (new Range (getStart ().getPosition (),
+ getEnd ().getPosition ()));
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return true if and only if the Marker objects at the ends of this
+ * segment can be changed.
+ **/
+ public boolean canDirectEdit () {
+ return ! (range instanceof FuzzyRange);
+ }
+
+ /**
+ * Implementation of the SequenceChangeListener interface. We listen for
+ * this event so that we can update the range of this segment.
+ **/
+ public void sequenceChanged (final SequenceChangeEvent event)
+ {
+ // the location of the feature itself will change later
+ if(event.getType () != SequenceChangeEvent.REVERSE_COMPLEMENT &&
+ event.getType () != SequenceChangeEvent.CONTIG_REVERSE_COMPLEMENT &&
+ event.getType () != SequenceChangeEvent.CONTIG_REORDER)
+ {
+ updateRange ();
+ }
+ }
+
+ /**
+ * Add this FeatureSegment to the SequenceChangeListener list.
+ **/
+ void startListening () {
+ final Bases bases = getFeature ().getEntry ().getBases ();
+
+ // it doesn't matter what the priority is as long as it is lower than
+ // the Marker priority, so that all Marker objects get updated before
+ // we try to update the FeatureSegment
+ final int PRIORITY = Marker.LISTENER_PRIORITY - 1;
+ bases.addSequenceChangeListener (this, PRIORITY);
+
+ this.bases = bases;
+ }
+
+ /**
+ * Remove this FeatureSegment from all listener lists.
+ **/
+ void stopListening () {
+ bases.removeSequenceChangeListener (this);
+ }
+
+ /**
+ * Adjust the range of this FeatureSegment to match the start and end
+ * markers of this object.
+ **/
+ private void updateRange () {
+ final Range new_range;
+
+ try {
+ if (getFeature ().isForwardFeature ()) {
+ new_range = range.change (getStart ().getRawPosition (),
+ getEnd ().getRawPosition ());
+ } else {
+ new_range = range.change (getEnd ().getRawPosition (),
+ getStart ().getRawPosition ());
+ }
+
+ range = new_range;
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Adds the specified event listener to receive marker change events from
+ * this object. An event will be sent when either the start or end Marker
+ * of this segment changes.
+ * @param l the change event listener.
+ **/
+ public void addMarkerChangeListener (MarkerChangeListener l) {
+ marker_listener_list.addElement (l);
+ if (marker_listener_list.size () == 1) {
+ // we need to start listening
+ start.addMarkerChangeListener (this);
+ end.addMarkerChangeListener (this);
+ }
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * marker change events from this object.
+ * @param l the change event listener.
+ **/
+ public void removeMarkerChangeListener (MarkerChangeListener l) {
+ marker_listener_list.removeElement (l);
+ if (marker_listener_list.size () == 0) {
+ // stop listening
+ start.removeMarkerChangeListener (this);
+ end.removeMarkerChangeListener (this);
+ }
+ }
+
+ /**
+ * This method fixes up the internals of this Feature when a Marker
+ * changes by calling initialise ().
+ **/
+ public void markerChanged (final MarkerChangeEvent event) {
+ updateRange ();
+ fireEvent (event);
+ }
+
+ /**
+ * Send a MarkerChangeEvent to each object that is listening for it.
+ **/
+ private void fireEvent (MarkerChangeEvent event) {
+ final Vector targets;
+ // copied from a book - synchronising the whole method might cause a
+ // deadlock
+ synchronized (this) {
+ targets = (Vector) marker_listener_list.clone ();
+ }
+
+ for (int i = 0 ; i < targets.size () ; ++i) {
+ final MarkerChangeListener target =
+ (MarkerChangeListener) targets.elementAt (i);
+
+ target.markerChanged (event);
+ }
+ }
+
+ /**
+ * Returns 0, 1 or 2 depending on which translation frame this segment is
+ * in. A frame shift of zero means that the bases should be translated
+ * starting at the start position of this segment, 1 means start
+ * translating one base ahead of the start position and 2 means start
+ * translating two bases ahead of the start position.
+ **/
+ private int getFrameShift()
+ {
+ // find the number of bases in the segments before this one
+ int base_count = 0;
+
+ final FeatureSegmentVector segments =
+ getFeature ().getSegments ();
+
+ int direction = 0;
+ for(int i = 0; i < segments.size(); ++i)
+ {
+ int this_direction;
+ if(segments.elementAt(i).isForwardSegment())
+ this_direction = 1;
+ else
+ this_direction = -1;
+
+ if(segments.elementAt (i) == this)
+ {
+ if(i != 0 && this_direction != direction)
+ base_count = 0;
+
+ break;
+ }
+ else
+ {
+ if(i == 0)
+ direction = this_direction;
+ else if(this_direction != direction)
+ base_count = 0;
+
+ base_count += segments.elementAt (i).getBaseCount ();
+ }
+ }
+
+ int mod_value = (base_count + 3
+ - (getFeature ().getCodonStart () - 1)) % 3;
+
+ if (mod_value == 1) {
+ return 2;
+ } else {
+ if (mod_value == 2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Return the frame ID of the segment. There are three forward and three
+ * reverse frame lines as well a forward strand and a backward strand line.
+ * This method returns an ID of the line to place this segment on.
+ **/
+ public int getFrameID () {
+ // this will be 0, 1 or 2 depending on which frame the segment is in
+ final int start_base_modulo =
+ (getStart ().getPosition () - 1 + getFrameShift ()) % 3;
+
+// if (getFeature ().getStrand ().isForwardStrand ())
+ if(isForwardSegment())
+ {
+ switch (start_base_modulo) {
+ case 0:
+ return FORWARD_FRAME_1;
+ case 1:
+ return FORWARD_FRAME_2;
+ case 2:
+ return FORWARD_FRAME_3;
+ }
+ } else {
+ switch (start_base_modulo) {
+ case 0:
+ return REVERSE_FRAME_1;
+ case 1:
+ return REVERSE_FRAME_2;
+ case 2:
+ return REVERSE_FRAME_3;
+ }
+ }
+
+ return NO_FRAME;
+ }
+
+ public boolean isForwardSegment()
+ {
+ return !complement;
+ }
+
+ /**
+ * The reference of the Feature that contains this segment, as passed to
+ * the constructor.
+ **/
+ private Feature feature;
+
+ /**
+ * The start position of the segment, set in getStart ().
+ **/
+ private Marker start = null;
+
+ /**
+ * The end position of the segment, set in getEnd ().
+ **/
+ private Marker end = null;
+
+ /**
+ * This is the Range reference that was passed to the constructor.
+ **/
+ private Range range = null;
+
+ /**
+ * A vector containing the references of those objects listening for
+ * marker change events.
+ **/
+ private final Vector marker_listener_list = new Vector ();
+
+ /**
+ * The object used by startListening () and stopListening ().
+ **/
+ private Bases bases;
+}
diff --git a/uk/ac/sanger/artemis/FeatureSegmentVector.java b/uk/ac/sanger/artemis/FeatureSegmentVector.java
new file mode 100644
index 0000000..d360cc5
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeatureSegmentVector.java
@@ -0,0 +1,171 @@
+/* FeatureSegmentVector.java
+ *
+ * created: Thu Oct 29 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeatureSegmentVector.java,v 1.2 2006-08-09 16:35:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.util.Collections;
+import java.util.Comparator;
+
+import uk.ac.sanger.artemis.util.FastVector;
+
+/**
+ * This class implements a Vector of FeatureSegment objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureSegmentVector.java,v 1.2 2006-08-09 16:35:31 tjc Exp $
+ *
+ **/
+
+public class FeatureSegmentVector {
+
+ /**
+ * Storage for FeatureSegment objects.
+ */
+ private FastVector vector = new FastVector ();
+
+ /**
+ * Create a new vector of FeatureSegment objects.
+ **/
+ public FeatureSegmentVector () {
+
+ }
+
+ /**
+ * Performs the same function as Vector.addElement ()
+ **/
+ public void addElement (FeatureSegment segment) {
+ vector.add (segment);
+ }
+
+ /**
+ * Performs the same function as Vector.addElement ()
+ **/
+ public void add (FeatureSegment segment) {
+ vector.add (segment);
+ }
+
+ /**
+ * Performs the same function as Vector.insertElementAt ()
+ **/
+ public void insertElementAt (FeatureSegment segment, int index) {
+ vector.add (index, segment);
+ }
+
+ /**
+ * Performs the same function as Vector.setElementAt ()
+ **/
+ public void setElementAt (FeatureSegment segment, int index) {
+ vector.setElementAt (segment, index);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ **/
+ public FeatureSegment elementAt (int index) {
+ return (FeatureSegment) vector.get(index);
+ }
+
+ /**
+ * Performs the same function as Vector.lastElement ()
+ **/
+ public FeatureSegment lastElement () {
+ return (FeatureSegment) vector.lastElement();
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public boolean removeElement (FeatureSegment segment) {
+ return vector.remove (segment);
+ }
+
+ /**
+ * Performs the same function as Vector.indexOf ()
+ **/
+ public int indexOf (FeatureSegment segment) {
+ return vector.indexOf (segment);
+ }
+
+ /**
+ * Return true if this object contains the given FeatureSegment.
+ **/
+ public boolean contains (FeatureSegment segment) {
+ if (indexOf (segment) == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Performs the same function as Vector.removeAllElement ()
+ **/
+ public void removeAllElements () {
+ vector.clear ();
+ }
+
+ /**
+ * Performs the same function as Vector.removeElementAt ()
+ **/
+ public void removeElementAt (int index) {
+ vector.remove (index);
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ */
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Add a feature to the end of the Vector.
+ **/
+ public void addElementAtEnd (FeatureSegment segment) {
+ vector.add (size(), segment);
+ }
+
+ /**
+ * Create a new FeatureVector with the same contents as this one.
+ **/
+ public Object clone () {
+ final FeatureSegmentVector return_vector = new FeatureSegmentVector ();
+ return_vector.vector = (FastVector) vector.clone ();
+ return return_vector;
+ }
+
+ public void sortByPosition() {
+ Collections.sort(vector, new PositionComparator());
+ }
+}
+
+class PositionComparator implements Comparator<FeatureSegment> {
+ public int compare(FeatureSegment s1, FeatureSegment s2) {
+ if(s1.getStart().getPosition() < s2.getStart().getPosition())
+ return -1;
+ else
+ return 1;
+ }
+}
diff --git a/uk/ac/sanger/artemis/FeatureVector.java b/uk/ac/sanger/artemis/FeatureVector.java
new file mode 100644
index 0000000..4f1c4c6
--- /dev/null
+++ b/uk/ac/sanger/artemis/FeatureVector.java
@@ -0,0 +1,166 @@
+/* FeatureVector.java
+ *
+ * created: Tue Oct 13 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FeatureVector.java,v 1.2 2006-08-09 16:35:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.io.IndexedGFFDocumentEntry;
+import uk.ac.sanger.artemis.util.FastVector;
+
+import java.util.*;
+
+/**
+ * This class implements a Vector of Feature objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureVector.java,v 1.2 2006-08-09 16:35:31 tjc Exp $
+ *
+ **/
+
+public class FeatureVector {
+ /**
+ * Create a new vector of Feature objects.
+ **/
+ public FeatureVector () {
+
+ }
+
+ /**
+ * Performs the same function as Vector.addElement ()
+ */
+ public void add (Feature feature) {
+ vector.add (feature);
+ }
+
+ /**
+ * Add a feature to the end of the Vector.
+ **/
+ public final void addElementAtEnd (Feature feature) {
+ vector.add (feature);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ */
+ public Feature elementAt (int index) {
+ return (Feature) vector.get(index);
+ }
+
+ /**
+ * Performs the same function as Vector.lastElement ()
+ **/
+ public Feature lastElement () {
+ return (Feature) vector.lastElement ();
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public boolean remove (Feature feature) {
+ return vector.remove (feature);
+ }
+
+ /**
+ * Return true if this object contains the given Feature.
+ **/
+ public boolean contains (Feature feature) {
+ if(feature.getEntry().getEMBLEntry() instanceof IndexedGFFDocumentEntry)
+ return IndexedGFFDocumentEntry.contains(feature, this);
+
+ return vector.contains (feature);
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public int indexOf (Feature feature) {
+ return vector.indexOf (feature);
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ */
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Performs the same function as Vector.removeAllElement ()
+ **/
+ public void removeAllElements () {
+ vector.clear ();
+ }
+
+ /**
+ * Performs the same function as Vector.removeElementAt ()
+ **/
+ public void removeElementAt(int index) {
+ vector.remove (index);
+ }
+
+ /**
+ * Performs the same function as Vector.insertElementAt ()
+ **/
+ public final void insertElementAt (Feature feature, int index) {
+ vector.add (index, feature);
+ }
+
+ /**
+ * Insert a Feature after another.
+ * @param old_feature The new_feature will be inserted after this feature
+ * or at the start if old_feature isn't in the vector.
+ * @param new_feature The new feature to insert.
+ **/
+ public void insertElementAfter (Feature old_feature, Feature new_feature) {
+ vector.insertElementAfter (old_feature, new_feature);
+ }
+
+ /**
+ * Create a new FeatureVector with the same contents as this one.
+ **/
+ public Object clone () {
+ final FeatureVector return_vector = new FeatureVector ();
+
+ return_vector.vector = (FastVector) vector.clone ();
+
+ return return_vector;
+ }
+
+ /**
+ * Return a sorted copy of this vector.
+ * @param cmp The returned vector will be sorted with this Comparator.
+ **/
+ public FeatureVector sort (final Comparator cmp) {
+ final FeatureVector return_vector = (FeatureVector) clone ();
+
+ return_vector.vector = return_vector.vector.sort (cmp);
+
+ return return_vector;
+ }
+
+ /**
+ * Storage for Feature objects.
+ **/
+ private FastVector vector = new FastVector ();
+}
diff --git a/uk/ac/sanger/artemis/FilteredEntryGroup.java b/uk/ac/sanger/artemis/FilteredEntryGroup.java
new file mode 100644
index 0000000..37c30b8
--- /dev/null
+++ b/uk/ac/sanger/artemis/FilteredEntryGroup.java
@@ -0,0 +1,768 @@
+/* FilteredEntryGroup.java
+ *
+ * created: Fri Sep 3 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/FilteredEntryGroup.java,v 1.3 2006-10-26 12:40:30 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+import java.util.NoSuchElementException;
+
+/**
+ * An EntryGroup that filters out some features using a FeaturePredicate
+ * object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FilteredEntryGroup.java,v 1.3 2006-10-26 12:40:30 tjc Exp $
+ **/
+
+public class FilteredEntryGroup implements EntryGroup
+{
+ /**
+ * This is the EntryGroup reference that was passed to the constructor.
+ **/
+ private EntryGroup entry_group;
+
+ /**
+ * This is the FeaturePredicate reference that was passed to the
+ * constructor.
+ **/
+ private FeaturePredicate feature_predicate;
+
+ /**
+ * This is a cache variable used by getFilteredFeatures(). It is set to
+ * null by featureChanged(), entryChanged() and entryGroupChanged().
+ **/
+ private FeatureVector filtered_features = null;
+
+ /**
+ * The name of the filter use to create this object(a passed to the
+ * constructor).
+ **/
+ private String filter_name;
+
+ /**
+ * Create a new FilteredEntryGroup.
+ * @param entry_group This is the EntryGroup to filter.
+ * @param feature_predicate This is the predicate to use for filtering.
+ * @param filter_name a short description of the filter(used for menus and
+ * title bars). Can be null if there is no easy way to describe what
+ * the filter does.
+ **/
+ public FilteredEntryGroup(final EntryGroup entry_group,
+ final FeaturePredicate feature_predicate,
+ final String filter_name)
+ {
+ this.entry_group = entry_group;
+ this.feature_predicate = feature_predicate;
+ this.filter_name = filter_name;
+
+ getEntryGroup().addFeatureChangeListener(new FeatureChangeListener()
+ {
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ filtered_features = null;
+ }
+ });
+
+ getEntryGroup().addEntryChangeListener(new EntryChangeListener()
+ {
+ public void entryChanged(EntryChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryChangeEvent.FEATURE_DELETED:
+ // fall through
+ case EntryChangeEvent.FEATURE_ADDED:
+ filtered_features = null;
+ return;
+ }
+ }
+ });
+
+ final EntryGroupChangeListener entry_group_listener =
+ new EntryGroupChangeListener()
+ {
+ public void entryGroupChanged(EntryGroupChangeEvent event)
+ {
+ filtered_features = null;
+ }
+ };
+ getEntryGroup().addEntryGroupChangeListener(entry_group_listener);
+ }
+
+ /**
+ * Create a new FilteredEntryGroup.
+ * @param entry_group This is the EntryGroup to filter.
+ * @param filtered_features This filtered features.
+ * @param filter_name a short description of the filter(used for menus and
+ * title bars). Can be null if there is no easy way to describe what
+ * the filter does.
+ **/
+ public FilteredEntryGroup(final EntryGroup entry_group,
+ final FeatureVector filtered_features,
+ final String filter_name)
+ {
+ this(entry_group, (FeaturePredicate)null, filter_name);
+ this.filtered_features = filtered_features;
+ }
+
+ /**
+ * Return the default Entry for this EntryGroup. The "default" is the
+ * Entry where new features are created.
+ **/
+ public Entry getDefaultEntry()
+ {
+ return getEntryGroup().getDefaultEntry();
+ }
+
+ /**
+ * Set the default Entry. The "default" is the Entry where new features
+ * are created.
+ * @param entry The new default entry. If this Entry is not active this
+ * method will return immediately.
+ **/
+ public void setDefaultEntry(final Entry entry)
+ {
+ getEntryGroup().setDefaultEntry(entry);
+ }
+
+ /**
+ * Return the name of the filter used to create this object(as passed to
+ * the constructor). Can return null if there is no easy way to describe
+ * what the filter does.
+ **/
+ public String getFilterName()
+ {
+ return filter_name;
+ }
+
+ /**
+ * Returns true if and only if there are any unsaved changes in any of the
+ * Entry objects in this EntryGroup.
+ **/
+ public boolean hasUnsavedChanges()
+ {
+ return getEntryGroup().hasUnsavedChanges();
+ }
+
+ /**
+ * Return the index of a feature within this object. This method treats
+ * all the features in all the active entries as if they were in one big
+ * array. The first feature of the first entry will have index 1, the
+ * first from the second entry will have index 1 +(the number of features
+ * in the first entry), etc.
+ * @param feature The feature to find the index of.
+ * @return The index of the feature or -1 if the feature isn't in any of
+ * the entries. The first index is 0 the last is the total number of
+ * features in all the entries of this object minus one.
+ **/
+ public int indexOf(final Feature feature)
+ {
+ return getFilteredFeatures().indexOf(feature);
+ }
+
+ /**
+ * Return the index of an Entry within this object.
+ * @return The index of the Entry or -1 if the Entry isn't in this Object.
+ **/
+ public int indexOf(final Entry entry)
+ {
+ return getEntryGroup().indexOf(entry);
+ }
+
+ /**
+ * Return true if any of the active entries in the group contains the given
+ * feature.
+ **/
+ public boolean contains(final Feature feature)
+ {
+ if(getPredicate().testPredicate(feature))
+ return getEntryGroup().contains(feature);
+ else
+ return false;
+ }
+
+ /**
+ * Return true if the given Entry is active(visible). The Feature objects
+ * in an Entry that is not active will be ignored by the methods that deal
+ * will features: featureAt(), indexOf(), contains(), features(), etc.
+ **/
+ public boolean isActive(final Entry entry)
+ {
+ return getEntryGroup().isActive(entry);
+ }
+
+ /**
+ * Set the "active" setting of the Entry at the given index. If the index
+ * refers to the default entry and new_active is false, the default entry
+ * will be set to the active entry or null if there are no active entries.
+ * @param index The index of the Entry to change.
+ * @param active The new active setting.
+ **/
+ public void setIsActive(final int index, final boolean new_active)
+ {
+ getEntryGroup().setIsActive(index,new_active);
+ }
+
+ /**
+ * Set the "active" setting of the given Entry. The Entry is the default
+ * entry and new_active is false, the default entry will be set to the
+ * active entry or null if there are no active entries.
+ * @param entry The Entry to activate or deactivate.
+ * @param new_active The new active setting.
+ **/
+ public void setIsActive(final Entry entry, final boolean new_active)
+ {
+ setIsActive(indexOf(entry), new_active);
+ }
+
+ /**
+ * Return the Entry from this EntryGroup that contains the sequence to view
+ * or return null if none of the entries contains a sequence.
+ **/
+ public Entry getSequenceEntry()
+ {
+ return getEntryGroup().getSequenceEntry();
+ }
+
+ /**
+ * Returns the base length of the sequence of the first Entry in this group
+ * or 0 if this group is empty.
+ **/
+ public int getSequenceLength()
+ {
+ return getEntryGroup().getSequenceLength();
+ }
+
+ /**
+ * Returns the Bases object of the first Entry in this group or null if
+ * this group is empty.
+ **/
+ public Bases getBases()
+ {
+ return getEntryGroup().getBases();
+ }
+
+ /**
+ * Reverse and complement the sequence and all features in every Entry in
+ * this EntryGroup.
+ **/
+ public void reverseComplement() throws ReadOnlyException
+ {
+ getEntryGroup().reverseComplement();
+ }
+
+ /**
+ * Return the number of entries in this EntryGroup.
+ **/
+ public int size()
+ {
+ return getEntryGroup().size();
+ }
+
+ /**
+ * Return the ith Entry in this EntryGroup.
+ **/
+ public Entry elementAt(final int i)
+ {
+ return getEntryGroup().elementAt(i);
+ }
+
+ /**
+ * Return the Feature at the given index. This method treats all the
+ * features in all the entries as if they were in one big array. See
+ * the comment on indexOf().
+ * @param index The index of the required Feature.
+ * @return The Feature at the given index. The first index is 0 the last
+ * is the total number of features in all the entries of this object minus
+ * one. If the index is out of range then null will be returned.
+ **/
+ public Feature featureAt(final int index)
+ {
+ return getFilteredFeatures().elementAt(index);
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range of indices.
+ * @param
+ **/
+ public FeatureVector getFeaturesInIndexRange(final int start_index,
+ final int end_index)
+ {
+ final FeatureVector features = getFilteredFeatures();
+
+ final FeatureVector return_vector = new FeatureVector();
+
+ for(int i = start_index ; i <= end_index ; ++i)
+ return_vector.add(features.elementAt(i));
+
+ return return_vector;
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range for all the active entries in the EntryGroup.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The non-source key features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ public FeatureVector getFeaturesInRange(final Range range)
+ throws OutOfRangeException
+ {
+ return filterFeatures(getEntryGroup().getFeaturesInRange(range));
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects from
+ * all the active entries in the EntryGroup.
+ * @return The non-source key features in active entries of this
+ * EntryGroup. The returned object is a copy - changes will not effect
+ * the EntryGroup object itself.
+ **/
+ public FeatureVector getAllFeatures()
+ {
+ return getFilteredFeatures();
+ }
+
+ /**
+ * Return a count of the number of Feature objects from all the active
+ * entries in the EntryGroup.
+ * @return A count of the non-source key features in active entries of this
+ * EntryGroup.
+ **/
+ public int getAllFeaturesCount()
+ {
+ return getFilteredFeatures().size();
+ }
+
+ /**
+ * Add an Entry to this object and then emit the appropriate EntryChange
+ * events.
+ **/
+ public void addElement(final Entry entry)
+ {
+ getEntryGroup().addElement(entry);
+ }
+
+ /**
+ * A convenience method that does the same as addElement(Entry).
+ **/
+ public void add(final Entry entry)
+ {
+ getEntryGroup().add(entry);
+ }
+
+ /**
+ * Remove an Entry from this object and then emit the appropriate
+ * EntryGroupChange events. The first entry in the group can only be
+ * removed if it is the only Entry because the first Entry contains the
+ * sequence.
+ * @return true if the removal succeeded, false if it fails(which can
+ * if the given Entry isn't in this EntryGroup or if the user tries to
+ * remove the first Entry).
+ **/
+ public boolean removeElement(final Entry entry)
+ {
+ return getEntryGroup().removeElement(entry);
+ }
+
+ /**
+ * A convenience method that does the same as removeElement(Entry).
+ **/
+ public boolean remove(final Entry entry)
+ {
+ return getEntryGroup().remove(entry);
+ }
+
+ /**
+ * Create a new(blank) Feature in the default Entry of this EntryGroup.
+ * See getDefaultEntry() and Entry.createFeature().
+ * @return The new Feature.
+ **/
+ public Feature createFeature() throws ReadOnlyException
+ {
+ return getEntryGroup().createFeature();
+ }
+
+ /**
+ * Create a new(empty) Entry in this EntryGroup. See Entry.newEntry().
+ * @return The reference of the new Entry.
+ **/
+ public Entry createEntry()
+ {
+ return getEntryGroup().createEntry();
+ }
+
+ /**
+ * Create a new(empty) Entry in this EntryGroup. See Entry.newEntry().
+ * @param name The(file) name of the new Entry.
+ * @return The reference of the new Entry.
+ **/
+ public Entry createEntry(final String name)
+ {
+ return getEntryGroup().createEntry(name);
+ }
+
+ /**
+ * Returns an enumeration of the Feature objects in this EntryGroup. The
+ * returned FeatureEnumeration object will generate all features in this
+ * object in turn. The first item generated is the item at index 0, then
+ * the item at index 1, and so on.
+ **/
+ public FeatureEnumeration features()
+ {
+ return new FeatureEnumerator();
+ }
+
+ /**
+ * An Enumeration of Feature objects.
+ **/
+ public class FeatureEnumerator implements FeatureEnumeration
+ {
+ /**
+ * The EntryVector object that we are enumerating. Set to null when there
+ * are no more Feature objects.
+ **/
+ private EntryVector active_entries;
+
+ /** Index of the Entry that we will get the next Feature from. */
+ private int entry_index = -1;
+
+ /** Enumeration for the current entry. */
+ private FeatureEnumeration feature_enumerator;
+
+ /**
+ * The next Feature in the feature_enumerator. This Feature reference
+ * will always contain a Feature that has pass the FeaturePredicate.
+ **/
+ private Feature next_feature;
+
+ /**
+ * Create a new FeatureEnumeration that will enumerate the enclosing
+ * FilteredEntryGroup object. The FilteredEntryGroup object must not be
+ * changed while the enumeration is active.
+ **/
+ public FeatureEnumerator()
+ {
+ active_entries = getEntryGroup().getActiveEntries();
+
+ entry_index = 0;
+
+ if(active_entries.size() > 0)
+ feature_enumerator = active_entries.elementAt(entry_index).features();
+ else
+ feature_enumerator = null;
+ }
+
+ /**
+ * See the FeatureEnumeration interface for details.
+ **/
+ public boolean hasMoreFeatures()
+ {
+ if(feature_enumerator == null)
+ return false;
+
+ while(true)
+ {
+ if(feature_enumerator.hasMoreFeatures())
+ {
+ next_feature = feature_enumerator.nextFeature();
+
+ if(getPredicate().testPredicate(next_feature))
+ return true;
+ else
+ continue; // loop again
+ }
+
+ ++entry_index;
+
+ if(entry_index == active_entries.size())
+ {
+ feature_enumerator = null;
+ return false;
+ }
+ else
+ {
+ feature_enumerator =
+ active_entries.elementAt(entry_index).features();
+ // loop again
+ continue;
+ }
+ }
+ }
+
+ /**
+ * See the FeatureEnumeration interface for details.
+ **/
+ public Feature nextFeature() throws NoSuchElementException
+ {
+ if(feature_enumerator == null)
+ throw new NoSuchElementException();
+
+ if(next_feature == null)
+ throw new NoSuchElementException();
+ else
+ {
+ final Feature return_feature = next_feature;
+ next_feature = null;
+ return return_feature;
+ }
+ }
+
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface.
+ **/
+ public void featureChanged(final FeatureChangeEvent event)
+ {
+ getEntryGroup().featureChanged(event);
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen for
+ * changes from every entry in this group and pass the events though to all
+ * the object listening for EntryChangeEvents for the event from this
+ * EntryGroup.
+ **/
+ public void entryChanged(final EntryChangeEvent event)
+ {
+ getEntryGroup().entryChanged(event);
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen for
+ * changed to the EntryGroup that this FilteredEntryGroup is filtering so
+ * that the filtered_features cache variable can be maintained.
+ **/
+ public void entryGroupChanged(final EntryGroupChangeEvent event)
+ {
+ filtered_features = null;
+ }
+
+ /**
+ * Adds the specified event listener to receive entry group change events
+ * from this object.
+ * @param l the event change listener.
+ **/
+ public void addEntryGroupChangeListener(EntryGroupChangeListener l)
+ {
+ getEntryGroup().addEntryGroupChangeListener(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * entry group change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeEntryGroupChangeListener(EntryGroupChangeListener l)
+ {
+ getEntryGroup().removeEntryGroupChangeListener(l);
+ }
+
+ /**
+ * Adds the specified event listener to receive entry change events from
+ * this object.
+ * @param l the event change listener.
+ **/
+ public void addEntryChangeListener(EntryChangeListener l)
+ {
+ getEntryGroup().addEntryChangeListener(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * entry change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeEntryChangeListener(EntryChangeListener l)
+ {
+ getEntryGroup().removeEntryChangeListener(l);
+ }
+
+ /**
+ * Adds the specified event listener to receive feature change events from
+ * this object.
+ * @param l the event change listener.
+ **/
+ public void addFeatureChangeListener(FeatureChangeListener l)
+ {
+ getEntryGroup().addFeatureChangeListener(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * feature change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeFeatureChangeListener(FeatureChangeListener l)
+ {
+ getEntryGroup().removeFeatureChangeListener(l);
+ }
+
+ /**
+ * Return the reference of an EntryVector containing the active entries of
+ * this EntryGroup.
+ **/
+ public EntryVector getActiveEntries()
+ {
+ return getEntryGroup().getActiveEntries();
+ }
+
+ /**
+ * This method translates the start and end of every each Range in every
+ * Location into another coordinate system. The Ranges will be truncated
+ * if necessary.
+ * @param constraint This contains the start and end base of the new
+ * coordinate system. The position given by constraint.getStart() will
+ * be at postion/base 1 in the new coordinate system.
+ * @return a copy of the EntryGroup which has been translated into the new
+ * coordinate system.
+ **/
+ public EntryGroup truncate(final Range constraint)
+ {
+ return getEntryGroup().truncate(constraint);
+ }
+
+ /**
+ * Return the ActionController for this EntryGroup(for undo).
+ **/
+ public ActionController getActionController()
+ {
+ return getEntryGroup().getActionController();
+ }
+
+ /**
+ * Return true if and only if one or more of the entries or features in
+ * this SimpleEntryGroup are read-only.
+ **/
+ public boolean isReadOnly()
+ {
+ return getEntryGroup().isReadOnly();
+ }
+
+ /**
+ * Increment the reference count for this EntryGroup.
+ **/
+ public void ref()
+ {
+ getEntryGroup().ref();
+ }
+
+ /**
+ * Decrement the reference count for this EntryGroup. When the reference
+ * count goes to zero a EntryGroupChangeEvent is sent to all
+ * EntryGroupChangeListeners with type EntryGroupChangeEvent.DONE_GONE.
+ * The listeners should then stop using the EntryGroup and release any
+ * associated resources.
+ **/
+ public void unref()
+ {
+ getEntryGroup().unref();
+ }
+
+ /**
+ * Return the current reference count.
+ **/
+ public int refCount()
+ {
+ return getEntryGroup().refCount();
+ }
+
+ /**
+ * Return those features in the given FeatureVector that pass
+ * feature_predicate.
+ **/
+ protected FeatureVector filterFeatures()
+ {
+ final FeatureEnumeration test_enumerator = entry_group.features();
+ final FeatureVector return_features = new FeatureVector();
+
+ while(test_enumerator.hasMoreFeatures())
+ {
+ final Feature this_feature = test_enumerator.nextFeature();
+
+ if (feature_predicate.testPredicate (this_feature))
+ return_features.add (this_feature);
+ }
+
+ return return_features;
+ }
+
+ /**
+ * Return those features in the given FeatureVector that pass
+ * feature_predicate.
+ **/
+ protected FeatureVector filterFeatures(final FeatureVector features)
+ {
+ final FeatureVector return_features = new FeatureVector();
+
+ for(int i = 0 ; i < features.size() ; ++i)
+ {
+ final Feature this_feature = features.elementAt(i);
+
+ if(getPredicate().testPredicate(this_feature))
+ return_features.add(this_feature);
+ }
+
+ return return_features;
+ }
+
+ /**
+ * Return the features in the EntryGroup that pass the feature_predicate.
+ **/
+ private FeatureVector getFilteredFeatures()
+ {
+ if(filtered_features == null)
+ filtered_features = filterFeatures();
+
+ return filtered_features;
+ }
+
+ /**
+ * Return the EntryGroup reference that was passed to the constructor.
+ **/
+ private EntryGroup getEntryGroup()
+ {
+ return entry_group;
+ }
+
+ /**
+ * Return the FeaturePredicate reference that was passed to the
+ * constructor.
+ **/
+ private FeaturePredicate getPredicate()
+ {
+ return feature_predicate;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/GotoEvent.java b/uk/ac/sanger/artemis/GotoEvent.java
new file mode 100644
index 0000000..1f66cb5
--- /dev/null
+++ b/uk/ac/sanger/artemis/GotoEvent.java
@@ -0,0 +1,62 @@
+/* GotoEvent.java
+ *
+ * created: Sat Jan 9 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/GotoEvent.java,v 1.1 2004-06-09 09:44:51 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.Marker;
+
+/**
+ * This event is sent when a view component (eg FeatureDisplay) should
+ * scroll to centre on a new base.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GotoEvent.java,v 1.1 2004-06-09 09:44:51 tjc Exp $
+ **/
+
+public class GotoEvent extends java.util.EventObject {
+ /**
+ * Create a new GotoEvent object from the given Marker.
+ * @param source The Object that generated the event - probably a component.
+ * @param goto_position This is the position that the listeners should goto.
+ **/
+ public GotoEvent (final Object source, final Marker goto_position) {
+ super (source);
+ this.goto_position = goto_position;
+ }
+
+ /**
+ * Return the Marker that was passed to the constructor.
+ **/
+ public Marker getMarker () {
+ return goto_position;
+ }
+
+ /**
+ * This is the Marker that was passed to the constructor.
+ **/
+ final private Marker goto_position;
+}
+
+
diff --git a/uk/ac/sanger/artemis/GotoEventSource.java b/uk/ac/sanger/artemis/GotoEventSource.java
new file mode 100644
index 0000000..4564032
--- /dev/null
+++ b/uk/ac/sanger/artemis/GotoEventSource.java
@@ -0,0 +1,89 @@
+/* GotoEventSource.java
+ *
+ * created: Sat Jan 9 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/GotoEventSource.java,v 1.1 2004-06-09 09:44:52 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ * This interface should be implemented by those classes whose objects need
+ * to send GotoEvents and can register GotoListeners.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GotoEventSource.java,v 1.1 2004-06-09 09:44:52 tjc Exp $
+ **/
+
+public interface GotoEventSource {
+ /**
+ * This method sends a GotoEvent to all the GotoEvent listeners that will
+ * make the given base visible.
+ * @param base_marker The Marker of the base to make visible.
+ **/
+ void gotoBase (final Marker base_marker);
+
+ /**
+ * This method sends a GotoEvent to all the GotoEvent listeners that will
+ * make the given base visible. This just calls makeBaseVisible ().
+ * @param destination_base The base on the forward strand to make visible.
+ * @return The reference of a MarkerRange object for the given base or null
+ * if the call fails. It will fail only if the destination_base is less
+ * than 1 or greater than the length of the sequence.
+ **/
+ MarkerRange gotoBase (final int destination_base);
+
+ /**
+ * This method sends a GotoEvent to all the GotoEvent listeners that will
+ * make the first base of the sequence visible.
+ **/
+ void gotoFirstBase ();
+
+ /**
+ * This method sends a GotoEvent to all the GotoEvent listeners that will
+ * make the last base of the sequence visible.
+ **/
+ void gotoLastBase ();
+
+ /**
+ * Send the given event to all the GotoListeners.
+ **/
+ void sendGotoEvent (final GotoEvent goto_event);
+
+ /**
+ * Adds the specified event listener to receive Goto events from this
+ * object.
+ * @param l the GotoEvent listener.
+ **/
+ void addGotoListener (final GotoListener l);
+
+ /**
+ * Removes the specified event listener so that it no longer receives Goto
+ * events from this object.
+ * @param l the GotoEvent listener.
+ **/
+ void removeGotoListener (final GotoListener l) ;
+
+}
+
+
diff --git a/uk/ac/sanger/artemis/GotoListener.java b/uk/ac/sanger/artemis/GotoListener.java
new file mode 100644
index 0000000..41e92c1
--- /dev/null
+++ b/uk/ac/sanger/artemis/GotoListener.java
@@ -0,0 +1,43 @@
+/* GotoListener.java
+ *
+ * created: Sat Jan 9 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/GotoListener.java,v 1.1 2004-06-09 09:44:53 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * The GotoListener interface is implemented by those classes that can
+ * display bases.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GotoListener.java,v 1.1 2004-06-09 09:44:53 tjc Exp $
+ **/
+
+public interface GotoListener extends java.util.EventListener {
+ /**
+ * Invoked when the listener should goto to the given base.
+ **/
+ void performGoto (final GotoEvent event);
+}
+
+
diff --git a/uk/ac/sanger/artemis/LastSegmentException.java b/uk/ac/sanger/artemis/LastSegmentException.java
new file mode 100644
index 0000000..242754d
--- /dev/null
+++ b/uk/ac/sanger/artemis/LastSegmentException.java
@@ -0,0 +1,45 @@
+/* LastSegmentException.java
+ *
+ * created: Sat Aug 28 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/LastSegmentException.java,v 1.1 2004-06-09 09:44:54 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * An object of this type is thrown if an attempt is made to remove the last
+ * segment of a Feature.
+ *
+ * @author Kim Rutherford
+ * @version $Id: LastSegmentException.java,v 1.1 2004-06-09 09:44:54 tjc Exp $
+ **/
+
+public class LastSegmentException extends Exception {
+ /**
+ * Create a new LastSegmentException with no message.
+ **/
+ public LastSegmentException () {
+
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/Logger.java b/uk/ac/sanger/artemis/Logger.java
new file mode 100644
index 0000000..4122913
--- /dev/null
+++ b/uk/ac/sanger/artemis/Logger.java
@@ -0,0 +1,45 @@
+/* Logger.java
+ *
+ * created: Wed Aug 30 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Logger.java,v 1.1 2004-06-09 09:44:55 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * An interface for simple logging of warnings/errors/messages.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: Logger.java,v 1.1 2004-06-09 09:44:55 tjc Exp $
+ **/
+
+import java.io.*;
+
+public interface Logger
+{
+ /** Send the given String to the log. */
+ void log (final String message);
+
+ /** Read from the given Reader and send it to the log. */
+ void log (final Reader reader)
+ throws IOException;
+}
diff --git a/uk/ac/sanger/artemis/MSPcrunchComparisonData.java b/uk/ac/sanger/artemis/MSPcrunchComparisonData.java
new file mode 100644
index 0000000..eefebd0
--- /dev/null
+++ b/uk/ac/sanger/artemis/MSPcrunchComparisonData.java
@@ -0,0 +1,150 @@
+/* MSPcrunchComparisonData.java
+ *
+ * created: Tue Apr 4 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/MSPcrunchComparisonData.java,v 1.2 2005-11-21 15:49:02 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.StringTokenizer;
+
+/**
+ * This class implements the ComparisonData interface for MSPcrunch -d
+ * output.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: MSPcrunchComparisonData.java,v 1.2 2005-11-21 15:49:02 tjc Exp $
+ **/
+
+public class MSPcrunchComparisonData extends SimpleComparisonData
+ implements ComparisonData {
+ /**
+ * Create a new MSPcrunchComparisonData by reading from the given
+ * LinePushBackReader.
+ **/
+ public MSPcrunchComparisonData (final LinePushBackReader stream)
+ throws IOException {
+ super (stream);
+ }
+
+ /**
+ * Create a new, empty instance of MSPcrunchComparisonData.
+ **/
+ protected MSPcrunchComparisonData () {
+ }
+
+ /**
+ * Returns a new, empty instance of this type of object;
+ **/
+ protected SimpleComparisonData getNewSimpleComparisonData () {
+ return new MSPcrunchComparisonData ();
+ }
+
+ /**
+ * Make an AlignMatch object from the given String.
+ **/
+ private static AlignMatch makeMatchFromStringStatic (final String line)
+ throws IOException {
+ final StringTokenizer tokenizer = new StringTokenizer (line, " ");
+
+ if (tokenizer.countTokens () < 8) {
+ final String message = "while reading MSPcrunch data: " +
+ "not enough fields in this line: " + line;
+ throw new ComparisonDataParseException (message);
+ }
+
+ final String score_token = tokenizer.nextToken ();
+ final String percent_ident_token = tokenizer.nextToken ();
+ final String q_start_token = tokenizer.nextToken ();
+ final String q_end_token = tokenizer.nextToken ();
+
+ tokenizer.nextToken ();
+
+ final String s_start_token = tokenizer.nextToken ();
+ final String s_end_token = tokenizer.nextToken ();
+
+ try {
+ int score;
+ try {
+ score = Integer.valueOf (score_token).intValue ();
+ } catch (NumberFormatException e) {
+ score = Float.valueOf (score_token).intValue (); // blast+
+ }
+ final int percent_ident =
+ (int)(Float.valueOf (percent_ident_token).floatValue ());
+ final int q_start = Integer.valueOf (q_start_token).intValue ();
+ final int q_end = Integer.valueOf (q_end_token).intValue ();
+ final int s_start = Integer.valueOf (s_start_token).intValue ();
+ final int s_end = Integer.valueOf (s_end_token).intValue ();
+
+ final AlignMatch new_match =
+ makeAlignMatch (s_start, s_end, q_start, q_end, score, percent_ident);
+
+ return new_match;
+ } catch (NumberFormatException e) {
+ throw new IOException ("while reading MSPcrunch data: " +
+ "failed to parse a number from this string: " +
+ e.getMessage ());
+ }
+ }
+
+ public static void writeMatchFromAlignMatch(final AlignMatch match,
+ final String query, final String subject,
+ final Writer writer)
+ throws IOException
+ {
+ writer.write( match.getScore() + " " +
+ match.getPercentID() + " " +
+ match.getQuerySequenceStart() + " " +
+ match.getQuerySequenceEnd() + " " +
+ query + " " +
+ match.getSubjectSequenceStart() + " " +
+ match.getSubjectSequenceEnd() + " " +
+ subject + "\n" );
+ }
+
+ /**
+ * Make an AlignMatch object from the given String. The String must be in
+ * a format appropriate for this object.
+ **/
+ protected AlignMatch makeMatchFromString (final String line)
+ throws IOException {
+ return makeMatchFromStringStatic (line);
+ }
+
+ /**
+ * Returns true if and only if the given line is in the correct format for
+ * this type of ComparisonData. This should be as strict as possible.
+ **/
+ public static boolean formatCorrect (final String line) {
+ try {
+ makeMatchFromStringStatic (line);
+ } catch (IOException e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/uk/ac/sanger/artemis/MUMmerComparisonData.java b/uk/ac/sanger/artemis/MUMmerComparisonData.java
new file mode 100644
index 0000000..5814cba
--- /dev/null
+++ b/uk/ac/sanger/artemis/MUMmerComparisonData.java
@@ -0,0 +1,130 @@
+/* MUMmerComparisonData.java
+ *
+ * created: Thu Jul 15 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * This class implements the ComparisonData interface for MUMmer output.
+ *
+ * @author Kim Rutherford
+ * @version $Id: MUMmerComparisonData.java,v 1.1 2006-11-07 17:29:30 tjc Exp $
+ **/
+
+public class MUMmerComparisonData extends SimpleComparisonData
+ implements ComparisonData
+{
+ /**
+ * Create a new MUMmerComparisonData object by reading from the given
+ * LinePushBackReader.
+ **/
+ public MUMmerComparisonData (final LinePushBackReader stream)
+ throws IOException
+ {
+ super (stream);
+ }
+
+ public MUMmerComparisonData ()
+ {
+
+ }
+
+ /**
+ * Make an AlignMatch object from the given String.
+ **/
+ protected AlignMatch makeMatchFromString(final String line)
+ throws IOException
+ {
+ if(line == null || line.startsWith(">"))
+ return null;
+
+ final StringTokenizer tokenizer = new StringTokenizer(line, " ");
+
+ if(tokenizer.countTokens() < 3)
+ {
+ final String message = "unable to understand this line: " + line;
+ throw new ComparisonDataParseException(message);
+ }
+
+ final String first_token = tokenizer.nextToken();
+ final String second_token = tokenizer.nextToken();
+ final String third_token = tokenizer.nextToken();
+
+ String forth_token = "+";
+
+ if(tokenizer.countTokens() > 0)
+ forth_token = tokenizer.nextToken();
+
+ try
+ {
+ final int first_number = Integer.valueOf(first_token).intValue();
+ final int second_number = Integer.valueOf(second_token).intValue();
+ final int match_length = Integer.valueOf(third_token).intValue();
+
+ // System.err.println ("token: " + forth_token + " " + line);
+
+ int score = -1;
+
+ if((forth_token.equals("+") || forth_token.equals("-"))
+ && tokenizer.countTokens() > 0)
+ {
+ score = Integer.valueOf(tokenizer.nextToken()).intValue();
+
+ if(score < -1)
+ score = -1;
+ }
+
+ if(forth_token.equals("-"))
+ {
+ return makeAlignMatch(first_number, first_number + match_length - 1,
+ second_number + match_length - 1, second_number, score, -1);
+ }
+ else
+ {
+ return makeAlignMatch(first_number, first_number + match_length - 1,
+ second_number, second_number + match_length - 1, score, -1);
+ }
+ }
+ catch(NumberFormatException e)
+ {
+ return null;
+/* throw new IOException("while reading MUMmer file: failed to "
+ + "parse a number from this string: " + e.getMessage());*/
+ }
+ }
+
+
+ protected SimpleComparisonData getNewSimpleComparisonData()
+ {
+ return new MUMmerComparisonData();
+ }
+
+
+
+}
diff --git a/uk/ac/sanger/artemis/MegaBlastComparisonData.java b/uk/ac/sanger/artemis/MegaBlastComparisonData.java
new file mode 100644
index 0000000..3e271c5
--- /dev/null
+++ b/uk/ac/sanger/artemis/MegaBlastComparisonData.java
@@ -0,0 +1,160 @@
+/* MegaBlastComparisonData.java
+ *
+ * created: Tue Apr 30 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/MegaBlastComparisonData.java,v 1.1 2004-06-09 09:44:57 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * This class implements the ComparisonData interface for MegaBlast output.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: MegaBlastComparisonData.java,v 1.1 2004-06-09 09:44:57 tjc Exp $
+ **/
+
+public class MegaBlastComparisonData extends SimpleComparisonData
+ implements ComparisonData {
+ /**
+ * Create a new MegaBlastComparisonData by reading from the given
+ * LinePushBackReader.
+ **/
+ public MegaBlastComparisonData (final LinePushBackReader stream)
+ throws IOException {
+ super (stream);
+ }
+
+ /**
+ * Create a new, empty instance of MegaBlastComparisonData.
+ **/
+ public MegaBlastComparisonData () {
+
+ }
+
+ /**
+ * Returns a new, empty instance of this type of object;
+ **/
+ protected SimpleComparisonData getNewSimpleComparisonData () {
+ return new MegaBlastComparisonData ();
+ }
+
+
+ /**
+ * Make an AlignMatch object from the given String.
+ **/
+ private static AlignMatch makeMatchFromStringStatic (final String line)
+ throws IOException {
+ final StringTokenizer tokenizer = new StringTokenizer (line, " ");
+
+ if (tokenizer.countTokens () != 6) {
+ final String message = "while reading MegaBlast data: " +
+ "not enough fields in this line: " + line;
+ throw new ComparisonDataParseException (message);
+ }
+
+ // first token looks like: 'all'=='-selected' - just check that we have
+ // something that looks sensible
+ final String file_names = tokenizer.nextToken ();
+
+ if (!file_names.startsWith ("'") || !file_names.endsWith ("'") ||
+ file_names.indexOf ("==") == -1) {
+ final String message = "while reading MegaBlast data: " +
+ "first field (" + file_names + ") is badly formatted in this line: " +
+ line;
+ throw new ComparisonDataParseException (message);
+ }
+
+ String s_start_token = tokenizer.nextToken ();
+ final String q_start_token = tokenizer.nextToken ();
+ final String s_end_token = tokenizer.nextToken ();
+ String q_end_token = tokenizer.nextToken ();
+
+ if (s_start_token.startsWith ("(")) {
+ s_start_token = s_start_token.substring (1);
+ } else {
+ final String message = "while reading MegaBlast data: " +
+ "second field " +
+ s_start_token + " " + q_start_token + " " +
+ s_end_token + " " + q_end_token +
+ " is badly formatted in this line: " + line;
+ throw new ComparisonDataParseException (message);
+ }
+
+ if (q_end_token.endsWith (")")) {
+ q_end_token = q_end_token.substring (0, q_end_token.length () - 1);
+ } else {
+ final String message = "while reading MegaBlast data: " +
+ "second field (" +
+ s_start_token + " " + q_start_token + " " +
+ s_end_token + " " + q_end_token +
+ " is badly formatted in this line: " + line;
+ throw new ComparisonDataParseException (message);
+ }
+
+ // dunno what it is - throw it away
+ tokenizer.nextToken ();
+
+ try {
+ final int q_start = Integer.valueOf (q_start_token).intValue ();
+ final int q_end = Integer.valueOf (q_end_token).intValue ();
+ final int s_start = Integer.valueOf (s_start_token).intValue ();
+ final int s_end = Integer.valueOf (s_end_token).intValue ();
+
+ return makeAlignMatch (s_start, s_end, q_start, q_end, 100,
+ 100);
+ } catch (NumberFormatException e) {
+ throw new IOException ("while reading blast -m 8 data: " +
+ "failed to parse a number from this string: " +
+ e.getMessage ());
+ }
+ }
+
+ /**
+ * Make an AlignMatch object from the given String. The String must be in
+ * a format appropriate for this object.
+ **/
+ protected AlignMatch makeMatchFromString (final String line)
+ throws IOException {
+ return makeMatchFromStringStatic (line);
+ }
+
+ /**
+ * Returns true if and only if the given line is in the correct format for
+ * this type of ComparisonData. This should be as strict as possible.
+ **/
+ public static boolean formatCorrect (final String line) {
+ try {
+ makeMatchFromStringStatic (line);
+ } catch (IOException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/OptionChangeEvent.java b/uk/ac/sanger/artemis/OptionChangeEvent.java
new file mode 100644
index 0000000..4ed9594
--- /dev/null
+++ b/uk/ac/sanger/artemis/OptionChangeEvent.java
@@ -0,0 +1,59 @@
+/* OptionChangeEvent.java
+ *
+ * created: Wed Jun 13 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/OptionChangeEvent.java,v 1.1 2004-06-09 09:44:58 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * This event is sent when an option changes.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: OptionChangeEvent.java,v 1.1 2004-06-09 09:44:58 tjc Exp $
+ **/
+
+public class OptionChangeEvent extends ChangeEvent {
+ /**
+ * Create a new OptionChangeEvent object.
+ * @param source The object that generated the event.
+ * @param option_name The name of option that changed
+ **/
+ public OptionChangeEvent (final Object source,
+ final String option_name)
+ {
+ super (source);
+ this.option_name = option_name;
+ }
+
+ /**
+ * Return the name of the option that changed.
+ **/
+ public String getOptionName () {
+ return option_name;
+ }
+
+ /**
+ * The option name that was passed to the constructor.
+ **/
+ private String option_name;
+}
diff --git a/uk/ac/sanger/artemis/OptionChangeListener.java b/uk/ac/sanger/artemis/OptionChangeListener.java
new file mode 100644
index 0000000..3497155
--- /dev/null
+++ b/uk/ac/sanger/artemis/OptionChangeListener.java
@@ -0,0 +1,41 @@
+/* OptionChangeListener.java
+ *
+ * created: Tue Jun 12 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/OptionChangeListener.java,v 1.1 2004-06-09 09:44:59 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * The OptionChangeListener interface is implemented by those classes that
+ * need to listen for changes to Option objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: OptionChangeListener.java,v 1.1 2004-06-09 09:44:59 tjc Exp $
+ **/
+
+public interface OptionChangeListener extends ChangeListener {
+ /**
+ * Invoked when an Option is changed.
+ **/
+ void optionChanged (OptionChangeEvent event);
+}
diff --git a/uk/ac/sanger/artemis/Options.java b/uk/ac/sanger/artemis/Options.java
new file mode 100644
index 0000000..d8d079e
--- /dev/null
+++ b/uk/ac/sanger/artemis/Options.java
@@ -0,0 +1,1496 @@
+/* Options.java
+ *
+ * created: Thu Dec 10 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Options.java,v 1.15 2009-06-19 15:18:04 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.components.Splash;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.KeyVector;
+import uk.ac.sanger.artemis.io.QualifierInfo;
+import uk.ac.sanger.artemis.io.QualifierInfoVector;
+import uk.ac.sanger.artemis.io.QualifierInfoException;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.SimpleEntryInformation;
+
+
+import java.awt.Color;
+import java.awt.Font;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * An object of this class is used to read, write and store all the
+ * configurable options in Diana. The method Properties.load () is used to
+ * read in new options.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Options.java,v 1.15 2009-06-19 15:18:04 tjc Exp $
+ **/
+
+public class Options extends Properties
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * This is the object that will be returned by getOptions().
+ **/
+ static private Options options = new Options();
+
+ /** The name of the "direct edit" option. */
+ static private final String direct_edit_string = "direct_edit";
+
+ /** The name of the "eukaryotic mode" option. */
+ static private final String eukaryotic_mode_string = "organism_type";
+
+ /** The name of the "highlight active entry" option. */
+ static private final String highlight_active_entry_string =
+ "highlight_active_entry";
+
+ /** Cache for getDisplayQualifierNames(). */
+ private static StringVector display_gene_qualifier_names = null;
+
+ /** Cache for getSystematicQualifierNames(). */
+ private static StringVector systematic_gene_qualifier_names = null;
+
+ /** Cache for getAllGeneNames(). */
+ private static StringVector all_gene_qualifier_names = null;
+
+ /** Used as cache by readWritePossible(). */
+ private static Boolean read_succeeded = null;
+
+ /** The EntryInformation object to use for EMBL and GENBANK entries. */
+ private static EntryInformation extended_entry_information;
+
+ /** The EntryInformation object to use for EMBL and GENBANK entries. */
+ private static EntryInformation db_entry_information;
+
+ /** The default font that should be used for all windows. */
+ private Font font = null;
+
+ /**
+ * A map of colour numbers to Color object. This is initialised by
+ * setDefaultColourMap().
+ **/
+ private Vector<Color> colour_map = null;
+
+ /** A vector containing ExternalProgram objects that we can run. */
+ private ExternalProgramVector external_programs = null;
+
+ /** A vector containing ExternalProgram objects that we can run. */
+ private ExternalProgramVector ncbi_programs = null;
+
+ /** A vector of those objects listening for option change events. */
+ private Hashtable option_listener_hash = new Hashtable();
+
+ /** Set by getInvisibleQualifiers() and reset by resetCachedValues() */
+ private StringVector invisible_qualifiers = null;
+
+ public static String CACHE_PATH =
+ System.getProperty("user.home") + File.separatorChar +
+ ".artemis" + File.separatorChar + "cache" + File.separatorChar;
+
+ /**
+ * Create a new Options object with default settings for the options.
+ **/
+ public Options()
+ {
+ try
+ {
+ put("font_size", "14");
+ reset();
+ }
+ catch(Throwable e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Returns true if and only if the given String is not "no", "false", "n"
+ * or "f".
+ **/
+ private boolean getPropertyTruthValueInternal(final String value)
+ {
+ if(value == null)
+ return false;
+
+ final String lowercase_value = value.toLowerCase();
+
+ if(lowercase_value.equals("false") ||
+ lowercase_value.equals("f") || lowercase_value.equals("no") ||
+ lowercase_value.equals("n"))
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Returns true if and only if the given property is set and is not "no",
+ * "false", "n" or "f".
+ **/
+ public boolean getPropertyTruthValue(final String name)
+ {
+ return getPropertyTruthValueInternal(getProperty(name));
+ }
+
+ /**
+ * Names of qualifiers to search when attempting to find the primary or
+ * display name of a gene.
+ **/
+ public StringVector getDisplayQualifierNames()
+ {
+ if(display_gene_qualifier_names == null)
+ {
+ final String name_string = getProperty("display_name_qualifiers");
+
+ if(name_string == null)
+ {
+ display_gene_qualifier_names = new StringVector();
+ display_gene_qualifier_names.add("gene");
+ }
+ else
+ display_gene_qualifier_names = StringVector.getStrings(name_string);
+ }
+ return display_gene_qualifier_names;
+ }
+
+ /**
+ * Names of qualifiers to search when attempting to find the systematic
+ * name of a gene.
+ **/
+ public StringVector getSystematicQualifierNames()
+ {
+ if(systematic_gene_qualifier_names == null)
+ {
+ final String name_string = getProperty("systematic_name_qualifiers");
+
+ if(name_string == null)
+ {
+ systematic_gene_qualifier_names = new StringVector();
+ systematic_gene_qualifier_names.add("gene");
+ }
+ else
+ systematic_gene_qualifier_names =
+ StringVector.getStrings(name_string);
+ }
+ return systematic_gene_qualifier_names;
+ }
+
+ /**
+ * Return all possible gene names by combining the return values of
+ * getSystematicQualifierNames() and getDisplayQualifierNames()
+ **/
+ public StringVector getAllGeneNames()
+ {
+ if(all_gene_qualifier_names == null)
+ {
+ all_gene_qualifier_names = getSystematicQualifierNames().copy();
+ all_gene_qualifier_names.add(getDisplayQualifierNames());
+ }
+
+ return all_gene_qualifier_names;
+ }
+
+
+ /**
+ * Read the options from the options file and uk.ac.sanger.artemis.ini
+ **/
+ private void readOptions()
+ {
+ try
+ {
+ final InputStream options_input_stream =
+ Options.class.getResourceAsStream("/etc/options");
+
+ if(options_input_stream == null)
+ return;
+
+ load(options_input_stream);
+
+ final boolean run_quietly =
+ getPropertyTruthValueInternal(System.getProperty("run_quietly"));
+
+ if(readWritePossible())
+ {
+ final String user_home = System.getProperty("user.home");
+
+ // properties are read in order from these files.
+ // the null is replaced by the file name given by the extra_options
+ // system property (if it is set)
+ final String [] standard_option_file_names =
+ {
+ "Diana.ini",
+ "options",
+ "options.txt",
+ "options.text",
+ user_home + File.separator + ".artemis_options"
+ };
+
+ String [] option_file_names = standard_option_file_names;
+
+ final Properties system_properties = System.getProperties();
+
+ if(system_properties != null)
+ {
+ final String extra_options_prop =
+ system_properties.getProperty("extra_options");
+
+ if(extra_options_prop !=null)
+ {
+ final StringVector extra_option_file_names =
+ StringVector.getStrings(extra_options_prop, ":");
+
+ option_file_names =
+ new String [option_file_names.length +
+ extra_option_file_names.size()];
+
+ for(int i = 0 ; i < extra_option_file_names.size() ; ++i)
+ {
+ final String extra_option_file_name =
+ (String)extra_option_file_names.elementAt(i);
+
+ if(new File(extra_option_file_name).canRead())
+ option_file_names[i] = extra_option_file_name;
+ else
+ {
+ System.err.println("warning: could not read options from \"" +
+ extra_option_file_name + "\"");
+ option_file_names[i] = null;
+ }
+ }
+
+ for(int i = 0 ; i < standard_option_file_names.length ; ++i)
+ option_file_names[i + extra_option_file_names.size()] =
+ standard_option_file_names[i];
+ }
+ }
+
+ for(int i = 0 ; i < option_file_names.length ; ++i)
+ {
+ final String this_options_file = option_file_names[i];
+
+ if(this_options_file == null)
+ continue;
+
+ final Document options_document =
+ new FileDocument(new File(this_options_file));
+
+ // read the option files if they exist
+ if(options_document.readable())
+ {
+
+ final InputStream options_document_stream =
+ options_document.getInputStream();
+
+ /*if(!run_quietly)
+ System.err.println("reading options from \"" +
+ this_options_file + "\"");*/
+
+ Splash.appendToLog(this_options_file+" options read");
+ load(options_document_stream);
+ }
+ else
+ Splash.appendToLog(this_options_file+" not found");
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ System.err.println("could not read an options file : " + e);
+ }
+
+ for(Enumeration enumProp = propertyNames(); enumProp.hasMoreElements();)
+ {
+ final String property_name = (String)enumProp.nextElement();
+ fireChangeEvent(property_name);
+ }
+ }
+
+ /**
+ * Reset this Options object by setting all the options to their default
+ * values and then call readOptions().
+ **/
+ public void reset()
+ {
+ clear();
+
+ setDefaultFeatureColours();
+ setDefaultColourMap();
+
+ readOptions();
+ setChadoOptions();
+
+ readSystemOptions();
+ resetCachedValues();
+
+/////
+// final Enumeration keys = propertyNames();
+// while (keys.hasMoreElements())
+// {
+// final String key = (String)keys.nextElement();
+// final String value = getProperty(key);
+// if(key.startsWith("colo"))
+// System.out.println(key+" \t"+value);
+// }
+/////
+
+ }
+
+ private void setChadoOptions()
+ {
+ final StringVector exon_models = getOptionValues("chado_exon_model");
+ if(exon_models != null)
+ {
+ DatabaseDocument.EXONMODEL = (String)exon_models.elementAt(0);
+ if(getProperty("colour_of_" + DatabaseDocument.EXONMODEL) == null)
+ put("colour_of_" + DatabaseDocument.EXONMODEL, "7");
+ }
+
+ final StringVector chado_transcript = getOptionValues("chado_transcript");
+ if(chado_transcript != null)
+ {
+ DatabaseDocument.TRANSCRIPT = (String)chado_transcript.elementAt(0);
+ if(getProperty("colour_of_" + DatabaseDocument.TRANSCRIPT) == null)
+ put("colour_of_" + DatabaseDocument.TRANSCRIPT, "1");
+ }
+
+ DatabaseDocument.CHADO_INFER_CDS = getPropertyTruthValue("chado_infer_CDS_UTR");
+ if(DatabaseDocument.CHADO_INFER_CDS)
+ DatabaseDocument.EXONMODEL = "exon";
+ }
+
+ /**
+ * Call load() from the super class and then reset the cached values for
+ * Font and Color objects.
+ **/
+ public void load(InputStream in_stream)
+ throws IOException
+ {
+ super.load(in_stream);
+
+ resetCachedValues();
+ }
+
+ /**
+ * Return the reference of the global Options object.
+ **/
+ public static Options getOptions()
+ {
+ return options;
+ }
+
+ /**
+ * Return true if and only if we are running on a Unix machine.
+ **/
+ public static boolean isUnixHost()
+ {
+ if(System.getProperty("artemis.environment") != null &&
+ System.getProperty("artemis.environment").equals("UNIX"))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if Artemis is in "Noddy mode", meaning there
+ * will be lots of requesters asking for confirmation when delete/editing.
+ **/
+ public static boolean isNoddyMode()
+ {
+ return !isBlackBeltMode();
+ }
+
+ /**
+ * Return true if and only if isNoddyMode() will return false.
+ **/
+ public static boolean isBlackBeltMode()
+ {
+ return getOptions().getPropertyTruthValue("black_belt_mode");
+ }
+
+ /**
+ * Return true if and only if we are running in an applet.
+ **/
+ public static boolean readWritePossible()
+ {
+ if(read_succeeded == null)
+ {
+ try
+ {
+ final File temp_file = File.createTempFile("dummy", "dummy");
+ read_succeeded = new Boolean(true);
+ }
+ catch(Throwable _)
+ {
+ read_succeeded = new Boolean(false);
+ }
+ }
+
+ return read_succeeded.booleanValue();
+ }
+
+ /**
+ * Return a StringVector containing the values of the given option or null
+ * if there is no such option. The values are separated by spaces in the
+ * properties file.
+ *
+ * @see #getPropertyValues()
+ **/
+ public StringVector getOptionValues(final String option)
+ {
+ return getPropertyValues(this, option);
+ }
+
+ /**
+ * Return a StringVector containing the values of the given option in the
+ * given Properties object or null if there is no such option. The values
+ * are separated by spaces in the properties file. For example, asking for
+ * the property foo in a Properties object where foo has the value: "thing1
+ * thing2 thing3", will return a vector containing three String objects:
+ * "thing1", "thing2", "thing3"
+ **/
+ public static StringVector getPropertyValues(final Properties properties,
+ final String option)
+ {
+ final String option_value = properties.getProperty(option);
+
+ if(option_value == null)
+ return null;
+
+ return StringVector.getStrings(option_value);
+ }
+
+ /**
+ * Return a QualifierInfoVector object containing one object for each
+ * qualifier given in extra_qualifiers option.
+ **/
+ public QualifierInfoVector getExtraQualifiers()
+ {
+ return getExtraQualifiers("extra_qualifiers");
+ }
+
+ /**
+ * Return a QualifierInfoVector object containing one object for each
+ * qualifier given in extra_qualifiers option.
+ **/
+ public QualifierInfoVector getExtraGffQualifiers()
+ {
+ return getExtraQualifiers("extra_qualifiers_gff");
+ }
+
+ /**
+ * Return a QualifierInfoVector object containing one object for each
+ * qualifier given in extra_qualifiers option.
+ **/
+ private QualifierInfoVector getExtraQualifiers(final String qualifier_options_flag)
+ {
+ final QualifierInfoVector return_vector =
+ new QualifierInfoVector();
+
+ final StringVector extra_qualifiers_strings =
+ getOptionValues(qualifier_options_flag);
+
+ for(int i = 0 ; i < extra_qualifiers_strings.size() / 2 ; ++i)
+ {
+ final String name = (String)extra_qualifiers_strings.elementAt(i * 2);
+ final String type_string =
+ (String)extra_qualifiers_strings.elementAt(i * 2 + 1);
+ final int type = QualifierInfo.getQualifierTypeID(type_string);
+
+ return_vector.add(new QualifierInfo(name, type, null, null, false));
+ }
+
+ return return_vector;
+ }
+
+ /**
+ * Return a Vector of the names of those qualifiers that shouldn't be shown
+ * by the QualifierChoice popup.
+ **/
+ public StringVector getInvisibleQualifiers(boolean isGFF)
+ {
+ if(invisible_qualifiers == null)
+ {
+ invisible_qualifiers = getOptionValues("invisible_qualifiers");
+
+ if(isGFF)
+ invisible_qualifiers.add( getOptionValues("invisible_qualifiers_gff") );
+
+ if(invisible_qualifiers == null)
+ invisible_qualifiers = new StringVector();
+ }
+
+ return invisible_qualifiers;
+ }
+
+ /**
+ * Return the path of the default sequence file or null if there is no
+ * default. This will usually have been set in the uk.ac.sanger.artemis.ini file,
+ * eg. SEQUENCE_FILE='c5H10.seq'
+ **/
+ public String getDefaultSequenceFileName()
+ {
+ final String property_string = getProperty("SEQUENCE_FILE");
+
+ if(property_string == null)
+ return null;
+ else
+ {
+ if(property_string.length() == 0)
+ return null;
+
+ // trim the quotes from the name
+ if(property_string.startsWith("'") &&
+ property_string.endsWith("'") &&
+ property_string.length() > 2)
+ return property_string.substring(1, property_string.length() - 1);
+ else
+ return property_string;
+ }
+ }
+
+ /**
+ * Return the path of the default feature file or null if there is no
+ * default. This will usually have been set in the uk.ac.sanger.artemis.ini file,
+ * eg. FEATURE_FILE='c5H10_embl.tab'
+ **/
+ public String getDefaultFeatureFileName()
+ {
+ final String property_string = getProperty("FEATURE_FILE");
+
+ if(property_string == null)
+ return null;
+ else
+ {
+ if(property_string.length() == 0)
+ return null;
+
+ // trim the quotes from the name
+ if(property_string.startsWith("'") &&
+ property_string.endsWith("'") &&
+ property_string.length() > 2)
+ return property_string.substring(1, property_string.length() - 1);
+ else
+ return property_string;
+ }
+ }
+
+ /**
+ * Return the minimum open reading frame size used when automatically
+ * creating ORFs with the "Mark Open Reading Frames" function.
+ **/
+ public int getMinimumORFSize()
+ {
+ final Integer minimum_orf_size = getIntegerProperty("minimum_orf_size");
+
+ if(minimum_orf_size == null) // default value
+ return 100;
+ else
+ return minimum_orf_size.intValue();
+ }
+
+ /**
+ * Get the default colour for the given feature key, as specified in the
+ * options file. If no colour is specified in the file then null is
+ * returned.
+ * @param key The feature key to get the colour of.
+ **/
+ public Color getDefaultFeatureColour(Key key)
+ {
+ final Integer colour_integer = getIntegerProperty("colour_of_" + key);
+
+ if(colour_integer == null)
+ return null;
+ else
+ return getColorFromColourNumber(colour_integer.intValue());
+ }
+
+ /**
+ * Given a colour number (perhaps from a /colour qualifier) return an
+ * appropriate Color object.
+ **/
+ public Color getColorFromColourNumber(int colour_number)
+ {
+ // first look up colour_map for speed. if that fails we then try to look
+ // for an appropriate property and turn into a Color object.
+ if(colour_number < 0)
+ return null;
+
+ if(colour_number >= colour_map.size() ||
+ colour_map.elementAt(colour_number) == null)
+ {
+ String col = getProperty("colour_" + colour_number);
+ if(col != null)
+ return parseColour(col);
+
+ // there is no colour for this colour_number
+ return null;
+ }
+ else
+ return colour_map.elementAt(colour_number);
+ }
+
+ /**
+ * Given a String containing three space separated integers (0-255), return
+ * a Color object. The three integers represent red, green and blue
+ * respectively. If a Color can't be parsed null is returned.
+ **/
+ private Color parseColour(String colour_string)
+ {
+ try
+ {
+ // first get three integers from the String
+
+ // trim any whitespace from the ends
+ final StringVector value_strings =
+ StringVector.getStrings(colour_string.trim());
+
+ final int first_int =
+ Integer.valueOf((String)value_strings.elementAt(0)).intValue();
+ final int second_int =
+ Integer.valueOf((String)value_strings.elementAt(1)).intValue();
+ final int third_int =
+ Integer.valueOf((String)value_strings.elementAt(2)).intValue();
+
+ return new Color(first_int, second_int, third_int);
+
+ }
+ catch(NumberFormatException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Get the default Color for a colour
+ **/
+ public void setDefaultColourMap()
+ {
+ put("colour_0", "255 255 255"); // white
+ put("colour_1", "100 100 100"); // dark grey
+ put("colour_2", "255 0 0"); // red
+ put("colour_3", "0 255 0"); // green
+ put("colour_4", "0 0 255"); // blue
+ put("colour_5", "0 255 255"); // cyan
+ put("colour_6", "255 0 255"); // magenta
+ put("colour_7", "255 255 0"); // yellow
+ put("colour_8", "152 251 152"); // pale green
+ put("colour_9", "135 206 250"); // light sky blue
+ put("colour_10", "255 165 0"); // orange
+ put("colour_11", "200 150 100"); // brown
+ put("colour_12", "255 200 200"); // pink
+ }
+
+ /**
+ * Return a vector containing the ExternalProgram objects of all the
+ * external programs that we can use.
+ **/
+ public ExternalProgramVector getExternalPrograms()
+ {
+ if(external_programs == null)
+ {
+ external_programs = new ExternalProgramVector();
+
+ final StringVector protein_value_strings =
+ getOptionValues("feature_protein_programs");
+
+ if(protein_value_strings != null)
+ {
+ for(int i = 0; i < protein_value_strings.size() / 2; ++i)
+ {
+ final String program_name =
+ (String)protein_value_strings.elementAt(i * 2);
+ final String program_options =
+ (String)protein_value_strings.elementAt(i * 2 + 1);
+
+ final ExternalProgram program =
+ new ExternalProgram(program_name, program_options,
+ ExternalProgram.AA_PROGRAM);
+
+ external_programs.add(program);
+ }
+ }
+
+ final StringVector dna_value_strings =
+ getOptionValues("feature_dna_programs");
+
+ if(dna_value_strings != null)
+ {
+ for(int i = 0; i < dna_value_strings.size() / 2; ++i)
+ {
+ final String program_name =
+ (String)dna_value_strings.elementAt(i * 2);
+ final String program_options =
+ (String)dna_value_strings.elementAt(i * 2 + 1);
+
+ final ExternalProgram program =
+ new ExternalProgram(program_name, program_options,
+ ExternalProgram.DNA_PROGRAM);
+
+ external_programs.add(program);
+ }
+ }
+
+ final StringVector application_value_strings =
+ getOptionValues("application_programs");
+
+ if(application_value_strings != null)
+ {
+ for(int i = 0; i < application_value_strings.size(); ++i)
+ {
+ final String program_name = (String)application_value_strings.elementAt(i);
+
+ final ExternalProgram program =
+ new ExternalProgram(program_name, null,
+ ExternalProgram.APPLICATION);
+
+ external_programs.add(program);
+ }
+ }
+ }
+ return external_programs;
+ }
+
+
+
+ /**
+ * Return a vector containing the ncbi ExternalProgram objects of all the
+ * external programs that we can use.
+ **/
+ public ExternalProgramVector getNCBIPrograms()
+ {
+ if(ncbi_programs == null)
+ {
+ ncbi_programs = new ExternalProgramVector();
+
+ final StringVector protein_value_strings =
+ getOptionValues("ncbi_protein_search");
+
+ if(protein_value_strings != null)
+ {
+ for(int i = 0; i < protein_value_strings.size() / 2; ++i)
+ {
+ final String program_name =
+ (String)protein_value_strings.elementAt(i * 2);
+ final String program_options =
+ (String)protein_value_strings.elementAt(i * 2 + 1);
+
+ final ExternalProgram program =
+ new ExternalProgram(program_name, program_options,
+ ExternalProgram.AA_PROGRAM);
+
+ ncbi_programs.add(program);
+ }
+ }
+
+ final StringVector dna_value_strings =
+ getOptionValues("ncbi_dna_search");
+
+ if(dna_value_strings != null)
+ {
+ for(int i = 0; i < dna_value_strings.size() / 2; ++i)
+ {
+ final String program_name =
+ (String)dna_value_strings.elementAt(i * 2);
+ final String program_options =
+ (String)dna_value_strings.elementAt(i * 2 + 1);
+
+ final ExternalProgram program =
+ new ExternalProgram(program_name, program_options,
+ ExternalProgram.DNA_PROGRAM);
+
+ ncbi_programs.add(program);
+ }
+ }
+ }
+ return ncbi_programs;
+ }
+
+ /**
+ * Return a StringVector containing the bases of the possible start codons
+ * for prokaryotes. This is stored in the prokaryotic_start_codons option
+ * in the options file.
+ **/
+//public StringVector getProkaryoticStartCodons()
+//{
+// final StringVector option_values =
+// getOptionValues("prokaryotic_start_codons");
+
+// for(int i = 0; i<option_values.size() ; ++i)
+// {
+// final String new_value = option_values.elementAt(i).toLowerCase();
+// option_values.setElementAt(new_value, i);
+// }
+// return option_values;
+//}
+
+ /**
+ * Return a StringVector containing the bases of the possible eukaryotic
+ * start codons. This is stored in the eukaryotic_start_codons option in
+ * the options file.
+ **/
+//public StringVector getEukaryoticStartCodons()
+//{
+// final StringVector option_values =
+// getOptionValues("eukaryotic_start_codons");
+
+// for(int i = 0; i<option_values.size() ; ++i)
+// {
+// final String new_value = option_values.elementAt(i).toLowerCase();
+// option_values.setElementAt(new_value, i);
+// }
+
+// return option_values;
+//}
+
+ public StringVector getStartCodons()
+ {
+ final StringVector option_values;
+
+ if(getProperty("start_codons") == null)
+ {
+ if(isEukaryoticMode())
+ option_values = getOptionValues("eukaryotic_start_codons");
+ else
+ option_values = getOptionValues("prokaryotic_start_codons");
+ }
+ else
+ option_values = getOptionValues("start_codons");
+
+ for(int i = 0; i<option_values.size() ; ++i)
+ {
+ final String new_value = ((String)option_values.elementAt(i)).toLowerCase();
+ option_values.setElementAt(new_value, i);
+ }
+
+ return option_values;
+ }
+
+ /**
+ * Return the default font that should be used for all windows.
+ **/
+ public Font getFont()
+ {
+ return font;
+ }
+
+ /**
+ * Return the UIResource for the default font that should be used for all
+ * windows.
+ **/
+ public javax.swing.plaf.FontUIResource getFontUIResource()
+ {
+ return new javax.swing.plaf.FontUIResource(getFont());
+ }
+
+ /**
+ * Adds the specified event listener to receive option change events from
+ * this object.
+ * @param l the event change listener.
+ **/
+ public void addOptionChangeListener(OptionChangeListener l)
+ {
+ option_listener_hash.put(l, this);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * option change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeOptionChangeListener(OptionChangeListener l)
+ {
+ option_listener_hash.remove(l);
+ }
+
+ /**
+ * Read all the system properties and overwrite this Options object with
+ * those values.
+ **/
+ private void readSystemOptions()
+ {
+ if(readWritePossible())
+ {
+ final Properties system_properties = System.getProperties();
+ if(system_properties != null)
+ {
+ final Enumeration enumeration = system_properties.keys();
+ while(enumeration.hasMoreElements())
+ {
+ final String key = (String)enumeration.nextElement();
+ final String value = system_properties.getProperty(key);
+ put(key, value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the default colour number for each a feature key.
+ **/
+ private void setDefaultFeatureColours()
+ {
+ final Object[] key_colour_map =
+ {
+ "CDS", "5",
+ "cds?", "7",
+ "BLASTCDS", "2",
+ "BLASTN_HIT", "6",
+ "source", "0",
+ "prim_tran", "0",
+ "stem_loop", "2",
+ "misc_feature", "3",
+ "delta", "3",
+ "repeat_region", "9",
+ "repeat_unit", "9",
+ "terminator", "3",
+ "promoter", "3",
+ "intron", "1",
+ "exon", "7",
+ DatabaseDocument.EXONMODEL, "7",
+ "mRNA", "1",
+ "tRNA", "8",
+ "TATA", "3",
+ "bldA", "2"
+ };
+
+ for(int i = 0 ; i < key_colour_map.length / 2 ; ++i)
+ put("colour_of_" + key_colour_map[i*2], key_colour_map[i*2+1]);
+ }
+
+ /**
+ * Clear all cached values (such as the font) and then recalculate.
+ **/
+ private void resetCachedValues()
+ {
+ /*final*/ int font_size;
+
+ try
+ {
+ final Integer font_size_integer = getIntegerProperty("font_size");
+
+ if(font_size_integer == null)
+ font_size = 12;
+ else
+ font_size = font_size_integer.intValue();
+ }
+ catch(NumberFormatException e)
+ {
+ System.err.println("error in options file - " +
+ "font_size should be an integer");
+ // a default value
+ font_size = 14;
+ put("font_size", String.valueOf(font_size));
+ }
+
+ if(getProperty("font_name") == null)
+ put("font_name", "Monospaced");
+
+ font = new Font(getProperty("font_name"), Font.PLAIN, font_size);
+
+ colour_map = new Vector<Color>(25);
+
+ int colour_number = 0;
+
+ while(true)
+ {
+ final String colour_value_string =
+ getProperty("colour_" + colour_number);
+
+ if(colour_value_string == null)
+ // we know nothing about this colour number so we assume that there
+ // aren't any more
+ break;
+ else
+ {
+ final Color new_colour = parseColour(colour_value_string);
+
+ if(new_colour == null)
+ {
+ // we couldn't parse the colour
+ System.err.println("error in options file could not understand " +
+ "this number: " + colour_value_string);
+ }
+ else
+ {
+ if(colour_number >= colour_map.size())
+ colour_map.setSize(colour_number + 50);
+ colour_map.setElementAt(new_colour, colour_number);
+ }
+ }
+ ++colour_number;
+ }
+
+ invisible_qualifiers = null;
+
+ display_gene_qualifier_names = null;
+ systematic_gene_qualifier_names = null;
+ all_gene_qualifier_names = null;
+ }
+
+ /**
+ * Return the value of a property/option as an Integer. If the property
+ * does not exist or cannot be parsed as an Integer, the method returns
+ * null.
+ **/
+ public Integer getIntegerProperty(String property_name)
+ {
+ final String property_string = getProperty(property_name);
+
+ if(property_string == null)
+ return null;
+ else
+ return Integer.valueOf(property_string);
+ }
+
+ /**
+ * Return the number of levels of undo to save or 0 if undo is disabled.
+ **/
+ public int getUndoLevels()
+ {
+ final Integer undo_integer = getIntegerProperty("undo_levels");
+ if(undo_integer == null)
+ return 0;
+ else
+ {
+ if(undo_integer.intValue() < 0)
+ return 0;
+ else
+ return undo_integer.intValue();
+ }
+ }
+
+ /**
+ * Enable direct editing if and only if the argument is true. "Direct
+ * Editing" allows feature locations to be changed by dragging the ends of
+ * the features around with the mouse.
+ **/
+ public void setDirectEdit(final boolean direct_edit)
+ {
+ if(direct_edit)
+ {
+ if(!canDirectEdit())
+ {
+ put(direct_edit_string, "true");
+ fireChangeEvent(direct_edit_string);
+ }
+ }
+ else
+ {
+ if(canDirectEdit())
+ {
+ put(direct_edit_string, "false");
+ fireChangeEvent(direct_edit_string);
+ }
+ }
+ }
+
+ /**
+ * Returns true if and only if direct editing is enabled.
+ **/
+ public boolean canDirectEdit()
+ {
+ if(getPropertyTruthValue(direct_edit_string))
+ return true;
+ else
+ return false;
+ }
+
+ public void setGeneticCode(String table)
+ {
+ put("translation_table",table);
+ fireChangeEvent("translation_table");
+ }
+
+ public void setDisplayNameQualifiers(final String display_name_qualifiers)
+ {
+ display_gene_qualifier_names = null;
+ put("display_name_qualifiers", display_name_qualifiers);
+ fireChangeEvent("display_name_qualifiers");
+ }
+
+ /**
+ * Names of qualifiers to search when attempting to find the systematic
+ * name of a gene.
+ **/
+ public void setSystematicQualifierNames(final String systematic_name_qualifiers)
+ {
+ systematic_gene_qualifier_names = null;
+ put("systematic_name_qualifiers", systematic_name_qualifiers);
+ fireChangeEvent("systematic_name_qualifiers");
+ }
+
+ /**
+ * Set the organism type to eukaryotic if and only if the argument is
+ * true. The other alternative is prokaryotic.
+ **/
+ public void setEukaryoticMode(final boolean eukaryotic_mode)
+ {
+ if(eukaryotic_mode)
+ {
+ if(!isEukaryoticMode())
+ {
+ put(eukaryotic_mode_string, "eukaryotic");
+ fireChangeEvent(eukaryotic_mode_string);
+ }
+ }
+ else
+ {
+ if(isEukaryoticMode())
+ {
+ put(eukaryotic_mode_string, "prokaryotic");
+ fireChangeEvent(eukaryotic_mode_string);
+ }
+ }
+ }
+
+ /**
+ * Returns true if and only if we should be using the eukaryotic settings.
+ * This is only the default.
+ **/
+ public boolean isEukaryoticMode()
+ {
+ final String organism_type_prop = getProperty("organism_type");
+
+ if(organism_type_prop == null ||
+ organism_type_prop.equals("eukaryotic") ||
+ organism_type_prop.equals("eukaryote") ||
+ organism_type_prop.equals("euk"))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Set whether or not to highlight the active entry.
+ **/
+ public void setHighlightActiveEntryFlag(final boolean highlight_active)
+ {
+ if(highlight_active)
+ {
+ if(!highlightActiveEntryFlag())
+ {
+ put(highlight_active_entry_string, "true");
+ fireChangeEvent(highlight_active_entry_string);
+ }
+ }
+ else
+ {
+ if(highlightActiveEntryFlag())
+ {
+ put(highlight_active_entry_string, "false");
+ fireChangeEvent(highlight_active_entry_string);
+ }
+ }
+ }
+
+ /**
+ * Returns true if and only if we should highlight the active entry in the
+ * display.
+ **/
+ public boolean highlightActiveEntryFlag()
+ {
+ if(getPropertyTruthValue(highlight_active_entry_string))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Returns true if this version of Java is running on a GNU/Linux machine
+ * and is version 1.2.0 or later.
+ **/
+ public boolean isBuggyLinuxVM()
+ {
+ if(!readWritePossible())
+ // Java in a browser has problems, but not this problem
+ return false;
+
+ final String os_name = (String) System.getProperties().get("os.name");
+
+ if(os_name.equals("Linux"))
+ {
+ final String java_version =
+ (String) System.getProperties().get("java.version");
+
+ if(java_version.startsWith("1.1."))
+ return false;
+ else
+ return true;
+ }
+ else
+ return false;
+ }
+
+
+ /**
+ * Return the EntryInformation object to use for EMBL and GENBANK entries.
+ **/
+ public static EntryInformation getDBEntryInformation()
+ {
+ return db_entry_information;
+ }
+
+ /**
+ * The EntryInformation object that has all the information that
+ * getDBEntryInformation () would return, but which has the non-standard
+ * qualifiers and keys added.
+ **/
+ public static EntryInformation getArtemisEntryInformation()
+ {
+ return extended_entry_information;
+ }
+
+ /**
+ * Return an EntryInformation object that is suitable for EMBL and GENBANK
+ * entries.
+ **/
+ private static EntryInformation makeEntryInformation()
+ throws IOException, QualifierInfoException
+ {
+ final InputStream feature_keys_stream =
+ Options.class.getResourceAsStream("/etc/feature_keys");
+
+ final InputStream qualifier_types_stream =
+ Options.class.getResourceAsStream("/etc/qualifier_types");
+
+ QualifierInfoVector qualifier_info_vector =
+ readQualifierInfo(qualifier_types_stream, feature_keys_stream);
+
+ final EntryInformation entry_information = new SimpleEntryInformation();
+
+ for(int i = 0 ; i < qualifier_info_vector.size() ; ++i)
+ {
+ final QualifierInfo qualifier_info =
+ qualifier_info_vector.elementAt(i);
+
+ entry_information.addQualifierInfo(qualifier_info);
+ }
+
+ entry_information.setEMBLFormat(true);
+
+ return entry_information;
+ }
+
+ /**
+ * Return an EntryInformation object that is suitable for EMBL and GENBANK
+ * entries, and has some useful non-standard additions (specified by the
+ * options file).
+ **/
+ private static EntryInformation
+ makeArtemisEntryInformation(final EntryInformation standard_entry_info)
+ throws QualifierInfoException
+ {
+
+ final StringVector extra_keys =
+ getOptions().getOptionValues("extra_keys");
+
+ final QualifierInfoVector extra_qualifiers =
+ getOptions().getExtraQualifiers();
+
+ final EntryInformation return_entry_information =
+ new SimpleEntryInformation(standard_entry_info);
+
+ for(int i = 0 ; i < extra_keys.size() ; ++i)
+ {
+ final Key new_key = new Key((String)extra_keys.elementAt(i));
+ return_entry_information.addKey(new_key);
+ }
+
+ for(int i = 0 ; i < extra_qualifiers.size() ; ++i)
+ {
+ final QualifierInfo new_qualifier_info = extra_qualifiers.elementAt(i);
+ return_entry_information.addQualifierInfo(new_qualifier_info);
+ }
+
+ // make a add qualifier info for each search program. eg. add blastp_file
+ // for blastp
+ final ExternalProgramVector external_programs =
+ getOptions().getExternalPrograms();
+
+ for(int i = 0 ; i < external_programs.size() ; ++i)
+ {
+ final ExternalProgram external_program = external_programs.elementAt(i);
+
+ if(external_program.getType() == ExternalProgram.AA_PROGRAM ||
+ external_program.getType() == ExternalProgram.DNA_PROGRAM)
+ {
+ final QualifierInfo new_qualifier_info =
+ new QualifierInfo(external_program.getName() + "_file",
+ QualifierInfo.QUOTED_TEXT,
+ null,
+ null,
+ true);
+
+ return_entry_information.addQualifierInfo(new_qualifier_info);
+ }
+ }
+
+ return_entry_information.setEMBLFormat(false);
+
+ return return_entry_information;
+ }
+
+ /**
+ * Read the possible feature key and qualifier names and types from the two
+ * given streams (see etc/feature_keys and etc/qualifier_types for details
+ * on the formats).
+ **/
+ private static QualifierInfoVector
+ readQualifierInfo(final InputStream qualifier_types_stream,
+ final InputStream feature_keys_stream)
+ throws IOException
+ {
+
+ final QualifierInfoVector return_vector = new QualifierInfoVector();
+
+ Properties feature_properties = new Properties();
+ final Properties qualifier_properties = new Properties();
+
+ feature_properties.load(feature_keys_stream);
+ qualifier_properties.load(qualifier_types_stream);
+
+ // parse the feature_properties
+
+ {
+ final Properties new_feature_properties = new Properties();
+
+ final Enumeration feature_enum = feature_properties.propertyNames();
+
+ while(feature_enum.hasMoreElements()) {
+ String current_feature_name = (String) feature_enum.nextElement();
+
+ final StringVector property_values =
+ Options.getPropertyValues(feature_properties, current_feature_name);
+
+ new_feature_properties.put(current_feature_name, property_values);
+ }
+
+ feature_properties = new_feature_properties;
+ }
+
+ final Enumeration qualifier_enum = qualifier_properties.propertyNames();
+
+ while(qualifier_enum.hasMoreElements())
+ {
+ String current_qualifier_name = (String) qualifier_enum.nextElement();
+
+ final StringVector current_qualifier_values =
+ Options.getPropertyValues(qualifier_properties,
+ current_qualifier_name);
+
+ final boolean once_only =
+ current_qualifier_values.elementAt(0).equals("yes");
+
+ final String type_string = (String)current_qualifier_values.elementAt(1);
+
+ // find the keys for which this qualifier name is valid or required
+
+ final KeyVector valid_keys = new KeyVector();
+ final KeyVector required_keys = new KeyVector();
+
+ final Enumeration features_enum = feature_properties.propertyNames();
+
+ while(features_enum.hasMoreElements())
+ {
+ final String current_key_string = (String)features_enum.nextElement();
+
+ final Key current_key = new Key(current_key_string);
+
+ final StringVector current_feature_qualifiers =
+ (StringVector) feature_properties.get(current_key_string);
+
+ if(current_feature_qualifiers.contains(current_qualifier_name))
+ valid_keys.add(current_key);
+ else
+ if(current_feature_qualifiers.contains("@" +
+ current_qualifier_name))
+ {
+ valid_keys.add(current_key);
+ required_keys.add(current_key);
+ }
+ }
+
+ final int type = QualifierInfo.getQualifierTypeID(type_string);
+
+ final QualifierInfo qualifier_info =
+ new QualifierInfo(current_qualifier_name, type, valid_keys,
+ required_keys, once_only);
+
+ return_vector.add(qualifier_info);
+ }
+
+ return return_vector;
+ }
+
+ /**
+ * Send a change event to all the listeners.
+ * @param option_name The name of the option that changed.
+ **/
+ private void fireChangeEvent(final String option_name)
+ {
+ for(final Enumeration e = option_listener_hash.keys() ;
+ e.hasMoreElements() ;)
+ {
+ final OptionChangeEvent event =
+ new OptionChangeEvent(this, option_name);
+
+ final OptionChangeListener target =
+ (OptionChangeListener) e.nextElement();
+
+ target.optionChanged(event);
+ }
+ }
+
+ static
+ {
+ try
+ {
+ db_entry_information = makeEntryInformation();
+ extended_entry_information =
+ makeArtemisEntryInformation(db_entry_information);
+ }
+ catch(QualifierInfoException e)
+ {
+ System.err.println("could not initialise the embl package: " +
+ e.getMessage());
+ System.exit(1);
+ }
+ catch(IOException e)
+ {
+ System.err.println("could not initialise the embl package: " +
+ e.getMessage());
+ System.exit(1);
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/ProcessMonitor.java b/uk/ac/sanger/artemis/ProcessMonitor.java
new file mode 100644
index 0000000..35bebba
--- /dev/null
+++ b/uk/ac/sanger/artemis/ProcessMonitor.java
@@ -0,0 +1,138 @@
+/* ProcessMonitor.java
+ *
+ * created: Wed Aug 6 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/ProcessMonitor.java,v 1.1 2004-06-09 09:45:01 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.io.*;
+
+/**
+ * Objects of this class watch a Process object and send a
+ * ExternalProgramEvent when the process status changes (eg. the end of a
+ * process).
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ProcessMonitor.java,v 1.1 2004-06-09 09:45:01 tjc Exp $
+ **/
+
+public class ProcessMonitor
+ extends SimpleExternalProgramMonitor
+ implements ExternalProgramMonitor
+{
+
+ /** The Process that was passed to the constructor. */
+ final public Process process;
+
+ /**
+ * Create a new ProcessMonitor object for the given Process.
+ * @param process The Process to monitor.
+ * @param name The name of the process that is being monitored.
+ * @param logger The log for errors, STDOUT and STDERR of the Process.
+ **/
+ public ProcessMonitor(final Process process, final String name,
+ final Logger logger)
+ {
+ super(name, logger);
+ this.process = process;
+ }
+
+ /**
+ * This code will wait for a Process to change status then log it and call
+ * sendEvent().
+ **/
+ public void run()
+ {
+ try
+ {
+ final Reader reader = new InputStreamReader (process.getErrorStream ());
+ getLogger().log (reader);
+ }
+ catch(IOException e)
+ {
+ getLogger().log("cannot read the errer stream from " +
+ getProgramName ());
+ }
+
+ try
+ {
+ final Reader reader = new InputStreamReader (process.getInputStream ());
+ getLogger().log (reader);
+ }
+ catch (IOException e)
+ {
+ getLogger().log("cannot read the ouput stream from " +
+ getProgramName ());
+ }
+
+ while(true)
+ {
+ try
+ {
+ final int return_value = process.waitFor();
+
+ final boolean core_dumped = (return_value & 0x80) != 0;
+
+ getLogger ().log ("\n--------------------------------------" +
+ "---------------------\n\n");
+
+ final String log_message;
+
+ if(core_dumped)
+ log_message = getProgramName() + " process dumped core";
+ else
+ {
+ final int sig_number = return_value & 0x7f;
+
+ if (sig_number > 0)
+ log_message =
+ getProgramName() + " process received signal: " + sig_number;
+ else
+ {
+ final int exit_code = return_value >> 8;
+
+ if(exit_code == 0)
+ log_message = getProgramName() + " process completed";
+ else
+ log_message = getProgramName() +
+ " process finished with exit code: " + exit_code;
+ }
+ }
+
+ final ExternalProgramEvent event =
+ new ExternalProgramEvent(ExternalProgramEvent.FINISHED,
+ log_message, process);
+
+ sendEvent(event);
+ getLogger().log (log_message + "\n");
+
+ return;
+ }
+ catch (InterruptedException e)
+ {
+ // go around the loop again
+ }
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/SSAHAComparisonData.java b/uk/ac/sanger/artemis/SSAHAComparisonData.java
new file mode 100644
index 0000000..baa1937
--- /dev/null
+++ b/uk/ac/sanger/artemis/SSAHAComparisonData.java
@@ -0,0 +1,138 @@
+/* SSAHAComparisonData.java
+ *
+ * created: Mon Jul 9 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/SSAHAComparisonData.java,v 1.1 2004-06-09 09:45:02 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * This class implements the ComparisonData interface for ssaha -pf
+ * output.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: SSAHAComparisonData.java,v 1.1 2004-06-09 09:45:02 tjc Exp $
+ **/
+
+public class SSAHAComparisonData extends SimpleComparisonData {
+ /**
+ * Create a new SSAHAComparisonData by reading from the given
+ * LinePushBackReader.
+ **/
+ public SSAHAComparisonData (final LinePushBackReader stream)
+ throws IOException {
+ super (stream);
+ }
+
+ /**
+ * Create a new, empty instance of SSAHAComparisonData.
+ **/
+ public SSAHAComparisonData () {
+
+ }
+
+ /**
+ * Returns a new, empty instance of this type of object;
+ **/
+ protected SimpleComparisonData getNewSimpleComparisonData () {
+ return new SSAHAComparisonData ();
+ }
+
+ /**
+ * Make an AlignMatch object from the given String.
+ **/
+ public static AlignMatch makeMatchFromStringStatic (final String line)
+ throws IOException {
+
+ final StringTokenizer tokenizer = new StringTokenizer (line, "\t");
+
+ if (tokenizer.countTokens () != 9) {
+ final String message = "while reading SSAHA data: " +
+ "not enough fields in this line: " + line;
+ throw new ComparisonDataParseException (message);
+ }
+
+ final String direction_token = tokenizer.nextToken ();
+ // throw away the query name
+ tokenizer.nextToken ();
+ final String q_start_token = tokenizer.nextToken ();
+ final String q_end_token = tokenizer.nextToken ();
+ // throw away the subject name
+ tokenizer.nextToken ();
+ final String s_start_token = tokenizer.nextToken ();
+ final String s_end_token = tokenizer.nextToken ();
+
+ final String score_token = tokenizer.nextToken ();
+ final String percent_ident_token = tokenizer.nextToken ();
+
+ try {
+ final int score = Integer.valueOf (score_token).intValue ();
+ final int percent_ident =
+ (int)(Float.valueOf (percent_ident_token).floatValue ());
+ final int q_start = Integer.valueOf (q_start_token).intValue ();
+ final int q_end = Integer.valueOf (q_end_token).intValue ();
+ final int s_start = Integer.valueOf (s_start_token).intValue ();
+ final int s_end = Integer.valueOf (s_end_token).intValue ();
+
+ if (direction_token.equals ("F")) {
+ return makeAlignMatch (s_start, s_end, q_start, q_end, score,
+ percent_ident);
+ } else {
+ return makeAlignMatch (s_start, s_end, q_end, q_start, score,
+ percent_ident);
+ }
+ } catch (NumberFormatException e) {
+ throw new IOException ("while reading SSAHA data: " +
+ "failed to parse a number from this string: " +
+ e.getMessage ());
+ }
+ }
+
+ /**
+ * Make an AlignMatch object from the given String. The String must be in
+ * a format appropriate for this object.
+ **/
+ protected AlignMatch makeMatchFromString (final String line)
+ throws IOException {
+ return makeMatchFromStringStatic (line);
+ }
+
+ /**
+ * Returns true if and only if the given line is in the correct format for
+ * this type of ComparisonData. This should be as strict as possible.
+ **/
+ public static boolean formatCorrect (final String line) {
+ try {
+ makeMatchFromStringStatic (line);
+ } catch (IOException e) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/uk/ac/sanger/artemis/Selectable.java b/uk/ac/sanger/artemis/Selectable.java
new file mode 100644
index 0000000..c629fcf
--- /dev/null
+++ b/uk/ac/sanger/artemis/Selectable.java
@@ -0,0 +1,41 @@
+/* Selectable.java
+ *
+ * created: Sat Nov 7 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Selectable.java,v 1.1 2004-06-09 09:45:03 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+/**
+ * A marker interface that indicates that the implementing class can be
+ * contained in a Selection object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Selectable.java,v 1.1 2004-06-09 09:45:03 tjc Exp $
+ *
+ **/
+
+public interface Selectable {
+
+}
+
+
diff --git a/uk/ac/sanger/artemis/Selection.java b/uk/ac/sanger/artemis/Selection.java
new file mode 100644
index 0000000..e3cc422
--- /dev/null
+++ b/uk/ac/sanger/artemis/Selection.java
@@ -0,0 +1,895 @@
+/* Selection.java
+ *
+ * created: Fri Nov 6 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Selection.java,v 1.3 2008-06-26 09:36:50 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+
+import java.io.*;
+import java.util.Vector;
+import java.awt.datatransfer.*;
+
+/**
+ * Objects of this class hold the features/feature segments or entries that
+ * the user has selected.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Selection.java,v 1.3 2008-06-26 09:36:50 tjc Exp $
+ *
+ **/
+
+public class Selection
+ implements FeatureChangeListener, EntryChangeListener,
+ Transferable, ClipboardOwner {
+ /**
+ * Create a new Selection object.
+ * @param clipboard The system clipboard reference. This may be null if
+ * this Selection should not set the system clipboard.
+ **/
+ public Selection (final Clipboard clipboard) {
+ this.clipboard = clipboard;
+ }
+
+ private final DataFlavor flavors [] = {
+ DataFlavor.stringFlavor,
+ // deprecated at 1.3
+ // DataFlavor.plainTextFlavor
+ };
+
+ /**
+ * Returns the array of the data flavors that this object can provide.
+ **/
+ public synchronized DataFlavor[] getTransferDataFlavors () {
+ return flavors;
+ }
+
+ /**
+ * Returns whether the requested flavor is supported by this object.
+ * @param flavor the requested flavor for the data
+ **/
+ public boolean isDataFlavorSupported (final DataFlavor flavor) {
+ return
+ flavor.equals (DataFlavor.stringFlavor)
+// deprecated at 1.3
+// || flavor.equals (DataFlavor.plainTextFlavor)
+ ;
+ }
+
+ /**
+ * If the data was requested in the "java.lang.String" flavor, return the
+ * String representing the selection, else throw an
+ * UnsupportedFlavorException.
+ * @param flavor the requested flavor for the data
+ **/
+ public synchronized Object getTransferData (final DataFlavor flavor)
+ throws UnsupportedFlavorException, IOException {
+ if (flavor.equals (DataFlavor.stringFlavor)) {
+ return getSelectionText ();
+// deprecated at 1.3
+// } else if (flavor.equals (DataFlavor.plainTextFlavor)) {
+// return new StringReader (getSelectionText ());
+ } else {
+ throw new UnsupportedFlavorException (flavor);
+ }
+ }
+
+ /**
+ * Implementation of the ClipboardOwner interface.
+ **/
+ public void lostOwnership(final Clipboard clipboard,
+ final Transferable contents) {
+
+ }
+
+ /**
+ * Returns this Selection in a String.
+ **/
+ public String getSelectionText () {
+ final FeatureVector selection_features = getAllFeatures ();
+
+ final StringBuffer buffer = new StringBuffer ();
+
+ final MarkerRange marker_range = getMarkerRange ();
+
+ if (marker_range != null) {
+ final String selection_bases = Strand.markerRangeBases (marker_range);
+
+ buffer.append (selection_bases);
+ }
+
+ for (int i = 0 ; i < selection_features.size () && i < 50 ; ++i) {
+ buffer.append (selection_features.elementAt (i).toString ());
+ }
+
+ return buffer.toString ();
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events so a SelectionChangeEvent can be sent if
+ * a selected feature is changed.
+ * @param event The change event.
+ **/
+ public void featureChanged (FeatureChangeEvent event) {
+ if (segments == null && features.contains (event.getFeature ()) ||
+ getAllFeatures ().contains (event.getFeature ())) {
+ if (event.getType () == FeatureChangeEvent.QUALIFIER_CHANGED ||
+ event.getType () == FeatureChangeEvent.KEY_CHANGED) {
+ // no need to reset the cache in this case
+ /*final SelectionChangeEvent selection_change_event =
+ new SelectionChangeEvent (this, SelectionChangeEvent.OBJECT_CHANGED);
+
+ fireAction (selection_listener_list, selection_change_event);*/
+ } else {
+ changeSelection (SelectionChangeEvent.OBJECT_CHANGED);
+ }
+ }
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so that we remove feature from the selection when
+ * they are removed from the Entry.
+ **/
+ public void entryChanged (final EntryChangeEvent event) {
+ switch (event.getType ()) {
+ case EntryChangeEvent.FEATURE_DELETED:
+
+ features.remove (event.getFeature ());
+
+ if (contains (event.getFeature ())) {
+ // we have a segment of the feature in the Selection -
+ // this will fire a SelectionChangeEvent
+ removeSegmentsOf (event.getFeature ());
+ } else {
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+
+ break;
+ }
+ }
+
+ /**
+ * Adds the specified event listener to receive selection change events
+ * from this object.
+ * @param l the event change listener.
+ **/
+ public void addSelectionChangeListener (final SelectionChangeListener l) {
+ selection_listener_list.addElement (l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * selection change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeSelectionChangeListener (final SelectionChangeListener l) {
+ selection_listener_list.removeElement (l);
+ }
+
+ /**
+ * Broadcast an event to notify SelectionChange event listeners that the
+ * selection has changed.
+ * @param type The type to use for the new event (see SelectionChangeEvent
+ * for details).
+ **/
+ private void changeSelection (final int type) {
+ resetCache ();
+
+ final SelectionChangeEvent event =
+ new SelectionChangeEvent (this, type);
+
+ fireAction (selection_listener_list, event);
+
+ if (clipboard != null) {
+ clipboard.setContents (this, this);
+ }
+ }
+
+ /**
+ * Add a Feature to the selection if it isn't there already.
+ **/
+ public void add (final Feature feature) {
+ if (addWithoutEvent (feature)) {
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+ }
+
+ /**
+ * Add the given Feature objects to the selection, and then send an
+ * appropriate event to the SelectionChangeEvent listeners.
+ **/
+ public void add (final FeatureVector features) {
+ for (int i = 0 ; i < features.size () ; ++i) {
+ addWithoutEvent (features.elementAt (i));
+ }
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+
+ /**
+ * Add a Feature to the selection if it isn't there already, but don't send
+ * an event to the SelectionChangeEvent listeners.
+ * @return true if and only if the segment with not already in the
+ * selection.
+ **/
+ private boolean addWithoutEvent (final Feature feature) {
+ if (features.contains (feature)) {
+ return false;
+ // do nothing
+ } else {
+ features.add (feature);
+ return true;
+ }
+ }
+
+ /**
+ * Add a FeatureSegment to the selection if it isn't there already, and
+ * send an event to the SelectionChangeEvent listeners..
+ **/
+ public void add (final FeatureSegment segment) {
+ if (addWithoutEvent (segment)) {
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+ }
+
+ /**
+ * Add a FeatureSegment to the selection if it isn't there already, but
+ * don't send an event to the SelectionChangeEvent listeners.
+ * @return true if and only if the segment with not already in the
+ * selection.
+ **/
+ private boolean addWithoutEvent (final FeatureSegment segment) {
+ if (segments.contains (segment)) {
+ return false;
+ // do nothing
+ } else {
+ segments.addElement (segment);
+ return true;
+ }
+ }
+
+ /**
+ * Remove all the objects from the selection, add the given Feature object
+ * to the selection, and then send an appropriate event to the
+ * SelectionChangeEvent listeners.
+ **/
+ public void set (final Feature feature) {
+ clearWithoutEvent ();
+ addWithoutEvent (feature);
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+
+ /**
+ * Remove all the objects from the selection, add the given FeatureSegment
+ * object to the selection, and then send an appropriate event to the
+ * SelectionChangeEvent listeners.
+ **/
+ public void set (final FeatureSegment feature_segment) {
+ clearWithoutEvent ();
+ addWithoutEvent (feature_segment);
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+
+ /**
+ * Remove all the objects from the selection, add the given Feature objects
+ * to the selection, and then send an appropriate event to the
+ * SelectionChangeEvent listeners.
+ **/
+ public void set (final FeatureVector features) {
+ clearWithoutEvent ();
+ for (int i = 0 ; i < features.size () ; ++i) {
+ addWithoutEvent (features.elementAt (i));
+ }
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+
+ /**
+ * Set the MarkerRange object that this Selection holds and remove all
+ * other objects.
+ **/
+ public void setMarkerRange (final MarkerRange marker_range) {
+ if (this.marker_range != marker_range) {
+ final MarkerRange old_marker_range = this.marker_range;
+ clearWithoutEvent ();
+ this.marker_range = marker_range;
+ if (old_marker_range == null) {
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ } else {
+ changeSelection (SelectionChangeEvent.OBJECT_CHANGED);
+ }
+ }
+ }
+
+
+ /**
+ * Return true if and only if there is nothing in the selection.
+ **/
+ public boolean isEmpty () {
+ if (features.size () == 0 &&
+ segments.size () == 0 &&
+ marker_range == null) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Remove all the objects from the selection, and send an event to the
+ * SelectionChangeEvent listeners.
+ **/
+ public void clear () {
+ if (!isEmpty ()) {
+ clearWithoutEvent ();
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+ }
+
+ /**
+ * Remove all the objects from the selection, but don't send an event to
+ * the SelectionChangeEvent listeners.
+ **/
+ private void clearWithoutEvent () {
+ features.removeAllElements ();
+ segments.removeAllElements ();
+ marker_range = null;
+ }
+
+ /**
+ * Remove a Feature from the selection.
+ **/
+ public void remove (final Feature feature) {
+ if (features.remove (feature)) {
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+ }
+
+ /**
+ * Remove the FeatureSegments of the given Feature from the selection.
+ **/
+ public void removeSegmentsOf (final Feature feature) {
+ final FeatureSegmentVector segments = feature.getSegments ();
+
+ for (int segment_index = 0 ;
+ segment_index < segments.size () ;
+ ++segment_index) {
+ final FeatureSegment this_segment = segments.elementAt (segment_index);
+
+ remove (this_segment);
+ }
+ }
+
+ /**
+ * Remove a FeatureSegment from the selection.
+ **/
+ public void remove (final FeatureSegment segment) {
+ if (segments.removeElement (segment)) {
+ changeSelection (SelectionChangeEvent.SELECTION_CHANGED);
+ }
+ }
+
+ /**
+ * Return true if this selection contains the given Feature.
+ **/
+ public boolean contains (final Feature feature) {
+ if (getSelectedFeatures ().contains (feature)) {
+ return true;
+ }
+
+ for (int i = 0 ; i < segments.size () ; ++i) {
+ final FeatureSegment this_segment = segments.elementAt (i);
+ final Feature this_feature = this_segment.getFeature ();
+
+ if (feature == this_feature) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return true if this selection contains the given Feature.
+ **/
+ public boolean contains (final FeatureSegment feature_segment) {
+ if (getAllSegments ().indexOf (feature_segment) == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Return a Range object that exactly covers the current selection.
+ * Returns null if and only if nothing is selected.
+ **/
+ public Range getSelectionRange () {
+ final Marker lowest_base_marker = getLowestBaseOfSelection ();
+ final Marker highest_base_marker = getHighestBaseOfSelection ();
+
+ if (lowest_base_marker == null || highest_base_marker == null) {
+ return null;
+ } else {
+ final Strand strand = lowest_base_marker.getStrand ();
+
+ final int first_postion = lowest_base_marker.getRawPosition ();
+ final int last_postion = highest_base_marker.getRawPosition ();
+
+ try {
+ return new Range (first_postion, last_postion);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+
+ /**
+ * Return a Range object for each of the selected objects.
+ **/
+ public RangeVector getSelectionRanges () {
+ final RangeVector return_ranges = new RangeVector ();
+
+ if (getSelectedFeatures ().size () > 0 ||
+ getSelectedSegments ().size () > 0) {
+ final FeatureSegmentVector segments = getAllSegments ();
+
+ for (int i = 0 ; i < segments.size () ; ++i) {
+ final FeatureSegment this_segment = segments.elementAt (i);
+
+ return_ranges.add (this_segment.getRawRange ());
+ }
+ } else {
+ final MarkerRange marker_range = getMarkerRange ();
+
+ if (marker_range != null) {
+ return_ranges.add (marker_range.getRawRange ());
+ }
+ }
+
+ return return_ranges;
+ }
+
+ /**
+ * Return a Marker for the base in this selection that is closest to the
+ * first base of the sequence, or null if nothing is selected.
+ **/
+ public Marker getLowestBaseOfSelection () {
+ if (lowest_base_marker != null) {
+ return lowest_base_marker;
+ }
+
+ if (getSelectedFeatures ().size () > 0 ||
+ getSelectedSegments ().size () > 0) {
+ final FeatureSegmentVector segments = getAllSegments ();
+
+ int current_min = 99999999;
+ Marker current_min_marker = null;
+
+ for (int i = 0 ; i < segments.size () ; ++i) {
+ final FeatureSegment this_segment = segments.elementAt (i);
+
+ final Marker start_marker = this_segment.getStart ();
+ if (start_marker.getRawPosition () < current_min) {
+ current_min = start_marker.getRawPosition ();
+ current_min_marker = start_marker;
+ }
+
+ final Marker end_marker = this_segment.getEnd ();
+ if (end_marker.getRawPosition () < current_min) {
+ current_min = end_marker.getRawPosition ();
+ current_min_marker = end_marker;
+ }
+ }
+
+ lowest_base_marker = current_min_marker;
+ } else {
+ final MarkerRange range = getMarkerRange ();
+
+ if (range == null) {
+ lowest_base_marker = null;
+ } else {
+ if (range.isForwardMarker ()) {
+ lowest_base_marker = range.getStart ();
+ } else {
+ lowest_base_marker = range.getEnd ();
+ }
+ }
+ }
+
+ return lowest_base_marker;
+ }
+
+ /**
+ * Return a Marker for the base in this selection that is closest to the
+ * last base of the sequence, or null if nothing is selected.
+ **/
+ public Marker getHighestBaseOfSelection () {
+ if (highest_base_marker != null) {
+ return highest_base_marker;
+ }
+
+ if (getSelectedFeatures ().size () > 0 ||
+ getSelectedSegments ().size () > 0) {
+ final FeatureSegmentVector segments = getAllSegments ();
+
+ int current_max = -1;
+ Marker current_max_marker = null;
+
+ for (int i = 0 ; i < segments.size () ; ++i) {
+ final FeatureSegment this_segment = segments.elementAt (i);
+
+ final Marker start_marker = this_segment.getStart ();
+ if (start_marker.getRawPosition () > current_max) {
+ current_max = start_marker.getRawPosition ();
+ current_max_marker = start_marker;
+ }
+
+ final Marker end_marker = this_segment.getEnd ();
+ if (end_marker.getRawPosition () > current_max) {
+ current_max = end_marker.getRawPosition ();
+ current_max_marker = end_marker;
+ }
+ }
+
+ highest_base_marker = current_max_marker;
+ } else {
+ final MarkerRange range = getMarkerRange ();
+
+ if (range == null) {
+ highest_base_marker = null;
+ } else {
+ if (range.isForwardMarker ()) {
+ highest_base_marker = range.getEnd ();
+ } else {
+ highest_base_marker = range.getStart ();
+ }
+ }
+ }
+
+ return highest_base_marker;
+ }
+
+
+ /**
+ * Return the first base of the MarkerRange (if it is set), or the first
+ * base of the selected features (if there are any selected features), or
+ * the first base of the selected FeatureSegment objects (if there are any
+ * selected FeatureSegment objects), otherwise null. If the selection
+ * contains features or segments from both strands, then the result of
+ * calling this method is the same as calling getLowestBaseOfSelection ().
+ **/
+ public Marker getStartBaseOfSelection () {
+ if (start_base_marker != null) {
+ return start_base_marker;
+ }
+
+ if (getSelectedFeatures ().size () > 0 ||
+ getSelectedSegments ().size () > 0) {
+ final FeatureSegmentVector segments = getAllSegments ();
+
+ boolean seen_forward_feature = false;
+ boolean seen_reverse_feature = false;
+
+ int current_min = 99999999;
+ FeatureSegment current_min_segment = null;
+
+ for (int i = 0 ; i < segments.size () ; ++i) {
+ final FeatureSegment this_segment = segments.elementAt (i);
+
+ if (this_segment.getFeature ().isForwardFeature ()) {
+ seen_forward_feature = true;
+ } else {
+ seen_reverse_feature = true;
+ }
+
+ if (seen_forward_feature && seen_reverse_feature) {
+ return getLowestBaseOfSelection ();
+ }
+
+ final Marker start_marker = this_segment.getStart ();
+
+ if (current_min_segment == null ||
+ start_marker.getPosition () < current_min) {
+ current_min = start_marker.getPosition ();
+ current_min_segment = this_segment;
+ }
+ }
+
+ if (current_min_segment == null) {
+ // there are no segments with real ranges
+ start_base_marker = null;
+ } else {
+ start_base_marker = current_min_segment.getStart ();
+ }
+ } else {
+ final MarkerRange range = getMarkerRange ();
+
+ if (range == null) {
+ start_base_marker = null;
+ } else {
+ start_base_marker = range.getStart ();
+ }
+ }
+
+ return start_base_marker;
+ }
+
+ /**
+ * Return the last base of the MarkerRange (if it is set), or the last base
+ * of the selected features (if there are any selected features), or the
+ * last base of the selected FeatureSegment objects (if there are any
+ * selected FeatureSegment objects), otherwise null. If the selection
+ * contains features or segments from both strands, then the result of
+ * calling this method is the same as calling getLowestBaseOfSelection ().
+ **/
+ public Marker getEndBaseOfSelection () {
+ if (end_base_marker != null) {
+ return end_base_marker;
+ }
+
+ if (getSelectedFeatures ().size () > 0 ||
+ getSelectedSegments ().size () > 0) {
+ final FeatureSegmentVector segments = getAllSegments ();
+
+ boolean seen_forward_feature = false;
+ boolean seen_reverse_feature = false;
+
+ int current_max = -1;
+ FeatureSegment current_max_segment = null;
+
+ for (int i = 0 ; i < segments.size () ; ++i) {
+ final FeatureSegment this_segment = segments.elementAt (i);
+
+ if (this_segment.getFeature ().isForwardFeature ()) {
+ seen_forward_feature = true;
+ } else {
+ seen_reverse_feature = true;
+ }
+
+ if (seen_forward_feature && seen_reverse_feature) {
+ return getHighestBaseOfSelection ();
+ }
+
+ final Marker start_marker = this_segment.getStart ();
+
+ if (current_max_segment == null ||
+ start_marker.getPosition () > current_max) {
+ current_max = start_marker.getPosition ();
+ current_max_segment = this_segment;
+ }
+ }
+
+ if (current_max_segment == null) {
+ // there are no segments with real ranges
+ end_base_marker = null;
+ } else {
+ end_base_marker = current_max_segment.getEnd ();
+ }
+ } else {
+ final MarkerRange range = getMarkerRange ();
+
+ if (range == null) {
+ end_base_marker = null;
+ } else {
+ end_base_marker = range.getEnd ();
+ }
+ }
+
+ return end_base_marker;
+ }
+
+ /**
+ * Return the bases of the selected features or the selected marker range.
+ **/
+ public String getSelectedBases () {
+ if (getMarkerRange () == null) {
+ final StringBuffer buffer = new StringBuffer ();
+
+ final FeatureVector all_features = getAllFeatures ();
+
+ for (int i = 0 ; i < all_features.size () ; ++i) {
+ final Feature this_feature = all_features.elementAt (i);
+ buffer.append (this_feature.getBases ());
+ }
+
+ return buffer.toString ();
+ } else {
+ return Strand.markerRangeBases (getMarkerRange ());
+ }
+ }
+
+ /**
+ * Return the object that was set with the setMarkerRange (MarkerRange)
+ * method.
+ **/
+ public MarkerRange getMarkerRange () {
+ return marker_range;
+ }
+
+ /**
+ * Return a vector of the Feature objects of this selection.
+ **/
+ public FeatureVector getSelectedFeatures () {
+ return features;
+ }
+
+ /**
+ * Return a vector of the FeatureSegment objects of this selection.
+ **/
+ public FeatureSegmentVector getSelectedSegments () {
+ return segments;
+ }
+
+ /**
+ * Return a vector of all the FeatureSegment objects of this selection.
+ * This will include the FeatureSegment objects that are owned by Feature
+ * objects in the selection.
+ **/
+ public FeatureSegmentVector getAllSegments () {
+ final FeatureSegmentVector return_segments = new FeatureSegmentVector ();
+
+ for (int i = 0 ; i < features.size () ; ++i) {
+ final Feature selection_feature = features.elementAt (i);
+ final FeatureSegmentVector segments = selection_feature.getSegments ();
+
+ for (int segment_index = 0 ;
+ segment_index < segments.size () ;
+ ++segment_index) {
+ final FeatureSegment this_segment = segments.elementAt (segment_index);
+
+ if (!return_segments.contains (this_segment)) {
+ return_segments.addElement (this_segment);
+ }
+ }
+ }
+
+ for (int i = 0 ; i < segments.size () ; ++i) {
+ final FeatureSegment this_segment = segments.elementAt (i);
+
+ if (!return_segments.contains (this_segment)) {
+ return_segments.addElement (this_segment);
+ }
+ }
+
+ return return_segments;
+ }
+
+ /**
+ * Return a vector of all the Feature objects of this selection. This will
+ * include the Feature objects that own the FeatureSegment objects in the
+ * selection.
+ **/
+ public FeatureVector getAllFeatures () {
+ if (all_features == null) {
+ all_features = (FeatureVector) features.clone ();
+
+ for (int i = 0 ; i < segments.size () ; ++i) {
+ final FeatureSegment this_segment = segments.elementAt (i);
+ final Feature this_feature = this_segment.getFeature ();
+
+ if (!all_features.contains (this_feature)) {
+ all_features.add (this_feature);
+ }
+ }
+ }
+
+ return all_features;
+ }
+
+ /**
+ * Send an event to those object listening for it.
+ * @param listeners A Vector of the objects that the event should be sent
+ * to.
+ * @param event The event to send
+ **/
+ private void fireAction (final Vector listeners, final ChangeEvent event) {
+ final Vector targets;
+ // copied from a book - synchronising the whole method might cause a
+ // deadlock
+ synchronized (this) {
+ targets = (Vector) listeners.clone ();
+ }
+
+ for ( int i = 0 ; i < targets.size () ; ++i ) {
+ ChangeListener target = (ChangeListener) targets.elementAt (i);
+
+ if (event instanceof SelectionChangeEvent) {
+ final SelectionChangeListener selection_change_listener =
+ (SelectionChangeListener) target;
+ selection_change_listener.selectionChanged ((SelectionChangeEvent) event);
+ } else {
+ throw new Error ("Selection.fireAction () - unknown event");
+ }
+ }
+ }
+
+ /**
+ * Reset all cached values in the Selection.
+ **/
+ private void resetCache () {
+ lowest_base_marker = null;
+ highest_base_marker = null;
+ start_base_marker = null;
+ end_base_marker = null;
+ all_features = null;
+ }
+
+ /**
+ * A Vector containing the Feature objects that this selection currently
+ * holds.
+ **/
+ private FeatureVector features = new FeatureVector ();
+
+ /**
+ * A Vector containing the FeatureSegment objects that this selection
+ * currently holds.
+ **/
+ private FeatureSegmentVector segments = new FeatureSegmentVector ();
+
+ /**
+ * This is a cache used by getAllFeatures (). This will be set to null
+ * anytime the selection changes.
+ **/
+ private FeatureVector all_features = null;
+
+ /**
+ * Each Selection object can hold one MarkerRange.
+ **/
+ private MarkerRange marker_range = null;
+
+ /**
+ * A vector of those objects listening for selection change events.
+ **/
+ final private Vector selection_listener_list = new Vector ();
+
+ /**
+ * The system clipboard as passed to the constructor.
+ **/
+ final private Clipboard clipboard;
+
+ /**
+ * The cached value that getLowestBaseOfSelection () returns/sets.
+ **/
+ private Marker lowest_base_marker = null;
+
+ /**
+ * The cached value that getHighestBaseOfSelection () returns/sets.
+ **/
+ private Marker highest_base_marker = null;
+
+ /**
+ * The cached value that getStartBaseOfSelection () returns/sets.
+ **/
+ private Marker start_base_marker = null;
+
+ /**
+ * The cached value that getEndBaseOfSelection () returns/sets.
+ **/
+ private Marker end_base_marker = null;
+}
diff --git a/uk/ac/sanger/artemis/SelectionChangeEvent.java b/uk/ac/sanger/artemis/SelectionChangeEvent.java
new file mode 100644
index 0000000..49a24d3
--- /dev/null
+++ b/uk/ac/sanger/artemis/SelectionChangeEvent.java
@@ -0,0 +1,73 @@
+/* SelectionChangeEvent.java
+ *
+ * created: Tue Oct 27 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/SelectionChangeEvent.java,v 1.1 2004-06-09 09:45:05 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+/**
+ * This event is sent when the currently selected object changes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: SelectionChangeEvent.java,v 1.1 2004-06-09 09:45:05 tjc Exp $
+ **/
+
+public class SelectionChangeEvent extends ChangeEvent {
+ /**
+ * Create a new SelectionChangeEvent object.
+ * @param source The Object that generated the event - probably a component.
+ * @param type The type of event. Should be one of OBJECT_ADDED,
+ * OBJECT_REMOVED or OBJECT_CHANGED.
+ **/
+ public SelectionChangeEvent (final Object source,
+ final int type) {
+ super (source);
+ this.type = type;
+ }
+
+ /**
+ * Return the type that was passed to the constructor.
+ **/
+ public int getType () {
+ return type;
+ }
+
+ /**
+ * Flag used when the event is caused by the addition or removal of an
+ * object from the selection.
+ **/
+ public final static int SELECTION_CHANGED = 1;
+
+ /**
+ * Flag used for all other events. Examples include the case when a
+ * qualifier or location changes for a selected feature.
+ **/
+ public final static int OBJECT_CHANGED = 3;
+
+ /**
+ * The event type that was passed to the constructor.
+ **/
+ private int type;
+}
+
+
diff --git a/uk/ac/sanger/artemis/SelectionChangeListener.java b/uk/ac/sanger/artemis/SelectionChangeListener.java
new file mode 100644
index 0000000..ce2f9fa
--- /dev/null
+++ b/uk/ac/sanger/artemis/SelectionChangeListener.java
@@ -0,0 +1,44 @@
+/* SelectionChangeListener.java
+ *
+ * created: Tue Oct 27 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/SelectionChangeListener.java,v 1.1 2004-06-09 09:45:06 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+/**
+ * The SelectionChangeListener interface is implemented by those classes that
+ * need to know when the selection changes. ie. when the user selects a
+ * different object (feature).
+ *
+ * @author Kim Rutherford
+ * @version $Id: SelectionChangeListener.java,v 1.1 2004-06-09 09:45:06 tjc Exp $
+ **/
+
+public interface SelectionChangeListener extends ChangeListener {
+ /**
+ * Invoked when the user selects a new object.
+ **/
+ void selectionChanged (SelectionChangeEvent event);
+}
+
+
diff --git a/uk/ac/sanger/artemis/SimpleComparisonData.java b/uk/ac/sanger/artemis/SimpleComparisonData.java
new file mode 100644
index 0000000..48d617b
--- /dev/null
+++ b/uk/ac/sanger/artemis/SimpleComparisonData.java
@@ -0,0 +1,359 @@
+/* SimpleComparisonData.java
+ *
+ * created: Wed May 17 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/SimpleComparisonData.java,v 1.3 2005-11-17 16:50:50 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.Hashtable;
+
+/**
+ * This class contains methods that are common to all ComparisonData
+ * objects. In particular it has methods for managing AlignMatch objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: SimpleComparisonData.java,v 1.3 2005-11-17 16:50:50 tjc Exp $
+ **/
+
+abstract class SimpleComparisonData implements ComparisonData
+{
+ /** array of matches created by the constructor */
+ private AlignMatch [] matches;
+
+ /** array is used as a buffer */
+ private AlignMatch [] match_buffer;
+
+ /** Set by the constructor and returned by getMaximumScore() */
+ private int max_score = -1;
+
+ /** Set by the constructor and returned by getMinimumScore() */
+ private int min_score = 999999999;
+
+ /** Set by setMatches() to be the highest base we see in the subject */
+ private int subject_sequence_max_base = -1;
+
+ /** Set by setMatches() to be the highest base we see in the query */
+ private int query_sequence_max_base = -1;
+
+ /**
+ * Create a new SimpleComparisonData by reading from the given
+ * LinePushBackReader.
+ **/
+ public SimpleComparisonData(final LinePushBackReader stream)
+ throws IOException
+ {
+ final Vector align_match_vector = new Vector();
+
+ String line;
+
+ while( (line = stream.readLine()) != null )
+ {
+ if(line.trim().length() == 0)
+ continue;
+
+ final AlignMatch new_match = makeMatchFromString(line);
+
+ // not a blank line or a comment
+ if(new_match != null)
+ align_match_vector.addElement(new_match);
+ }
+
+ final AlignMatch[] matches = new AlignMatch[align_match_vector.size()];
+
+ for(int i = 0; i < matches.length; ++i)
+ matches[i] = (AlignMatch)align_match_vector.elementAt(i);
+
+ setMatches(matches);
+ }
+
+ /**
+ * Create a new, empty instance of SimpleComparisonData.
+ **/
+ protected SimpleComparisonData()
+ {
+ }
+
+ /**
+ * Return an array containing all the AlignMatch objects for this
+ * comparison.
+ **/
+ public AlignMatch[] getMatches()
+ {
+ return matches;
+ }
+
+
+ /**
+ * If this object contains only valid matches for a comparison between
+ * subject_sequence and query_sequence return null (subject_sequence is the
+ * subject of the comparison query_sequence is the query). If the
+ * comparison would be valid if the data for the ends of the matches were
+ * swapped, then return a copy of this object with all the matches flipped.
+ * (For now, valid means that none of the matches goes over the end of the
+ * sequence.)
+ * @exception OutOfRangeException Thrown if the data in this object is not
+ * valid for either orientation.
+ **/
+ public ComparisonData flipMatchesIfNeeded(final Bases subject_sequence,
+ final Bases query_sequence)
+ throws OutOfRangeException
+ {
+ final AlignMatch forward_error_match =
+ checkMatches(subject_sequence, query_sequence);
+
+ if(forward_error_match == null)
+ return null;
+ else
+ {
+ final AlignMatch reverse_error_match =
+ checkMatches(query_sequence, subject_sequence);
+
+ if(reverse_error_match == null)
+ {
+ final SimpleComparisonData new_comparison_data =
+ getNewSimpleComparisonData();
+
+ int length = matches.length;
+ final AlignMatch[] new_matches = new AlignMatch[length];
+
+ for(int i = 0; i < length; ++i)
+ {
+ final AlignMatch this_match = matches[i];
+
+ final AlignMatch new_match =
+ new AlignMatch(this_match.getQuerySequenceRange(),
+ this_match.getSubjectSequenceRange(),
+ this_match.isRevMatch(),
+ this_match.getScore(),
+ this_match.getPercentID());
+
+ new_matches[i] = new_match;
+ }
+
+ new_comparison_data.setMatches(new_matches);
+
+ return new_comparison_data;
+ }
+ else
+ {
+ final String message;
+
+ if(forward_error_match.getSubjectSequenceStart() >
+ subject_sequence.getLength())
+ message = "match goes off end of subject sequence: " +
+ forward_error_match.getSubjectSequenceStart();
+ else
+ {
+ if(forward_error_match.getSubjectSequenceEnd() >
+ subject_sequence.getLength())
+ message = "match goes off end of subject sequence: " +
+ forward_error_match.getSubjectSequenceEnd();
+ else
+ {
+ if(forward_error_match.getQuerySequenceStart() >
+ query_sequence.getLength())
+ message = "match goes off end of query sequence: " +
+ forward_error_match.getQuerySequenceStart();
+ else
+ {
+ if(forward_error_match.getQuerySequenceEnd() >
+ query_sequence.getLength())
+ message = "match goes off end of query sequence: " +
+ forward_error_match.getQuerySequenceEnd();
+ else
+ throw new Error("internal error - unreachable code");
+ }
+ }
+ }
+ throw new OutOfRangeException(message);
+ }
+ }
+ }
+
+ /**
+ * Returns a new, empty instance of this type of object;
+ **/
+ abstract protected SimpleComparisonData getNewSimpleComparisonData();
+
+ /**
+ * Make an AlignMatch object from the given String. The String must be in
+ * a format appropriate for this object.
+ **/
+ abstract protected AlignMatch makeMatchFromString(final String line)
+ throws IOException;
+
+ /**
+ * Return null if and only if this object contains only valid matches for a
+ * comparison between subject_sequence and query_sequence. The first
+ * invalid AlignMatch is returned otherwise.
+ **/
+ private AlignMatch checkMatches(final Bases subject_sequence,
+ final Bases query_sequence)
+ {
+ int length = matches.length;
+ for(int i = 0; i < length; ++i)
+ {
+ final AlignMatch match = matches[i];
+
+ if(match.getSubjectSequenceStart() > subject_sequence.getLength() ||
+ match.getSubjectSequenceEnd() > subject_sequence.getLength())
+ return match;
+
+ if(match.getQuerySequenceStart() > query_sequence.getLength() ||
+ match.getQuerySequenceEnd() > query_sequence.getLength())
+ return match;
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Set the array of AlignMatch objects.
+ **/
+ protected void setMatches(final AlignMatch[] matches)
+ {
+ this.matches = matches;
+
+ int length = matches.length;
+ match_buffer = new AlignMatch[length];
+
+ for(int i = 0 ; i < length ; ++i)
+ {
+ final AlignMatch this_match = matches[i];
+
+ final int score = this_match.getScore();
+
+ final int this_match_subject_sequence_end =
+ this_match.getSubjectSequenceEnd();
+
+ final int this_match_query_sequence_end =
+ this_match.getQuerySequenceEnd();
+
+ if(this_match_subject_sequence_end > subject_sequence_max_base)
+ subject_sequence_max_base = this_match_subject_sequence_end;
+
+ if(this_match_query_sequence_end > query_sequence_max_base)
+ query_sequence_max_base = this_match_query_sequence_end;
+ }
+ }
+
+ /**
+ * Make and return a new AlignMatch.
+ **/
+ static protected AlignMatch makeAlignMatch(int subject_sequence_start,
+ int subject_sequence_end,
+ int query_sequence_start,
+ int query_sequence_end,
+ final int score,
+ final int percent_id)
+ {
+ try
+ {
+ // true if and only if the query hits the reverse complement of the
+ // subject
+ boolean rev_match = false;
+
+ if(subject_sequence_end < subject_sequence_start)
+ {
+ final int tmp = subject_sequence_start;
+ subject_sequence_start = subject_sequence_end;
+ subject_sequence_end = tmp;
+ rev_match = !rev_match;
+ }
+
+ if(query_sequence_end < query_sequence_start)
+ {
+ final int tmp = query_sequence_start;
+ query_sequence_start = query_sequence_end;
+ query_sequence_end = tmp;
+ rev_match = !rev_match;
+ }
+
+ return new AlignMatch(new Range(subject_sequence_start,
+ subject_sequence_end),
+ new Range(query_sequence_start,
+ query_sequence_end),
+ rev_match, score, percent_id);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Set the values of min_score and max_score.
+ **/
+ private void setMinMaxScore()
+ {
+ int length = matches.length;
+ for(int i = 0; i < length; ++i)
+ {
+ final AlignMatch this_match = matches[i];
+
+ final int score = this_match.getScore();
+
+ if(score > -1)
+ {
+ if(score > max_score)
+ max_score = score;
+
+ if(score < min_score)
+ min_score = score;
+ }
+ }
+ }
+
+ /**
+ * Return the maximum score of all the AlignMatch objects in this object.
+ **/
+ public int getMaximumScore()
+ {
+ if(max_score == -1)
+ setMinMaxScore();
+
+ return max_score;
+ }
+
+ /**
+ * Return the minimum score of all the AlignMatch objects in this object.
+ **/
+ public int getMinimumScore()
+ {
+ if(max_score == -1)
+ setMinMaxScore();
+
+ return min_score;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/SimpleEntryGroup.java b/uk/ac/sanger/artemis/SimpleEntryGroup.java
new file mode 100644
index 0000000..9d1d8ee
--- /dev/null
+++ b/uk/ac/sanger/artemis/SimpleEntryGroup.java
@@ -0,0 +1,1047 @@
+/* SimpleEntryGroup.java
+ *
+ * created: Wed Nov 11 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/SimpleEntryGroup.java,v 1.8 2008-06-11 15:15:23 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.IndexedGFFDocumentEntry;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.StreamSequence;
+import uk.ac.sanger.artemis.io.SimpleDocumentEntry;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+import java.util.Vector;
+import java.util.NoSuchElementException;
+
+/**
+ * This class implements a vector of Entry objects, with additional methods
+ * for querying and changing the feature tables of all the entries at
+ * once. Objects of this class act a bit like single Entry objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: SimpleEntryGroup.java,v 1.8 2008-06-11 15:15:23 tjc Exp $
+ **/
+
+public class SimpleEntryGroup extends EntryVector
+ implements EntryGroup
+{
+
+ /** vector of those objects listening for entry change events. */
+ final private Vector entry_group_listener_list = new Vector();
+
+ /** vector of those objects listening for entry change events. */
+ final private Vector entry_listener_list = new Vector();
+
+ /** vector of those objects listening for feature change events. */
+ final private Vector feature_listener_list = new Vector();
+
+ /** vector of entries that are currently active (visible). */
+ final private EntryVector active_entries = new EntryVector();
+
+ /**
+ * The default Entry for this SimpleEntryGroup. The "default" is the Entry
+ * where new features are created.
+ **/
+ private Entry default_entry = null;
+
+ /** Bases object that was passed to the constructor. */
+ private Bases bases;
+
+ /** Incremented by ref(), decremented by unref(). */
+ private int reference_count = 0;
+
+ /** The ActionController of this EntryGroup (used for undo). */
+ final private ActionController action_controller = new ActionController();
+
+ /**
+ * Create a new empty SimpleEntryGroup object.
+ **/
+ public SimpleEntryGroup(final Bases bases)
+ {
+ this.bases = bases;
+
+ addFeatureChangeListener(getActionController());
+ addEntryChangeListener(getActionController());
+ getBases().addSequenceChangeListener(getActionController(),
+ Bases.MIN_PRIORITY);
+ addEntryGroupChangeListener(getActionController());
+ }
+
+ public SimpleEntryGroup()
+ {
+ addFeatureChangeListener(getActionController());
+ addEntryGroupChangeListener(getActionController());
+ }
+
+ /**
+ * Returns true if and only if there are any unsaved changes in any of the
+ * Entry objects in this EntryGroup.
+ **/
+ public boolean hasUnsavedChanges()
+ {
+ final int my_size = size();
+ for(int entry_index = 0; entry_index < my_size;
+ ++entry_index)
+ {
+ if(elementAt(entry_index).hasUnsavedChanges())
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the default Entry for this SimpleEntryGroup. The "default" is the
+ * Entry where new features are created.
+ **/
+ public Entry getDefaultEntry()
+ {
+ return default_entry;
+ }
+
+ /**
+ * Set the default Entry. The "default" is the Entry where new features
+ * are created.
+ * @param entry The new default entry. If this Entry is not active this
+ * method will return immediately.
+ **/
+ public void setDefaultEntry(Entry entry)
+ {
+ if(entry != null && !isActive(entry))
+ return;
+
+ // do nothing
+ if(default_entry == entry)
+ return;
+ else
+ default_entry = entry;
+
+ // now inform the listeners that a change has occured
+ final EntryGroupChangeEvent event =
+ new EntryGroupChangeEvent(this, getDefaultEntry(),
+ EntryGroupChangeEvent.NEW_DEFAULT_ENTRY);
+
+ fireEvent(entry_group_listener_list, event);
+ }
+
+ /**
+ * Return the index of a feature within this object. This method treats
+ * all the features in all the active entries as if they were in one big
+ * array. The first feature of the first entry will have index 1, the
+ * first from the second entry will have index 1 +(the number of features
+ * in the first entry), etc.
+ * @param feature The feature to find the index of.
+ * @return The index of the feature or -1 if the feature isn't in any of
+ * the entries. The first index is 0 the last is the total number of
+ * features in all the entries of this object minus one.
+ **/
+ public int indexOf(Feature feature)
+ {
+ int feature_count_of_previous_entries = 0;
+ final int active_entries_size = active_entries.size();
+
+ for(int entry_index = 0; entry_index < active_entries_size;
+ ++entry_index)
+ {
+ final Entry this_entry = active_entries.elementAt(entry_index);
+ final int feature_index = this_entry.indexOf(feature);
+
+ if(feature_index != -1)
+ return feature_index + feature_count_of_previous_entries;
+
+ feature_count_of_previous_entries += this_entry.getFeatureCount();
+ }
+
+ return -1;
+ }
+
+ /**
+ * Return true if any of the active entries in the group contains the given
+ * feature.
+ **/
+ public boolean contains(Feature feature)
+ {
+ final int active_entries_size = active_entries.size();
+
+ for(int i = 0; i < active_entries_size; ++i)
+ {
+ final Entry current_entry = active_entries.elementAt(i);
+
+ if(current_entry.contains(feature))
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return true if the given Entry is active(visible). The Feature objects
+ * in an Entry that is not active will be ignored by the methods that deal
+ * will features: featureAt(), indexOf(), contains(), features(), etc.
+ **/
+ public boolean isActive(Entry entry)
+ {
+ if(active_entries.contains(entry))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Set the "active" setting of the Entry at the given index. If the index
+ * refers to the default entry and new_active is false, the default entry
+ * will be set to the active entry or null if there are no active entries.
+ * @param index The index of the Entry to change.
+ * @param active The new active setting.
+ **/
+ public void setIsActive(int index, boolean new_active)
+ {
+ final Entry entry = elementAt(index);
+
+ if(new_active)
+ {
+ // no change
+ if(isActive(entry))
+ return;
+ else
+ {
+ // this is slow but it guarantees that the Entry references in the
+ // active_entries vector are in the same order as in the
+ // SimpleEntryGroup
+
+ final EntryVector new_active_entries = new EntryVector();
+ final int my_size = size();
+
+ for(int i = 0; i < my_size; ++i)
+ {
+ if(active_entries.contains(elementAt(i)) || index == i)
+ new_active_entries.add(elementAt(i));
+ }
+
+ active_entries.removeAllElements();
+
+ final int new_active_entries_size = new_active_entries.size();
+
+ for(int i = 0; i < new_active_entries_size; ++i)
+ active_entries.add(new_active_entries.elementAt(i));
+
+ if(active_entries.size() >= 1 && getDefaultEntry() == null)
+ {
+ // there was no default entry before calling addElement() so
+ // make the first non-sequence entry the default entry
+ if(active_entries.elementAt(0) == getSequenceEntry() &&
+ active_entries.size() == 1)
+ {
+ // don't set the default entry to be the sequence entry unless
+ // the user asks for it
+ setDefaultEntry(null);
+ }
+ else
+ {
+ if(active_entries.size() == 1)
+ setDefaultEntry(active_entries.elementAt(0));
+ else
+ setDefaultEntry(active_entries.elementAt(1));
+ }
+ }
+ }
+ }
+ else
+ {
+ // no change
+ if(!isActive(entry))
+ return;
+ else
+ {
+ active_entries.removeElement(entry);
+
+ if(entry == getDefaultEntry())
+ {
+ if(active_entries.size() > 0)
+ {
+ if(active_entries.elementAt(0) == getSequenceEntry())
+ {
+ // don't set the default entry to be the sequence entry unless
+ // the user asks for it
+ if(active_entries.size() > 1)
+ setDefaultEntry(active_entries.elementAt(1));
+ else
+ setDefaultEntry(null);
+ }
+ else
+ setDefaultEntry(active_entries.elementAt(0));
+ }
+ else
+ setDefaultEntry(null);
+ }
+ }
+ }
+
+ // now inform the listeners that a change has occured
+ final EntryGroupChangeEvent event;
+
+ // change state
+ if(new_active) // become active
+ event = new EntryGroupChangeEvent(this, entry,
+ EntryGroupChangeEvent.ENTRY_ACTIVE);
+ else // become inactive
+ event = new EntryGroupChangeEvent(this, entry,
+ EntryGroupChangeEvent.ENTRY_INACTIVE);
+
+ fireEvent(entry_group_listener_list, event);
+ }
+
+ /**
+ * Set the "active" setting of the given Entry. The Entry is the default
+ * entry and new_active is false, the default entry will be set to the
+ * active entry or null if there are no active entries.
+ * @param entry The Entry to activate or deactivate.
+ * @param new_active The new active setting.
+ **/
+ public void setIsActive(final Entry entry, final boolean new_active)
+ {
+ setIsActive(indexOf(entry), new_active);
+ }
+
+
+ /**
+ * Return the Entry from this SimpleEntryGroup that contains the sequence
+ * to view or return null if none of the entries contains a sequence.
+ **/
+ public Entry getSequenceEntry()
+ {
+ if(size() == 0)
+ return null;
+ else
+ return elementAt(0);
+ }
+
+ /**
+ * Returns the base length of the sequence of the first Entry in this group
+ * or 0 if this group is empty.
+ **/
+ public int getSequenceLength()
+ {
+ return getBases().getLength();
+ }
+
+ /**
+ * Returns the Bases object of the first Entry in this group or null if
+ * this group is empty.
+ **/
+ public Bases getBases()
+ {
+ return bases;
+ }
+
+ /**
+ * Reverse and complement the sequence and all features in every Entry in
+ * this SimpleEntryGroup.
+ **/
+ public void reverseComplement()
+ throws ReadOnlyException
+ {
+ if(isReadOnly())
+ throw new ReadOnlyException();
+
+ // reverse the sequence
+ getBases().reverseComplement();
+ }
+
+ /**
+ * Return true if and only if one or more of the entries or features in
+ * this SimpleEntryGroup are read-only.
+ **/
+ public boolean isReadOnly()
+ {
+ final int my_size = size();
+ for(int i = 0; i < my_size; ++i)
+ {
+ final Entry this_entry = elementAt(i);
+
+ if(this_entry.isReadOnly())
+ return true;
+
+ final FeatureEnumeration feature_enum = this_entry.features();
+
+ while(feature_enum.hasMoreFeatures())
+ {
+ if(feature_enum.nextFeature().isReadOnly())
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Increment the reference count for this EntryGroup.
+ **/
+ public void ref()
+ {
+ ++reference_count;
+ }
+
+ /**
+ * Decrement the reference count for this EntryGroup. When the reference
+ * count goes to zero a EntryGroupChangeEvent is sent to all
+ * EntryGroupChangeListeners with type EntryGroupChangeEvent.DONE_GONE.
+ * The listeners should then stop using the EntryGroup and release any
+ * associated resources.
+ **/
+ public void unref()
+ {
+ --reference_count;
+
+ if(reference_count == 0)
+ {
+ // remove all the entries which will close any edit or view windows
+ while(size() > 0)
+ {
+ final Entry this_entry = elementAt(0);
+ remove(this_entry);
+
+ this_entry.getEMBLEntry().dispose();
+ }
+
+ // now inform the listeners that the EntryGroup is no more
+ final EntryGroupChangeEvent event =
+ new EntryGroupChangeEvent(this, null,
+ EntryGroupChangeEvent.DONE_GONE);
+
+ fireEvent(entry_group_listener_list, event);
+ }
+ }
+
+ /**
+ * Return the current reference count.
+ **/
+ public int refCount()
+ {
+ return reference_count;
+ }
+
+ /**
+ * Return the Feature at the given index. This method treats all the
+ * features in all the entries as if they were in one big array. See
+ * the comment on indexOf().
+ * @param index The index of the required Feature.
+ * @return The Feature at the given index. The first index is 0 the last
+ * is the total number of features in all the entries of this object minus
+ * one. If the index is out of range then null will be returned.
+ **/
+ public Feature featureAt(int index)
+ {
+ if(index < 0)
+ throw new Error("internal error - index out of range: " + index);
+
+ final int active_entries_size = active_entries.size();
+
+ for(int entry_index = 0; entry_index < active_entries_size;
+ ++entry_index)
+ {
+ final Entry this_entry = active_entries.elementAt(entry_index);
+
+ if(index < this_entry.getFeatureCount())
+ return this_entry.getFeature(index);
+
+ index -= this_entry.getFeatureCount();
+ }
+
+ throw new Error("internal error - index out of range: " + index);
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range of indices.
+ * @param start_index The index of the first feature to return.
+ * @param end_index The index of the last feature to return.
+ **/
+ public FeatureVector getFeaturesInIndexRange(final int start_index,
+ final int end_index)
+ {
+ final FeatureVector return_vector = new FeatureVector();
+
+ for(int i = start_index; i <= end_index; ++i)
+ return_vector.add(featureAt(i));
+
+ return return_vector;
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range for all the active entries in the SimpleEntryGroup.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The non-source key features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ public FeatureVector getFeaturesInRange(Range range)
+ throws OutOfRangeException
+ {
+ final FeatureVector return_vector = new FeatureVector();
+ final int my_size = size();
+
+ for(int i = 0; i < my_size; ++i)
+ {
+ final Entry this_entry = elementAt(i);
+
+ if(isActive(this_entry))
+ {
+ final FeatureVector visible_entry_features =
+ elementAt(i).getFeaturesInRange(range);
+
+ final int visible_entry_features_size = visible_entry_features.size();
+
+ for(int feature_index = 0; feature_index < visible_entry_features_size;
+ ++feature_index)
+ {
+ final Feature this_feature =
+ visible_entry_features.elementAt(feature_index);
+ return_vector.add(this_feature);
+ }
+ }
+ }
+
+ return return_vector;
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects from
+ * all the active entries in the SimpleEntryGroup.
+ * @return The non-source key features in active entries of this
+ * SimpleEntryGroup. The returned object is a copy - changes will not
+ * effect the SimpleEntryGroup object itself.
+ **/
+ public FeatureVector getAllFeatures()
+ {
+ final FeatureVector return_vector = new FeatureVector();
+ final int my_size = size();
+
+ for(int i = 0; i < my_size; ++i)
+ {
+ final Entry this_entry = elementAt(i);
+
+ if(isActive(this_entry))
+ {
+ final FeatureVector entry_features = elementAt(i).getAllFeatures();
+ final int entry_features_size = entry_features.size();
+
+ for(int feature_index = 0; feature_index < entry_features_size;
+ ++feature_index)
+ {
+ final Feature this_feature =
+ entry_features.elementAt(feature_index);
+ return_vector.add(this_feature);
+ }
+ }
+ }
+
+ return return_vector;
+ }
+
+ /**
+ * Return a count of the number of Feature objects from all the active
+ * entries in the SimpleEntryGroup.
+ * @return A count of the non-source key features in active entries of this
+ * SimpleEntryGroup.
+ **/
+ public int getAllFeaturesCount()
+ {
+ int return_count = 0;
+ final int my_size = size();
+
+ for(int i = 0; i < my_size; ++i)
+ {
+ final Entry this_entry = elementAt(i);
+
+ if(isActive(this_entry))
+ return_count += this_entry.getFeatureCount();
+ }
+
+ return return_count;
+ }
+
+ /**
+ * Add an Entry to this object and then emit the appropriate EntryChange
+ * events.
+ **/
+ public void addElement(Entry entry)
+ {
+ super.addElement(entry);
+
+ // set the default Entry to whichever Entry gets added first
+ if(default_entry == null)
+ default_entry = entry;
+
+ active_entries.add(entry);
+
+ // now inform the listeners that an addition has occured
+ final EntryGroupChangeEvent event =
+ new EntryGroupChangeEvent(this, entry,
+ EntryGroupChangeEvent.ENTRY_ADDED);
+
+ fireEvent(entry_group_listener_list, event);
+
+ // make the new entry the default entry if and only if there was no entry
+ // previously or there was only one entry previously and it contained
+ // sequence but no features
+ if(size() == 1)
+ setDefaultEntry(entry);
+ else
+ {
+ if(size() == 2)
+ {
+ final Entry first_entry = elementAt(0);
+
+ if(first_entry.getFeatureCount() == 0)
+ {
+ final Bases first_entry_bases = first_entry.getBases();
+
+ if(first_entry_bases != null &&
+ first_entry_bases.getLength() > 0)
+ setDefaultEntry(entry);
+ }
+ }
+ }
+
+ entry.addEntryChangeListener(this);
+ entry.addFeatureChangeListener(this);
+ }
+
+ /**
+ * A convenience method that does the same as addElement(Entry).
+ **/
+ public void add(final Entry entry)
+ {
+ if(entry.getEMBLEntry() instanceof IndexedGFFDocumentEntry)
+ ((IndexedGFFDocumentEntry)entry.getEMBLEntry()).setEntryGroup(this);
+ else if(entry.getEMBLEntry() instanceof GFFDocumentEntry)
+ {
+ ((GFFDocumentEntry)entry.getEMBLEntry()).adjustCoordinates( getSequenceEntry() );
+ if(!Options.isBlackBeltMode() && size() > 1 &&
+ entry.getEMBLEntry().getSequence() != null )
+ {
+ new MessageDialog (null, "Warning",
+ "Overlaying a GFF with a sequence onto an entry with a sequence.",
+ false);
+ }
+ }
+
+ addElement(entry);
+ }
+
+ /**
+ * Remove an Entry from this object and then emit the appropriate
+ * EntryGroupChange events. The first entry in the group can only be
+ * removed if it is the only Entry because the first Entry contains the
+ * sequence.
+ * @return true if the removal succeeded, false if it fails(which can if
+ * the given Entry isn't in this SimpleEntryGroup or if the user tries to
+ * remove the first Entry).
+ **/
+ public boolean removeElement(final Entry entry)
+ {
+ // this call will sort out the default entry
+ setIsActive(indexOf(entry), false);
+
+ entry.dispose();
+
+ final boolean remove_return = super.removeElement(entry);
+
+ entry.removeEntryChangeListener(this);
+ entry.removeFeatureChangeListener(this);
+
+ active_entries.removeElement(entry);
+
+ // now inform the listeners that a deletion has occured
+ final EntryGroupChangeEvent event =
+ new EntryGroupChangeEvent(this, entry,
+ EntryGroupChangeEvent.ENTRY_DELETED);
+
+ fireEvent(entry_group_listener_list, event);
+
+ return remove_return;
+ }
+
+ /**
+ * A convenience method that does the same as removeElement(Entry).
+ **/
+ public boolean remove(final Entry entry)
+ {
+ return removeElement(entry);
+ }
+
+ /**
+ * Create a new(blank) Feature in the default Entry of this
+ * SimpleEntryGroup. See getDefaultEntry() and Entry.createFeature().
+ * @return The new Feature.
+ **/
+ public Feature createFeature() throws ReadOnlyException
+ {
+ final Feature new_feature = getDefaultEntry().createFeature();
+ return new_feature;
+ }
+
+ /**
+ * Create a new(empty) Entry in this SimpleEntryGroup. See
+ * Entry.newEntry().
+ * @return The reference of the new Entry.
+ **/
+ public Entry createEntry()
+ {
+ Entry new_entry = null;
+ Entry default_entry = getDefaultEntry();
+ if(default_entry != null &&
+ default_entry.getEMBLEntry() != null &&
+ default_entry.getEMBLEntry() instanceof DatabaseDocumentEntry)
+ {
+ DatabaseDocument doc =
+ (DatabaseDocument)((DocumentEntry)default_entry.getEMBLEntry()).getDocument();
+ DatabaseDocument new_doc = doc.createDatabaseDocument();
+
+ try
+ {
+ DatabaseDocumentEntry new_doc_entry =
+ new DatabaseDocumentEntry();
+ new_doc_entry.setDocument(new_doc);
+ new_entry = new Entry(getBases(), new_doc_entry);
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ }
+ else
+ new_entry = Entry.newEntry(getBases());
+
+ add(new_entry);
+ return new_entry;
+ }
+
+ /**
+ * Create a new(empty) Entry in this SimpleEntryGroup. See
+ * Entry.newEntry().
+ * @param name The(file) name of the new Entry.
+ * @return The reference of the new Entry.
+ **/
+ public Entry createEntry(final String name)
+ {
+ final Entry new_entry = createEntry();
+ new_entry.setName(name);
+ return new_entry;
+ }
+
+ /**
+ * Returns an enumeration of the Feature objects in this
+ * SimpleEntryGroup. The returned FeatureEnumeration object will generate
+ * all features in this object in turn. The first item generated is the
+ * item at index 0, then the item at index 1, and so on.
+ **/
+ public FeatureEnumeration features()
+ {
+ return new FeatureEnumerator();
+ }
+
+ /**
+ * An Enumeration of Feature objects.
+ **/
+ public class FeatureEnumerator implements FeatureEnumeration
+ {
+
+ /**
+ * The EntryVector object that we are enumerating. Set to null when there
+ * are no more Feature objects.
+ **/
+ private EntryVector active_entries;
+
+ /**
+ * The index of the Entry that we will get the next Feature from.
+ **/
+ private int entry_index = -1;
+
+ /** Enumeration for the current entry */
+ private FeatureEnumeration feature_enumerator;
+
+ /**
+ * Create a new FeatureEnumeration that will enumerate the enclosing
+ * SimpleEntryGroup object. The SimpleEntryGroup object must not be
+ * changed while the enumeration is active.
+ **/
+ public FeatureEnumerator()
+ {
+ active_entries = getActiveEntries();
+
+ entry_index = 0;
+
+ if(active_entries.size() > 0)
+ feature_enumerator =
+ active_entries.elementAt(entry_index).features();
+ else
+ feature_enumerator = null;
+ }
+
+ /**
+ * See the FeatureEnumeration interface for details.
+ **/
+ public boolean hasMoreFeatures()
+ {
+ if(feature_enumerator == null)
+ return false;
+
+ if(feature_enumerator.hasMoreFeatures())
+ return true;
+
+ ++entry_index;
+
+ if(entry_index == active_entries.size())
+ return false;
+ else
+ {
+ feature_enumerator =
+ active_entries.elementAt(entry_index).features();
+ return hasMoreFeatures();
+ }
+ }
+
+ /**
+ * See the FeatureEnumeration interface for details.
+ **/
+ public Feature nextFeature()
+ throws NoSuchElementException
+ {
+ if(feature_enumerator == null)
+ throw new NoSuchElementException();
+
+ return feature_enumerator.nextFeature();
+ }
+
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We listen for
+ * changes in every feature of every entry in this group.
+ **/
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ // pass the action straight through
+ fireEvent(feature_listener_list, event);
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen for
+ * changes from every entry in this group and pass the events though to all
+ * the object listening for EntryChangeEvents for the event from this
+ * SimpleEntryGroup.
+ **/
+ public void entryChanged(EntryChangeEvent event)
+ {
+ // pass the action straight through
+ fireEvent(entry_listener_list, event);
+ }
+
+ /**
+ * Send an event to those object listening for it.
+ * @param listeners A Vector of the objects that the event should be sent
+ * to.
+ * @param event The event to send
+ **/
+ private void fireEvent(Vector listeners, ChangeEvent event)
+ {
+ final Vector targets;
+ // copied from a book - synchronising the whole method might cause a
+ // deadlock
+ synchronized(this)
+ {
+ targets = (Vector)listeners.clone();
+ }
+
+ //boolean seen_chado_manager = false;
+ final int targets_size = targets.size();
+ for(int i = 0; i < targets_size; ++i)
+ {
+ ChangeListener target =(ChangeListener) targets.elementAt(i);
+
+ if(event instanceof EntryGroupChangeEvent)
+ {
+ final EntryGroupChangeListener entry_group_change_listener =
+ (EntryGroupChangeListener) target;
+ final EntryGroupChangeEvent group_change_event =
+ (EntryGroupChangeEvent) event;
+ entry_group_change_listener.entryGroupChanged(group_change_event);
+ }
+ else
+ {
+ if(event instanceof EntryChangeEvent)
+ {
+ final EntryChangeListener entry_change_listener =
+ (EntryChangeListener) target;
+
+// if(entry_change_listener instanceof ChadoTransactionManager)
+// {
+ // just call this listener once
+// if(!seen_chado_manager)
+// {
+// entry_change_listener.entryChanged((EntryChangeEvent) event);
+// seen_chado_manager = true;
+// }
+// }
+// else
+ entry_change_listener.entryChanged((EntryChangeEvent) event);
+ }
+ else
+ {
+ final FeatureChangeListener feature_change_listener =
+ (FeatureChangeListener) target;
+ feature_change_listener.featureChanged((FeatureChangeEvent) event);
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Adds the specified event listener to receive entry group change events
+ * from this object.
+ * @param l the event change listener.
+ **/
+ public void addEntryGroupChangeListener(EntryGroupChangeListener l)
+ {
+ entry_group_listener_list.addElement(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * entry group change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeEntryGroupChangeListener(EntryGroupChangeListener l)
+ {
+ entry_group_listener_list.removeElement(l);
+ }
+
+ /**
+ * Adds the specified event listener to receive entry change events from
+ * this object.
+ * @param l the event change listener.
+ **/
+ public void addEntryChangeListener(EntryChangeListener l)
+ {
+ entry_listener_list.addElement(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * entry change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeEntryChangeListener(EntryChangeListener l)
+ {
+ entry_listener_list.removeElement(l);
+ }
+
+ /**
+ * Adds the specified event listener to receive feature change events from
+ * this object.
+ * @param l the event change listener.
+ **/
+ public void addFeatureChangeListener(FeatureChangeListener l)
+ {
+ feature_listener_list.addElement(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * feature change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeFeatureChangeListener(FeatureChangeListener l)
+ {
+ feature_listener_list.removeElement(l);
+ }
+
+ /**
+ * Return the reference of an EntryVector containing the active entries of
+ * this EntryGroup.
+ **/
+ public EntryVector getActiveEntries()
+ {
+ return(EntryVector) active_entries.clone();
+ }
+
+ /**
+ * This method translates the start and end of every each Range in every
+ * Location into another coordinate system. The Ranges will be truncated
+ * if necessary.
+ * @param constraint This contains the start and end base of the new
+ * coordinate system. The position given by constraint.getStart() will
+ * be at postion/base 1 in the new coordinate system.
+ * @return a copy of the EntryGroup which has been translated into the new
+ * coordinate system.
+ **/
+ public EntryGroup truncate(final Range constraint)
+ {
+ final Bases new_bases = getBases().truncate(constraint);
+
+ final EntryGroup new_entry_group = new SimpleEntryGroup(new_bases);
+ final int my_size = size();
+
+ for(int i = 0; i < my_size; ++i)
+ {
+ final Entry this_entry = elementAt(i);
+ final Entry new_entry = this_entry.truncate(new_bases, constraint);
+ new_entry_group.add(new_entry);
+ }
+
+ if(size() > 0)
+ {
+ final StreamSequence sequence =
+ (StreamSequence) new_bases.getSequence();
+ final SimpleDocumentEntry document_entry =
+ (SimpleDocumentEntry) new_entry_group.elementAt(0).getEMBLEntry();
+ document_entry.setSequence(sequence);
+ }
+
+ return new_entry_group;
+ }
+
+ /**
+ * Return the ActionController for this EntryGroup(for undo).
+ **/
+ public ActionController getActionController()
+ {
+ return action_controller;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/SimpleExternalProgramMonitor.java b/uk/ac/sanger/artemis/SimpleExternalProgramMonitor.java
new file mode 100644
index 0000000..656b9a7
--- /dev/null
+++ b/uk/ac/sanger/artemis/SimpleExternalProgramMonitor.java
@@ -0,0 +1,97 @@
+/* SimpleExternalProgramMonitor.java
+ *
+ * created: Wed Aug 6 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/SimpleExternalProgramMonitor.java,v 1.1 2004-06-09 09:45:09 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import java.util.Vector;
+
+/**
+ * This class contains methods common to all implementations of
+ * ExternalProgramMonitor
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: SimpleExternalProgramMonitor.java,v 1.1 2004-06-09 09:45:09 tjc Exp $
+ **/
+
+public class SimpleExternalProgramMonitor
+ extends Thread
+ implements ExternalProgramMonitor
+{
+
+ /** Process name that was passed to the constructor. */
+ final public String name;
+
+ /** The Logger that was passed to the constructor. */
+ final private Logger logger;
+
+ /**
+ * Create a new SimpleExternalProgramMonitor.
+ * @param name The name of the external program that is being monitored.
+ * @param logger The log for errors, STDOUT and STDERR of the external
+ * program.
+ **/
+ public SimpleExternalProgramMonitor(final String name,
+ final Logger logger)
+ {
+ this.name = name;
+ this.logger = logger;
+ }
+
+ /**
+ * Add a listener for ExternalProgramEvents.
+ **/
+ public void addExternalProgramListener (ExternalProgramListener l)
+ {
+ listeners.add (l);
+ }
+
+ /**
+ * Send the given event to all the listeners.
+ **/
+ protected void sendEvent(ExternalProgramEvent e)
+ {
+ for(int i = 0 ; i < listeners.size () ; ++i)
+ ((ExternalProgramListener)listeners.elementAt(i)).statusChanged (e);
+ }
+
+ private Vector listeners = new Vector ();
+
+ /**
+ * Return the Logger that was passed to the constructor.
+ **/
+ public Logger getLogger()
+ {
+ return logger;
+ }
+
+ /**
+ * Return the name of the external program we are monitoring.
+ **/
+ public String getProgramName()
+ {
+ return name;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/SimpleGotoEventSource.java b/uk/ac/sanger/artemis/SimpleGotoEventSource.java
new file mode 100644
index 0000000..fdd27ab
--- /dev/null
+++ b/uk/ac/sanger/artemis/SimpleGotoEventSource.java
@@ -0,0 +1,173 @@
+/* SimpleGotoEventSource.java
+ *
+ * created: Sat Jun 17 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/SimpleGotoEventSource.java,v 1.1 2004-06-09 09:45:10 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.util.Vector;
+import java.util.EventObject;
+
+/**
+ * An simple implementation of GotoEventSource.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: SimpleGotoEventSource.java,v 1.1 2004-06-09 09:45:10 tjc Exp $
+ **/
+
+public class SimpleGotoEventSource
+ implements GotoEventSource {
+ /**
+ * Create a new SimpleGotoEventSource that operates on the given
+ * EntryGroup. We need an EntryGroup reference so we know where the last
+ * base is.
+ **/
+ public SimpleGotoEventSource (final EntryGroup entry_group) {
+ this.entry_group = entry_group;
+ }
+
+ /**
+ * This method sends an GotoEvent to all the GotoEvent listeners that will
+ * make the given base visible.
+ * @param base_marker The base to make visible.
+ **/
+ public void gotoBase (final Marker base_marker) {
+ final GotoEvent new_event = new GotoEvent (this, base_marker);
+
+ sendGotoEvent (new_event);
+ }
+
+ /**
+ * This method sends a GotoEvent to all the GotoEvent listeners that will
+ * make the first base of the sequence visible.
+ **/
+ public void gotoFirstBase () {
+ gotoBase (1);
+ }
+
+ /**
+ * This method sends a GotoEvent to all the GotoEvent listeners that will
+ * make the last base of the sequence visible.
+ **/
+ public void gotoLastBase () {
+ gotoBase (getEntryGroup ().getSequenceLength ());
+ }
+
+ /**
+ * This method sends an GotoEvent to all the GotoEvent listeners that will
+ * make the given base visible.
+ * @param destination_base The base to make visible.
+ * @return The reference of a MarkerRange object for the given base or null
+ * if the call fails. It will fail only if the destination_base is less
+ * than 1 or greater than the length of the sequence.
+ **/
+ public MarkerRange gotoBase (final int destination_base) {
+ try {
+ final Strand forward_strand =
+ getEntryGroup ().getBases ().getForwardStrand ();
+ final Marker destination_marker =
+ forward_strand.makeMarker (destination_base);
+ gotoBase (destination_marker);
+ return new MarkerRange (destination_marker.getStrand (),
+ destination_base, destination_base);
+ } catch (OutOfRangeException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Send the given event to all the GotoListeners.
+ **/
+ public void sendGotoEvent (final GotoEvent goto_event) {
+ // don't send if there is nowhere to go to
+ if (goto_event.getMarker () != null) {
+ fireAction (goto_listener_list, goto_event);
+ }
+ }
+
+ /**
+ * Send an event to those object listening for it.
+ * @param listeners A Vector of the objects that the event should be sent
+ * to.
+ * @param event The event to send
+ **/
+ private void fireAction (final Vector listeners, final EventObject event) {
+ final Vector targets;
+ // copied from a book - synchronising the whole method might cause a
+ // deadlock
+ synchronized (this) {
+ targets = (Vector) listeners.clone ();
+ }
+
+ for (int i = 0 ; i < targets.size () ; ++i) {
+ GotoListener target = (GotoListener) targets.elementAt (i);
+
+ if (event instanceof GotoEvent) {
+ final GotoListener goto_listener = (GotoListener) target;
+ goto_listener.performGoto ((GotoEvent) event);
+ } else {
+ throw new Error ("EntryEdit.fireAction () - unknown event");
+ }
+ }
+ }
+
+ /**
+ * Adds the specified event listener to receive Goto events from this
+ * object.
+ * @param l the GotoEvent listener.
+ **/
+ public void addGotoListener (final GotoListener l) {
+ goto_listener_list.addElement (l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives Goto
+ * events from this object.
+ * @param l the GotoEvent listener.
+ **/
+ public void removeGotoListener (final GotoListener l) {
+ goto_listener_list.removeElement (l);
+ }
+
+ /**
+ * Return the EntryGroup object that was passed to the constructor.
+ **/
+ public EntryGroup getEntryGroup () {
+ return entry_group;
+ }
+
+ /**
+ * The EntryGroup object that was passed to the constructor.
+ **/
+ private EntryGroup entry_group;
+
+ /**
+ * A vector of those objects listening for Goto events.
+ **/
+ final private Vector goto_listener_list = new Vector ();
+}
diff --git a/uk/ac/sanger/artemis/chado/ArtemisUtils.java b/uk/ac/sanger/artemis/chado/ArtemisUtils.java
new file mode 100644
index 0000000..f0eea5f
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/ArtemisUtils.java
@@ -0,0 +1,723 @@
+/* ArtemisUtil
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import org.gmod.schema.analysis.Analysis;
+import org.gmod.schema.analysis.AnalysisFeature;
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.general.Db;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureCvTermDbXRef;
+import org.gmod.schema.sequence.FeatureCvTermProp;
+import org.gmod.schema.sequence.FeatureDbXRef;
+import org.gmod.schema.sequence.FeatureLoc;
+import org.gmod.schema.sequence.FeatureProp;
+import org.gmod.schema.sequence.FeatureRelationship;
+
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+public class ArtemisUtils
+{
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(ArtemisUtils.class);
+
+ protected static String getCurrentSchema()
+ {
+ String schema = System.getProperty("chado");
+ int index = schema.indexOf("?");
+ int index2 = schema.indexOf("user=");
+ if(index2 < 0)
+ schema = schema.substring(index+1);
+ else
+ schema = schema.substring(index2+5);
+ return schema;
+ }
+
+ /**
+ * Insert featureCvTerm with an appropriate rank
+ * @param dao
+ * @param featureCvTerm
+ */
+ public static void inserFeatureCvTerm(GmodDAO dao, FeatureCvTerm featureCvTerm)
+ {
+ List<FeatureCvTerm> featureCvTerms = dao.getFeatureCvTermsByFeature(featureCvTerm.getFeature());
+ logger4j.debug("In inserFeatureCvTerm() inserting");
+
+ boolean isNotProduct = true;
+ try
+ {
+ // product feature_cvterm.rank is used to define alternative products
+ // where there are multiple products, so the rank may already be set
+ if(featureCvTerm.getCvTerm().getCv().getName().equals(
+ DatabaseDocument.PRODUCTS_TAG_CVNAME))
+ isNotProduct = false;
+ } catch(Exception e){}
+
+ if(isNotProduct || featureCvTerm.getRank() == 0)
+ {
+ int rank = 0;
+ for(FeatureCvTerm this_feature_cvterm: featureCvTerms)
+ {
+ if(this_feature_cvterm.getCvTerm().getName().equals(
+ featureCvTerm.getCvTerm().getName() ) &&
+ this_feature_cvterm.getCvTerm().getCv().getName().equals(
+ featureCvTerm.getCvTerm().getCv().getName() ))
+ {
+ rank++;
+ }
+ }
+ featureCvTerm.setRank(rank);
+ }
+ dao.persist(featureCvTerm);
+ }
+
+ /**
+ * Delete featureCvTerm and update associated feature_cvterm.rank's
+ * if appropriate
+ * @param dao
+ * @param featureCvTerm
+ */
+ public static void deleteFeatureCvTerm(GmodDAO dao, FeatureCvTerm featureCvTerm)
+ {
+ List featureCvTerms = dao.getFeatureCvTermsByFeature(featureCvTerm.getFeature());
+
+ logger4j.debug("In deleteFeatureCvTerm() deleting one of "+featureCvTerms.size());
+
+ List featureCvTermDbXRefs = new Vector();
+
+ if(featureCvTerm.getFeatureCvTermDbXRefs() != null &&
+ featureCvTerm.getFeatureCvTermDbXRefs().size() > 0)
+ featureCvTermDbXRefs = (List)featureCvTerm.getFeatureCvTermDbXRefs();
+
+ List featureCvTermProps = new Vector();
+
+ if(featureCvTerm.getFeatureCvTermProps()!= null &&
+ featureCvTerm.getFeatureCvTermProps().size() > 0)
+ featureCvTermProps = (List)featureCvTerm.getFeatureCvTermProps();
+
+ // delete feature_cvterm and update ranks if appropriate
+ FeatureCvTerm deleteme = null;
+ Vector rankable = null;
+
+ logger4j.debug("In deleteFeatureCvTerm() looking to delete ");
+ for(int i=0; i<featureCvTerms.size(); i++)
+ {
+ FeatureCvTerm this_feature_cvterm = (FeatureCvTerm)featureCvTerms.get(i);
+
+ if(this_feature_cvterm.getCvTerm().getName().equals(
+ featureCvTerm.getCvTerm().getName() ) &&
+ this_feature_cvterm.getCvTerm().getCv().getName().equals(
+ featureCvTerm.getCvTerm().getCv().getName() ))
+ {
+ logger4j.debug("Found CvTerm.name "+featureCvTerm.getCvTerm().getName());
+ Collection this_featureCvTermDbXRefs = this_feature_cvterm.getFeatureCvTermDbXRefs();
+ Collection this_featureCvTermProps = this_feature_cvterm.getFeatureCvTermProps();
+
+ if(this_featureCvTermDbXRefs == null)
+ this_featureCvTermDbXRefs = new Vector();
+
+
+ if(this_featureCvTermDbXRefs.size() != featureCvTermDbXRefs.size() ||
+ featureCvTermProps.size() != this_featureCvTermProps.size())
+ {
+ if(rankable == null)
+ rankable = new Vector();
+
+ rankable.add(this_feature_cvterm);
+
+ logger4j.debug("FeatureCvTermDbXRefs not the same - ignore "+
+ this_featureCvTermDbXRefs.size()+" != "+featureCvTermDbXRefs.size() + " || "+
+ featureCvTermProps.size()+" != "+this_featureCvTermProps.size());
+ continue;
+ }
+
+ boolean found = true;
+ Iterator it = this_featureCvTermDbXRefs.iterator();
+ while(it.hasNext())
+ {
+ FeatureCvTermDbXRef fcd = (FeatureCvTermDbXRef)it.next();
+
+ if(!containsFeatureCvTermDbXRef(fcd, featureCvTermDbXRefs))
+ {
+ found = false;
+ break;
+ }
+ }
+
+ it = this_featureCvTermProps.iterator();
+ while(it.hasNext())
+ {
+ FeatureCvTermProp fcp = (FeatureCvTermProp)it.next();
+ if(!containsFeatureCvTermProp(fcp, featureCvTermProps))
+ {
+ logger4j.debug(fcp.getCvTerm().getName()+" "+fcp.getValue());
+
+ found = false;
+ break;
+ }
+ }
+
+ if(!found)
+ {
+ if(rankable == null)
+ rankable = new Vector();
+
+ rankable.add(this_feature_cvterm);
+ continue;
+ }
+
+ deleteme = this_feature_cvterm;
+ }
+ }
+ if(deleteme != null)
+ dao.delete(deleteme);
+ else
+ logger4j.debug("FeatureCvTerm not found");
+
+ if(rankable != null)
+ {
+ // feature_cvterm.rank may need updating for those stored here
+ for(int i=0; i<rankable.size(); i++)
+ {
+ FeatureCvTerm fc = (FeatureCvTerm)rankable.get(i);
+
+ if(fc.getRank() == i)
+ continue;
+
+ logger4j.debug("UPDATE rank for "+ fc.getCvTerm().getCv().getName() + " rank = " +
+ fc.getRank()+" -> "+i);
+ fc.setRank(i);
+ dao.merge(fc);
+ }
+ }
+ }
+
+ /**
+ * Return true if the list contains a given feature_cvterm_dbxref
+ * @param fcd
+ * @param featureCvTermDbXRefs
+ * @return
+ */
+ private static boolean containsFeatureCvTermDbXRef(FeatureCvTermDbXRef fcd,
+ List featureCvTermDbXRefs)
+ {
+ for(int i=0; i<featureCvTermDbXRefs.size(); i++)
+ {
+ FeatureCvTermDbXRef this_fcd = (FeatureCvTermDbXRef)featureCvTermDbXRefs.get(i);
+ if( this_fcd.getDbXRef().getAccession().equals( fcd.getDbXRef().getAccession() ) &&
+ this_fcd.getDbXRef().getDb().getName().equals( fcd.getDbXRef().getDb().getName() ))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return true if the list contains a given feature_cvterm_prop
+ * @param fcp
+ * @param featureCvTermProps
+ * @return
+ */
+ private static boolean containsFeatureCvTermProp(FeatureCvTermProp fcp,
+ List featureCvTermProps)
+ {
+ for(int i = 0; i < featureCvTermProps.size(); i++)
+ {
+ FeatureCvTermProp this_fcp = (FeatureCvTermProp)featureCvTermProps.get(i);
+ if(this_fcp.getValue().equals(fcp.getValue()))
+ return true;
+
+ if(this_fcp.getValue().equals(GFFStreamFeature.decode(fcp.getValue())))
+ return true;
+ }
+ return false;
+ }
+
+ //
+ //
+ //
+
+
+ /**
+ * Build an AnalysisFeature object for a /similarity qualifier
+ * @param uniqueName
+ * @param qualifier_string
+ * @param feature
+ * @return
+ */
+ protected static AnalysisFeature getAnalysisFeature(String uniqueName,
+ String qualifier_string, final GFFStreamFeature feature)
+ {
+ // - allow for case with no featureId!
+ int queryFeatureId;
+ if(feature.getQualifierByName("feature_id") == null)
+ queryFeatureId = -1;
+ else
+ queryFeatureId = Integer.parseInt((String) feature.getQualifierByName(
+ "feature_id").getValues().get(0));
+
+ org.gmod.schema.sequence.Feature queryFeature = new org.gmod.schema.sequence.Feature();
+ org.gmod.schema.sequence.Feature subjectFeature = new org.gmod.schema.sequence.Feature();
+ org.gmod.schema.sequence.Feature matchFeature = new org.gmod.schema.sequence.Feature();
+ FeatureDbXRef featureDbXRef = new FeatureDbXRef();
+
+ final Hashtable rangeHash = feature.getSegmentRangeStore();
+ if(rangeHash != null)
+ {
+ final Enumeration id_keys= rangeHash.keys();
+ uniqueName = (String)id_keys.nextElement();
+
+ DatabaseDocument doc;
+
+ try
+ {
+ doc = (DatabaseDocument)feature.getDocumentEntry().getDocument();
+ }
+ catch(Exception e)
+ {
+ doc = (DatabaseDocument) (
+ (DatabaseDocumentEntry)((uk.ac.sanger.artemis.Feature)feature.getUserData()).
+ getEntry().getEMBLEntry()).getDocument();
+ }
+ FeatureLoc loc = new FeatureLoc();
+ Feature srcFeature = new Feature();
+ srcFeature.setFeatureId(Integer.parseInt(doc.getSrcFeatureId()));
+ loc.setFeatureBySrcFeatureId(srcFeature);
+ queryFeature.setFeatureLoc(loc);
+ logger4j.debug(uniqueName);
+ }
+
+ AnalysisFeature analysisFeature = new AnalysisFeature();
+ Analysis analysis = new Analysis();
+
+ subjectFeature.setCvTerm(getCvTerm("region")); // similarity_region
+
+ queryFeature.setUniqueName(uniqueName);
+ queryFeature.setFeatureId(queryFeatureId);
+ matchFeature.setUniqueName("MATCH_" + uniqueName);
+
+ analysisFeature.setAnalysis(analysis);
+ analysisFeature.setFeature(matchFeature);
+
+ List analysisFeatures = new Vector();
+ analysisFeatures.add(analysisFeature);
+ matchFeature.setAnalysisFeatures(analysisFeatures);
+
+ // algorithm
+ // StringTokenizer tok = new StringTokenizer(qualifier_string, ";");
+ while(qualifier_string.startsWith("\""))
+ qualifier_string = qualifier_string.substring(1);
+ while(qualifier_string.endsWith("\""))
+ qualifier_string = qualifier_string.substring(0,qualifier_string.length()-1);
+
+ final StringVector qualifier_strings = StringVector.getStrings(qualifier_string,
+ ";");
+
+ analysis.setProgram((String) qualifier_strings.get(0));
+ analysis.setProgramVersion("1.0");
+
+ // primary dbxref
+ DbXRef dbXRef_1 = new DbXRef();
+ Db db_1 = new Db();
+ dbXRef_1.setDb(db_1);
+ String value = ((String) qualifier_strings.get(1)).trim();
+
+ if(value.startsWith("with="))
+ value = value.substring(5);
+ String values[] = value.split(" ");
+
+ int ind = values[0].indexOf(':');
+ final String primary_name = values[0].substring(0, ind);
+ db_1.setName(primary_name);
+ dbXRef_1.setAccession(values[0].substring(ind + 1));
+ logger4j.debug("Primary dbXRef " + db_1.getName() + ":"
+ + dbXRef_1.getAccession());
+ subjectFeature.setDbXRef(dbXRef_1);
+ subjectFeature
+ .setUniqueName(db_1.getName() + ":" + dbXRef_1.getAccession());
+
+ if(primary_name.equalsIgnoreCase("UniProt"))
+ matchFeature.setCvTerm(getCvTerm("protein_match"));
+ else
+ matchFeature.setCvTerm(getCvTerm("nucleotide_match"));
+
+ // secondary dbxref
+ if(values.length > 1)
+ {
+ DbXRef dbXRef_2 = new DbXRef();
+ Db db_2 = new Db();
+ dbXRef_2.setDb(db_2);
+
+ values[1] = values[1].replaceAll("^\\W", "");
+ values[1] = values[1].replaceAll("\\W$", "");
+
+ ind = values[1].indexOf(':');
+ db_2.setName(values[1].substring(0, ind));
+ dbXRef_2.setAccession(values[1].substring(ind + 1));
+ logger4j.debug("Secondary dbXRef " + db_2.getName() + " "
+ + dbXRef_2.getAccession());
+ featureDbXRef.setDbXRef(dbXRef_2);
+ featureDbXRef.setFeature(subjectFeature);
+ List featureDbXRefs = new Vector();
+ featureDbXRefs.add(featureDbXRef);
+ subjectFeature.setFeatureDbXRefs(featureDbXRefs);
+ }
+
+ // organism
+ final String organismStr = (String) qualifier_strings.get(2);
+ if(!organismStr.equals(""))
+ {
+ FeatureProp featureProp = new FeatureProp();
+ featureProp.setCvTerm(getCvTerm("organism"));
+ featureProp.setValue(organismStr);
+ featureProp.setRank(0);
+ subjectFeature.addFeatureProp(featureProp);
+ }
+
+ // product
+ final String product = (String) qualifier_strings.get(3);
+ if(!product.equals(""))
+ {
+ FeatureProp featureProp = new FeatureProp();
+ featureProp.setCvTerm(getCvTerm("product"));
+ featureProp.setValue(product);
+ featureProp.setRank(1);
+ subjectFeature.addFeatureProp(featureProp);
+ }
+
+ // gene
+ if(qualifier_strings.size() > 4)
+ {
+ final String gene = (String) qualifier_strings.get(4);
+ if(!gene.equals(""))
+ {
+ FeatureProp featureProp = new FeatureProp();
+ featureProp.setCvTerm(getCvTerm("gene"));
+ featureProp.setValue(gene);
+ featureProp.setRank(2);
+ subjectFeature.addFeatureProp(featureProp);
+ }
+ }
+
+ // length
+ String length = getString(qualifier_strings, "length");
+ if(!length.equals(""))
+ {
+ if(length.startsWith("length=") || length.startsWith("length "))
+ length = length.substring(7);
+ if(length.endsWith("aa"))
+ length = length.substring(0, length.length() - 2).trim();
+ subjectFeature.setSeqLen(new Integer(length));
+ }
+
+ // percentage identity
+ String id = getString(qualifier_strings, "id");
+ if(!id.equals(""))
+ {
+ if(id.startsWith("id="))
+ id = id.substring(3);
+ if(id.endsWith("%"))
+ id = id.substring(0, id.length() - 1);
+
+ int index = id.indexOf(" ");
+ if(index > -1)
+ id = id.substring(index);
+
+ analysisFeature.setIdentity(new Double(id));
+ }
+
+ // ungapped id
+ String ungappedId = getString(qualifier_strings, "ungapped id");
+ if(!ungappedId.equals(""))
+ {
+ if(ungappedId.startsWith("ungapped id"))
+ ungappedId = ungappedId.substring(11);
+ if(ungappedId.startsWith("="))
+ ungappedId = ungappedId.substring(1);
+
+ if(ungappedId.endsWith("%"))
+ ungappedId = ungappedId.substring(0, ungappedId.length() - 1);
+ FeatureProp featureProp = new FeatureProp();
+ featureProp.setCvTerm(getCvTerm("ungapped id"));
+ featureProp.setValue(ungappedId);
+ matchFeature.addFeatureProp(featureProp);
+ }
+
+ // e-value
+ String evalue = getString(qualifier_strings, "E()=");
+ if(!evalue.equals(""))
+ {
+ if(evalue.startsWith("E()="))
+ evalue = evalue.substring(4);
+ analysisFeature.setSignificance(new Double(evalue));
+ }
+
+ // score
+ String score = getString(qualifier_strings, "score=");
+ if(!score.equals(""))
+ {
+ if(score.startsWith("score="))
+ score = score.substring(6);
+ analysisFeature.setRawScore(new Double(score));
+ }
+
+ // overlap
+ String overlap = getString(qualifier_strings, "overlap");
+ if(overlap.equals(""))
+ overlap = getStringEndsWith(qualifier_strings, "overlap");
+
+ if(!overlap.equals(""))
+ {
+ if(overlap.startsWith("overlap="))
+ overlap = overlap.substring(8);
+
+ FeatureProp featureProp = new FeatureProp();
+ featureProp.setCvTerm(getCvTerm("overlap"));
+ featureProp.setValue(overlap);
+ matchFeature.addFeatureProp(featureProp);
+ }
+
+ final Short strand;
+ if(feature.getLocation().isComplement())
+ strand = new Short("-1");
+ else
+ strand = new Short("1");
+
+ // query location
+ String queryLoc = getString(qualifier_strings, "query");
+ final FeatureLoc featureLoc = getFeatureLoc(queryLoc, queryFeature, strand, 0);
+ matchFeature.addFeatureLocsForFeatureId(featureLoc);
+
+ // subject location
+ String subjectLoc = getString(qualifier_strings, "subject");
+ final FeatureLoc subjectFeatureLoc = getFeatureLoc(subjectLoc, subjectFeature, strand, 1);
+ matchFeature.addFeatureLocsForFeatureId(subjectFeatureLoc);
+
+ return analysisFeature;
+ }
+
+
+
+
+
+
+ /**
+ * Build an AnalysisFeature object for a orthologous_to/paralogous_to qualifier
+ * @param uniqueName
+ * @param qualifier_string
+ * @param feature
+ * @return
+ */
+ protected static Feature getMatchFeatureWithFeatureRelations(final String uniqueName,
+ final String qualifierName, String qualifierString, final GFFStreamFeature feature,
+ final boolean isDelete)
+ {
+ final int queryFeatureId;
+
+ if(feature.getQualifierByName("feature_id") != null)
+ queryFeatureId = Integer.parseInt((String) feature.getQualifierByName(
+ "feature_id").getValues().get(0));
+ else
+ queryFeatureId = 0;
+
+ Feature thisFeature = new Feature();
+ Feature orthologFeature = new Feature();
+ Feature matchFeature = new Feature();
+
+ thisFeature.setUniqueName(uniqueName);
+ thisFeature.setFeatureId(queryFeatureId);
+
+ while(qualifierString.startsWith("\""))
+ qualifierString = qualifierString.substring(1);
+ while(qualifierString.endsWith("\""))
+ qualifierString = qualifierString.substring(0,qualifierString.length()-1);
+
+ final StringVector qualifierStrings = StringVector.getStrings(qualifierString,
+ ";");
+
+ String ortholog = (String)qualifierStrings.get(0);
+ String orthologStr[] = ortholog.split(":", 2);
+
+ final Organism organism = new Organism();
+ organism.setCommonName(orthologStr[0]);
+
+ int index1 = orthologStr[1].indexOf("link=")+5;
+ int index2 = orthologStr[1].indexOf("type=");
+ String link = orthologStr[1].substring(index1, index2).trim();
+
+ orthologFeature.setUniqueName(link);
+ orthologFeature.setOrganism(organism);
+
+ String matchName = getString(qualifierStrings, "match_name");
+ String clusterName = getString(qualifierStrings, "cluster_name");
+ if(!matchName.equals(""))
+ {
+ // there is no match feature just a direct feature_relationship link
+ if(link.equals(matchName.substring(11)))
+ {
+ if(isDelete)
+ return null;
+ else
+ matchFeature.setUniqueName("ORTHO_PARA_" + uniqueName + "_" + link);
+ }
+ else
+ matchFeature.setUniqueName(matchName.substring(11));
+ }
+ else if(!clusterName.equals(""))
+ matchFeature.setUniqueName(clusterName.substring(13));
+ else
+ matchFeature.setUniqueName("ORTHO_PARA_" + uniqueName + "_" + link);
+ matchFeature.setCvTerm(getCvTerm("protein_match"));
+ matchFeature.setOrganism(organism);
+
+ // rank
+ String rank = getString(qualifierStrings, "rank");
+ if(!rank.equals(""))
+ {
+ if(rank.startsWith("rank=") || rank.startsWith("rank "))
+ rank = rank.substring(5);
+ }
+
+ FeatureRelationship thisFeatureRelationship = new FeatureRelationship();
+ thisFeatureRelationship.setFeatureByObjectId(matchFeature);
+ thisFeatureRelationship.setFeatureBySubjectId(thisFeature);
+ thisFeatureRelationship.setCvTerm(getCvTerm(qualifierName));
+ if(!rank.equals(""))
+ thisFeatureRelationship.setRank(Integer.parseInt(rank));
+
+ List featureRelationshipsForSubjectId = new Vector();
+ featureRelationshipsForSubjectId.add(thisFeatureRelationship);
+
+ FeatureRelationship thisOrthologRelationship = new FeatureRelationship();
+ thisOrthologRelationship.setFeatureByObjectId(matchFeature);
+ thisOrthologRelationship.setFeatureBySubjectId(orthologFeature);
+ thisOrthologRelationship.setCvTerm(getCvTerm(qualifierName));
+ featureRelationshipsForSubjectId.add(thisOrthologRelationship);
+ matchFeature.setFeatureRelationshipsForSubjectId(featureRelationshipsForSubjectId);
+
+ return matchFeature;
+ }
+
+
+
+
+ /**
+ * Return a FeatureLoc for a Feature
+ * @param location
+ * @param feature
+ * @param strand
+ * @param rank
+ * @return
+ */
+ private static FeatureLoc getFeatureLoc(
+ final String location,
+ final org.gmod.schema.sequence.Feature feature,
+ final Short strand,
+ final int rank)
+ {
+ final FeatureLoc featureLoc = new FeatureLoc();
+
+ if(!location.equals(""))
+ {
+ String locs[] = location.split(" ");
+ locs = locs[1].split("-");
+ int fmin = Integer.parseInt(locs[0]) - 1;
+ featureLoc.setFmin(new Integer(fmin));
+ int fmax = Integer.parseInt(locs[1]);
+ featureLoc.setFmax(new Integer(fmax));
+ }
+ else
+ {
+ featureLoc.setFmin(new Integer(-1));
+ featureLoc.setFmax(new Integer(-1));
+ }
+ featureLoc.setRank(rank);
+ featureLoc.setStrand(strand);
+ featureLoc.setFeatureBySrcFeatureId(feature);
+ return featureLoc;
+ }
+
+ public static String getString(final StringVector sv, final String name)
+ {
+ for(int i = 0; i < sv.size(); i++)
+ {
+ String value = (String) sv.get(i);
+ if(value.trim().startsWith(name))
+ return value.trim();
+ }
+ return "";
+ }
+
+ private static String getStringEndsWith(final StringVector sv, final String name)
+ {
+ for(int i = 0; i < sv.size(); i++)
+ {
+ String value = (String) sv.get(i);
+ if(value.trim().endsWith(name))
+ return value.trim();
+ }
+ return "";
+ }
+
+ /**
+ * Get CvTerm that have been cached
+ * @param cvTermName
+ * @return
+ */
+ private static CvTerm getCvTerm(String cvTermName)
+ {
+ if(cvTermName.startsWith("\""))
+ cvTermName = cvTermName.substring(1, cvTermName.length() - 1);
+
+ CvTerm cvTerm = DatabaseDocument.getCvTermByCvTermName(cvTermName);
+
+ if(cvTerm != null)
+ {
+ logger4j.debug("USE CvTerm from cache, CvTermId=" + cvTermName
+ + " -> " + cvTerm.getCvTermId() + " " +
+ cvTerm.getCv().getName() + ":" + cvTerm.getName());
+ }
+ else
+ {
+ logger4j.warn("CvTerm not found in cache = " + cvTermName);
+ cvTerm = new CvTerm();
+ cvTerm.setName(cvTermName);
+ }
+ return cvTerm;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/chado/ChadoCvTermView.java b/uk/ac/sanger/artemis/chado/ChadoCvTermView.java
new file mode 100644
index 0000000..1705170
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/ChadoCvTermView.java
@@ -0,0 +1,469 @@
+/*
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import org.gmod.schema.cv.Cv;
+import org.gmod.schema.cv.CvTerm;
+
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+import uk.ac.sanger.artemis.components.genebuilder.cv.CvTermsComparator;
+import uk.ac.sanger.artemis.util.DatabaseLocationParser;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingConstants;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+
+/**
+ * Chado data access example code. This searches for features by their
+ * uniquename and returns their properties and attributes.
+ *
+ * @author tjc
+ *
+ */
+public class ChadoCvTermView extends JFrame
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /** database URL */
+ private String location;
+
+ /** password fields */
+ private JPasswordField pfield;
+
+ private GmodDAO dao;
+
+ /**
+ * Chado demo
+ */
+ public ChadoCvTermView(GmodDAO dao)
+ {
+ super("Controlled Vocabulary");
+ super.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+
+ try
+ {
+ if(dao == null)
+ {
+ setLocation();
+ dao = getDAO();
+ }
+
+ this.dao = dao;
+
+ final List cvs = dao.getAllCvs();
+
+ Cv cvSequence = null;
+ for(int i=0; i<cvs.size(); i++)
+ {
+ Cv cv = (Cv)cvs.get(i);
+ if(cv.getName().equalsIgnoreCase("sequence"))
+ {
+ cvSequence = cv;
+ break;
+ }
+ }
+
+ final JComboBox cvList = new JComboBox(new Vector(cvs));
+ cvList.setRenderer(new CvRenderer());
+
+ if(cvSequence != null)
+ cvList.setSelectedItem(cvSequence);
+
+ final JPanel panel = new JPanel(new BorderLayout());
+ final Box searchBox = Box.createVerticalBox();
+ final JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ searchBox.add(searchPanel);
+ panel.add(searchBox, BorderLayout.NORTH);
+
+ searchPanel.add(new JLabel("CV:"));
+ searchPanel.add(cvList);
+
+ final JTextField searchTerm = new JTextField(15);
+ searchTerm.setText("gene");
+
+ searchPanel.add(new JLabel("Term:"));
+ searchPanel.add(searchTerm);
+
+ final JButton searchButt = new JButton("GO");
+
+ searchButt.addActionListener(new ActionListener()
+ {
+ private JExtendedComboBox cvTermsList;
+ private JTable detailTable;
+
+ public void actionPerformed(ActionEvent e)
+ {
+ final GmodDAO dao = ChadoCvTermView.this.dao;
+
+ String search = searchTerm.getText().trim();
+ search = search.replaceAll("\\*", "%");
+
+ List cvTerms = dao.getCvTermByNameInCv(search,
+ (Cv)cvList.getSelectedItem());
+
+ Collections.sort(cvTerms, new CvTermsComparator());
+
+ if(cvTermsList != null)
+ searchBox.remove(cvTermsList);
+
+ if(detailTable != null)
+ panel.remove(detailTable);
+
+ cvTermsList = new JExtendedComboBox(new Vector(cvTerms));
+ cvTermsList.setPreferredSize(new Dimension(
+ searchPanel.getPreferredSize().width,cvTermsList.getPreferredSize().height));
+ cvTermsList.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ detailTable = showCvTable(dao, searchTerm,
+ cvTermsList, cvList, searchBox, panel,
+ searchPanel, detailTable);
+ }
+ });
+
+ cvTermsList.setRenderer(new CvRenderer());
+ searchBox.add(cvTermsList);
+ panel.revalidate();
+ pack();
+ }
+ });
+ searchPanel.add(searchButt);
+
+ getContentPane().add(panel);
+ pack();
+ setVisible(true);
+
+ searchTerm.requestFocus();
+ searchTerm.selectAll();
+ }
+ catch(java.net.ConnectException ce)
+ {
+ ce.printStackTrace();
+ }
+ catch(SQLException sqlExp)
+ {
+ JOptionPane.showMessageDialog(null, "SQL Problems...\n"
+ + sqlExp.getMessage(), "SQL Error", JOptionPane.ERROR_MESSAGE);
+ sqlExp.printStackTrace();
+ }
+
+ }
+
+
+ /**
+ *
+ * @param dao
+ * @param searchTerm
+ * @param cvTermsList
+ * @param cvList
+ * @param searchBox
+ * @param panel
+ * @param searchPanel
+ * @param detailTable
+ * @param frame
+ * @return
+ */
+ private JTable showCvTable(final GmodDAO dao, final JTextField searchTerm,
+ final JExtendedComboBox cvTermsList, final JComboBox cvList,
+ final Box searchBox, final JPanel panel, final JPanel searchPanel,
+ JTable detailTable)
+ {
+ CvTerm cvTerm = (CvTerm) cvTermsList.getSelectedItem();
+
+ final String columnData[] = { "", "" };
+ final String rowData[][] =
+ {
+ { "CV Name", cvTerm.getCv().getName() },
+ { "Term Name", cvTerm.getName() },
+ { "Definition", cvTerm.getDefinition() },
+ {
+ "DbXRef",
+ cvTerm.getDbXRef().getDb().getName() + ":"
+ + cvTerm.getDbXRef().getAccession() }
+ };
+
+ if(detailTable != null)
+ panel.remove(detailTable);
+ detailTable = new JTable(rowData, columnData);
+ TableColumn col = detailTable.getColumnModel().getColumn(0);
+ col.setMinWidth(35);
+ col.setMaxWidth(80);
+ col.setPreferredWidth(80);
+
+ TextCellRenderer renderer = new TextCellRenderer();
+ detailTable.getColumnModel().getColumn(1).setCellRenderer(renderer);
+ detailTable.getColumnModel().getColumn(0).setCellRenderer(
+ new LabelTableRender());
+
+ panel.add(detailTable, BorderLayout.CENTER);
+ panel.revalidate();
+ pack();
+
+ int rowHeight = 0;
+ for(int i = 0; i < detailTable.getRowCount(); i++)
+ {
+ renderer.getTableCellRendererComponent(detailTable,
+ rowData[i][1], false,
+ false, i, 1);
+ rowHeight += detailTable.getRowHeight(i);
+ }
+
+ detailTable.setPreferredSize(new Dimension(
+ detailTable.getPreferredSize().width, rowHeight));
+ panel.revalidate();
+ pack();
+ return detailTable;
+ }
+
+
+
+ /**
+ * Build a <code>JMenuBar</code>.
+ *
+ * @return a <code>JMenuBar</code>
+ */
+ public JMenuBar getJMenuBar(final GmodDAO dao)
+ {
+ JMenuBar mbar = new JMenuBar();
+ JMenu file = new JMenu("File");
+ mbar.add(file);
+
+ JMenuItem exit = new JMenuItem("Exit");
+ exit.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ System.exit(0);
+ }
+ });
+ file.add(exit);
+
+ return mbar;
+ }
+
+
+ /**
+ * Get the data access object (DAO).
+ *
+ * @return data access object
+ */
+ private GmodDAO getDAO() throws java.net.ConnectException, SQLException
+ {
+ if(System.getProperty("ibatis") == null)
+ return new JdbcDAO(location, pfield);
+ else
+ return new IBatisDAO(pfield);
+ }
+
+ /**
+ * Set the database location as:
+ * jdbc:postgresql://localhost:13001/chadoCVS?user=es2
+ *
+ * @return true if location chosen
+ */
+ private boolean setLocation()
+ {
+ Container bacross = new Container();
+ bacross.setLayout(new GridLayout(6, 2, 5, 5));
+
+ JLabel lServer = new JLabel("Server : ");
+ bacross.add(lServer);
+ JTextField inServer = new JTextField("localhost");
+ bacross.add(inServer);
+
+ JLabel lPort = new JLabel("Port : ");
+ bacross.add(lPort);
+ JTextField inPort = new JTextField("5432");
+ bacross.add(inPort);
+
+ JLabel lDB = new JLabel("Database : ");
+ bacross.add(lDB);
+ JTextField inDB = new JTextField("chado");
+ bacross.add(inDB);
+
+ JLabel lUser = new JLabel("User : ");
+ bacross.add(lUser);
+ JTextField inUser = new JTextField("afumigatus");
+ bacross.add(inUser);
+
+ JLabel lpasswd = new JLabel("Password : ");
+ bacross.add(lpasswd);
+ pfield = new JPasswordField(16);
+ bacross.add(pfield);
+
+
+ DatabaseLocationParser dlp;
+ // given -Dchado=localhost:port/dbname?username
+ if(System.getProperty("chado") != null)
+ {
+ String db_url = System.getProperty("chado").trim();
+ dlp = new DatabaseLocationParser(db_url);
+ inServer.setText(dlp.getHost());
+ inPort.setText("" + dlp.getPort());
+ inDB.setText(dlp.getDatabase());
+ inUser.setText(dlp.getUsername());
+
+ }
+ else
+ {
+ // Still need to initialise the object
+ dlp = new DatabaseLocationParser();
+ }
+
+ int n = JOptionPane.showConfirmDialog(null, bacross,
+ "Enter Database Address", JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+ if(n == JOptionPane.CANCEL_OPTION)
+ return false;
+
+
+ dlp.setHost(inServer.getText());
+ dlp.setPort(inPort.getText());
+ dlp.setDatabase(inDB.getText());
+ dlp.setUsername(inUser.getText());
+
+ location = dlp.getCompleteURL();
+
+ System.setProperty("chado", location);
+ return true;
+ }
+
+ class CvRenderer extends JLabel implements ListCellRenderer
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public CvRenderer()
+ {
+ setOpaque(true);
+ }
+
+ public Component getListCellRendererComponent(
+ JList list,
+ Object value,
+ int index,
+ boolean isSelected,
+ boolean cellHasFocus)
+ {
+ if(value instanceof Cv)
+ {
+ Cv cv = (Cv)value;
+ setText(cv.getName());
+ }
+ else if(value instanceof CvTerm)
+ {
+ CvTerm cvTerm = (CvTerm)value;
+ setText(cvTerm.getName());
+ }
+ else
+ setText((String)value);
+
+ setBackground(isSelected ? Color.red : Color.white);
+ setForeground(isSelected ? Color.white : Color.black);
+ return this;
+ }
+ }
+
+
+ class TextCellRenderer extends JTextArea implements TableCellRenderer
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public TextCellRenderer()
+ {
+ setLineWrap(true);
+ setWrapStyleWord(true);
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object
+ value, boolean isSelected, boolean hasFocus, int row, int column)
+ {
+ if(value == null)
+ setText("");
+ else
+ setText(value.toString());
+ setSize(table.getColumnModel().getColumn(column).getWidth(),
+ getPreferredSize().height);
+
+ if(table.getRowHeight(row) != getPreferredSize().height)
+ table.setRowHeight(row, getPreferredSize().height);
+
+ return this;
+ }
+
+ }
+
+ public class LabelTableRender extends DefaultTableCellRenderer
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public LabelTableRender()
+ {
+ super();
+ this.setVerticalAlignment(SwingConstants.TOP);
+ }
+ }
+
+
+ public static void main(String args[])
+ {
+ new ChadoCvTermView(null);
+ }
+}
diff --git a/uk/ac/sanger/artemis/chado/ChadoDAO.java b/uk/ac/sanger/artemis/chado/ChadoDAO.java
new file mode 100644
index 0000000..5f70469
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/ChadoDAO.java
@@ -0,0 +1,202 @@
+/*
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import java.sql.*;
+import java.util.List;
+
+import org.gmod.schema.sequence.Synonym;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.cv.CvTerm;
+
+/**
+ * Data access object
+ * @see uk.ac.sanger.artemis.chado.JdbcDAO
+ * @see uk.ac.sanger.artemis.chado.IBatisDAO
+ */
+public interface ChadoDAO
+{
+
+ /**
+ * Return the feature corresponding to this feature_id
+ *
+ * @param id the systematic id
+ * @return the Feature, or null
+ */
+ public org.gmod.schema.sequence.Feature getFeatureById(int id);
+
+ /**
+ * Return a features with this systematic id
+ *
+ * @param name the systematic id
+ * @return the Feature, or null
+ */
+ public org.gmod.schema.sequence.Feature getFeatureByUniqueName(String name);
+
+ /**
+ * This can be used to get individual features or children.
+ * If Feature.featureloc.srcfeature_id is set this is used
+ * to return the children of that srcfeature_id.
+ * @param feature the feature used to query
+ * @return the <code>List</code> of child <code>Feature</code> objects
+ * @throws SQLException
+ */
+ public List getFeaturesByLocatedOnFeature(org.gmod.schema.sequence.Feature parent);
+
+ /**
+ * Return a list of features with any current (ie non-obsolete) name or synonym
+ *
+ * @param name the lookup name
+ * @return a (possibly empty) List<Feature> of children with this current name
+ */
+ public List getFeaturesByAnyCurrentName(String name);
+
+ /**
+ * Return a list of features with this name or synonym (including obsolete names)
+ *
+ * @param name the lookup name
+ * @return a (possibly empty) List<Feature> of children with this name
+ */
+ public List getFeaturesByAnyName(String name, String featureType);
+
+ /**
+ * Return all the FeatureDbXRefs for a given feature, <b>specified by name</b>, or all if
+ * <code>null</code> is passed
+ *
+ * @param uniqueName the uniquename of a Feature, or null for all FeatureDbXRefs
+ * @return a (possibly empty) List<FeatureDbXRefI>
+ */
+ public List getFeatureDbXRefsByFeatureUniquename(final String uniqueName);
+
+ /**
+ * Return the list of FeatureSynonyms for a given Feature, <b>specified by name</b>, or all if
+ * <code>null</code> is passed
+ *
+ * @param uniqueName the uniquename of a Feature, or null for all
+ * @return a (possibly empty) List<FeatureSynonymI> of matching synonyms
+ */
+ public List getFeatureSynonymsByFeatureUniquename(final String uniqueName);
+
+ /**
+ * Given a list of distict cvterm_id/type_id's of feature types
+ * that have residues (from getResidueType()) in the given schema
+ * and the schema name return a list of features in the schema
+ * with residues.
+ * @param cvterm_ids list of cvterm_id/type_id's
+ * @param schema schema/organism name or null
+ * @return the <code>List</code> of <code>Feature</code> objects
+ */
+ public List getResidueFeatures(List cvterm_ids,
+ final String schema);
+
+ /**
+ * For a schema return the type_id's with residues.
+ * @param schema schema/organism name or null
+ * @return the <code>List</code> of type_id's as <code>String</code>
+ * objects
+ */
+ public List getResidueType(String schema);
+
+ /**
+ * Get available schemas (as a <code>List</code> of <code>String</code>
+ * objects).
+ * @return the available schemas
+ */
+ public List getSchema();
+
+ /**
+ * Get the all the <code>CvTerm</code> objects as a<code>List</code>.
+ * @return the full list of cvterm_id and name
+ */
+ public List getCvTerms();
+
+ /**
+ * Return a synonym of the given name and type if it exists
+ *
+ * @param name the name to lookup
+ * @param type the type of the Synonym
+ * @return a Synonym, or null
+ */
+ public Synonym getSynonymByNameAndCvTerm(String name, CvTerm type);
+
+
+ /**
+ * Return a list of FeatureSynonyms which link a given Feature and Synonym
+ *
+ * @param feature the test Feature
+ * @param synonym the test Synonym
+ * @return a (possibly empty) List<FeatureSynonym>
+ */
+ public List getFeatureSynonymsByFeatureAndSynonym(
+ org.gmod.schema.sequence.Feature feature, Synonym synonym);
+
+
+ /**
+ * Return the FeatureCvTerm that links a given Feature and CvTerm,
+ * with a given value of 'not'
+ *
+ * @param feature the Feature to test the link for
+ * @param cvTerm the CvTerm to test the link for
+ * @param not test for the not flag in the FeatureCvTerm
+ * @return the Feature, or null
+ */
+ public FeatureCvTerm getFeatureCvTermByFeatureAndCvTerm(
+ org.gmod.schema.sequence.Feature feature,
+ CvTerm cvTerm, boolean not);
+
+ public List getOrganisms();
+
+//
+// WRITE BACK
+//
+
+ /**
+ * Merge (update) an already persistent object back to the database
+ * (at the end of the current transaction, or depending upon flush mode).
+ * This method is defined in all the DAOs. It's recommended to call it
+ * through an appropriate one eg SequenceDaoI for FeatureI
+ * @param o The object to merge
+ */
+ public void merge(Object o);
+
+ /**
+ * Save the object to the database (at the end of the current transaction,
+ * or depending upon flush mode). This method is defined in all the DAOs.
+ * It's recommended to call it through an appropriate one eg SequenceDaoI
+ * for FeatureI
+ * @param o The object to store
+ */
+ public void persist(Object o);
+
+ /**
+ * Remove the object from the database (at the end of the current transaction,
+ * or depending upon flush mode). This method is defined in all the DAOs.
+ * It's recommended to call it through an appropriate one eg SequenceDaoI for
+ * FeatureI
+ * @param o The object to delete
+ */
+ public void delete(Object o);
+
+}
diff --git a/uk/ac/sanger/artemis/chado/ChadoDemo.java b/uk/ac/sanger/artemis/chado/ChadoDemo.java
new file mode 100644
index 0000000..8cfe8dd
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/ChadoDemo.java
@@ -0,0 +1,884 @@
+/*
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.pub.Pub;
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureCvTermDbXRef;
+import org.gmod.schema.sequence.FeatureCvTermProp;
+import org.gmod.schema.sequence.FeatureCvTermPub;
+import org.gmod.schema.sequence.FeatureDbXRef;
+import org.gmod.schema.sequence.FeaturePub;
+import org.gmod.schema.sequence.FeatureSynonym;
+import org.gmod.schema.sequence.FeatureProp;
+import org.gmod.schema.sequence.FeatureLoc;
+
+//import uk.ac.sanger.artemis.io.GFFStreamFeature;
+//import uk.ac.sanger.artemis.util.DatabaseDocument;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Cursor;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.Dimension;
+import java.net.ConnectException;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Collection;
+import java.util.Vector;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JButton;
+import javax.swing.JTabbedPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.JTable;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+import javax.swing.JTextArea;
+import javax.swing.JList;
+import javax.swing.JScrollPane;
+import javax.swing.JPanel;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.Box;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.ListSelectionEvent;
+
+import uk.ac.sanger.artemis.util.ByteBuffer;
+import uk.ac.sanger.artemis.util.DatabaseLocationParser;
+
+/**
+ * Chado data access example code. This searches for features by their
+ * uniquename and returns their properties and attributes.
+ *
+ * @author tjc
+ *
+ */
+public class ChadoDemo
+{
+ /** JDBC DAO */
+ /*private JdbcDAO jdbcDAO = null;*/
+
+ /** iBatis DAO */
+ private IBatisDAO connIB = null;
+
+ /** database URL */
+ private String location;
+
+ /** password fields */
+ private JPasswordField pfield;
+
+ /** results table */
+ private JTable result_table;
+
+ /** feature attributes */
+ private JTextArea attr_text;
+
+ /** <code>List</code> of <code>Feature</code> objects */
+ private List featureList;
+
+ /** row data containing results */
+ private String rowData[][];
+
+ private List pubDbXRefs[];
+
+ private JTabbedPane tabbedPane;
+
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(ChadoDemo.class);
+
+ /** controlled_curation controlled vocabulary */
+ public static String CONTROLLED_CURATION_TAG_CVNAME =
+ "CC_";
+ /** product controlled vocabulary */
+ public static String PRODUCTS_TAG_CVNAME = "genedb_products";
+ public static String RILEY_TAG_CVNAME = "RILEY";
+
+ /**
+ * Chado demo
+ */
+ public ChadoDemo()
+ {
+ //uk.ac.sanger.artemis.components.Splash.initLogger();
+
+ try
+ {
+ setLocation();
+ final GmodDAO dao = getDAO();
+
+ //
+ // TESTING - updating feature.residues
+ //
+ /*((IBatisDAO)dao).startTransaction();
+ FeatureForUpdatingResidues chadoFeature = new FeatureForUpdatingResidues();
+ chadoFeature.setStartBase(10);
+ chadoFeature.setEndBase(20);
+ dao.merge(chadoFeature);
+ ((IBatisDAO)dao).endTransaction();*/
+ //
+ //
+
+ showFeatureSearchPanel(dao);
+ //getCvterm(dao);
+ }
+ catch(java.net.ConnectException ce)
+ {
+ ce.printStackTrace();
+ }
+ catch(SQLException sqlExp)
+ {
+ JOptionPane.showMessageDialog(null, "SQL Problems...\n"
+ + sqlExp.getMessage(), "SQL Error", JOptionPane.ERROR_MESSAGE);
+ sqlExp.printStackTrace();
+ }
+
+ }
+
+ /**
+ * Display a window for searching for features.
+ *
+ * @throws java.net.ConnectException
+ * @throws SQLException
+ */
+ private void showFeatureSearchPanel(final GmodDAO dao)
+ throws java.net.ConnectException, SQLException
+ {
+ int index = location.indexOf('=') + 1;
+ String schema = location.substring(index);
+
+ final List schemas = dao.getOrganismsContainingSrcFeatures();
+
+ Vector v_schemas = new Vector();
+ v_schemas.add(0, "All");
+
+ for(int i=0; i<schemas.size(); i++)
+ {
+ Organism o = (Organism)schemas.get(i);
+ v_schemas.add(o.getCommonName());
+ }
+
+ final JFrame frame = new JFrame("Feature Search");
+ final JPanel panel = new JPanel(new BorderLayout());
+ final JList schema_list = new JList(v_schemas);
+ schema_list.setSelectedValue(schema, true);
+ if(schema_list.getSelectedIndex() == -1)
+ schema_list.setSelectedValue("All", true);
+
+ Box xbox2 = Box.createHorizontalBox();
+ JScrollPane jsp = new JScrollPane(schema_list);
+
+ Box xbox = Box.createHorizontalBox();
+ final JTextField gene_text = new JTextField("PF3D7_0100100*",20);
+ xbox.add(gene_text);
+ gene_text.selectAll();
+
+ result_table = new JTable();
+
+ final JScrollPane scrollpane = new JScrollPane(result_table);
+ scrollpane.setPreferredSize(new Dimension(600, 250));
+
+ //panel.add(scrollpane, BorderLayout.CENTER);
+ if(schemas != null)
+ pubDbXRefs = new List[schemas.size()];
+ else
+ pubDbXRefs = new List[1];
+
+ JButton findButt = new JButton("FIND");
+ findButt.addActionListener(new ActionListener()
+ {
+ private String columnNames[] = { "schema", "name", "type",
+ "feature ID", "loc", "strand", "time modified" };
+
+ public void actionPerformed(ActionEvent event)
+ {
+ frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ String search_gene = gene_text.getText();
+ String schema = (String)schema_list.getSelectedValue();
+ List schema_search;
+ if(schema.equalsIgnoreCase("All"))
+ schema_search = schemas;
+ else
+ {
+ schema_search = new Vector();
+ schema_search.add(schema);
+ }
+
+ try
+ {
+ rowData = search(search_gene, schema_search, dao);
+
+ result_table = new JTable(rowData, columnNames);
+ result_table.getSelectionModel().addListSelectionListener(
+ new SelectionListener(dao, frame));
+ result_table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ result_table.addMouseListener(new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ int row = result_table.getSelectedRow();
+
+ if(pubDbXRefs[row] == null)
+ pubDbXRefs[row] = dao.getPubDbXRef();
+ showAttributes(dao, pubDbXRefs[row]);
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ });
+
+ scrollpane.setViewportView(result_table);
+ }
+ catch(SQLException sqlExp)
+ {
+ JOptionPane.showMessageDialog(null, "SQL Problems...\n"
+ + sqlExp.getMessage(), "SQL Error", JOptionPane.ERROR_MESSAGE);
+ sqlExp.printStackTrace();
+ }
+ catch(ConnectException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ });
+ xbox.add(findButt);
+ xbox.add(Box.createHorizontalGlue());
+
+ Box ybox = Box.createVerticalBox();
+ xbox2.add(scrollpane);
+ xbox2.add(jsp);
+ xbox2.add(Box.createHorizontalGlue());
+
+ ybox.add(xbox);
+ ybox.add(xbox2);
+
+ panel.add(ybox, BorderLayout.NORTH);
+
+ attr_text = new JTextArea();
+ JScrollPane jsp_attr = new JScrollPane(attr_text);
+ jsp_attr.setPreferredSize(new Dimension(600, 150));
+
+
+ tabbedPane = new JTabbedPane();
+ tabbedPane.add("Core", jsp_attr);
+
+ panel.add(tabbedPane, BorderLayout.CENTER);
+
+
+ frame.getContentPane().add(panel);
+ frame.setJMenuBar(getJMenuBar(dao));
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ /**
+ * Build a <code>JMenuBar</code>.
+ *
+ * @return a <code>JMenuBar</code>
+ */
+ public JMenuBar getJMenuBar(final GmodDAO dao)
+ {
+ JMenuBar mbar = new JMenuBar();
+ JMenu file = new JMenu("File");
+ mbar.add(file);
+
+ JMenuItem exit = new JMenuItem("Exit");
+ exit.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ System.exit(0);
+ }
+ });
+ file.add(exit);
+
+ return mbar;
+ }
+
+
+
+
+ /**
+ * Search for a feature/gene name from a <code>List</code> of schemas.
+ *
+ * @param search_gene
+ * the feature name
+ * @param schema_search
+ * the <code>List</code> to search
+ * @param dao
+ * the data access object
+ * @return string array of results
+ * @throws SQLException
+ * @throws ConnectException
+ */
+ public String[][] search(final String search_gene, final List schema_search,
+ GmodDAO dao) throws SQLException, ConnectException
+ {
+ final String search_name = search_gene.replaceAll("[*]","%");
+ Feature feature = new Feature();
+ //feature.setUniquename(search_gene.replaceAll("[*]","%"));
+ featureList = new Vector();
+
+ for(int i=0; i<schema_search.size(); i++)
+ {
+ featureList.addAll(dao.getFeaturesByAnyCurrentName(search_name));
+ }
+
+ String rowData[][] = new String[featureList.size()][7];
+
+ for(int i = 0; i < featureList.size(); i++)
+ {
+ feature = (Feature) featureList.get(i);
+
+ // assume only one featureloc
+ Collection locs = feature.getFeatureLocsForFeatureId();
+
+ if(locs != null && locs.size() > 0)
+ {
+ Iterator it = locs.iterator();
+ FeatureLoc loc = (FeatureLoc)it.next();
+ int fmin = loc.getFmin().intValue() + 1;
+ int fmax = loc.getFmax().intValue();
+ rowData[i][4] = fmin + "..." + fmax;
+ rowData[i][5] = Integer.toString(loc.getStrand().shortValue());
+ }
+ else if(feature.getFeatureLoc() != null)
+ {
+ FeatureLoc loc = feature.getFeatureLoc();
+ int fmin = loc.getFmin().intValue() + 1;
+ int fmax = loc.getFmax().intValue();
+ rowData[i][4] = fmin + "..." + fmax;
+ rowData[i][5] = Integer.toString(loc.getStrand().shortValue());
+ }
+
+ String schema = feature.getOrganism().getAbbreviation();
+ int ind = schema.indexOf('.');
+ if(ind > 0)
+ schema = schema.substring(0,ind)+schema.substring(ind+1);
+
+ System.out.println("\n\nNow get feature type_id.......\n\n");
+ rowData[i][0] = schema;
+ rowData[i][1] = feature.getUniqueName();
+ rowData[i][2] = feature.getCvTerm().getName();
+ //rowData[i][2] = (String)cvterm.get(new Long(feature.getType_id()));
+ rowData[i][3] = Integer.toString(feature.getFeatureId());
+ rowData[i][6] = feature.getTimeLastModified().toString();
+
+ }
+ return rowData;
+ }
+
+
+ /**
+ * Get the data access object (DAO).
+ *
+ * @return data access object
+ */
+ private GmodDAO getDAO() throws java.net.ConnectException, SQLException
+ {
+/* if(System.getProperty("ibatis") == null)
+ {
+ logger4j.debug("Using JDBC");
+ if(jdbcDAO == null)
+ jdbcDAO = new JdbcDAO(location, pfield);
+ return jdbcDAO;
+ }
+ else*/
+ {
+ logger4j.debug("Using iBatis");
+ if(connIB == null)
+ connIB = new IBatisDAO(pfield);
+ return connIB;
+ }
+ }
+
+ /**
+ * Set the database location as:
+ * jdbc:postgresql://localhost:13001/chadoCVS?user=es2
+ *
+ * @return true if location chosen
+ */
+ private boolean setLocation()
+ {
+ Container bacross = new Container();
+ bacross.setLayout(new GridLayout(6, 2, 5, 5));
+
+ JLabel lServer = new JLabel("Server : ");
+ bacross.add(lServer);
+ JTextField inServer = new JTextField("db.genedb.org");
+ bacross.add(inServer);
+
+ JLabel lPort = new JLabel("Port : ");
+ bacross.add(lPort);
+ JTextField inPort = new JTextField("5432");
+ bacross.add(inPort);
+
+ JLabel lDB = new JLabel("Database : ");
+ bacross.add(lDB);
+ JTextField inDB = new JTextField("snapshot");
+ bacross.add(inDB);
+
+ JLabel lUser = new JLabel("User : ");
+ bacross.add(lUser);
+ JTextField inUser = new JTextField("genedb_ro");
+ bacross.add(inUser);
+
+ JLabel lpasswd = new JLabel("Password : ");
+ bacross.add(lpasswd);
+ pfield = new JPasswordField(16);
+ bacross.add(pfield);
+
+
+ DatabaseLocationParser dlp = new DatabaseLocationParser();
+ // given -Dchado=localhost:port/dbname?username
+ if(System.getProperty("chado") != null)
+ {
+ String db_url = System.getProperty("chado").trim();
+ dlp.setFromURLString(db_url);
+ inServer.setText(dlp.getHost());
+ inPort.setText("" + dlp.getPort());
+ inDB.setText(dlp.getDatabase());
+ inUser.setText(dlp.getUsername());
+
+ }
+
+ int n = JOptionPane.showConfirmDialog(null, bacross,
+ "Enter Database Address", JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+ if(n == JOptionPane.CANCEL_OPTION)
+ return false;
+
+ dlp.setHost(inServer.getText());
+ dlp.setPort(inPort.getText());
+ dlp.setDatabase(inDB.getText());
+ dlp.setUsername(inUser.getText());
+
+ location = dlp.getCompleteURL();
+
+ System.setProperty("chado", location);
+
+ return true;
+ }
+
+
+ public class SelectionListener implements ListSelectionListener
+ {
+ private GmodDAO dao;
+ private JFrame frame;
+
+ public SelectionListener(final GmodDAO dao, final JFrame frame)
+ {
+ super();
+ this.dao = dao;
+ this.frame = frame;
+ }
+
+ public void valueChanged(ListSelectionEvent e)
+ {
+ frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ int row = result_table.getSelectedRow();
+ //reset(location, rowData[row][0]);
+
+ if(pubDbXRefs[row] == null)
+ pubDbXRefs[row] = dao.getPubDbXRef();
+ showAttributes(dao, pubDbXRefs[row]);
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+
+ /**
+ * Show the attributes of a selected feature.
+ */
+ private void showAttributes(final GmodDAO dao,
+ final List pubDbXRefs)
+ {
+ final int row = result_table.getSelectedRow();
+ final ByteBuffer attr_buff = new ByteBuffer();
+ final Feature chado_feature = (Feature)featureList.get(row);
+
+ Collection locs = chado_feature.getFeatureLocsForFeatureId();
+ FeatureLoc loc;
+
+ if(locs != null && locs.size() > 0)
+ {
+ Iterator it = locs.iterator();
+ loc = (FeatureLoc)it.next();
+ }
+ else
+ loc = chado_feature.getFeatureLoc();
+ /*
+ int fmin = loc.getFmin().intValue() + 1;
+ int fmax = loc.getFmax().intValue();
+ */
+ String uniquename = chado_feature.getUniqueName();
+
+ attr_buff.append("/ID="+uniquename+"\n");
+ List attributes = (List)chado_feature.getFeatureProps();
+ List dbxrefs = dao.getFeatureDbXRefsByFeatureUniquename(uniquename);
+ List featureCvTerms = dao.getFeatureCvTermsByFeature(chado_feature);
+ List featureCvTermDbXRefs = dao.getFeatureCvTermDbXRefByFeature(chado_feature);
+ List featureCvTermPubs = dao.getFeatureCvTermPubByFeature(chado_feature);
+ Feature srcFeature = new Feature();
+ srcFeature.setFeatureId(loc.getSrcFeatureId());
+ List featurePubs = dao.getFeaturePubsBySrcFeature(srcFeature);
+
+ if(dbxrefs.size() > 0)
+ {
+ attr_buff.append("/Dbxref=");
+ for(int i = 0; i < dbxrefs.size(); i++)
+ {
+ FeatureDbXRef dbxref = (FeatureDbXRef) dbxrefs.get(i);
+ attr_buff.append(dbxref.getDbXRef().getDb().getName() + ":"
+ + dbxref.getDbXRef().getAccession() + "; ");
+ }
+ attr_buff.append("\n");
+ }
+
+ Collection synonyms = chado_feature.getFeatureSynonyms();
+
+ // append synonyms
+ if(synonyms != null && synonyms.size() > 0)
+ {
+ FeatureSynonym alias;
+
+ System.out.println("\n\nNow get synonym & type_id.......\n\n");
+ Iterator it = synonyms.iterator();
+ while(it.hasNext())
+ {
+ alias = (FeatureSynonym)it.next();
+ attr_buff.append("/");
+ attr_buff.append(alias.getSynonym().getCvTerm().getName() + "=");
+ attr_buff.append(alias.getSynonym().getName());
+ attr_buff.append(";");
+ attr_buff.append("\n");
+ }
+ }
+
+ if(attributes != null)
+ {
+ Iterator it = attributes.iterator();
+ while(it.hasNext())
+ {
+ FeatureProp featprop = (FeatureProp)it.next();
+
+ attr_buff.append("/" + featprop.getCvTerm().getName() + "="
+ + decode(featprop.getValue()) + "\n");
+ }
+ }
+
+ if(featureCvTerms != null)
+ {
+ for(int j=0; j<featureCvTerms.size(); j++)
+ {
+ attr_buff.append("/");
+ FeatureCvTerm feature_cvterm = (FeatureCvTerm)featureCvTerms.get(j);
+
+ appendControlledVocabulary(attr_buff, dao, feature_cvterm,
+ featureCvTermDbXRefs, featureCvTermPubs, pubDbXRefs, false);
+
+ attr_buff.append("\n");
+ }
+ }
+
+ // /literature
+ if(featurePubs != null)
+ {
+ for(int i=0; i<featurePubs.size(); i++)
+ {
+ FeaturePub featurePub = (FeaturePub)featurePubs.get(i);
+ if(chado_feature.getFeatureId() == featurePub.getFeature().getFeatureId())
+ {
+ attr_buff.append( "/literature=" );
+ attr_buff.append(featurePub.getPub().getUniqueName());
+ attr_buff.append("\n");
+ }
+ }
+ }
+
+ attr_text.setText(decode((new String(attr_buff.getBytes()))));
+ }
+
+
+ /**
+ * Appends controlled vocabulary terms to the buffer
+ * @param attr_buff
+ * @param dao
+ * @param feature_cvterm
+ * @param featureCvTermDbXRef
+ */
+ public static void appendControlledVocabulary(final ByteBuffer attr_buff,
+ final GmodDAO dao,
+ final FeatureCvTerm feature_cvterm,
+ final List featureCvTermDbXRefs,
+ final List featureCvTermPubs,
+ final List pubDbXRefs,
+ final boolean gene_builder)
+ {
+ CvTerm cvterm = dao.getCvTermById( feature_cvterm.getCvTerm().getCvTermId() );
+ DbXRef dbXRef = feature_cvterm.getCvTerm().getDbXRef();
+
+ if(cvterm.getCv().getName().startsWith(CONTROLLED_CURATION_TAG_CVNAME))
+ {
+ attr_buff.append("controlled_curation=");
+
+ attr_buff.append("term="+
+ feature_cvterm.getCvTerm().getName()+";");
+ attr_buff.append("cv="+
+ feature_cvterm.getCvTerm().getCv().getName()+";");
+
+ // N.B. the db_xref may be a FeatureCvTermDbXRef or a Pub for /controlled_curation
+ int nfound_dbxref = 0;
+ if(feature_cvterm.getPub().getUniqueName() != null &&
+ !feature_cvterm.getPub().getUniqueName().equalsIgnoreCase("NULL"))
+ {
+ // PMID
+ Pub pub = feature_cvterm.getPub();
+
+ // internal check
+ //checkPubDbXRef(pubDbXRefs, pub.getPubId(), pub, feature_cvterm);
+
+ attr_buff.append("db_xref="+ pub.getUniqueName());
+ nfound_dbxref++;
+ }
+
+ if(featureCvTermDbXRefs != null &&
+ featureCvTermDbXRefs.size() > 0)
+ {
+ for(int i=0; i<featureCvTermDbXRefs.size(); i++)
+ {
+ FeatureCvTermDbXRef featureCvTermDbXRef =
+ (FeatureCvTermDbXRef)featureCvTermDbXRefs.get(i);
+
+ if(feature_cvterm.getFeatureCvTermId() !=
+ featureCvTermDbXRef.getFeatureCvTerm().getFeatureCvTermId())
+ continue;
+
+ if(nfound_dbxref == 0)
+ attr_buff.append("db_xref=");
+ else if(nfound_dbxref > 0)
+ attr_buff.append("|");
+
+ DbXRef fc_dbXRef = featureCvTermDbXRef.getDbXRef();
+ attr_buff.append(fc_dbXRef.getDb().getName()+":");
+ attr_buff.append(fc_dbXRef.getAccession());
+ nfound_dbxref++;
+ }
+ }
+
+ if(nfound_dbxref > 0)
+ attr_buff.append("%3B");
+
+ List feature_cvtermprops = (List) feature_cvterm.getFeatureCvTermProps();
+ for(int i = 0; i < feature_cvtermprops.size(); i++)
+ {
+ FeatureCvTermProp feature_cvtermprop =
+ (FeatureCvTermProp)feature_cvtermprops.get(i);
+ attr_buff.append( dao.getCvTermById( feature_cvtermprop.getCvTerm().getCvTermId() ).getName());
+ attr_buff.append("=");
+ attr_buff.append(feature_cvtermprop.getValue());
+ if(i < feature_cvtermprops.size()-1)
+ attr_buff.append(";");
+ }
+
+ attr_buff.append(";");
+ }
+ else if(cvterm.getCv().getName().equals(PRODUCTS_TAG_CVNAME))
+ {
+ attr_buff.append("product=");
+ attr_buff.append(feature_cvterm.getCvTerm().getName()+";");
+ }
+ else if(cvterm.getCv().getName().equals(RILEY_TAG_CVNAME))
+ {
+ // class include the cvTermId as a convenience for looking up the term
+ attr_buff.append("class=");
+ attr_buff.append(dbXRef.getAccession()+"::"+
+ feature_cvterm.getCvTerm().getCvTermId()+";");
+ }
+ else
+ {
+ attr_buff.append("GO=");
+
+ if(cvterm.getCv().getName().equals("molecular_function"))
+ attr_buff.append("aspect=F%3B");
+ else if(cvterm.getCv().getName().equals("cellular_component"))
+ attr_buff.append("aspect=C%3B");
+ else if(cvterm.getCv().getName().equals("biological_process"))
+ attr_buff.append("aspect=P%3B");
+
+ if(feature_cvterm.isNot())
+ attr_buff.append("qualifier=NOT%3B");
+
+ attr_buff.append("GOid="+dbXRef.getDb().getName() + ":"
+ + dbXRef.getAccession() + "%3B");
+
+ attr_buff.append("term="+
+ feature_cvterm.getCvTerm().getName()+";");
+
+ // PMID
+ int nfound_pub = 0;
+ if(feature_cvterm.getPub() != null &&
+ feature_cvterm.getPub().getUniqueName() != null &&
+ !feature_cvterm.getPub().getUniqueName().equalsIgnoreCase("NULL"))
+ {
+ Pub pub = feature_cvterm.getPub();
+ attr_buff.append("db_xref="+
+ pub.getUniqueName());
+ nfound_pub++;
+ }
+
+ if(featureCvTermPubs != null &&
+ featureCvTermPubs.size() > 0)
+ {
+ for(int i=0; i<featureCvTermPubs.size(); i++)
+ {
+ FeatureCvTermPub featureCvTermPub =
+ (FeatureCvTermPub)featureCvTermPubs.get(i);
+
+ if(feature_cvterm.getFeatureCvTermId() !=
+ featureCvTermPub.getFeatureCvTerm().getFeatureCvTermId())
+ continue;
+
+ if(nfound_pub == 0)
+ attr_buff.append("db_xref=");
+ else if(nfound_pub > 0)
+ attr_buff.append("|");
+
+ attr_buff.append(featureCvTermPub.getPub().getUniqueName());
+ nfound_pub++;
+ }
+ }
+
+ if(nfound_pub > 0)
+ attr_buff.append("%3B");
+
+ if(featureCvTermDbXRefs != null &&
+ featureCvTermDbXRefs.size() > 0 )
+ {
+ int nfound = 0;
+ for(int i=0; i<featureCvTermDbXRefs.size(); i++)
+ {
+ FeatureCvTermDbXRef featureCvTermDbXRef =
+ (FeatureCvTermDbXRef)featureCvTermDbXRefs.get(i);
+
+
+ if(feature_cvterm.getFeatureCvTermId() !=
+ featureCvTermDbXRef.getFeatureCvTerm().getFeatureCvTermId())
+ {
+ continue;
+ }
+
+ if(nfound == 0)
+ attr_buff.append("with=");
+ else if(nfound > 1)
+ attr_buff.append("|");
+
+ DbXRef fc_dbXRef = featureCvTermDbXRef.getDbXRef();
+ attr_buff.append(fc_dbXRef.getDb().getName()+":");
+ attr_buff.append(fc_dbXRef.getAccession());
+ nfound++;
+ }
+
+ if(nfound > 0)
+ attr_buff.append("%3B");
+ }
+
+ List feature_cvtermprops = (List)feature_cvterm
+ .getFeatureCvTermProps();
+ for(int i = 0; i < feature_cvtermprops.size(); i++)
+ {
+ FeatureCvTermProp feature_cvtermprop =
+ (FeatureCvTermProp)feature_cvtermprops.get(i);
+
+ if(feature_cvtermprop.getValue() == null)
+ continue;
+
+ attr_buff.append(dao.getCvTermById(feature_cvtermprop.getCvTerm().getCvTermId()).getName());
+ attr_buff.append("=");
+ attr_buff.append(feature_cvtermprop.getValue());
+ if(i < feature_cvtermprops.size()-1)
+ attr_buff.append(";");
+ }
+
+ attr_buff.append(";");
+ }
+ }
+
+
+
+ /**
+ *
+ * For gff-version 3:
+ * http://song.sourceforge.net/gff3-jan04.shtml
+ *
+ * Remove URL escaping rule (e.g. space="%20" or "+")
+ *
+ */
+ private String decode(String s)
+ {
+ final String map[][] = {
+ { " ", "%20" }, // white space
+ { ",", "%2C" }, // comma
+ { ";", "%3B" }, // semi-colon
+ { "=", "%3D" }, // equals
+ { "\t", "%09" }, // tab
+ { " ", "+" }, // white space
+ { "+", "%2B" },
+ { "(", "%28" }, // left bracket
+ { ")", "%29" } // right bracket
+ };
+
+ int ind;
+ String enc;
+ String dec;
+
+ for(int i=0; i<map.length; i++)
+ {
+ enc = map[i][1];
+ dec = map[i][0];
+ while( (ind = s.indexOf(enc)) > -1)
+ s = s.substring(0,ind) + dec + s.substring(ind+enc.length());
+ }
+
+ return s;
+ }
+
+ public static void main(String args[])
+ {
+ new ChadoDemo();
+ }
+}
diff --git a/uk/ac/sanger/artemis/chado/ChadoTransaction.java b/uk/ac/sanger/artemis/chado/ChadoTransaction.java
new file mode 100644
index 0000000..aea1335
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/ChadoTransaction.java
@@ -0,0 +1,204 @@
+/* ChadoTransaction
+ *
+ * created: July 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import java.sql.Timestamp;
+
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+
+
+/**
+ * Store information about a SQL transaction <i>e.g</i> UPDATE, INSERT, DELETE
+ * a feature, featureloc, featureprop, feature_dbxref, feature_synonym.
+ **/
+public class ChadoTransaction
+{
+ //
+ // FEATURE TRANSACTIONS
+ /** update statement */
+ public static final int UPDATE = 1;
+ /** insert statement */
+ public static final int INSERT = 2;
+ /** delete statement */
+ public static final int DELETE = 3;
+
+ /** type of statement <i>e.g.</i> UPDATE, INSERT, DELETE, ... */
+ protected int type;
+ /** feature unique name */
+ protected String old_uniquename;
+ private String uniquename;
+
+ /** last time feature was modified */
+ private Timestamp lastmodified;
+ /** the feature object */
+ private Object feature_obj;
+ /** the feature key */
+ private String featureKey;
+
+ private GFFStreamFeature gff_feature;
+
+ private String logComment;
+
+ /**
+ * Create a transaction to represent a single insert, delete or
+ * update.
+ * @param type the type of transcation, e.g. insert, delete, update.
+ * @param feature_obj the <code>Feature</code>, <code>FeatureLoc</code>,
+ * <code>FeatureProp</code>, <code>FeatureRelationship</code>, <code>FeatureDbxref</code>,
+ * or <code>FeatureSynonym</code>.
+ * @param lastmodified the last modified timestamp
+ * @param gff_feature the artemis GFF feature effected
+ */
+ public ChadoTransaction(final int type,
+ final Object feature_obj,
+ final Timestamp lastmodified,
+ final GFFStreamFeature gff_feature,
+ final String featureKey,
+ final String logComment)
+ {
+ this.type = type;
+ this.lastmodified = lastmodified;
+ this.feature_obj = feature_obj;
+ this.gff_feature = gff_feature;
+ this.logComment = logComment;
+
+ if(featureKey != null &&
+ featureKey.equals(DatabaseDocument.EXONMODEL))
+ this.featureKey = "exon";
+ else
+ this.featureKey = featureKey;
+ }
+
+ /**
+ * Copy this transaction
+ * @return
+ */
+ public ChadoTransaction copy()
+ {
+ final ChadoTransaction tsn = new ChadoTransaction(getType(), getFeatureObject(),
+ getLastModified(), getGff_feature(),
+ getFeatureKey(), logComment);
+
+ if(uniquename != null)
+ tsn.setUniquename(uniquename);
+
+ tsn.setOldUniquename(old_uniquename);
+ return tsn;
+ }
+
+ /**
+ * The type of SQL transaction
+ * @return the transaction type
+ */
+ public int getType()
+ {
+ return type;
+ }
+
+ public String getTypeAsString()
+ {
+ if(type == UPDATE)
+ return "UPDATE";
+ else if(type == INSERT)
+ return "INSERT";
+ else if(type == DELETE)
+ return "DELETE";
+ return "";
+ }
+
+ /**
+ * Set the old uniquename, used when updating the uniquename
+ * @param uniquename
+ */
+ public void setOldUniquename(final String old_uniquename)
+ {
+ this.old_uniquename = old_uniquename;
+ }
+
+ /**
+ * Get the unique names of features to change.
+ */
+ public String getOldUniquename()
+ {
+ return old_uniquename;
+ }
+
+ /**
+ * Set a uniquename, e.g. to be used when changing a featureprop
+ * @param uniquename
+ */
+ public void setUniquename(final String uniquename)
+ {
+ this.uniquename = uniquename;
+ }
+
+ public String getUniquename()
+ {
+ if(uniquename != null)
+ return uniquename;
+
+ if(getGff_feature() == null)
+ return null;
+
+ return (String)
+ getGff_feature().getQualifierByName("ID").getValues().get(0);
+ }
+
+ /**
+ * Get the last time modified time stamp.
+ * @return the <code>Timestamp</code>
+ */
+ public Timestamp getLastModified()
+ {
+ return this.lastmodified;
+ }
+
+ public Object getFeatureObject()
+ {
+ return feature_obj;
+ }
+
+ public GFFStreamFeature getGff_feature()
+ {
+ return gff_feature;
+ }
+
+
+ public String getFeatureKey()
+ {
+ return featureKey;
+ }
+
+
+ public String getLogComment()
+ {
+ String key = "";
+ if(getFeatureKey() != null)
+ key = " KEY=" + getFeatureKey();
+ return "["+getTypeAsString()+"] "+logComment+key;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/chado/ChadoTransactionManager.java b/uk/ac/sanger/artemis/chado/ChadoTransactionManager.java
new file mode 100644
index 0000000..8e00d32
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/ChadoTransactionManager.java
@@ -0,0 +1,2979 @@
+/* ChadoTransactionManager.java
+ *
+ * created: July 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureSegment;
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.sequence.SequenceChangeListener;
+import uk.ac.sanger.artemis.sequence.SequenceChangeEvent;
+import uk.ac.sanger.artemis.components.filetree.LocalAndRemoteFileManager;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.genebuilder.ProteinMapPanel;
+import uk.ac.sanger.artemis.components.genebuilder.cv.DatePanel;
+import uk.ac.sanger.artemis.components.genebuilder.cv.GoBox;
+import uk.ac.sanger.artemis.components.genebuilder.cv.HistoryBox;
+import uk.ac.sanger.artemis.components.genebuilder.ortholog.MatchPanel;
+import uk.ac.sanger.artemis.components.genebuilder.ortholog.OrthoParalogTable;
+import uk.ac.sanger.artemis.components.genebuilder.ortholog.SimilarityTable;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.DatabaseInferredFeature;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.LazyQualifierValue;
+import uk.ac.sanger.artemis.io.QualifierLazyLoading;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.io.StreamQualifier;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.EntryChangeListener;
+import uk.ac.sanger.artemis.EntryChangeEvent;
+import uk.ac.sanger.artemis.Options;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Enumeration;
+import java.util.regex.Pattern;
+
+import javax.swing.JOptionPane;
+
+import org.gmod.schema.sequence.FeatureCvTermProp;
+import org.gmod.schema.sequence.FeatureCvTermPub;
+import org.gmod.schema.sequence.FeatureLoc;
+import org.gmod.schema.sequence.FeatureProp;
+import org.gmod.schema.sequence.FeatureDbXRef;
+import org.gmod.schema.sequence.FeaturePub;
+import org.gmod.schema.sequence.FeatureRelationship;
+import org.gmod.schema.sequence.FeatureSynonym;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.Synonym;
+
+import org.gmod.schema.analysis.AnalysisFeature;
+import org.gmod.schema.cv.*;
+import org.gmod.schema.pub.Pub;
+import org.gmod.schema.general.Db;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.sequence.FeatureCvTermDbXRef;
+
+/**
+ *
+ * Chado transaction manager listens for feature, entry and sequence changes.
+ * <code>ChadoTransactionManager</code> creates and tracks the feature insertions,
+ * deletions, and updates to commit back to the database.
+ *
+ **/
+public class ChadoTransactionManager
+ implements FeatureChangeListener, EntryChangeListener, SequenceChangeListener
+{
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(ChadoTransactionManager.class);
+
+ public static boolean addSegments = true;
+ private Vector<ChadoTransaction> sql = new Vector<ChadoTransaction>();
+
+ /** GFF3 predefined tags, i.e. not feature_prop's */
+ private static String reserved_tags[] =
+ { "ID",
+ "Name",
+ "Alias",
+ "Parent",
+ "Target",
+ "Gap",
+ "Derives_from",
+ "Dbxref",
+ "Ontology_term",
+ "score",
+ "codon_start",
+ "Start_range",
+ "End_range",
+ "isObsolete",
+ MatchPanel.SIMILARITY,
+ MatchPanel.ORTHOLOG,
+ MatchPanel.PARALOG,
+ "literature",
+ "gff_source", // program or database
+ "gff_seqname" }; // seqID of coord system
+
+ //controlled vocab tags
+ public static String CV_NAME[];
+
+ //synonym tags from cv
+ private static String synonym_tags[] = null;
+ public static String SYNONYM_TAG_CVNAME =
+ Options.getOptions().getProperty("synonym_cvname");
+ private EntryGroup entryGroup;
+ // Db where db entries are stored corresponding to controlled curation CV terms
+ public static String CONTROLLED_CURATION_DB = "CCGEN";
+ public static String PRODUCT_DB = "PRODUCT";
+ public static String PRODUCT_CV =
+ Options.getOptions().getProperty("product_cvname");
+ public static String HISTORY_CV =
+ Options.getOptions().getProperty("history_cvname");
+
+ // number of SQL commands successfully processed during a commit
+ public static int commitReturnValue = 0;
+
+ static
+ {
+ initCV();
+ }
+
+ public ChadoTransactionManager()
+ {
+
+ }
+
+ private static void initCV()
+ {
+ if(Options.getOptions().getPropertyTruthValue("product_cv"))
+ {
+ logger4j.debug("PRODUCT STORED AS A CV (product_cv=yes) IN "+PRODUCT_CV);
+ /*int nsize = 4;
+ if(HISTORY_CV != null)
+ nsize++;*/
+
+ CV_NAME = new String[]
+ { "GO",
+ "controlled_curation",
+ "product",
+ "class" };
+ }
+ else
+ {
+ logger4j.debug("PRODUCT STORED AS A FEATUREPROP (product_cv=no)");
+ /*int nsize = 3;
+ if(HISTORY_CV != null)
+ nsize++;*/
+
+ CV_NAME = new String[]
+ { "GO",
+ "controlled_curation",
+ "class" };
+ }
+ if(HISTORY_CV != null)
+ CV_NAME[CV_NAME.length-1] = "history";
+ logger4j.debug("SYNONYM NAMES ARE STORED IN "+SYNONYM_TAG_CVNAME);
+ }
+
+ public void setEntryGroup(final EntryGroup entryGroup)
+ {
+ this.entryGroup = entryGroup;
+ }
+
+ private void logMDC()
+ {
+ try
+ {
+ DatabaseDocument.initMDC((DatabaseDocument)
+ ((DocumentEntry)entryGroup.getSequenceEntry().getEMBLEntry()).getDocument());
+ }
+ catch(Exception e){}
+ }
+
+ /**
+ * Invoked when a deletion or insertion occurs in a Bases object.
+ **/
+ public void sequenceChanged(final SequenceChangeEvent event)
+ {
+ logMDC();
+ if(event.getType() == SequenceChangeEvent.DELETION ||
+ event.getType() == SequenceChangeEvent.INSERTION)
+ {
+ int start = event.getPosition();
+ int length = event.getSubSequence().length();
+
+ //
+ // update residues in srcfeature
+ DatabaseDocument doc = (DatabaseDocument)
+ ((DocumentEntry)entryGroup.getSequenceEntry().getEMBLEntry()).getDocument();
+ int newSequenceLength = entryGroup.getSequenceEntry().getEMBLEntry().getSequence().length();
+
+ /*org.gmod.schema.sequence.Feature regionFeature = new org.gmod.schema.sequence.Feature();
+ CvTerm cvTerm = new CvTerm();
+ cvTerm.setName("region");
+ regionFeature.setCvTerm(cvTerm);
+ org.gmod.schema.sequence.Feature srcFeature = new org.gmod.schema.sequence.Feature();
+ srcFeature.setFeatureId( Integer.parseInt(doc.getSrcFeatureId()) );
+ srcFeature.setSeqLen(new Integer(
+ entryGroup.getSequenceEntry().getEMBLEntry().getSequence().length()));
+ FeatureLoc featureLoc = new FeatureLoc();
+ featureLoc.setFeatureBySrcFeatureId(srcFeature);
+ featureLoc.setFmin(new Integer(start-1));
+ regionFeature.setFeatureLoc(featureLoc);
+ regionFeature.setSeqLen(new Integer(length));
+
+ if(event.getType() == SequenceChangeEvent.INSERTION)
+ {
+ regionFeature.setResidues(event.getSubSequence().getBytes());
+ featureLoc.setFmax(new Integer(start));
+ }
+ else
+ {
+ featureLoc.setFmax(new Integer(start+length));
+ }
+ ChadoTransaction tsn = new ChadoTransaction(ChadoTransaction.UPDATE, regionFeature,
+ null, null, null);*/
+
+ final FeatureForUpdatingResidues chadoFeature = new FeatureForUpdatingResidues();
+ chadoFeature.setStartBase(start-1);
+ chadoFeature.setLength(length);
+
+ final String logMsg;
+ if(event.getType() == SequenceChangeEvent.INSERTION)
+ {
+ chadoFeature.setNewSubSequence(event.getSubSequence());
+ chadoFeature.setEndBase(start);
+ chadoFeature.setBasesToEnd(newSequenceLength-event.getSubSequence().length()-start+1);
+ logMsg = "SEQUENCE INSERT AT "+start;
+ }
+ else
+ {
+ chadoFeature.setEndBase(start+length);
+ chadoFeature.setBasesToEnd(newSequenceLength+event.getSubSequence().length()-start);
+ logMsg = "SEQUENCE DELETE AT "+start;
+ }
+
+ chadoFeature.setFeatureId( Integer.parseInt(doc.getSrcFeatureId()) );
+ chadoFeature.setSeqLen(new Integer(
+ entryGroup.getSequenceEntry().getEMBLEntry().getSequence().length()));
+
+ ChadoTransaction tsn =
+ new ChadoTransaction(ChadoTransaction.UPDATE, chadoFeature,
+ null, null, null, logMsg);
+ sql.add(tsn);
+ }
+ else
+ {
+
+ }
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We listen for
+ * changes in every feature of every entry in this group.
+ **/
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ logMDC();
+ if(event.featureHasChanged())
+ {
+ if(!(event.getFeature().getEmblFeature() instanceof GFFStreamFeature) ||
+ (event.getFeature().getEmblFeature() instanceof DatabaseInferredFeature))
+ return;
+
+ final GFFStreamFeature feature =
+ (GFFStreamFeature)event.getFeature().getEmblFeature();
+
+ if(event.getType() == FeatureChangeEvent.SEGMENT_CHANGED)
+ {
+ RangeVector rv_new = event.getNewLocation().getRanges();
+ RangeVector rv_old = event.getOldLocation().getRanges();
+
+ segmentNumberChanged(feature, rv_new, rv_old);
+ }
+ else if(event.getType() == FeatureChangeEvent.LOCATION_CHANGED)
+ {
+ final RangeVector rv_new = event.getNewLocation().getRanges();
+ final RangeVector rv_old = event.getOldLocation().getRanges();
+
+ logger4j.debug("LOCATION_CHANGED "+
+ feature.getFirstBase()+".."+feature.getLastBase()+
+ " new="+rv_new.size()+" old="+rv_old.size());
+ if(rv_new.size() != rv_old.size())
+ {
+ // location and segment number change
+ final RangeVector rangesToAdd = new RangeVector();
+ for(Range range: rv_new)
+ {
+ if(!rv_old.containsRange(range))
+ rangesToAdd.add(range);
+ }
+
+ try
+ {
+ final String parent =
+ feature.getQualifierByName("Parent").getValues().get(0);
+ GeneUtils.addSegment(feature, rangesToAdd, parent);
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+
+ segmentNumberChanged(feature, rv_new, rv_old);
+ return;
+ }
+
+ ChadoTransaction tsn;
+ int ichanged;
+ Vector<Integer> changes = new Vector<Integer>();
+ for(ichanged=0; ichanged<rv_old.size(); ichanged++)
+ {
+ Range rnew = rv_new.elementAt(ichanged);
+ Range rold = rv_old.elementAt(ichanged);
+
+ if(rnew.getStart() != rold.getStart() ||
+ rnew.getEnd() != rold.getEnd() ||
+ (event.getOldLocation().isComplement(rold) !=
+ event.getNewLocation().isComplement(rnew)))
+ changes.add(new Integer(ichanged));
+ }
+
+ for(Integer c: changes)
+ {
+ ichanged = c.intValue();
+
+ Range range_new = rv_new.elementAt(ichanged);
+ Range range_old = rv_old.elementAt(ichanged);
+ String seg_id = feature.getSegmentID(range_new);
+
+ if(seg_id == null)
+ seg_id = feature.getSegmentID(range_old);
+
+ if(feature.getSegmentRangeStore() != null)
+ {
+ feature.getSegmentRangeStore().put(seg_id, range_new);
+ }
+
+ if(sql.size() > 0)
+ {
+ // collapse updating featureloc into one statement
+ ChadoTransaction lastTsn = (ChadoTransaction)sql.lastElement();
+ String thisKey = feature.getKey().getKeyString();
+ if(thisKey.equals(DatabaseDocument.EXONMODEL))
+ thisKey = "exon";
+ if(lastTsn.getGff_feature() != null &&
+ lastTsn.getType() == ChadoTransaction.UPDATE &&
+ lastTsn.getFeatureKey() != null &&
+ lastTsn.getFeatureKey().equals( thisKey ) &&
+ lastTsn.getFeatureObject() instanceof FeatureLoc)
+ {
+ FeatureLoc floc = (FeatureLoc)lastTsn.getFeatureObject();
+ if(floc.getFeatureByFeatureId().getUniqueName().equals(seg_id))
+ {
+ logger4j.debug("REMOVE LAST FeatureLoc ChadoTransaction");
+ sql.remove(sql.size()-1);
+ }
+ }
+ }
+
+ logger4j.debug("UPDATE FEATURELOC: "+seg_id);
+ FeatureLoc featureloc = getFeatureLoc(feature, seg_id, range_new);
+
+ tsn = new ChadoTransaction(ChadoTransaction.UPDATE,
+ featureloc,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "FEATURELOC: ID="+seg_id+" "+
+ featureloc.getFmin()+".."+featureloc.getFmax());
+ sql.add(tsn);
+ updateResidueColumn(tsn);
+ }
+
+ }
+ else if(event.getType() == FeatureChangeEvent.QUALIFIER_CHANGED)
+ {
+ logger4j.debug("QUALIFIER_CHANGED for "
+ +event.getOldQualifiers().getQualifierByName("ID").getValues().get(0));
+
+ editKeyAndQualifiers(event.getOldQualifiers(),event.getNewQualifiers(),
+ event.getOldKey(), event.getNewKey(),
+ feature, FeatureChangeEvent.QUALIFIER_CHANGED);
+ }
+ else if(event.getType() == FeatureChangeEvent.ALL_CHANGED)
+ {
+ logger4j.debug("ALL_CHANGED "+event.getOldKey().toString()+" "+
+ event.getNewKey().toString());
+
+ editKeyAndQualifiers(event.getOldQualifiers(),event.getNewQualifiers(),
+ event.getOldKey(), event.getNewKey(),
+ feature, FeatureChangeEvent.ALL_CHANGED);
+
+ if(event.getOldKey().compareTo( event.getNewKey() ) != 0 &&
+ (event.getNewKey().toString().equals("gene") ||
+ event.getNewKey().toString().equals("pseudogene")) &&
+ feature.getChadoGene() == null)
+ {
+ ChadoCanonicalGene chado_gene = new ChadoCanonicalGene();
+ chado_gene.setGene(feature);
+ feature.setChadoGene(chado_gene);
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoked when an Entry is changed.
+ **/
+ public void entryChanged(EntryChangeEvent event)
+ {
+ logMDC();
+
+ if(event.getType() == EntryChangeEvent.FEATURE_ADDED)
+ {
+ // if this is a duplicate feature then ignore
+ if(event.isDuplicate())
+ {
+ Feature feature = event.getFeature();
+ Qualifier qualifier_uniquename;
+ try
+ {
+ qualifier_uniquename = feature.getQualifierByName("ID");
+ logger4j.debug("FEATURE_ADDED ------> DUPLICATE "+
+ qualifier_uniquename.getValues().elementAt(0));
+ }
+ catch(InvalidRelationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ return;
+ }
+
+ if(!(event.getFeature().getEmblFeature() instanceof GFFStreamFeature) ||
+ event.getFeature().getEmblFeature() instanceof DatabaseInferredFeature)
+ return;
+
+ final Feature feature = event.getFeature();
+ if(!GeneUtils.isDatabaseEntry(feature.getEmblFeature()))
+ return;
+
+ final FeatureSegmentVector segments = feature.getSegments();
+ if(segments != null && segments.size() > 1)
+ {
+ for(int iadd = 0; iadd < segments.size(); iadd++)
+ {
+ FeatureSegment segment = segments.elementAt(iadd);
+ Range range = segment.getRawRange();
+ final String segment_uniquename =
+ ((GFFStreamFeature)feature.getEmblFeature()).getSegmentID(range);
+ insertFeatureSegment(segment, segment_uniquename);
+ }
+ }
+ else
+ insertFeature(feature);
+ }
+ else if(event.getType() == EntryChangeEvent.FEATURE_DELETED)
+ {
+ if(event.isDuplicate())
+ {
+ logger4j.debug("FEATURE_DELETED looks like duplicate - ignore");
+ return;
+ }
+
+ if(!(event.getFeature().getEntry().getEMBLEntry() instanceof DatabaseDocumentEntry) )
+ {
+ logger4j.debug("FEATURE_DELETED not a Database deletion");
+ return;
+ }
+
+ try
+ {
+ Qualifier qualifier_uniquename = event.getFeature().getQualifierByName("ID");
+ String feature_uniquename = qualifier_uniquename.getValues().elementAt(0);
+
+ final GFFStreamFeature gff_feature =
+ (GFFStreamFeature)event.getFeature().getEmblFeature();
+
+ deletePolypetideDomains(gff_feature, event.getFeature());
+ deleteSimilarity(feature_uniquename, gff_feature);
+
+ if(event.getFeature().getSegments().size() > 0)
+ {
+ RangeVector ranges = gff_feature.getLocation().getRanges();
+ for(Range range: ranges)
+ {
+ feature_uniquename = gff_feature.getSegmentID(range);
+ deleteFeature(feature_uniquename, gff_feature.getKey().getKeyString(), gff_feature);
+ }
+ }
+ else
+ deleteFeature(feature_uniquename, gff_feature.getKey().getKeyString(), gff_feature);
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+ /**
+ * Update this features residue column where necessary.
+ * @param tsn
+ */
+ private void updateResidueColumn(final ChadoTransaction tsn)
+ {
+ String keyStr = tsn.getGff_feature().getKey().getKeyString();
+ if(GeneUtils.isFeatureToUpdateResidues(keyStr))
+ {
+ FeatureForUpdatingResidues featureForUpdatingResidues =
+ GeneUtils.getFeatureForUpdatingResidues(tsn.getGff_feature());
+ if(featureForUpdatingResidues != null)
+ {
+ ChadoTransaction tsnResidue =
+ new ChadoTransaction(ChadoTransaction.UPDATE,
+ featureForUpdatingResidues,
+ null, null, null, "RESIDUE SEQUENCE UPDATE ");
+ sql.add(tsnResidue);
+ }
+ }
+ }
+
+ /**
+ * Process segment additions and deletions
+ * @param feature
+ * @param rv_new
+ * @param rv_old
+ */
+ private void segmentNumberChanged(final GFFStreamFeature feature,
+ final RangeVector rv_new,
+ final RangeVector rv_old)
+ {
+ logger4j.debug("SEGMENT_CHANGED "+rv_new.size()+" "+rv_old.size());
+
+ // check for deleted segments
+ final Vector<Integer> deleted = new Vector<Integer>();
+ for(int ideleted = 0; ideleted < rv_old.size(); ideleted++)
+ {
+ final Range range = rv_old.get(ideleted);
+ if(!rv_new.containsRange(range))
+ deleted.add(new Integer(ideleted));
+ }
+
+ for(Integer d: deleted)
+ {
+ Range range_old = rv_old.elementAt(d.intValue());
+ String seg_id = feature.getSegmentID(range_old);
+ deleteFeature(seg_id, feature.getKey().getKeyString(), feature);
+ feature.getSegmentRangeStore().remove(seg_id);
+ logger4j.debug("SEGMENT_CHANGED DELETED: " + seg_id);
+ }
+
+ if(deleted.size() > 0)
+ {
+ String new_id = feature.getSegmentID(rv_new);
+ Qualifier qualifier = new Qualifier("ID", new_id);
+ try
+ {
+ feature.setQualifier(qualifier);
+ }
+ catch(ReadOnlyException e){}
+ catch(EntryInformationException e){}
+ }
+
+ if(addSegments)
+ {
+ FeatureSegmentVector segments = ((uk.ac.sanger.artemis.Feature) feature
+ .getUserData()).getSegments();
+
+ for(int iadd = 0; iadd < segments.size(); iadd++)
+ {
+ final FeatureSegment segment = segments.elementAt(iadd);
+ final Range range = segment.getRawRange();
+ if(rv_old.containsRange(range))
+ continue;
+
+ String segment_uniquename = feature.getSegmentID(range);
+ logger4j.debug("SEGMENT_CHANGED ADDED: " + segment_uniquename);
+ insertFeatureSegment(segment, segment_uniquename);
+ }
+ }
+
+ processFeatureRelationshipRank(feature, rv_new, ChadoTransaction.UPDATE);
+ }
+
+ /**
+ * Update spliced features rank
+ * @param feature
+ * @param rv_new
+ */
+ private void processFeatureRelationshipRank(final GFFStreamFeature feature,
+ final RangeVector rv_new,
+ final int type)
+ {
+ // update feature_relationship.rank
+ ChadoTransaction tsn;
+ Hashtable<String, Integer> feature_relationship_rank_store = new Hashtable<String, Integer>();
+ Qualifier qualifier_relation = feature.getQualifierByName("Parent");
+
+ for(int rank=0; rank<rv_new.size(); rank++)
+ {
+ Range range = rv_new.elementAt(rank);
+ String seq_id = feature.getSegmentID(range);
+
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+ chado_feature.setUniqueName(seq_id);
+
+ List<FeatureRelationship> featureRelationshipsForSubjectId = null;
+ if(qualifier_relation != null)
+ {
+ StringVector parents = qualifier_relation.getValues();
+ if(parents.size() > 0)
+ featureRelationshipsForSubjectId = new Vector<FeatureRelationship>();
+
+ for(int i=0; i<parents.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature parent =
+ new org.gmod.schema.sequence.Feature();
+ parent.setUniqueName(parents.get(i));
+ FeatureRelationship feature_relationship =
+ new FeatureRelationship();
+
+ //
+ // should be retrieved from relationship ontology !!
+ CvTerm cvterm = DatabaseDocument.getCvTermByCvAndCvTerm("part_of", "relationship");
+
+ //CvTerm cvterm = new CvTerm();
+ //cvterm.setCvTermId(DatabaseDocument.getCvtermID("part_of").intValue());
+
+ feature_relationship.setFeatureByObjectId(parent);
+ feature_relationship.setFeatureBySubjectId(chado_feature);
+ feature_relationship.setCvTerm(cvterm);
+ feature_relationship.setRank(rank);
+ featureRelationshipsForSubjectId.add(feature_relationship);
+
+ tsn = new ChadoTransaction(type,
+ feature_relationship,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "FEATURE_RELATIONSHIP: ID="+seq_id+
+ " part_of "+parent.getUniqueName()+" RANK="+rank);
+ sql.add(tsn);
+ }
+ }
+
+ feature_relationship_rank_store.put(seq_id, new Integer(rank));
+ }
+ feature.setFeature_relationship_rank_store(feature_relationship_rank_store);
+ }
+
+ /**
+ * Create transaction for inserting a feature.
+ * @param feature
+ */
+ private void insertFeature(final Feature feature)
+ {
+ String feature_uniquename = null;
+ Qualifier qualifier_name = null;
+ try
+ {
+ final Qualifier qualifier_uniquename = feature.getQualifierByName("ID");
+ qualifier_name = feature.getQualifierByName("Name");
+ if(qualifier_uniquename != null)
+ {
+ feature_uniquename = qualifier_uniquename.getValues().elementAt(0);
+ logger4j.debug("FEATURE_ADDED "+feature_uniquename);
+ }
+
+ while(feature_uniquename == null ||
+ feature_uniquename.equals("") ||
+ feature_uniquename.equals("to_be_set"))
+ {
+ feature_uniquename = JOptionPane.showInputDialog(null,
+ "Provide a systematic_id : ",
+ "systematic_id missing in "+
+ feature.getIDString(),
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(feature_uniquename == null)
+ return;
+ }
+ feature.setQualifier(new Qualifier("ID", feature_uniquename.trim()));
+ }
+ catch(EntryInformationException eie)
+ {
+ eie.printStackTrace();
+ }
+ catch(ReadOnlyException roe)
+ {
+ roe.printStackTrace();
+ }
+
+
+ FeatureLoc featureloc = getFeatureLoc(
+ (GFFStreamFeature)feature.getEmblFeature(),
+ feature_uniquename,
+ feature.getLocation().getTotalRange());
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+ chado_feature.setFeatureLoc(featureloc);
+
+ final StringBuffer log = new StringBuffer();
+ log.append("FEATURELOC="+
+ ((featureloc.getStrand().intValue() == -1) ? "complement(" : "(")+
+ (featureloc.getFmin().intValue()+1)+".."+featureloc.getFmax().intValue()+")");
+
+ try
+ {
+ // relationship attributes
+ Qualifier qualifier_relation = feature.getQualifierByName("Parent");
+ List<FeatureRelationship> featureRelationshipsForSubjectId = null;
+ if(qualifier_relation != null)
+ {
+ StringVector parents = qualifier_relation.getValues();
+ if(parents.size() > 0)
+ featureRelationshipsForSubjectId = new Vector<FeatureRelationship>();
+
+ for(int i=0; i<parents.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature parent =
+ new org.gmod.schema.sequence.Feature();
+ parent.setUniqueName(parents.get(i));
+ FeatureRelationship feature_relationship =
+ new FeatureRelationship();
+
+ //
+ // should be retrieved from relationship ontology !!
+ CvTerm cvterm = DatabaseDocument.getCvTermByCvAndCvTerm("part_of", "relationship");
+ feature_relationship.setFeatureByObjectId(parent);
+ feature_relationship.setFeatureBySubjectId(chado_feature);
+ feature_relationship.setCvTerm(cvterm);
+ featureRelationshipsForSubjectId.add(feature_relationship);
+
+ log.append(" PART_OF="+parent.getUniqueName());
+ }
+ }
+
+ qualifier_relation = feature.getQualifierByName("Derives_from");
+ if(qualifier_relation != null)
+ {
+ StringVector derives = qualifier_relation.getValues();
+ if(derives.size() > 0 && featureRelationshipsForSubjectId == null)
+ featureRelationshipsForSubjectId = new Vector<FeatureRelationship>();
+
+ for(int i=0; i<derives.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature parent =
+ new org.gmod.schema.sequence.Feature();
+ parent.setUniqueName(derives.get(i));
+ FeatureRelationship feature_relationship = new FeatureRelationship();
+
+ //
+ // should be retrieved from relationship ontology !!
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(DatabaseDocument.getCvtermID("derives_from")
+ .intValue());
+
+ feature_relationship.setFeatureByObjectId(parent);
+ feature_relationship.setFeatureBySubjectId(chado_feature);
+ feature_relationship.setCvTerm(cvterm);
+ featureRelationshipsForSubjectId.add(feature_relationship);
+ log.append(" DERIVES_FROM="+parent.getUniqueName());
+ }
+ }
+ chado_feature.setFeatureRelationshipsForSubjectId(
+ featureRelationshipsForSubjectId);
+ }
+ catch(InvalidRelationException ire){}
+
+ chado_feature.setUniqueName(feature_uniquename);
+ if(qualifier_name != null)
+ chado_feature.setName(qualifier_name.getValues().elementAt(0));
+
+ String key = feature.getKey().toString();
+ if(key.equals(DatabaseDocument.EXONMODEL))
+ key = "exon";
+ CvTerm cvTerm = DatabaseDocument.getCvTermByCvAndCvTerm(key, "sequence");
+
+ if(cvTerm == null)
+ {
+ final String msg =
+ key+" is not a valid/known database key (check the sequence ontology)!";
+
+ logger4j.error(msg);
+ JOptionPane.showMessageDialog(null,msg);
+ return;
+ }
+
+ chado_feature.setCvTerm(cvTerm);
+
+ // create transaction object
+
+ ChadoTransaction tsn = new ChadoTransaction(ChadoTransaction.INSERT,
+ chado_feature,
+ null, (GFFStreamFeature)feature.getEmblFeature(), null,
+ "FEATURE: ID="+feature_uniquename+
+ " "+log.toString()+
+ " KEY="+key);
+ sql.add(tsn);
+
+ addQualifiers(feature.getQualifiers(), chado_feature,
+ (GFFStreamFeature)feature.getEmblFeature(), feature_uniquename);
+ }
+
+ /**
+ * Create transaction for inserting a feature.
+ * @param feature
+ */
+ private void insertFeatureSegment(final FeatureSegment segment,
+ final String segment_uniquename)
+ {
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+ FeatureLoc featureloc = new FeatureLoc();
+ chado_feature.setFeatureLoc(featureloc);
+
+ if(segment.isForwardSegment())
+ featureloc.setStrand(new Short((short)1));
+ else
+ featureloc.setStrand(new Short((short)-1));
+
+ // codon_start attribute
+ Feature feature = segment.getFeature();
+ try
+ {
+ Qualifier qualifier_phase = feature.getQualifierByName("codon_start");
+ if(qualifier_phase != null)
+ {
+ String phase = qualifier_phase.getValues().elementAt(0);
+
+ if(phase.equals ("1"))
+ featureloc.setPhase(new Integer(0));
+ else if(phase.equals("2"))
+ featureloc.setPhase(new Integer(1));
+ else if(phase.equals("3"))
+ featureloc.setPhase(new Integer(2));
+ }
+ else
+ featureloc.setPhase(null);
+
+ // relationship attributes
+ Qualifier qualifier_relation = feature.getQualifierByName("Parent");
+ List<FeatureRelationship> featureRelationshipsForSubjectId = null;
+ if(qualifier_relation != null)
+ {
+ StringVector parents = qualifier_relation.getValues();
+ if(parents.size() > 0)
+ featureRelationshipsForSubjectId = new Vector<FeatureRelationship>();
+
+ for(int i=0; i<parents.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature parent =
+ new org.gmod.schema.sequence.Feature();
+ parent.setUniqueName(parents.get(i));
+ FeatureRelationship feature_relationship =
+ new FeatureRelationship();
+
+ //
+ // should be retrieved from relationship ontology !!
+ CvTerm cvterm = DatabaseDocument.getCvTermByCvAndCvTerm("part_of", "relationship");
+
+ //CvTerm cvterm = new CvTerm();
+ //cvterm.setCvTermId(DatabaseDocument.getCvtermID("part_of").intValue());
+
+ feature_relationship.setFeatureByObjectId(parent);
+ feature_relationship.setFeatureBySubjectId(chado_feature);
+ feature_relationship.setCvTerm(cvterm);
+ featureRelationshipsForSubjectId.add(feature_relationship);
+ }
+ }
+
+ qualifier_relation = feature.getQualifierByName("Derives_from");
+ if(qualifier_relation != null)
+ {
+ StringVector derives = qualifier_relation.getValues();
+ if(derives.size() > 0 && featureRelationshipsForSubjectId == null)
+ featureRelationshipsForSubjectId = new Vector<FeatureRelationship>();
+
+ for(int i=0; i<derives.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature parent =
+ new org.gmod.schema.sequence.Feature();
+ parent.setUniqueName(derives.get(i));
+ FeatureRelationship feature_relationship = new FeatureRelationship();
+
+ //
+ // should be retrieved from relationship ontology !!
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(DatabaseDocument.getCvtermID("derives_from")
+ .intValue());
+
+ feature_relationship.setFeatureByObjectId(parent);
+ feature_relationship.setFeatureBySubjectId(chado_feature);
+ feature_relationship.setCvTerm(cvterm);
+ featureRelationshipsForSubjectId.add(feature_relationship);
+ }
+ }
+ chado_feature.setFeatureRelationshipsForSubjectId(
+ featureRelationshipsForSubjectId);
+ }
+ catch(InvalidRelationException ire){}
+
+ featureloc.setFmin(new Integer(segment.getRawRange().getStart()-1));
+ featureloc.setFmax(new Integer(segment.getRawRange().getEnd()));
+ chado_feature.setUniqueName(segment_uniquename);
+ //chado_feature.setName(segment_uniquename);
+
+ String key = feature.getKey().toString();
+ if(key.equals(DatabaseDocument.EXONMODEL))
+ key = "exon";
+ CvTerm cvterm = getCvTerm(key, "sequence");
+ chado_feature.setCvTerm(cvterm);
+
+ //addQualifiers(feature.getQualifiers(), chado_feature);
+ // create transaction object
+
+ ChadoTransaction tsn = new ChadoTransaction(ChadoTransaction.INSERT,
+ chado_feature,
+ null, (GFFStreamFeature)segment.getFeature().getEmblFeature(), null,
+ "SEGMENT: ID="+segment_uniquename+" KEY="+key);
+
+ sql.add(tsn);
+
+ List<ChadoTransaction> tsns = DatabaseDocument.getUpdateResiduesColumnTransactions(tsn);
+ if(tsns != null)
+ sql.addAll(tsns);
+ }
+
+ /**
+ * Delete protein domains, non_cytoplasm_location, cytoplasm_location,
+ * membrane_structure, transmembrane, signal_peptide
+ * @param gff_feature
+ * @param feature
+ */
+ private void deletePolypetideDomains(final GFFStreamFeature gff_feature,
+ final Feature feature)
+ {
+ if(!gff_feature.getKey().getKeyString().equals("polypeptide"))
+ return;
+
+ QualifierVector qualifiers =
+ ProteinMapPanel.getProteinMapQualifiers(feature);
+
+ if(qualifiers == null)
+ return;
+ for(int i=0; i<qualifiers.size(); i++)
+ {
+ if(!(qualifiers.get(i) instanceof QualifierLazyLoading))
+ continue;
+
+ QualifierLazyLoading qualifier = (QualifierLazyLoading) qualifiers.get(i);
+ List<LazyQualifierValue> values = qualifier.getLazyValues();
+ for(int j=0; j<values.size(); j++)
+ {
+ if(values.get(j) instanceof FeatureLocLazyQualifierValue)
+ {
+ FeatureLocLazyQualifierValue val =
+ (FeatureLocLazyQualifierValue)values.get(j);
+ org.gmod.schema.sequence.Feature chadoFeature = val.getMatchFeature();
+
+ logger4j.debug("FEATURE_DELETED "+val.getMatchFeature().getUniqueName()+
+ " "+qualifier.getName());
+
+ ChadoTransaction tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ chadoFeature,
+ null, null, "polypeptide_domain",
+ "FEATURE: ID="+chadoFeature.getUniqueName()+" "+qualifier.getName());
+ sql.add(tsn);
+ }
+ }
+ }
+ }
+
+ /**
+ * Delete similarity qualifiers
+ * @param uniquename
+ * @param feature
+ */
+ private void deleteSimilarity(final String uniquename,
+ final GFFStreamFeature feature)
+ {
+ if(feature.getQualifierByName("similarity") != null)
+ {
+ try
+ {
+ StringVector old_qualifier_strings =
+ StreamQualifier.toStringVector(null, feature.getQualifierByName("similarity"));
+
+ ChadoTransaction tsn = null;
+ for(int i = 0; i < old_qualifier_strings.size(); ++i)
+ {
+ String qualifierString = old_qualifier_strings.elementAt(i);
+ int index = qualifierString.indexOf("=");
+ qualifierString = qualifierString.substring(index+1);
+ AnalysisFeature analysisFeature =
+ ArtemisUtils.getAnalysisFeature(uniquename, qualifierString, feature);
+
+ tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ analysisFeature,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "SIMILARITY: ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ logger4j.debug(i+" DELETE FEATURE SIMILARITY");
+ }
+ }
+ catch(Exception e)
+ {
+ logger4j.warn("DELETE FEATURE SIMILARITY\n"+e.getMessage());
+ }
+ }
+ }
+
+ /**
+ *
+ * Set the transaction for deleting a feature.
+ */
+ private void deleteFeature(final String uniquename, String featureKey,
+ final GFFStreamFeature feature)
+ {
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+ chado_feature.setUniqueName(uniquename);
+ //CvTerm cvTerm = getCvTerm(featureType, "sequence");
+
+ if(featureKey.equals(DatabaseDocument.EXONMODEL))
+ featureKey = "exon";
+ CvTerm cvTerm = DatabaseDocument.getCvTermByCvAndCvTerm(featureKey, "sequence");
+ if(cvTerm == null)
+ {
+ final String msg =
+ featureKey+" is not a valid/known database key (check the sequence ontology)!";
+
+ logger4j.error(msg);
+ System.out.println(msg);
+ return;
+ }
+
+ if(feature != null)
+ {
+ // delete from internal chado gene model
+ final ChadoCanonicalGene chado_gene = feature.getChadoGene();
+ if(chado_gene != null)
+ {
+ String msg = "DELETE FROM CHADO MODEL ";
+ if(feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL) ||
+ feature.getKey().getKeyString().equals("pseudogenic_exon"))
+ {
+ if(feature.getSegmentRangeStore() != null)
+ {
+ feature.getSegmentRangeStore().remove(uniquename);
+
+ if(feature.getSegmentRangeStore().size() == 0)
+ chado_gene.deleteFeature(feature);
+ msg = msg + " ** " + DatabaseDocument.EXONMODEL + " ";
+ }
+ }
+ else
+ chado_gene.deleteFeature(feature);
+ logger4j.debug(msg+uniquename);
+ //Object deleteFeature = chado_gene.getFeatureFromId(uniquename);
+ //if(deleteFeature != null)
+ // chado_gene.deleteFeature((uk.ac.sanger.artemis.io.Feature)deleteFeature);
+ }
+ }
+
+ chado_feature.setCvTerm(cvTerm);
+ logger4j.debug("FEATURE_DELETED "+uniquename+" cv name="+
+ cvTerm.getCv().getName()+" term="+cvTerm.getName());
+
+ ChadoTransaction tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ chado_feature,
+ null, feature, featureKey,
+ "FEATURE: ID="+uniquename+" "+featureKey);
+
+ sql.add(tsn);
+ List<ChadoTransaction> tsns = DatabaseDocument.getUpdateResiduesColumnTransactions(tsn);
+ if(tsns != null)
+ sql.addAll(tsns);
+ }
+
+ /**
+ * Add qualifiers that are in a <code>QualifierVector</code> to a
+ * <code>ChadoFeature</code>.
+ * @param qualifiers the <code>QualifierVector</code>
+ * @param chado_feature the <code>ChadoFeature</code>
+ */
+ private void addQualifiers(final QualifierVector qualifiers,
+ final org.gmod.schema.sequence.Feature chado_feature,
+ final GFFStreamFeature feature,
+ final String uniqueName)
+ {
+ // add qualifiers/attributes
+ for(int qualifier_index = 0; qualifier_index < qualifiers.size();
+ ++qualifier_index)
+ {
+ final Qualifier this_qualifier = qualifiers.elementAt(qualifier_index);
+ if(this_qualifier instanceof QualifierLazyLoading)
+ ((QualifierLazyLoading)this_qualifier).setForceLoad(true);
+ final String name = this_qualifier.getName();
+ final StringVector qualifier_values = this_qualifier.getValues();
+
+ if(qualifier_values == null)
+ continue;
+
+ try
+ {
+ for(int value_index = 0; value_index < qualifier_values.size();
+ ++value_index)
+ {
+ final String qualifierStr = qualifier_values.elementAt(value_index);
+ // ignore reserved tags
+ if(isReservedTag(name) ||
+ isSynonymTag(name, feature) ||
+ isCvTag(name))
+ {
+ if(!name.equals("Parent") && !name.equals("Derives_from"))
+ addReservedTag(name+"="+qualifierStr, name,
+ uniqueName, feature, null, null, true);
+ continue;
+ }
+
+ // happens when duplicating features
+ final CvTerm cvTerm = DatabaseDocument.getCvTermByCvTermName(name);
+ final FeatureProp featureprop = new FeatureProp();
+ featureprop.setValue(qualifier_values.elementAt(value_index));
+ featureprop.setRank(value_index);
+ featureprop.setCvTerm(cvTerm);
+ chado_feature.addFeatureProp(featureprop);
+
+ logger4j.debug("ADD FEATUREPROP "+name+"="+qualifier_values.elementAt(value_index));
+ }
+ }
+ catch(NullPointerException npe)
+ {
+ npe.printStackTrace();
+
+ /*JOptionPane.showMessageDialog(null,
+ msg,"Invalid Qualifier",
+ JOptionPane.WARNING_MESSAGE);*/
+ }
+ }
+ }
+
+ public static boolean isSpecialTag(final String tag)
+ {
+ if(isReservedTag(tag) || isSynonymTag(tag, null) || isCvTag(tag))
+ return true;
+ return false;
+ }
+
+ /**
+ * Determine if this is a GFF3 predefined tag.
+ * @param tag
+ * @return true if the tag is a GFF3 predefined tag
+ */
+ private static boolean isReservedTag(final String tag)
+ {
+ for(int i=0; i<reserved_tags.length; i++)
+ if(tag.equals(reserved_tags[i]))
+ return true;
+ return false;
+ }
+
+ /**
+ * Determine if this is a controlled vocabulary tag, e.g GO.
+ * @param tag
+ * @return true if the tag is a CV tag
+ */
+ public static boolean isCvTag(final String tag)
+ {
+ for(int i=0; i<CV_NAME.length; i++)
+ if(tag.equals(CV_NAME[i]))
+ return true;
+ return false;
+ }
+
+ /**
+ * Determine if this is a GFF3 predefined tag.
+ * @param tag
+ * @return true if the tag is a GFF3 predefined tag
+ */
+ public static boolean isSynonymTag(final String tag,
+ final GFFStreamFeature feature)
+ {
+ if(synonym_tags == null)
+ {
+ synonym_tags = DatabaseDocument.getSynonymTypeNames(
+ SYNONYM_TAG_CVNAME, feature);
+ if(synonym_tags == null || synonym_tags.length < 1)
+ {
+ logger4j.debug("Using default synonym names");
+ synonym_tags = new String[6];
+ synonym_tags[0] = "synonym";
+ synonym_tags[1] = "gene";
+ synonym_tags[2] = "systematic_id";
+ synonym_tags[3] = "primary_name";
+ synonym_tags[4] = "reserved_name";
+ synonym_tags[5] = "obsolete_name";
+ }
+ }
+
+ for(int i=0; i<synonym_tags.length; i++)
+ if(tag.equals(synonym_tags[i]))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Compare the old and new keys and qualifiers and find the qualifiers
+ * that have changed or been added and UPDATE, INSERT or DELETE accordingly.
+ * @param qualifiers_old old qualifiers
+ * @param qualifiers_new new qualifiers
+ * @param feature GFF feature that has been changed
+ */
+ private void editKeyAndQualifiers(final QualifierVector qualifiers_old,
+ final QualifierVector qualifiers_new,
+ final Key old_key,
+ final Key new_key,
+ final GFFStreamFeature feature,
+ final int event_type)
+ {
+ String uniquename = feature.getQualifierByName("ID").getValues().elementAt(0);
+ ChadoTransaction tsn;
+
+ // updating the key unless just a qualifier changed
+ if(event_type != FeatureChangeEvent.QUALIFIER_CHANGED &&
+ !new_key.equals(old_key))
+ {
+ String key = new_key.getKeyString();
+ if(key.equals(DatabaseDocument.EXONMODEL))
+ key = "exon";
+ CvTerm cvTerm = getCvTerm(key, "sequence");
+ if(cvTerm == null) // chado doesn't recognise this
+ {
+ JOptionPane.showMessageDialog(null,
+ new_key.getKeyString()+" is not a valid key!\n"+
+ "There is no CV term set for this key.",
+ "Invalid Feature Key",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ else
+ {
+ RangeVector rv = feature.getLocation().getRanges();
+
+ if(rv.size() > 0)
+ {
+ for(int i=0; i<rv.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+
+ chado_feature.setCvTerm(cvTerm);
+ chado_feature.setUniqueName( feature.getSegmentID(rv.elementAt(i)) );
+
+ logger4j.debug("KEY CHANGE "+chado_feature.getUniqueName()+" "+old_key+" -> "+new_key);
+ tsn = new ChadoTransaction(ChadoTransaction.UPDATE,
+ chado_feature,
+ feature.getLastModified(), feature, null,
+ "KEY: ID="+chado_feature.getUniqueName()+" "+old_key+" -> "+new_key);
+
+ sql.add(tsn);
+ }
+ }
+ else
+ {
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+
+ chado_feature.setCvTerm(cvTerm);
+ chado_feature.setUniqueName(uniquename);
+
+ logger4j.debug("KEY CHANGE "+chado_feature.getUniqueName()+" "+old_key+" -> "+new_key);
+ tsn = new ChadoTransaction(ChadoTransaction.UPDATE,
+ chado_feature,
+ feature.getLastModified(), feature, null,
+ "KEY: ID="+chado_feature.getUniqueName()+" "+old_key+" -> "+new_key);
+
+ sql.add(tsn);
+ }
+ }
+ }
+
+ // look for qualifiers to DELETE
+ for(int qualifier_index = 0; qualifier_index < qualifiers_old.size();
+ ++qualifier_index)
+ {
+ final Qualifier this_qualifier = qualifiers_old.elementAt(qualifier_index);
+ String name = this_qualifier.getName();
+
+ if(!qualifiers_new.contains(name))
+ {
+ if(isReservedTag(name) || isSynonymTag(name, feature) || isCvTag(name))
+ {
+ handleReservedTags(feature, uniquename,
+ null,
+ this_qualifier);
+ continue;
+ }
+
+ // get the cvterm_id for this featureprop/qualifier
+ CvTerm cvterm = DatabaseDocument.getCvTermByCvAndCvTerm(name, "feature_property");
+ Integer lcvterm_id;
+ if(cvterm != null)
+ lcvterm_id = cvterm.getCvTermId();
+ else
+ lcvterm_id = DatabaseDocument.getCvtermID(name);
+
+ if(lcvterm_id == null)
+ lcvterm_id = DatabaseDocument.getCvtermID(name);
+
+ if(lcvterm_id == null) // chado doesn't recognise this
+ {
+ JOptionPane.showMessageDialog(null,
+ name+" is not a valid qualifier!",
+ "Invalid Qualifier",
+ JOptionPane.WARNING_MESSAGE);
+ continue;
+ }
+
+ processFeatureProp(feature, name, null, -1, ChadoTransaction.DELETE,
+ uniquename, lcvterm_id);
+ }
+ }
+
+ // look for qualifiers to INSERT/UPDATE
+ for(int qualifier_index = 0; qualifier_index < qualifiers_new.size();
+ ++qualifier_index)
+ {
+ final Qualifier this_qualifier = qualifiers_new.elementAt(qualifier_index);
+ String name = this_qualifier.getName();
+ int old_index = qualifiers_old.indexOfQualifierWithName(name);
+
+ Qualifier this_old_qualifier = null;
+ StringVector old_qualifier_strings = null;
+ final StringVector new_qualifier_strings =
+ StreamQualifier.toStringVector(null, this_qualifier);
+
+ if(old_index> -1) // update qualifier
+ {
+ this_old_qualifier = qualifiers_old.elementAt(old_index);
+
+ old_qualifier_strings =
+ StreamQualifier.toStringVector(null, this_old_qualifier);
+
+ // check if anything has changed for this qualifier name
+ boolean need_to_update = false;
+ for(int value_index = 0; value_index < new_qualifier_strings.size();
+ ++value_index)
+ {
+ String qualifier_string = new_qualifier_strings.elementAt(value_index);
+ if(!old_qualifier_strings.contains(qualifier_string))
+ need_to_update = true;
+ }
+ if(!need_to_update &&
+ new_qualifier_strings.size() == old_qualifier_strings.size())
+ continue;
+ }
+
+ if(isReservedTag(name) ||
+ isSynonymTag(name, feature) ||
+ isCvTag(name))
+ {
+ handleReservedTags(feature, uniquename,
+ this_qualifier,
+ qualifiers_old.getQualifierByName(name));
+ continue;
+ }
+
+ // get the cvterm_id for this featureprop/qualifier
+ Integer lcvterm_id = null;
+ if(!name.equals("timelastmodified"))
+ {
+ CvTerm cvterm = DatabaseDocument.getCvTermByCvAndCvTerm(name, "feature_property");
+ if(cvterm != null)
+ lcvterm_id = cvterm.getCvTermId();
+ else
+ lcvterm_id = DatabaseDocument.getCvtermID(name);
+
+ if(lcvterm_id == null) // chado doesn't recognise this
+ {
+ JOptionPane.showMessageDialog(null,
+ name+" is not a valid qualifier!\n"+
+ "There is no CV term set for this qualifier.",
+ "Invalid Qualifier",
+ JOptionPane.WARNING_MESSAGE);
+ continue;
+ }
+ }
+
+ if(old_index > -1 &&
+ new_qualifier_strings.size() == old_qualifier_strings.size())
+ {
+ //
+ // UPDATE existing featureprop's
+ //
+ for(int rank = 0; rank < new_qualifier_strings.size();
+ ++rank)
+ {
+ String qualifier_string = new_qualifier_strings.elementAt(rank);
+ int index = qualifier_string.indexOf("=");
+
+ if(index > -1)
+ qualifier_string = qualifier_string.substring(index+1);
+
+ processFeatureProp(feature, name, qualifier_string, rank, ChadoTransaction.UPDATE,
+ uniquename, lcvterm_id);
+ }
+
+ }
+ else
+ {
+ //
+ // DELETE any existing featureprops
+ //
+ if(old_index > -1)
+ {
+ processFeatureProp(feature, name, null, -1, ChadoTransaction.DELETE,
+ uniquename, lcvterm_id);
+ }
+
+ //
+ // INSERT new featureprops
+ //
+ for(int rank = 0; rank < new_qualifier_strings.size();
+ ++rank)
+ {
+ String qualifier_string = new_qualifier_strings.elementAt(rank);
+
+ int index = qualifier_string.indexOf("=");
+ if(index > -1)
+ qualifier_string = qualifier_string.substring(index+1);
+ else
+ qualifier_string = null;
+
+ processFeatureProp(feature, name, qualifier_string, rank, ChadoTransaction.INSERT,
+ uniquename, lcvterm_id);
+ }
+
+ }
+ }
+
+ }
+
+ /**
+ *
+ * @param feature
+ * @param type ChadoTransaction type DELETE/UPDATE/INSERT
+ * @param uniquename
+ * @param lcvterm_id
+ */
+ private void processFeatureProp(final GFFStreamFeature feature,
+ final String qualifier_name,
+ final String qualifier_string, final int rank,
+ final int type, String uniquename, Integer lcvterm_id)
+ {
+ ChadoTransaction tsn;
+ if(feature.getFeature_relationship_rank_store() != null)
+ {
+ Hashtable<String, Integer> rank_hash = feature.getFeature_relationship_rank_store();
+ Enumeration<String> id_keys= rank_hash.keys();
+ while(id_keys.hasMoreElements())
+ {
+ uniquename = id_keys.nextElement();
+ final FeatureProp featureprop = getFeatureProp(uniquename, qualifier_string,
+ lcvterm_id, rank);
+
+ logger4j.debug("FEATUREPROP "+type+" "+qualifier_name+"="+qualifier_string);
+ tsn = new ChadoTransaction(type,
+ featureprop,
+ feature.getLastModified(), feature, feature.getKey().getKeyString(),
+ "FEATUREPROP: ID="+uniquename+" "+qualifier_name+"="+qualifier_string);
+
+ tsn.setUniquename(uniquename);
+ sql.add(tsn);
+ }
+ }
+ else
+ {
+ FeatureProp featureprop = getFeatureProp(uniquename,
+ qualifier_string, lcvterm_id, rank);
+
+ logger4j.debug("FEATUREPROP "+type+" "+qualifier_name+"="+qualifier_string);
+ tsn = new ChadoTransaction(type,
+ featureprop,
+ feature.getLastModified(), feature, feature.getKey().getKeyString(),
+ "FEATUREPROP: ID="+uniquename+" "+qualifier_name+"="+qualifier_string);
+ tsn.setUniquename(uniquename);
+ sql.add(tsn);
+ }
+
+ // synchronise the polypeptide and exon colours
+ if(qualifier_name.equals("colour") &&
+ feature.getKey().getKeyString().equals("polypeptide"))
+ {
+ synchColourInGeneModel(feature, uniquename,
+ new Qualifier(qualifier_name, qualifier_string));
+ }
+ }
+
+ /**
+ * Synchronise the colour on the exons
+ * @param feature
+ * @param uniquename
+ */
+ private void synchColourInGeneModel(final GFFStreamFeature feature,
+ final String uniquename,
+ final Qualifier colourQualifier)
+ {
+ try
+ {
+ ChadoCanonicalGene gene = feature.getChadoGene();
+ uk.ac.sanger.artemis.io.Feature transcript =
+ gene.getTranscriptFeatureFromName(uniquename);
+ List<uk.ac.sanger.artemis.io.Feature> exons = gene.getSpliceSitesOfTranscript(
+ GeneUtils.getUniqueName(transcript), DatabaseDocument.EXONMODEL);
+ Feature exon = (Feature)exons.get(0).getUserData();
+ exon.setQualifier(colourQualifier);
+ exon.resetColour();
+ }
+ catch (Exception e){}
+ }
+
+ /**
+ * Handle database transactions involving the GFF3 reserved tags.
+ * @param feature the <code>GFFStreamFeature</code>
+ * @param type the transaction type (INSERT/UPDATE/DELETE)
+ * @param new_qualifier the new qualifier
+ * @param old_qualifier the old qualifier
+ */
+ private void handleReservedTags(final GFFStreamFeature feature,
+ String uniquename,
+ final Qualifier new_qualifier,
+ final Qualifier old_qualifier)
+ {
+ StringVector new_qualifier_strings = null;
+
+ if(new_qualifier != null)
+ new_qualifier_strings = StreamQualifier.toStringVector(null, new_qualifier);
+
+ final StringVector old_qualifier_strings;
+
+ if(old_qualifier != null)
+ old_qualifier_strings = StreamQualifier.toStringVector(null, old_qualifier);
+ else
+ old_qualifier_strings = new StringVector();
+
+ final String qualifierName;
+
+ if(old_qualifier != null)
+ qualifierName = old_qualifier.getName();
+ else
+ qualifierName = new_qualifier.getName();
+
+ if(qualifierName.equals("ID") ||
+ qualifierName.equals("isObsolete") ||
+ qualifierName.equals("Name"))
+ {
+ updateFeature(feature, qualifierName, new_qualifier, old_qualifier);
+ return;
+ }
+
+ ChadoTransaction tsn = null;
+ // find tags that have been deleted
+ for(int i = 0; i < old_qualifier_strings.size(); ++i)
+ {
+ String qualifierString = old_qualifier_strings.elementAt(i);
+
+ if( new_qualifier_strings == null ||
+ !new_qualifier_strings.contains(qualifierString) ||
+ isRemovingDuplicateQualifiers(i, new_qualifier_strings, old_qualifier_strings, qualifierString))
+ {
+ int index = qualifierString.indexOf("=");
+ qualifierString = qualifierString.substring(index+1);
+
+ // strip out double quotes
+ if(qualifierString.startsWith("\"\""))
+ qualifierString = qualifierString.replaceAll("\"\"", "\"");
+
+ if(qualifierName.equals("Dbxref"))
+ {
+ logger4j.debug(uniquename+" in handleReservedTags() DELETE db="+
+ qualifierString);
+
+ FeatureDbXRef old_dbxref = getFeatureDbXRef(qualifierString,
+ uniquename);
+
+ if(old_dbxref == null)
+ continue;
+ tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ old_dbxref,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "DBXREF: ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ else if(qualifierName.equals("codon_start") ||
+ qualifierName.equals("Start_range") ||
+ qualifierName.equals("End_range"))
+ {
+ updateFeatureLoc(feature, uniquename);
+ }
+ else if(qualifierName.equals("literature"))
+ {
+ logger4j.debug(uniquename+" in handleReservedTags() DELETE literature");
+ FeaturePub featurePub = getFeaturePub(qualifierString, uniquename);
+
+ tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ featurePub,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "LITERATURE: ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ else if(isCvTag(qualifierName))
+ {
+ logger4j.debug(uniquename+" in handleReservedTags() DELETE "+
+ qualifierName+" "+qualifierString);
+
+ FeatureCvTerm feature_cvterm = getFeatureCvTerm(qualifierName, qualifierString,
+ uniquename, feature);
+ tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ feature_cvterm,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ qualifierName.toUpperCase()+ ": ID="+
+ uniquename+" "+qualifierString);
+ sql.add(tsn);
+
+ if(LocalAndRemoteFileManager.isAutomaticHistory())
+ addHistory(tsn, qualifierName.toUpperCase(), feature_cvterm.getCvTerm().getName());
+ }
+ else if(isSynonymTag(qualifierName, feature))
+ {
+ logger4j.debug(uniquename+" in handleReservedTags() DELETE "+qualifierName+" "+
+ qualifierString);
+
+ FeatureSynonym feature_synonym = getFeatureSynonym(qualifierName,
+ qualifierString, uniquename);
+
+ tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ feature_synonym,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ qualifierName.toUpperCase()+ ": ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ else if(qualifierName.equals("similarity"))
+ {
+ if(new_qualifier_strings != null &&
+ SimilarityTable.containsStringInStringVector(
+ old_qualifier_strings.elementAt(i), new_qualifier_strings))
+ continue;
+
+ logger4j.debug(uniquename+" in handleReservedTags() DELETE "+qualifierName+" "+
+ qualifierString);
+
+ AnalysisFeature analysisFeature =
+ ArtemisUtils.getAnalysisFeature(uniquename, qualifierString, feature);
+
+ tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ analysisFeature,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "SIMILARITY: ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ else if(MatchPanel.isClusterTag(qualifierName))
+ {
+ if(qualifierString.equals("") ||
+ (new_qualifier_strings != null &&
+ OrthoParalogTable.containsStringInStringVector(
+ qualifierString, new_qualifier_strings)))
+ continue;
+
+ logger4j.debug(uniquename+" in handleReservedTags() DELETE "+qualifierName+" "+
+ qualifierString);
+
+ org.gmod.schema.sequence.Feature matchFeature =
+ ArtemisUtils.getMatchFeatureWithFeatureRelations(uniquename, qualifierName,
+ qualifierString, feature, true);
+
+ if(matchFeature == null)
+ {
+ logger4j.debug("NO MATCH FEATURE FOUND. DELETE FEATURE_RELATIONSHIP");
+ deleteFeatureRelationShip(qualifierName, qualifierString, uniquename, feature);
+ }
+ else
+ {
+ tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ matchFeature,
+ null, (GFFStreamFeature)null, feature.getKey().getKeyString(),
+ qualifierName.toUpperCase()+ ": ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ }
+ else
+ logger4j.warn("Ignoring reserved tag missing : "+qualifierName);
+
+ }
+ }
+
+ if(new_qualifier_strings == null)
+ return;
+
+ // find tags that have been inserted
+ for(int i = 0; i < new_qualifier_strings.size(); ++i)
+ {
+ String qualifierString = new_qualifier_strings.elementAt(i);
+ if(!old_qualifier_strings.contains(qualifierString))
+ {
+ addReservedTag(qualifierString, qualifierName, uniquename,
+ feature, old_qualifier_strings, new_qualifier_strings, false);
+ }
+ }
+ }
+
+ /**
+ * Check to see if the qualifier appears in the old qualifiers more than
+ * the new qualifiers
+ * @param index
+ * @param newValues
+ * @param oldValues
+ * @param qualifier_string
+ * @return
+ */
+ private boolean isRemovingDuplicateQualifiers(final int index,
+ final StringVector newValues,
+ final StringVector oldValues,
+ final String qualifier_string)
+ {
+ if(index > oldValues.indexOf(qualifier_string))
+ return false;
+
+ int n_new = 0;
+ int n_old = 0;
+
+ for(int i=0; i<newValues.size(); i++)
+ if(newValues.get(i).equals(qualifier_string))
+ n_new++;
+
+ for(int i=0; i<oldValues.size(); i++)
+ if(oldValues.get(i).equals(qualifier_string))
+ n_old++;
+
+ if(n_new < n_old)
+ return true;
+ return false;
+ }
+
+ /**
+ * Add reserved tag
+ * @param qualifierStr
+ * @param qualifierName
+ * @param uniquename
+ * @param feature
+ * @param old_qualifier_strings
+ * @param new_qualifier_strings
+ */
+ private void addReservedTag(final String qualifierStr,
+ final String qualifierName,
+ final String uniquename,
+ final GFFStreamFeature feature,
+ final StringVector old_qualifier_strings,
+ final StringVector new_qualifier_strings,
+ final boolean isDuplicate)
+ {
+ ChadoTransaction tsn = null;
+ int index = qualifierStr.indexOf("=");
+ final String qualifierString = qualifierStr.substring(index+1);
+
+ if(qualifierName.equals("Dbxref"))
+ {
+ logger4j.debug(uniquename+" in handleReservedTags() INSERT db="+
+ qualifierString);
+ FeatureDbXRef new_dbxref = getFeatureDbXRef(qualifierString,
+ uniquename);
+
+ if(new_dbxref == null)
+ {
+ logger4j.warn("Cannot create FeatureDbXRef");
+ return;
+ }
+
+ tsn = new ChadoTransaction(ChadoTransaction.INSERT,
+ new_dbxref,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "DBXREF: ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ else if(qualifierName.equals("codon_start") ||
+ qualifierName.equals("Start_range") ||
+ qualifierName.equals("End_range"))
+ {
+ updateFeatureLoc(feature, uniquename);
+ }
+ else if(qualifierName.equals("literature"))
+ {
+ logger4j.debug(uniquename+" in handleReservedTags() INSERT literature");
+ FeaturePub featurePub = getFeaturePub(qualifierString, uniquename);
+
+ tsn = new ChadoTransaction(ChadoTransaction.INSERT,
+ featurePub,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "LITERATURE: ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ else if(qualifierName.equals("Parent"))
+ {
+ processFeatureRelationshipRank(feature, feature.getLocation().getRanges(),
+ ChadoTransaction.INSERT);
+ }
+ else if(isCvTag(qualifierName))
+ {
+ logger4j.debug(uniquename+" in handleReservedTags() INSERT "+
+ qualifierName+" "+qualifierString);
+ FeatureCvTerm feature_cvterm = getFeatureCvTerm(qualifierName,
+ qualifierString, uniquename, feature);
+ tsn = new ChadoTransaction(ChadoTransaction.INSERT,
+ feature_cvterm,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ qualifierName.toUpperCase()+ ": ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+
+ if(LocalAndRemoteFileManager.isAutomaticHistory())
+ addHistory(tsn, qualifierName.toUpperCase(), feature_cvterm.getCvTerm().getName());
+ }
+ else if(isSynonymTag(qualifierName, feature))
+ {
+ logger4j.debug(uniquename+" in handleReservedTags() INSERT "+
+ qualifierName+" "+qualifierString);
+
+ FeatureSynonym feature_synonym = getFeatureSynonym(qualifierName,
+ qualifierString, uniquename);
+
+ tsn = new ChadoTransaction(ChadoTransaction.INSERT,
+ feature_synonym,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ qualifierName.toUpperCase()+ ": ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ else if(qualifierName.equals("similarity"))
+ {
+ if(old_qualifier_strings != null &&
+ SimilarityTable.containsStringInStringVector(
+ qualifierStr, old_qualifier_strings))
+ return;
+
+ logger4j.debug(uniquename+" in handleReservedTags() INSERT "+
+ qualifierName+" "+qualifierString);
+
+ AnalysisFeature analysisFeature = ArtemisUtils.getAnalysisFeature(uniquename,
+ qualifierString, feature);
+
+ if(analysisFeature == null)
+ {
+ logger4j.warn("Cannot create AnalysisFeature!!");
+ return;
+ }
+
+ tsn = new ChadoTransaction(ChadoTransaction.INSERT,
+ analysisFeature,
+ null, feature,
+ feature.getKey().getKeyString(),
+ "SIMILARITY: ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ else if(MatchPanel.isClusterTag(qualifierName))
+ {
+ if(old_qualifier_strings != null &&
+ OrthoParalogTable.containsStringInStringVector(
+ qualifierString, old_qualifier_strings))
+ return;
+
+ if(qualifierString.equals(""))
+ return;
+
+ logger4j.debug(uniquename+" in handleReservedTags() INSERT "+qualifierName+" "+
+ qualifierString);
+
+ org.gmod.schema.sequence.Feature matchFeature =
+ ArtemisUtils.getMatchFeatureWithFeatureRelations(uniquename, qualifierName,
+ qualifierString, feature, false);
+
+ if(isDuplicate)
+ {
+ int ind;
+ if(uniquename.startsWith("DUP") && (ind=uniquename.indexOf("-"))>1)
+ matchFeature.setUniqueName( matchFeature.getUniqueName() +
+ ":"+uniquename.substring(0,ind) );
+ else
+ matchFeature.setUniqueName( matchFeature.getUniqueName() +
+ ":DUPLICATE" );
+ }
+
+ tsn = new ChadoTransaction(ChadoTransaction.INSERT,
+ matchFeature,
+ null, (GFFStreamFeature)null, feature.getKey().getKeyString(),
+ qualifierName.toUpperCase()+ ": ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+ else
+ logger4j.warn("Ignoring reserved tag "+qualifierName);
+ }
+
+ /**
+ * Update feature - uniquename, name and is_obsolete
+ * @param feature
+ * @param qualifierName
+ * @param new_qualifier
+ * @param old_qualifier
+ */
+ private void updateFeature(final GFFStreamFeature feature,
+ final String qualifierName,
+ final Qualifier new_qualifier,
+ final Qualifier old_qualifier)
+ {
+ final String uniqueName[];
+ String oldUniqueNames[] = null;
+ final String isObsolete;
+ String name = null;
+
+ final String log;
+ if(qualifierName.equals("ID"))
+ {
+ Hashtable<String, Range> segmentRangeStore = feature.getSegmentRangeStore();
+ if( segmentRangeStore != null &&
+ ( segmentRangeStore.size() > 1 ||
+ feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL)) )
+ {
+ Hashtable<String, String> mappingIds = feature.getNewIdMapToOldId();
+
+ if(mappingIds == null)
+ {
+ logger4j.debug("No mapping found.");
+ return;
+ }
+ Object newKeys[] = segmentRangeStore.keySet().toArray();
+ uniqueName = new String[newKeys.length];
+ oldUniqueNames = new String[newKeys.length];
+
+ for(int i=0; i<newKeys.length; i++)
+ {
+ uniqueName[i] = (String)newKeys[i];
+ oldUniqueNames[i] = mappingIds.get(uniqueName[i]);
+ }
+ }
+ else
+ {
+ uniqueName = new String[1];
+ oldUniqueNames = new String[1];
+ uniqueName[0] = new_qualifier.getValues().get(0);
+ oldUniqueNames[0] = old_qualifier.getValues().get(0);
+ }
+
+ if(feature.getQualifierByName("isObsolete") != null)
+ isObsolete = feature.getQualifierByName("isObsolete").getValues().get(0);
+ else
+ isObsolete = "false";
+ log = "UNIQUENAME: ID=";
+ }
+ else
+ {
+ if(feature.getFeature_relationship_rank_store() != null)
+ {
+ Hashtable<String, Integer> rank_hash = feature.getFeature_relationship_rank_store();
+ Enumeration<String> id_keys= rank_hash.keys();
+ uniqueName = new String[rank_hash.size()];
+ int next = 0;
+ while(id_keys.hasMoreElements())
+ {
+ uniqueName[next] = id_keys.nextElement();
+ next++;
+ }
+ }
+ else
+ {
+ uniqueName = new String[1];
+ uniqueName[0] = feature.getQualifierByName("ID").getValues().get(0);
+ }
+
+ if(qualifierName.equals("Name"))
+ {
+ if(feature.getQualifierByName("isObsolete") != null)
+ isObsolete = feature.getQualifierByName("isObsolete").getValues().get(0);
+ else
+ isObsolete = "false";
+ if(new_qualifier != null)
+ name = new_qualifier.getValues().get(0);
+ log = "PRIMARY NAME:";
+ }
+ else
+ {
+ isObsolete = new_qualifier.getValues().get(0);
+ log = "IS_OBSOLETE:";
+ }
+ }
+
+ for(int i = 0; i < uniqueName.length; i++)
+ {
+ org.gmod.schema.sequence.Feature chadoFeature =
+ new org.gmod.schema.sequence.Feature();
+ chadoFeature.setUniqueName(uniqueName[i]);
+
+ if(qualifierName.equals("Name"))
+ chadoFeature.setName(name);
+ else
+ chadoFeature.setName("0");
+
+ chadoFeature.setObsolete(Boolean.parseBoolean(isObsolete));
+
+ String logVal = uniqueName[i] + " " + log
+ + (new_qualifier !=null ? new_qualifier.getValues().get(0) : "NULL")
+ + " OLD="
+ + (old_qualifier != null ? old_qualifier.getValues().get(0) : "NULL");
+ logger4j.debug(logVal);
+ ChadoTransaction tsn = new ChadoTransaction(ChadoTransaction.UPDATE,
+ chadoFeature, feature.getLastModified(), feature,
+ feature.getKey().getKeyString(), "ID="+logVal);
+
+ if(qualifierName.equals("ID") && oldUniqueNames != null)
+ tsn.setOldUniquename(oldUniqueNames[i]);
+
+ sql.add(tsn);
+ }
+
+ // change uniquename of child features
+ if(qualifierName.equals("ID"))
+ {
+ GFFStreamFeature gffFeature = ((GFFStreamFeature)feature);
+ if(gffFeature.getChadoGene() == null)
+ return;
+ Set<uk.ac.sanger.artemis.io.Feature> children = gffFeature.getChadoGene().getChildren(gffFeature);
+ if(children != null && children.size()>0)
+ {
+ final String prefix;
+ int index;
+ if((index = uniqueName[0].lastIndexOf(".") )> -1)
+ {
+ boolean numbered = Pattern.matches("\\S+\\.\\d+$", uniqueName[0]);
+ //prefix = uniqueName[0].split("\\.")[0];
+ if(numbered)
+ prefix = uniqueName[0].substring(0, index);
+ else
+ prefix = uniqueName[0];
+ }
+ else
+ prefix = uniqueName[0].split(":")[0];
+ int val = JOptionPane.showConfirmDialog(null,
+ "Change name of children based on this ["+prefix+"]?",
+ "Name Change", JOptionPane.OK_CANCEL_OPTION);
+
+ if(val == JOptionPane.OK_OPTION)
+ GeneUtils.propagateId(((GFFStreamFeature)feature),
+ prefix, children);
+ }
+ }
+ }
+
+ /**
+ * Used to update a gff feature location
+ * @param feature
+ * @param uniquename
+ */
+ private void updateFeatureLoc(final GFFStreamFeature feature,
+ final String uniquename)
+ {
+ final Hashtable<String, Range> rangeHash = feature.getSegmentRangeStore();
+ ChadoTransaction tsn;
+ if(rangeHash != null)
+ {
+ Enumeration<String> id_keys= rangeHash.keys();
+ while(id_keys.hasMoreElements())
+ {
+ String seqId = id_keys.nextElement();
+ Range range = rangeHash.get(seqId);
+ FeatureLoc featureloc = getFeatureLoc(feature, seqId,
+ range);
+
+ tsn = new ChadoTransaction(ChadoTransaction.UPDATE,
+ featureloc,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "FEATURELOC: ID="+seqId+" "+
+ featureloc.getFmin()+".."+featureloc.getFmax());
+ sql.add(tsn);
+ }
+ }
+ else
+ {
+ FeatureLoc featureloc = getFeatureLoc(feature, uniquename,
+ feature.getLocation().getTotalRange());
+
+ tsn = new ChadoTransaction(ChadoTransaction.UPDATE,
+ featureloc,
+ feature.getLastModified(), feature,
+ feature.getKey().getKeyString(),
+ "FEATURELOC: ID="+uniquename+" "+
+ featureloc.getFmin()+".."+featureloc.getFmax());
+ sql.add(tsn);
+ }
+ }
+
+ /**
+ * Strip out double quotes around a string.
+ * @param s a <code>String</code> to strip quotes
+ * @return the resulting <code>String</code>
+ */
+ private String stripQuotes(String s)
+ {
+ if(s == null)
+ return null;
+
+ if(s.startsWith("\"") && s.endsWith("\""))
+ s = s.substring(1,s.length()-1);
+
+ return s;
+ }
+
+ /**
+ * Get the FeatureLoc object
+ * @param feature
+ * @param seg_id
+ * @param range_new
+ * @return
+ */
+ private FeatureLoc getFeatureLoc(final GFFStreamFeature gffFeature,
+ final String seg_id, final Range range_new)
+ {
+ FeatureLoc featureloc = new FeatureLoc();
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+ chado_feature.setUniqueName(seg_id);
+
+ featureloc.setFeatureByFeatureId(chado_feature);
+ featureloc.setFmax(new Integer(range_new.getEnd()));
+ featureloc.setFmin(new Integer(range_new.getStart()-1));
+ //featureloc.setRank(0);
+
+ DatabaseDocument doc =
+ (DatabaseDocument)gffFeature.getDocumentEntry().getDocument();
+
+ org.gmod.schema.sequence.Feature featureBySrcFeatureId =
+ new org.gmod.schema.sequence.Feature();
+ featureBySrcFeatureId.setFeatureId(Integer.parseInt(doc.getSrcFeatureId()));
+ featureloc.setFeatureBySrcFeatureId(featureBySrcFeatureId);
+
+
+ boolean is_complement = gffFeature.getLocation().isComplement();
+ if(is_complement)
+ featureloc.setStrand(new Short((short) -1));
+ else
+ featureloc.setStrand(new Short((short) 1));
+
+ Qualifier qualifier_phase = gffFeature.getQualifierByName("codon_start");
+ if(qualifier_phase != null)
+ {
+ String phase = qualifier_phase.getValues().elementAt(0);
+
+ if(phase.equals ("1"))
+ featureloc.setPhase(new Integer(0));
+ else if(phase.equals("2"))
+ featureloc.setPhase(new Integer(1));
+ else if(phase.equals("3"))
+ featureloc.setPhase(new Integer(2));
+ }
+ else
+ featureloc.setPhase(null);
+
+ final RangeVector ranges = gffFeature.getLocation().getRanges();
+ boolean firstSeg = true;
+ boolean lastSeg = true;
+ if(ranges.size() > 1) // define if first/last segment
+ {
+ firstSeg = false;
+ lastSeg = false;
+ if(range_new.getStart() == gffFeature.getFirstBase())
+ firstSeg = true;
+ if(range_new.getEnd() == gffFeature.getLastBase())
+ lastSeg = true;
+ }
+
+ if(firstSeg && gffFeature.getQualifierByName("Start_range") != null)
+ featureloc.setFminPartial(true);
+ else
+ featureloc.setFminPartial(false);
+
+ if(lastSeg && gffFeature.getQualifierByName("End_range") != null)
+ featureloc.setFmaxPartial(true);
+ else
+ featureloc.setFmaxPartial(false);
+
+ return featureloc;
+ }
+
+ /**
+ * Create the <code>FeatureProp</code> object
+ * @param uniquename
+ * @param qualifier_string
+ * @param lcvterm_id
+ * @param rank
+ * @return
+ */
+ private FeatureProp getFeatureProp(final String uniquename,
+ final String qualifier_string,
+ final Integer lcvterm_id,
+ final int rank)
+ {
+ FeatureProp featureprop = new FeatureProp();
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+ chado_feature.setUniqueName(uniquename);
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(lcvterm_id.intValue());
+ featureprop.setValue(stripQuotes(qualifier_string));
+ featureprop.setRank(rank);
+ featureprop.setCvTerm(cvterm);
+ featureprop.setFeature(chado_feature);
+ return featureprop;
+ }
+
+ /**
+ * Create the <code>FeatureSynonym</code> object.
+ * If the synonym is not current then the qualifier ends with
+ * current=false (e.g. /systematic_id=xyz;current=false
+ *
+ * @param qualifier_name
+ * @param qualifier_string
+ * @param uniqueName
+ * @return
+ */
+ private FeatureSynonym getFeatureSynonym(final String qualifier_name,
+ final String qualifier_string,
+ final String uniqueName)
+ {
+ boolean isCurrent = true;
+ StringVector strings = StringVector.getStrings(qualifier_string, ";");
+ final String synonymValue = strings.get(0);
+ if(strings.size() > 1 && strings.get(1).equals("current=false"))
+ isCurrent = false;
+ else
+ isCurrent = true;
+
+ Integer lcvterm_id = DatabaseDocument.getCvtermID(qualifier_name);
+ FeatureSynonym feature_synonym = new FeatureSynonym();
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+ chado_feature.setUniqueName(uniqueName);
+
+ Synonym synonym = new Synonym();
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(lcvterm_id.intValue());
+ synonym.setName(synonymValue);
+ synonym.setCvTerm(cvterm);
+
+ feature_synonym.setSynonym(synonym);
+ feature_synonym.setFeature(chado_feature);
+ feature_synonym.setCurrent(isCurrent);
+ return feature_synonym;
+ }
+
+ /**
+ * Create the <code>FeatureDbXRef</code> object
+ * @param qualifier_string
+ * @param uniqueName
+ * @return
+ */
+ private FeatureDbXRef getFeatureDbXRef(final String qualifier_string,
+ final String uniqueName)
+ {
+ int index = qualifier_string.lastIndexOf(":");
+ if(index == -1)
+ {
+ final String msg =
+ "Wrong format for Dbxref:\n"+qualifier_string+"\n(expecting DB:XXXX).";
+ logger4j.warn(msg);
+ JOptionPane.showMessageDialog(null, msg,
+ "Format Error", JOptionPane.WARNING_MESSAGE);
+ return null;
+ }
+ FeatureDbXRef feature_dbxref = new FeatureDbXRef();
+ DbXRef dbxref = new DbXRef();
+ Db db = new Db();
+ db.setName(qualifier_string.substring(0,index));
+ dbxref.setDb(db);
+ dbxref.setAccession(qualifier_string.substring(index+1));
+ feature_dbxref.setDbXRef(dbxref);
+ org.gmod.schema.sequence.Feature feat =
+ new org.gmod.schema.sequence.Feature();
+ feat.setUniqueName(uniqueName);
+ feature_dbxref.setFeature(feat);
+ return feature_dbxref;
+ }
+
+
+ /**
+ * Create the <code>FeaturePub</code> object for /literature
+ * qualifiers
+ * @param qualifier_string
+ * @param uniqueName
+ * @return
+ */
+ private FeaturePub getFeaturePub(final String qualifier_string,
+ final String uniqueName)
+ {
+ FeaturePub featurePub = new FeaturePub();
+ Pub pub = new Pub();
+ pub.setUniqueName(qualifier_string.trim());
+
+ org.gmod.schema.sequence.Feature feat =
+ new org.gmod.schema.sequence.Feature();
+ feat.setUniqueName(uniqueName);
+
+ featurePub.setPub(pub);
+ featurePub.setFeature(feat);
+ return featurePub;
+ }
+ /**
+ * Create the <code>FeatureCvTerm</code> object
+ * @param qualifier_name
+ * @param qualifier_string
+ * @param uniqueName
+ * @return
+ */
+ private FeatureCvTerm getFeatureCvTerm(final String qualifier_name,
+ String qualifier_string,
+ final String uniqueName,
+ final GFFStreamFeature feature)
+ {
+ logger4j.debug("Build FeatureCvTerm for "+qualifier_string);
+
+ if(qualifier_string.startsWith("\""))
+ qualifier_string = qualifier_string.substring(1,qualifier_string.length()-1);
+
+ FeatureCvTerm feature_cvterm = new FeatureCvTerm();
+ org.gmod.schema.sequence.Feature chado_feature =
+ new org.gmod.schema.sequence.Feature();
+ chado_feature.setUniqueName(uniqueName);
+ feature_cvterm.setFeature(chado_feature);
+
+ if(qualifier_name.toLowerCase().equals("class"))
+ {
+ int index = qualifier_string.indexOf("::");
+
+ CvTerm cvTerm = getCvTerm( DatabaseDocument.getCvTermByCvTermId(
+ Integer.parseInt(qualifier_string.substring(index+2)), feature).getName(), "RILEY" );
+
+ feature_cvterm.setCvTerm(cvTerm);
+ logger4j.debug("Finished building FeatureCvTerm for "+uniqueName);
+ return feature_cvterm;
+ }
+
+ List<FeatureCvTermProp> featureCvTermProps = new Vector<FeatureCvTermProp>();
+ StringVector strings = StringVector.getStrings(qualifier_string, ";");
+
+ String cvName = null;
+ if(qualifier_name.equals("controlled_curation"))
+ {
+ cvName = "CC_";
+ if(qualifier_string.indexOf("cv=") > -1) // get actual cv name
+ {
+ for(int i=0; i<strings.size(); i++)
+ {
+ String qual = strings.get(i).trim();
+ if(qual.startsWith("cv="))
+ cvName = qual.substring(3);
+ }
+ }
+ }
+ else if(qualifier_name.equals("product"))
+ cvName = PRODUCT_CV;
+ else if(qualifier_name.equals("history"))
+ cvName = HISTORY_CV;
+
+ for(int i=0; i<strings.size(); i++)
+ {
+ final String this_qualifier_part = strings.get(i).trim();
+ final String this_qualifier_part_lowercase = this_qualifier_part.toLowerCase();
+
+ if(this_qualifier_part_lowercase.startsWith("term="))
+ {
+ final String cvTermName = this_qualifier_part.substring(5);
+ CvTerm cvTerm;
+
+ if(qualifier_name.startsWith("GO"))
+ cvTerm = GoBox.getGOCvTerm(cvTermName);
+ else
+ cvTerm = getCvTerm(cvTermName, cvName);
+
+ if(cvTerm == null && cvName.equals(PRODUCT_CV))
+ cvTerm = createCvTerm(cvTermName,
+ PRODUCT_CV, PRODUCT_DB);
+
+ feature_cvterm.setCvTerm(cvTerm);
+ logger4j.debug("CV name "+cvName);
+ continue;
+ }
+
+ if(this_qualifier_part_lowercase.startsWith("cv="))
+ continue;
+
+ // the WITH column is associated with one or more FeatureCvTermDbXRef
+ if(this_qualifier_part_lowercase.startsWith("with="))
+ {
+ String withStr = this_qualifier_part.substring(5);
+ loadDbXRefsAndPubs(withStr, feature_cvterm);
+ continue;
+ }
+
+ if(this_qualifier_part_lowercase.equals("qualifier=not"))
+ {
+ feature_cvterm.setNot(true);
+ continue;
+ }
+
+ // N.B.
+ // 1) for /GO the db_xref is a Pub (for primary pubs)
+ // or FeatureCvTermPub (for others) in /GO
+ // 2) for /controlled_curation the db_xref is a FeatureCvTermDbXRef
+ // or a Pub
+ if(this_qualifier_part_lowercase.startsWith("db_xref="))
+ {
+ String dbxrefStr = this_qualifier_part.substring(8);
+ loadDbXRefsAndPubs(dbxrefStr, feature_cvterm);
+ continue;
+ }
+
+ // for product use the rank=1 to specify an alternative product
+ // where there are multiple products for a feature
+ if(cvName != null && cvName.equals(PRODUCT_CV) &&
+ this_qualifier_part_lowercase.startsWith("rank="))
+ {
+ if(this_qualifier_part_lowercase.equals("rank=1"))
+ feature_cvterm.setRank(1);
+ else
+ feature_cvterm.setRank(0);
+ continue;
+ }
+ // feature_cvterm_prop's
+
+ if(!this_qualifier_part_lowercase.startsWith("goid=") &&
+ !this_qualifier_part_lowercase.startsWith("aspect="))
+ {
+ int index = this_qualifier_part_lowercase.indexOf('=');
+ String prop = this_qualifier_part.substring(index+1);
+
+ logger4j.debug("FeatureCvTermProp = "+this_qualifier_part_lowercase);
+ CvTerm cvTerm;
+ if(index == -1 && cvName.equals(HISTORY_CV))
+ cvTerm = getCvTerm("qualifier", null);
+ else
+ cvTerm = getCvTerm(this_qualifier_part.substring(0,index), null);
+
+ if(cvTerm == null)
+ {
+ if(cvName.equals(HISTORY_CV))
+ {
+ cvTerm = getCvTerm("qualifier", null);
+ prop = this_qualifier_part_lowercase;
+ }
+ else
+ JOptionPane.showMessageDialog(null,
+ "This cv term is missing :\n"+this_qualifier_part.substring(0,index),
+ "cv term not found", JOptionPane.ERROR_MESSAGE);
+ }
+ FeatureCvTermProp featureCvTermProp = new FeatureCvTermProp();
+ featureCvTermProp.setValue(prop);
+ featureCvTermProp.setCvTerm(cvTerm);
+ featureCvTermProp.setRank(
+ getFeatureCvTermPropRank(featureCvTermProps, cvTerm.getName()));
+
+ featureCvTermProps.add(featureCvTermProp);
+
+ continue;
+ }
+ }
+
+ feature_cvterm.setFeatureCvTermProps(featureCvTermProps);
+
+ logger4j.debug("Finished building FeatureCvTerm for "+uniqueName);
+ return feature_cvterm;
+ }
+
+ private void deleteFeatureRelationShip(final String qualifierName,
+ final String qualifierString,
+ final String uniquename,
+ final GFFStreamFeature feature)
+ {
+ FeatureRelationship fr = new FeatureRelationship();
+
+ // orthologous_to or paralogous_to should be retrieved from sequence ontology !!
+ CvTerm cvterm = DatabaseDocument.getCvTermByCvAndCvTerm(qualifierName, "sequence");
+
+ org.gmod.schema.sequence.Feature objectFeature = new org.gmod.schema.sequence.Feature();
+ objectFeature.setUniqueName(uniquename);
+
+ int index1 = qualifierString.indexOf("link=")+5;
+ int index2 = qualifierString.indexOf("type=");
+ String link = qualifierString.substring(index1, index2).trim();
+ org.gmod.schema.sequence.Feature subjectFeature = new org.gmod.schema.sequence.Feature();
+ subjectFeature.setUniqueName(link);
+
+ fr.setFeatureByObjectId(objectFeature);
+ fr.setFeatureBySubjectId(subjectFeature);
+ fr.setCvTerm(cvterm);
+
+ ChadoTransaction tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ fr,
+ null, (GFFStreamFeature)null, feature.getKey().getKeyString(),
+ qualifierName.toUpperCase()+ ": ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+
+
+ fr.setFeatureByObjectId(subjectFeature);
+ fr.setFeatureBySubjectId(objectFeature);
+ tsn = new ChadoTransaction(ChadoTransaction.DELETE,
+ fr,
+ null, (GFFStreamFeature)null, feature.getKey().getKeyString(),
+ qualifierName.toUpperCase()+ ": ID="+uniquename+" "+qualifierString);
+ sql.add(tsn);
+ }
+
+ public static CvTerm getCvTerm(final String cvTermName, final String cvName,
+ final String definition,
+ final String dbName)
+ {
+ CvTerm cvTerm = new CvTerm();
+ cvTerm.setName(cvTermName);
+ Cv cv = new Cv();
+ cv.setName(cvName);
+ cvTerm.setCv(cv);
+
+ if(definition != null && !definition.equals(""))
+ cvTerm.setDefinition(definition);
+
+ // need to create a unique dbxref for the cvterm
+ DbXRef dbXRef = new DbXRef();
+ Db db = new Db();
+ db.setName(dbName);
+ dbXRef.setDb(db);
+ dbXRef.setAccession(cvTermName); // use cvterm.name as the accession
+ cvTerm.setDbXRef(dbXRef);
+ return cvTerm;
+ }
+
+ /**
+ * Make a new cvterm.
+ * @param cvTermName
+ * @param cvName
+ * @param dbName
+ * @return
+ */
+ private CvTerm createCvTerm(final String cvTermName, final String cvName,
+ final String dbName)
+ {
+ final CvTerm cvTerm = getCvTerm(cvTermName, cvName, null, dbName);
+
+ logger4j.debug("INSERT cvTerm "+cvTermName);
+ ChadoTransaction tsn = new ChadoTransaction(ChadoTransaction.INSERT,
+ cvTerm, null, null, null,
+ "INSERT TERM "+cvName+"="+cvTermName+" DB="+dbName);
+ sql.add(tsn);
+ return cvTerm;
+ }
+
+ /**
+ * Get the rank to give a FeatureCvTermProp
+ * @param featureCvTermProps - existing featureprop's
+ * @param cvTermName - new featureprop cvterm.name
+ * @return
+ */
+ private int getFeatureCvTermPropRank(List<FeatureCvTermProp> featureCvTermProps, final String cvTermName)
+ {
+ int rank = 0;
+ for(FeatureCvTermProp prop: featureCvTermProps)
+ {
+ if(prop.getCvTerm().getName().equals(cvTermName))
+ rank++;
+ }
+ return rank;
+ }
+
+ /**
+ * Get CvTerm that have been cached
+ * @param cvTermName term name
+ * @param cvName name of controlled vocabulary
+ * @return
+ */
+ private CvTerm getCvTerm(String cvTermName, final String cvName)
+ {
+ if(cvTermName.startsWith("\""))
+ cvTermName = cvTermName.substring(1, cvTermName.length()-1);
+
+ CvTerm cvTerm = null;
+ if(cvName != null)
+ cvTerm = DatabaseDocument.getCvTermByCvPartAndCvTerm(cvTermName, cvName);
+ else
+ cvTerm = DatabaseDocument.getCvTermByCvTermName(cvTermName);
+
+ if(cvTerm != null)
+ {
+ logger4j.debug("USE CvTerm from cache, CvTermId="
+ + cvTermName + " -> " + cvTerm.getCvTermId()+ " " +
+ cvTerm.getName()+" -> "+cvTerm.getCv().getName());
+ }
+ else
+ {
+ logger4j.warn("CvTerm not found in cache = " + cvTermName);
+ //cvTerm = new CvTerm();
+ //cvTerm.setName(cvTermName);
+ }
+ return cvTerm;
+ }
+
+ /**
+ * Use to load feature_cvterm_dbxref's and feature_cvterm_pub's into a
+ * feature_cvterm.
+ * Note:
+ * 1) for /GO the db_xref is a Pub (for primary pubs)
+ * or FeatureCvTermPub (for others) in /GO
+ * 2) for /controlled_curation the db_xref is a FeatureCvTermDbXRef
+ * or a Pub
+ * @param searchStr
+ * @param feature_cvterm
+ */
+ private void loadDbXRefsAndPubs(final String searchStr,
+ final FeatureCvTerm feature_cvterm)
+ {
+ List<FeatureCvTermDbXRef> featureCvTermDbXRefs = null;
+
+ //StringVector strings = StringVector.getStrings(searchStr, "|");
+ StringTokenizer tok = new StringTokenizer(searchStr, "|,");
+ while(tok.hasMoreTokens())
+ {
+ String this_part = tok.nextToken(); //(String)strings.get(i);
+ int ind = this_part.indexOf(':');
+
+ if(this_part.equals("null"))
+ continue;
+
+ if(ind == -1)
+ {
+ JOptionPane.showMessageDialog(null,
+ this_part+" does not appear to be in\n"+
+ "a valid format (e.g. XXX:YYY). This will be ignored.",
+ "Invalid DbXRef or Pub", JOptionPane.WARNING_MESSAGE);
+ continue;
+ }
+
+ final String dbName = this_part.substring(0, ind);
+ final String accession = this_part.substring(ind+1);
+
+ if(dbName.equals("PMID"))
+ {
+ // enter as Pub if primary
+
+ Pub pub = new Pub();
+ pub.setUniqueName(dbName + ":" + accession);
+
+ if(feature_cvterm.getPub() == null)
+ {
+ logger4j.debug("Set primary Pub for " +
+ dbName + ":" + accession);
+ feature_cvterm.setPub(pub);
+ }
+ else
+ {
+ // secondary pub
+ logger4j.debug("Set secondary Pub for " +
+ dbName + ":" + accession);
+ Collection<FeatureCvTermPub> featureCvTermPubs = feature_cvterm.getFeatureCvTermPubs();
+ if(featureCvTermPubs == null ||
+ featureCvTermPubs.size() < 1)
+ {
+ featureCvTermPubs = new Vector<FeatureCvTermPub>();
+ feature_cvterm.setFeatureCvTermPubs(featureCvTermPubs);
+ }
+ FeatureCvTermPub featureCvTermPub = new FeatureCvTermPub();
+ featureCvTermPub.setPub(pub);
+ featureCvTermPubs.add(featureCvTermPub);
+ }
+ }
+ else
+ {
+ // enter as feature_cvterm_dbxref
+ logger4j.debug("CREATE FeatureCvTermDbXRef for " +
+ dbName + ":" + accession);
+
+ DbXRef dbxref = new DbXRef();
+ dbxref.setAccession(accession);
+ Db db = new Db();
+ db.setName(dbName);
+ dbxref.setDb(db);
+
+ FeatureCvTermDbXRef featureCvTermDbXRef = new FeatureCvTermDbXRef();
+ featureCvTermDbXRef.setDbXRef(dbxref);
+
+ if(featureCvTermDbXRefs == null)
+ featureCvTermDbXRefs = new Vector<FeatureCvTermDbXRef>();
+ featureCvTermDbXRefs.add(featureCvTermDbXRef);
+ }
+ }
+
+ if(featureCvTermDbXRefs != null)
+ feature_cvterm.setFeatureCvTermDbXRefs(featureCvTermDbXRefs);
+ }
+
+
+
+ public Vector<String> getFeatureInsertUpdate()
+ {
+ Vector<String> features = null;
+
+ for(int i=0; i<sql.size(); i++)
+ {
+ ChadoTransaction transaction = (ChadoTransaction)sql.get(i);
+ if(transaction.getType() == ChadoTransaction.INSERT ||
+ transaction.getType() == ChadoTransaction.UPDATE)
+ {
+ if(transaction.getFeatureObject() instanceof
+ org.gmod.schema.sequence.Feature)
+ {
+ if(features == null)
+ features = new Vector<String>();
+ org.gmod.schema.sequence.Feature feature =
+ (org.gmod.schema.sequence.Feature)transaction.getFeatureObject();
+ features.add( feature.getUniqueName() );
+ }
+ }
+ }
+ return features;
+ }
+
+ /**
+ * Commit the transactions back to the database.
+ * @param dbDoc
+ * @param force ignore any sql commits that fail
+ * @param ctm
+ */
+ public static void commit(final DatabaseDocument dbDoc,
+ final boolean force,
+ final ChadoTransactionManager ctm)
+ {
+ DatabaseDocument.initMDC(dbDoc);
+
+ final Vector<ChadoTransaction> sqlCopy = ctm.getSql();
+ commitReturnValue = dbDoc.commit(sqlCopy, force);
+
+ boolean nocommit = true;
+ if(System.getProperty("nocommit") == null ||
+ System.getProperty("nocommit").equals("false"))
+ nocommit = false;
+
+ if(commitReturnValue == sqlCopy.size())
+ {
+ if(!nocommit)
+ {
+ for(int i = 0; i < sqlCopy.size() && i < commitReturnValue; i++)
+ {
+ ChadoTransaction tsn = sqlCopy.get(i);
+ logger4j.debug("COMMIT DONE " + tsn.getLogComment());
+ }
+ }
+
+ logger4j.debug("COMMIT SUCCESS");
+ if(!nocommit)
+ ctm.setSql(new Vector<ChadoTransaction>());
+ }
+ else if(commitReturnValue > 0)
+ {
+ ChadoTransaction tsn = sqlCopy.get(commitReturnValue);
+ logger4j.warn("COMMIT FAILED AT " + tsn.getLogComment());
+ }
+ sqlCopy.clear();
+ }
+
+ /**
+ * Add a history qualifier
+ * @param tsn
+ * @param qualifierName
+ * @param log
+ */
+ private void addHistory(final ChadoTransaction tsn,
+ final String qualifierName, final String log)
+ {
+ if( qualifierName.equalsIgnoreCase("history") )
+ return;
+
+ // append to event queue to be run after any other changes
+ java.awt.EventQueue.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ String msg;
+ switch(tsn.getType())
+ {
+ case ChadoTransaction.INSERT:
+ msg = "added_";
+ break;
+ case ChadoTransaction.DELETE:
+ msg = "removed_";
+ break;
+ default:
+ msg = "update_";
+ }
+ msg += qualifierName+"="+log;
+
+ updateHistory(tsn.getGff_feature(), msg);
+ }
+ });
+ }
+
+ /**
+ * Add a history qualifier
+ * @param f - GFF feature
+ * @param msg - history
+ */
+ private void updateHistory(final GFFStreamFeature f, final String msg)
+ {
+ Qualifier cv_qualifier = f.getQualifierByName("history");
+ final int idx;
+ if(cv_qualifier == null)
+ {
+ cv_qualifier = new Qualifier("history");
+ idx = -1;
+ }
+ else
+ idx = f.getQualifiers().indexOf(cv_qualifier);
+
+ final String term;
+ if(HistoryBox.getCvTermStrings().contains("annotation"))
+ term = "annotation";
+ else
+ term = HistoryBox.getDefaultTerm().getName();
+
+ final String qual =
+ "term="+term+";"+
+ "curatorName="+((DatabaseDocument) (f.getDocumentEntry().getDocument())).getUserName()+";"+
+ "date="+ DatePanel.getDate()+";"+
+ "qualifier="+msg;
+ if(idx > -1)
+ {
+ String dateStr = "date="+ DatePanel.getDate();
+ StringVector sv = cv_qualifier.getValues();
+ String qStr = null;
+ int ind = -1;
+ for(int i=0; i<sv.size(); i++)
+ {
+ String v = sv.get(i);
+ if(v.indexOf(dateStr)> -1)
+ {
+ qStr = HistoryBox.getFieldIgnoreSeparator("qualifier", v);
+ ind = i;
+ break;
+ }
+ }
+
+ if(ind > -1)
+ {
+ // avoid duplicating a history qualifier
+ if(qStr.indexOf(msg+";") == -1 && !qStr.endsWith(msg))
+ {
+ sv.remove(ind);
+ sv.add(ind, qual+";qualifier="+qStr);
+ }
+ }
+ else
+ sv.add(qual);
+
+ cv_qualifier = new Qualifier("history", sv);
+ }
+ else
+ cv_qualifier.addValue(qual);
+
+ try
+ {
+ ((Feature)f.getUserData()).setQualifier(cv_qualifier);
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Determines if there are transactions registered.
+ * @return
+ */
+ public boolean hasTransactions()
+ {
+ if(sql.size() > 0)
+ return true;
+ return false;
+ }
+
+ public int getTransactionCount()
+ {
+ return sql.size();
+ }
+
+ public ChadoTransaction getTransactionAt(final int index)
+ {
+ return sql.elementAt(index);
+ }
+
+
+ private Vector<ChadoTransaction> getSql()
+ {
+ final Vector<ChadoTransaction> tmpSql = new Vector<ChadoTransaction>(sql.size());
+ for(ChadoTransaction tsn: sql)
+ tmpSql.add(tsn.copy());
+
+ return tmpSql;
+ }
+
+
+ private void setSql(Vector<ChadoTransaction> sql)
+ {
+ this.sql = sql;
+ }
+
+
+ protected int getCommitReturnValue()
+ {
+ return commitReturnValue;
+ }
+}
diff --git a/uk/ac/sanger/artemis/chado/ClusterLazyQualifierValue.java b/uk/ac/sanger/artemis/chado/ClusterLazyQualifierValue.java
new file mode 100644
index 0000000..e81ae4f
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/ClusterLazyQualifierValue.java
@@ -0,0 +1,445 @@
+/*
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+
+
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import org.gmod.schema.analysis.AnalysisFeature;
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureRelationship;
+
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.LazyQualifierValue;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+public class ClusterLazyQualifierValue implements LazyQualifierValue
+{
+ /** force complete loading of the data */
+ private boolean forceLoad = false;
+ /** data loaded */
+ private boolean lazyLoaded = false;
+ /** include gene name */
+ private boolean loadGeneName = true;
+
+ private String value;
+ private String name;
+ private GFFStreamFeature feature;
+ private List clusters;
+
+ /**
+ * Qualifier object to handle lazy loading of cluster/ortholog/paralog data
+ * @param matchFeature
+ * @param featureId
+ */
+ public ClusterLazyQualifierValue(final String value,
+ final String name,
+ final GFFStreamFeature feature)
+ {
+ this.value = value;
+ this.name = name;
+ this.feature = feature;
+ }
+
+ public String getString()
+ {
+ if(forceLoad && !lazyLoaded)
+ return getHardString();
+ else
+ return value;
+ }
+
+ public boolean isLazyLoaded()
+ {
+ return lazyLoaded;
+ }
+
+ public void setForceLoad(boolean forceLoad)
+ {
+ this.forceLoad = forceLoad;
+ }
+
+ /**
+ * This speeds up loading for a list of ortho/paralogs/clusters
+ * @param values List of ClusterLazyQualifierValue
+ * @param feature
+ */
+ public static void setClusterFromValueList(final List values, final DatabaseDocument document)
+ {
+ final List clusterFeatureIds = new Vector();
+ final Hashtable hash = new Hashtable(values.size());
+ for(int i=0; i<values.size(); i++)
+ {
+ ClusterLazyQualifierValue lazyValue = (ClusterLazyQualifierValue)values.get(i);
+ StringVector strings = StringVector.getStrings(lazyValue.getValue(), ";");
+ String f_id[] = ArtemisUtils.getString(strings, "object_id=").split("=");
+
+ Integer clusterFeatureId = Integer.valueOf(f_id[1]);
+ clusterFeatureIds.add(clusterFeatureId);
+ lazyValue.initCluster();
+
+ final Vector v;
+ if(hash.containsKey(clusterFeatureId))
+ v = (Vector)hash.get(clusterFeatureId);
+ else
+ v = new Vector();
+
+ v.add(lazyValue);
+ hash.put(clusterFeatureId, v);
+ }
+
+ //final Document document = ((DocumentEntry)feature.getEntry()).getDocument();
+ List allClusters = ((DatabaseDocument)document).getClustersByFeatureIds(clusterFeatureIds);
+ List<Integer> featureIds = new Vector<Integer>();
+ for(int i=0;i<allClusters.size(); i++)
+ {
+ final Feature clusterFeature = (Feature)allClusters.get(i);
+ Collection subjects = clusterFeature.getFeatureRelationshipsForSubjectId();
+ Iterator it = subjects.iterator();
+ while(it.hasNext())
+ {
+ FeatureRelationship fr = (FeatureRelationship)it.next();
+ featureIds.add(fr.getFeatureBySubjectId().getFeatureId());
+ }
+ }
+
+ // bulk load parent features
+ List<FeatureRelationship> parentFrs =
+ ((DatabaseDocument)document).getParentFeaturesByChildFeatureIds(featureIds);
+ featureIds.clear();
+ Hashtable<Integer, FeatureRelationship> hashFR = new Hashtable<Integer, FeatureRelationship>();
+ for(int i=0; i<parentFrs.size(); i++)
+ {
+ FeatureRelationship frBulk = parentFrs.get(i);
+ featureIds.add(frBulk.getFeatureByObjectId().getFeatureId());
+ hashFR.put(frBulk.getFeatureBySubjectId().getFeatureId(), frBulk);
+ }
+
+ // bulk load grand parent features (to find gene)
+ parentFrs =
+ ((DatabaseDocument)document).getParentFeaturesByChildFeatureIds(featureIds);
+ for(int i=0; i<parentFrs.size(); i++)
+ {
+ FeatureRelationship frBulk = parentFrs.get(i);
+ hashFR.put(frBulk.getFeatureBySubjectId().getFeatureId(), frBulk);
+ }
+
+ for(int i=0;i<allClusters.size(); i++)
+ {
+ final Feature clusterFeature = (Feature)allClusters.get(i);
+ Collection<FeatureRelationship> sbjts = clusterFeature.getFeatureRelationshipsForSubjectId();
+ List<FeatureRelationship> newSubject = new Vector<FeatureRelationship>();
+ Iterator<FeatureRelationship> it = sbjts.iterator();
+ while(it.hasNext())
+ {
+ FeatureRelationship fr = it.next();
+ FeatureRelationship frBulk = hashFR.get(fr.getFeatureBySubjectId().getFeatureId());
+ if(frBulk == null)
+ continue;
+ List<FeatureRelationship> frsForSbjtId = new Vector<FeatureRelationship>();
+
+ Feature parent = frBulk.getFeatureByObjectId();
+ if(!parent.getCvTerm().getName().equals("gene") ||
+ !parent.getCvTerm().getName().equals("pseudogene") &&
+ hashFR.contains(frBulk.getFeatureByObjectId().getFeatureId()))
+ {
+ frBulk = hashFR.get(frBulk.getFeatureByObjectId().getFeatureId());
+ }
+ frsForSbjtId.add(frBulk);
+
+ fr.getFeatureBySubjectId().setFeatureRelationshipsForSubjectId(frsForSbjtId);
+ newSubject.add(fr);
+
+ //System.out.println(fr.getFeatureBySubjectId().getUniqueName()+" "+
+ // frBulk.getFeatureByObjectId().getUniqueName());
+ }
+ clusterFeature.setFeatureRelationshipsForSubjectId(newSubject);
+
+ final Vector v = (Vector)hash.get(new Integer(clusterFeature.getFeatureId()));
+ for(int j=0; j<v.size(); j++)
+ {
+ ClusterLazyQualifierValue lazyValue = (ClusterLazyQualifierValue)v.get(j);
+ lazyValue.addToCluster(clusterFeature);
+ }
+ }
+
+ clusterFeatureIds.clear();
+ featureIds.clear();
+ parentFrs.clear();
+ hashFR.clear();
+ }
+
+ private void addToCluster(final Feature clusterFeature)
+ {
+ if(clusters == null)
+ clusters = new Vector();
+ clusters.add(clusterFeature);
+ }
+
+ private void initCluster()
+ {
+ clusters = new Vector();
+ }
+
+ private synchronized String getHardString()
+ {
+ lazyLoaded = true;
+ final String featureId = (String) feature.getQualifierByName("feature_id").getValues().get(0);
+
+ StringVector strings = StringVector.getStrings(value, ";");
+ String rank = ArtemisUtils.getString(strings, "rank");
+
+ //
+ // should already have the clusters loaded by calling setClusterFromValueList()
+ if(clusters == null)
+ {
+ final List featureIds = new Vector();
+ String f_id[] = ArtemisUtils.getString(strings, "object_id=").split("=");
+
+ if(f_id.length < 2)
+ return value;
+
+ featureIds.add( Integer.valueOf(f_id[1]) );
+ final Document document = ((DocumentEntry)feature.getEntry()).getDocument();
+ clusters = ((DatabaseDocument)document).getClustersByFeatureIds(featureIds);
+ for(int i=0;i<clusters.size(); i++)
+ System.out.println("*********NOT PRELOADED "+
+ ((Feature)clusters.get(i)).getUniqueName() );
+ }
+
+ value = "";
+ for(int i=0;i<clusters.size(); i++)
+ {
+ final Feature clusterFeature = (Feature)clusters.get(i);
+
+ if(clusterFeature.getCvTerm().getName().indexOf("match") < 0)
+ {
+ // this looks like an ortho/paralog stored without a match feature
+ // and just a feature relationship between features
+ if(clusterFeature.getCvTerm().getName().equals("polypeptide") ||
+ clusterFeature.getCvTerm().getName().equals("gene"))
+ {
+ final DatabaseDocument document =
+ (DatabaseDocument)feature.getDocumentEntry().getDocument();
+ Feature matchFeature =
+ document.getFeatureByUniquename(clusterFeature.getUniqueName());
+
+ value = value.concat(matchFeature.getOrganism().getCommonName()+":");
+ if(loadGeneName)
+ {
+ String geneName = getGeneName(matchFeature);
+ value = value.concat(geneName+" ");
+ }
+
+ value = value.concat("link="+matchFeature.getUniqueName());
+ value = value.concat(" type="+name);
+
+ String product = getProduct(matchFeature);
+ if(product != null)
+ value = value.concat(";product="+product);
+
+ value = value.concat(";match_name="+matchFeature.getUniqueName());
+ }
+
+ continue;
+ }
+
+ final Collection subjects = clusterFeature.getFeatureRelationshipsForSubjectId();
+ Iterator it = subjects.iterator();
+ int cnt = 0;
+
+ while(it.hasNext())
+ {
+ FeatureRelationship fr = (FeatureRelationship)it.next();
+ Feature subjectFeature = fr.getFeatureBySubjectId();
+
+ if(subjectFeature.getFeatureId() != Integer.parseInt(featureId))
+ cnt++;
+ }
+
+ it = subjects.iterator();
+ while(it.hasNext())
+ {
+ FeatureRelationship fr = (FeatureRelationship)it.next();
+ Feature subjectFeature = fr.getFeatureBySubjectId();
+
+ if(subjectFeature.getFeatureId() != Integer.parseInt(featureId))
+ {
+ if(!value.equals(""))
+ value = value.concat(", ");
+
+ value = value.concat(subjectFeature.getOrganism().getCommonName()+":");
+
+ if(loadGeneName)
+ {
+ try
+ {
+ String geneName = getGeneName(subjectFeature);
+ value = value.concat(geneName+" ");
+ }
+ catch(NullPointerException npe)
+ {
+ System.err.println("Cannot get the gene name of "+
+ subjectFeature.getUniqueName());
+ }
+ }
+
+ value = value.concat("link="+
+ subjectFeature.getUniqueName());
+
+ value = value.concat(" type="+fr.getCvTerm().getName());
+
+ // if not a cluster
+ if(cnt < 2 &&
+ fr.getCvTerm().getName().equals("orthologous_to"))
+ {
+ String product = getProduct(subjectFeature);
+ if(product != null)
+ value = value.concat("; product="+product);
+ }
+ }
+ }
+ if(cnt > 1 ||
+ (clusterFeature.getUniqueName().startsWith("CLUSTER_") && cnt > 0))
+ {
+ value = value.concat("; cluster_name="+clusterFeature.getUniqueName());
+
+ Collection analysisFeatures = clusterFeature.getAnalysisFeatures();
+ Iterator itAnalysis = analysisFeatures.iterator();
+ if(itAnalysis.hasNext())
+ {
+ AnalysisFeature analysisFeature = (AnalysisFeature) itAnalysis.next();
+ value = value.concat("; program="+analysisFeature.getAnalysis().getProgram());
+ }
+ //else
+ // value = value.concat("; program=unknown");
+ }
+ else if(cnt > 0)
+ value = value.concat("; match_name="+clusterFeature.getUniqueName());
+ }
+
+ if(value.equals(""))
+ return value;
+
+ value = value.concat("; "+rank);
+
+ //
+ //
+ loadGeneName = false;
+
+ return value;
+ }
+
+ private String getProduct(final Feature subjectFeature)
+ {
+ Collection featureCvTerms = subjectFeature.getFeatureCvTerms();
+
+ if(featureCvTerms != null)
+ {
+ Iterator itFCT = featureCvTerms.iterator();
+ while(itFCT.hasNext())
+ {
+ FeatureCvTerm featureCvTerm = (FeatureCvTerm) itFCT.next();
+ CvTerm cvTerm = featureCvTerm.getCvTerm();
+ if(cvTerm.getCv().getName().equals(
+ uk.ac.sanger.artemis.chado.ChadoTransactionManager.PRODUCT_CV))
+ return featureCvTerm.getCvTerm().getName();
+ }
+ }
+ return null;
+ }
+
+ private String getGeneName(final Feature subjectFeature)
+ {
+ String geneName = subjectFeature.getUniqueName();
+ if(!subjectFeature.getCvTerm().getName().equals("gene") ||
+ !subjectFeature.getCvTerm().getName().equals("pseudogene"))
+ {
+ Feature parent = getParentFeature(subjectFeature);
+ try
+ {
+ if(parent.getCvTerm().getName().equals("gene") ||
+ parent.getCvTerm().getName().equals("pseudogene"))
+ geneName = parent.getUniqueName();
+ else if(parent != null)
+ {
+ parent = getParentFeature(parent);
+ if(parent.getCvTerm().getName().equals("gene") ||
+ parent.getCvTerm().getName().equals("pseudogene"))
+ geneName = parent.getUniqueName();
+ }
+ }
+ catch(NullPointerException npe)
+ {
+ System.err.println(geneName+" parent not found");
+ }
+ }
+ return geneName;
+ }
+
+ /**
+ * Given a chado Feature find the parent from its feature_relationship
+ * @param childFeature
+ * @return
+ */
+ private Feature getParentFeature(final Feature childFeature)
+ {
+ Collection featureRelationships = childFeature.getFeatureRelationshipsForSubjectId();
+ Iterator it = featureRelationships.iterator();
+ while(it.hasNext())
+ {
+ FeatureRelationship frSubject = (FeatureRelationship)it.next();
+ CvTerm cvTerm = frSubject.getCvTerm();
+
+ if(cvTerm.getName().equals("derives_from") ||
+ cvTerm.getName().indexOf("part_of")>-1)
+ return frSubject.getFeatureByObjectId();
+
+ }
+ return null;
+ }
+
+
+ public String getValue()
+ {
+ return value;
+ }
+
+ public void setLoadGeneName(boolean loadGeneName)
+ {
+ this.loadGeneName = loadGeneName;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/chado/CommitFrame.java b/uk/ac/sanger/artemis/chado/CommitFrame.java
new file mode 100644
index 0000000..9b692d2
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/CommitFrame.java
@@ -0,0 +1,178 @@
+/* CommitFrame
+ *
+ * created: July 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.GotoEventSource;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.components.BasePlotGroup;
+import uk.ac.sanger.artemis.components.EntryEdit;
+import uk.ac.sanger.artemis.components.Utilities;
+
+
+public class CommitFrame extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private JList commitList = new JList();
+ private Color HIGHLIGHT = Color.red;
+ private static Cursor CBUSY = new Cursor(Cursor.WAIT_CURSOR);
+ private static Cursor CDONE = new Cursor(Cursor.DEFAULT_CURSOR);
+ private JButton testCommitButton = new JButton("Test Commit");
+
+ public CommitFrame(final ChadoTransactionManager ctm,
+ final EntryGroup entry_group,
+ final EntryEdit entry_edit,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group)
+ {
+ setList(ctm);
+
+ final JPanel panel = (JPanel) getContentPane();
+ panel.setLayout(new BorderLayout());
+
+ final JScrollPane jsp = new JScrollPane(commitList);
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ jsp.setPreferredSize(new Dimension(screen.width/2, jsp.getPreferredSize().height));
+ panel.add(jsp, BorderLayout.CENTER);
+
+ commitList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ commitList.setSelectionBackground(HIGHLIGHT);
+ commitList.setVisibleRowCount(10);
+
+ final Box xBox = Box.createHorizontalBox();
+ xBox.add(Box.createHorizontalGlue());
+ xBox.add(testCommitButton);
+
+ testCommitButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ System.setProperty("nocommit","true");
+ setCursor(CBUSY);
+ EntryEdit.commitToDatabase(entry_group, ctm, entry_edit,
+ selection, goto_event_source, base_plot_group);
+ setCursor(CDONE);
+ if(ctm.getCommitReturnValue() < ctm.getTransactionCount())
+ {
+ commitList.setSelectedIndex(ctm.getCommitReturnValue());
+ commitList.ensureIndexIsVisible(ctm.getCommitReturnValue());
+
+ JOptionPane.showMessageDialog(CommitFrame.this,
+ "Test commit failed!",
+ "Commit Test Result",
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ else
+ JOptionPane.showMessageDialog(CommitFrame.this,
+ "Test commit (of "+ctm.getCommitReturnValue()+
+ " changes) succeeded!",
+ "Commit Test Result",
+ JOptionPane.INFORMATION_MESSAGE);
+
+ System.setProperty("nocommit","false");
+ }
+ });
+
+ final JButton closeButton = new JButton("Close");
+ xBox.add(closeButton);
+ closeButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ CommitFrame.this.dispose();
+ }
+ });
+
+ panel.add(xBox, BorderLayout.NORTH);
+ pack();
+
+ Utilities.centreFrame(this);
+ setVisible(true);
+ }
+
+ /**
+ * Set the data for the JList
+ * @param ctm
+ */
+ public void setList(final ChadoTransactionManager ctm)
+ {
+ setTitle("Commit List :: "+(ctm.getTransactionCount()>0 ?
+ ctm.getTransactionCount()+" commit(s):" : "Nothing to commit"));
+
+ final Vector transactions = new Vector(ctm.getTransactionCount());
+ for(int i=0; i<ctm.getTransactionCount(); i++)
+ {
+ uk.ac.sanger.artemis.chado.ChadoTransaction tsn =
+ ctm.getTransactionAt(i);
+
+ transactions.add(tsn.getLogComment());
+ }
+
+ commitList.setListData(transactions);
+ setCommitButtonColour(ctm);
+
+ validate();
+ }
+
+ /**
+ * Set the colour of the button depending on whether there is
+ * anything to commit
+ * @param ctm
+ */
+ private void setCommitButtonColour(final ChadoTransactionManager ctm)
+ {
+ if(ctm.hasTransactions())
+ {
+ testCommitButton.setFont(testCommitButton.getFont().deriveFont(Font.BOLD));
+ testCommitButton.setForeground(Color.red);
+ }
+ else
+ {
+ testCommitButton.setFont(testCommitButton.getFont().deriveFont(Font.PLAIN));
+ testCommitButton.setForeground(Color.black);
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/chado/DbSqlConfig.java b/uk/ac/sanger/artemis/chado/DbSqlConfig.java
new file mode 100644
index 0000000..5190f5f
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/DbSqlConfig.java
@@ -0,0 +1,117 @@
+/* DbSqlConfig.java
+ *
+ * created: 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+package uk.ac.sanger.artemis.chado;
+
+import com.ibatis.sqlmap.client.SqlMapClient;
+import com.ibatis.sqlmap.client.SqlMapClientBuilder;
+import com.ibatis.common.resources.Resources;
+
+import uk.ac.sanger.artemis.util.DatabaseLocationParser;
+
+import javax.swing.JPasswordField;
+import java.util.Properties;
+import java.io.Reader;
+
+/**
+ *
+ * Initialises iBatis configuration.
+ *
+ */
+public class DbSqlConfig
+{
+
+ private SqlMapClient sqlMap;
+
+ /**
+ *
+ * Initialises iBatis configuration by reading the
+ * artemis_sqlmap/chado_iBatis_config.xml file and the
+ * database location properties defined by the system property,
+ * <i>e.g.</i> -Dchado=localhost:2997/chado?tjc
+ *
+ * It is especially important that the -Dchado argument is not prefixed with
+ * jdbc:postgresql:// as that is handled in iBatis
+ *
+ */
+ public void init(JPasswordField fpasswd)
+ {
+ try
+ {
+ String resource = "artemis_sqlmap/chado_iBatis_config.xml";
+ Reader reader = Resources.getResourceAsReader(resource);
+
+ Properties properties = null;
+ if(System.getProperty("chado") != null)
+ {
+ String url = System.getProperty("chado");
+ DatabaseLocationParser dlp = new DatabaseLocationParser(url);
+ properties = new Properties();
+
+ properties.put("chado",dlp.getUnprefixedURL());
+
+ properties.put("username",dlp.getUsername());
+
+ if(fpasswd != null && fpasswd.getPassword().length > 0)
+ properties.put("password", new String(fpasswd.getPassword()));
+ }
+
+ sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader,
+ properties);
+ }
+ catch(Exception e)
+ {
+ // If you get an error at this point, it doesnt matter what it was. It is going to be
+ // unrecoverable and we will want the app to blow up hard so we are aware of the
+ // problem. You should always log such errors and re-throw them in such a way that
+ // you can be made immediately aware of the problem.
+ e.printStackTrace();
+ throw new RuntimeException("Error initializing DbSqlConfig class. Cause: " + e);
+ }
+ }
+
+
+ public void init2(JPasswordField fpasswd)
+ {
+ try
+ {
+ String resource = "artemis_sqlmap/chado_iBatis_config.xml";
+ Reader reader = Resources.getResourceAsReader(resource);
+ sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ throw new RuntimeException("Error initializing DbSqlConfig class. Cause: " + e);
+ }
+ }
+
+ public SqlMapClient getSqlMapInstance()
+ {
+ return sqlMap;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/chado/FeatureForUpdatingResidues.java b/uk/ac/sanger/artemis/chado/FeatureForUpdatingResidues.java
new file mode 100644
index 0000000..ca1025e
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/FeatureForUpdatingResidues.java
@@ -0,0 +1,87 @@
+/* FeatureForUpdatingResidues
+ *
+ * created: July 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import org.gmod.schema.sequence.Feature;
+
+public class FeatureForUpdatingResidues extends Feature
+{
+ private int startBase;
+ private int endBase;
+ private int basesToEnd;
+ private int length;
+ private String newSubSequence;
+ private boolean isResidueUpdate = false;
+
+ public int getEndBase()
+ {
+ return endBase;
+ }
+ public void setEndBase(int endBase)
+ {
+ this.endBase = endBase;
+ }
+ public String getNewSubSequence()
+ {
+ return newSubSequence;
+ }
+ public void setNewSubSequence(String newSubSequence)
+ {
+ this.newSubSequence = newSubSequence;
+ }
+ public int getStartBase()
+ {
+ return startBase;
+ }
+ public void setStartBase(int startBase)
+ {
+ this.startBase = startBase;
+ }
+ public int getBasesToEnd()
+ {
+ return basesToEnd;
+ }
+ public void setBasesToEnd(int basesToEnd)
+ {
+ this.basesToEnd = basesToEnd;
+ }
+ public int getLength()
+ {
+ return length;
+ }
+ public void setLength(int length)
+ {
+ this.length = length;
+ }
+ public boolean isResidueUpdate()
+ {
+ return isResidueUpdate;
+ }
+ public void setResidueUpdate(boolean isResidueUpdate)
+ {
+ this.isResidueUpdate = isResidueUpdate;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/chado/FeatureLocLazyQualifierValue.java b/uk/ac/sanger/artemis/chado/FeatureLocLazyQualifierValue.java
new file mode 100644
index 0000000..20aa66b
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/FeatureLocLazyQualifierValue.java
@@ -0,0 +1,431 @@
+/* FeatureLocLazyQualifierValue
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import org.gmod.schema.analysis.AnalysisFeature;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.sequence.FeatureDbXRef;
+import org.gmod.schema.sequence.FeatureLoc;
+import org.gmod.schema.sequence.FeatureProp;
+import org.gmod.schema.sequence.Feature;
+
+import uk.ac.sanger.artemis.io.LazyQualifierValue;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+
+public class FeatureLocLazyQualifierValue implements LazyQualifierValue
+{
+ /** match feature associated with the similarity */
+ private Feature matchFeature;
+ /** feature_id of the query feature */
+ private int featureId;
+ /** force complete loading of the data */
+ private boolean forceLoad = false;
+ /** data loaded */
+ private boolean lazyLoaded = false;
+
+ /**
+ * Qualifier object to handle lazy loading of properties that
+ * are featureloc'ed to the feature being read in. e.g. similarity,
+ * polypeptide_domain, protein predictions (TMHMM, signal_peptide).
+ * @param matchFeature
+ * @param featureId
+ */
+ public FeatureLocLazyQualifierValue(final Feature matchFeature, final int featureId)
+ {
+ this.matchFeature = matchFeature;
+ this.featureId = featureId;
+ }
+
+ /**
+ * Bulk retrieval of lazy properties (used to speed up writing to files)
+ * @param similarity a <code>List</code> of Similarity qualifier values
+ * @param doc the Document to which these features belong
+ */
+ public static void bulkRetrieve(final List similarity,
+ final DatabaseDocument doc)
+ {
+ final Iterator it = similarity.iterator();
+ final Hashtable featureLocHash = new Hashtable(similarity.size()*2);
+ final Hashtable matchFeatures = new Hashtable(similarity.size());
+
+ while(it.hasNext())
+ {
+ FeatureLocLazyQualifierValue thisSimilarity = (FeatureLocLazyQualifierValue)it.next();
+ Feature thisMatchFeature = thisSimilarity.getMatchFeature();
+
+ Collection featureLocs = thisMatchFeature.getFeatureLocsForFeatureId();
+ Iterator it2 = featureLocs.iterator();
+
+ while(it2.hasNext())
+ {
+ org.gmod.schema.sequence.FeatureLoc featureLoc =
+ (org.gmod.schema.sequence.FeatureLoc)it2.next();
+
+ if(featureLoc.getSrcFeatureId() <= 0)
+ continue;
+
+ final Integer srcFeatureId = new Integer(featureLoc.getSrcFeatureId());
+ List locs;
+ if(featureLocHash.containsKey(srcFeatureId))
+ locs = (Vector)featureLocHash.get(srcFeatureId);
+ else
+ locs = new Vector();
+
+ locs.add(featureLoc);
+ featureLocHash.put(srcFeatureId, locs);
+ }
+
+ matchFeatures.put(new Integer(thisMatchFeature.getFeatureId()), thisMatchFeature);
+ }
+
+ final List queryAndSubjectFeatureIds = new Vector(featureLocHash.keySet());
+ //
+ // bulk load the subject and query features
+ //
+ final List sims = doc.getFeaturesByListOfIds(queryAndSubjectFeatureIds);
+
+ for(int i=0; i<sims.size();i++)
+ {
+ Feature srcFeature = (Feature)sims.get(i);
+ Integer srcFeatureId = new Integer(srcFeature.getFeatureId());
+ if(featureLocHash.containsKey(srcFeatureId))
+ {
+ Vector locs = (Vector)featureLocHash.get(srcFeatureId);
+ for(int j=0;j<locs.size();j++)
+ {
+ FeatureLoc featureLoc = (FeatureLoc)locs.get(j);
+ featureLoc.setFeatureBySrcFeatureId(srcFeature);
+ }
+ }
+ }
+ sims.clear();
+
+ //
+ // bulk load subject / query feature_dbxref's
+ //
+ final List featureDbXRefs = doc.getFeatureDbXRefsByFeatureId(queryAndSubjectFeatureIds);
+
+ for(int i=0;i<featureDbXRefs.size();i++)
+ {
+ Feature srcFeature = (Feature)featureDbXRefs.get(i);
+ Integer srcFeatureId = new Integer(srcFeature.getFeatureId());
+ if(featureLocHash.containsKey(srcFeatureId))
+ {
+ Vector locs = (Vector)featureLocHash.get(srcFeatureId);
+ for(int j=0;j<locs.size();j++)
+ {
+ FeatureLoc featureLoc = (FeatureLoc)locs.get(j);
+ featureLoc.getFeatureBySrcFeatureId().setFeatureDbXRefs(
+ srcFeature.getFeatureDbXRefs());
+ }
+ }
+ }
+ featureDbXRefs.clear();
+
+ //
+ // bulk load the match feature properties
+ //
+ final List matchFeaturesWithProps = doc.getFeaturePropByFeatureIds(
+ new Vector(matchFeatures.keySet()) );
+
+ for(int i=0; i<matchFeaturesWithProps.size(); i++)
+ {
+ Feature thisMatch = (Feature)matchFeaturesWithProps.get(i);
+ Integer featureId = new Integer(thisMatch.getFeatureId());
+ if(matchFeatures.containsKey(featureId))
+ {
+ Feature storedMatch = ((Feature)matchFeatures.get(featureId));
+ storedMatch.setFeatureProps(thisMatch.getFeatureProps());
+ //storedMatch.setDbXRef(thisMatch.getDbXRef());
+ }
+ }
+ matchFeaturesWithProps.clear();
+ }
+
+ /**
+ * Handle the loading of the data into a String
+ */
+ public String getString()
+ {
+ if(forceLoad)
+ return getHardString();
+ else
+ return getSoftString();
+ }
+
+ /**
+ * This returns the completed value, loading any lazy properties
+ * @return
+ */
+ private String getHardString()
+ {
+ final StringBuffer buff = new StringBuffer();
+
+ Collection featureLocs = matchFeature.getFeatureLocsForFeatureId();
+ Iterator it2 = featureLocs.iterator();
+
+ Collection analysisFeatures = matchFeature.getAnalysisFeatures();
+ Iterator it3 = analysisFeatures.iterator();
+ AnalysisFeature analysisFeature = null;
+
+ if(it3.hasNext()) // attached analysisfeature
+ {
+ analysisFeature = (AnalysisFeature) it3.next();
+ buff.append(analysisFeature.getAnalysis().getProgram()+";");
+ }
+
+ if(analysisFeature == null ||
+ matchFeature.getCvTerm().getName().equals("polypeptide_domain"))
+ {
+ // predictions and polypeptide_domains have dbxrefs
+ buff.append(getMatchFeatureDbXRefs());
+ }
+
+
+ org.gmod.schema.sequence.Feature subject = null;
+ org.gmod.schema.sequence.FeatureLoc queryLoc = null;
+ org.gmod.schema.sequence.FeatureLoc subjectLoc = null;
+
+ while(it2.hasNext())
+ {
+ org.gmod.schema.sequence.FeatureLoc featureLoc =
+ (org.gmod.schema.sequence.FeatureLoc)it2.next();
+
+ if(featureLoc.getSrcFeatureId() <= 0)
+ continue;
+
+ org.gmod.schema.sequence.Feature queryOrSubject =
+ featureLoc.getFeatureBySrcFeatureId();
+
+ if(queryOrSubject.getFeatureId() != featureId)
+ {
+ subject = queryOrSubject;
+ subjectLoc = featureLoc;
+ }
+ else
+ {
+ queryLoc = featureLoc;
+ }
+ }
+
+ if(subject != null)
+ {
+ if(subject.getDbXRef() != null)
+ {
+ buff.append(subject.getDbXRef().getDb().getName() + ":");
+ buff.append(subject.getDbXRef().getAccession());
+ }
+
+ Collection dbXRefs = subject.getFeatureDbXRefs();
+
+ if(dbXRefs != null && dbXRefs.size() > 0)
+ {
+ final StringBuffer buffDbXRefs = new StringBuffer();
+ Iterator it4 = dbXRefs.iterator();
+ while(it4.hasNext())
+ {
+ FeatureDbXRef featureDbXRef = (FeatureDbXRef) it4.next();
+ featureDbXRef.getDbXRef();
+ try
+ {
+ buffDbXRefs.append(featureDbXRef.getDbXRef().getDb().getName() + ":");
+ buffDbXRefs.append(featureDbXRef.getDbXRef().getAccession());
+ if(it4.hasNext())
+ buffDbXRefs.append(",");
+ }
+ catch(NullPointerException npe){}
+ }
+
+ if(buffDbXRefs.length() > 0)
+ {
+ buff.append(" (");
+ buff.append(buffDbXRefs);
+ buff.append(")");
+ }
+ }
+ buff.append(";");
+
+ List featureProps = new Vector(subject.getFeatureProps());
+ Collections.sort(featureProps, new FeaturePropComparator());
+
+ for(int i = 0; i < featureProps.size(); i++)
+ {
+ FeatureProp featureProp = (FeatureProp) featureProps.get(i);
+
+ if(featureProp.getValue() != null)
+ buff.append(featureProp.getValue().trim());
+ buff.append(";");
+ }
+
+ buff.append("length " + subject.getSeqLen());
+ }
+
+ if(matchFeature.getCvTerm().getName().equals("protein_match"))
+ buff.append(" aa; ");
+ else
+ buff.append(";");
+
+ if(analysisFeature != null && analysisFeature.getIdentity() != null)
+ buff.append("id="+analysisFeature.getIdentity()+"%;");
+ if(analysisFeature != null && analysisFeature.getSignificance() != null)
+ buff.append("E()="+analysisFeature.getSignificance()+";");
+ if(analysisFeature != null && analysisFeature.getRawScore() != null)
+ buff.append("score="+analysisFeature.getRawScore()+";");
+
+ if(queryLoc != null && queryLoc.getFmin().intValue() > -1)
+ {
+ final int fmin;
+ if(queryLoc.getFmin().compareTo(queryLoc.getFmax()) == 0)
+ fmin = queryLoc.getFmin().intValue();
+ else
+ fmin = queryLoc.getFmin().intValue()+1;
+ buff.append("query "+fmin+"-"+queryLoc.getFmax());
+ if(matchFeature.getCvTerm().getName().equals("protein_match"))
+ buff.append(" aa;");
+ else
+ buff.append(";");
+ }
+
+ if(subjectLoc != null && subjectLoc.getFmin().intValue() > -1)
+ {
+ int fmin = subjectLoc.getFmin().intValue()+1;
+ buff.append("subject "+fmin+"-"+subjectLoc.getFmax());
+ if(matchFeature.getCvTerm().getName().equals("protein_match"))
+ buff.append(" aa;");
+ else
+ buff.append(";");
+ }
+
+ if(matchFeature.getFeatureProps() != null)
+ {
+ List featureProps = new Vector(matchFeature.getFeatureProps());
+ Collections.sort(featureProps, new FeaturePropComparator());
+
+ for(int i=0; i<featureProps.size(); i++)
+ {
+ FeatureProp featureProp = (FeatureProp)featureProps.get(i);
+
+ final String cvTermName;
+ if(featureProp.getCvTerm().getName() == null ||
+ featureProp.getCvTerm().getName().equals("null"))
+ cvTermName = DatabaseDocument.getCvTermByCvTermId(
+ featureProp.getCvTerm().getCvTermId(), null).getName();
+ else
+ cvTermName = featureProp.getCvTerm().getName();
+
+ buff.append(cvTermName+"="+featureProp.getValue());
+ if(i < featureProps.size()-1)
+ buff.append(";");
+ }
+ }
+
+ lazyLoaded = true;
+ return new String(buff);
+ }
+
+ /**
+ * Get dbxrefs associated with the match feature
+ * @return
+ */
+ private String getMatchFeatureDbXRefs()
+ {
+ final StringBuffer dbXRefs = new StringBuffer();
+ final Collection featureDbXRefs = matchFeature.getFeatureDbXRefs();
+ final Iterator it3 = featureDbXRefs.iterator();
+ if(it3 == null)
+ return "";
+
+ while(it3.hasNext())
+ {
+ DbXRef dbXRef = ((FeatureDbXRef) it3.next()).getDbXRef();
+ dbXRefs.append(dbXRef.getDb().getName()+":"+
+ dbXRef.getAccession());
+
+ if( dbXRef.getDescription() != null &&
+ !dbXRef.getDescription().equals("") )
+ dbXRefs.append(" :\t"+dbXRef.getDescription());
+ dbXRefs.append(";");
+ }
+
+ final DbXRef dbXRef = matchFeature.getDbXRef();
+
+ if(dbXRef != null)
+ {
+ dbXRefs.append(dbXRef.getDb().getName()+":"+
+ dbXRef.getAccession());
+
+ /*if( dbXRef.getDescription() != null &&
+ !dbXRef.getDescription().equals("") )
+ dbXRefs.append(" :\t"+dbXRef.getDescription()+";");*/
+ dbXRefs.append(";");
+ }
+
+ return dbXRefs.toString();
+ }
+
+ private String getSoftString()
+ {
+ return new String("LAZY LOADING...;");
+ }
+
+ public boolean isForceLoad()
+ {
+ return forceLoad;
+ }
+
+ public void setForceLoad(boolean forceLoad)
+ {
+ this.forceLoad = forceLoad;
+ }
+
+ public Feature getMatchFeature()
+ {
+ return matchFeature;
+ }
+
+ public boolean isLazyLoaded()
+ {
+ return lazyLoaded;
+ }
+
+ class FeaturePropComparator implements Comparator
+ {
+ public int compare(Object o1, Object o2)
+ {
+ int rank1 = ((FeatureProp)o1).getRank();
+ int rank2 = ((FeatureProp)o2).getRank();
+ return rank1-rank2;
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/chado/GmodDAO.java b/uk/ac/sanger/artemis/chado/GmodDAO.java
new file mode 100644
index 0000000..199c5c0
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/GmodDAO.java
@@ -0,0 +1,599 @@
+/*
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.gmod.schema.analysis.AnalysisFeature;
+import org.gmod.schema.cv.Cv;
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.dao.*;
+import org.gmod.schema.general.Db;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.pub.Pub;
+import org.gmod.schema.pub.PubDbXRef;
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureCvTermDbXRef;
+import org.gmod.schema.sequence.FeatureCvTermProp;
+import org.gmod.schema.sequence.FeatureCvTermPub;
+import org.gmod.schema.sequence.FeatureDbXRef;
+import org.gmod.schema.sequence.FeatureLoc;
+
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+
+public abstract class GmodDAO
+ implements SequenceDaoI, SchemaDaoI, OrganismDaoI, CvDaoI, PubDaoI, GeneralDaoI
+{
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(GmodDAO.class);
+
+ public abstract Graph getGraph(final Integer graphId);
+ public abstract List getGraphs(final Integer featureId);
+ public abstract List getTableColumns(final String tableName);
+
+ public abstract List getOrganismsContainingSrcFeatures();
+ public abstract List getSimilarityMatchesByFeatureIds(final List featureIds);
+ public abstract List getSimilarityMatches(final Integer srcFeatureId);
+ public abstract List getClustersByFeatureIds(final List featureIds);
+ public abstract List getParentFeaturesByChildFeatureIds(final List subjectIds);
+ public abstract Feature getLazyFeatureNoResiduesById(final Integer featureId);
+
+
+ public abstract List getFeatureLocsByFeatureId(int featureId);
+ public abstract List getFeatureLocsBySrcFeatureId(int srcFeatureId);
+
+ /**
+ * Return a <code>List</code> of featureLoc's corresponding for a
+ * <code>List</code> of feature_id's.
+ * @param featureIds the list of featureIds to search
+ * @return a (possibly empty) List<Feature>
+ */
+ public abstract List getFeatureLocsByListOfIds(final Collection featureIds);
+
+ /**
+ * Return all the Feature.featureDbXRefs for a <code>List</code> of feature_id's.
+ * These are grouped by their feature_id and returned in a <code>List</code>
+ * of Feature's.
+ * @param featureIds the list of featureIds to search
+ * @return a (possibly empty) List<Feature>
+ */
+ public abstract List getFeatureDbXRefsByFeatureId(final List featureIds);
+
+ /**
+ * Return a <code>List</code> of feature's corresponding for a
+ * <code>List</code> of feature_id's.
+ * @param featureIds the list of featureIds to search
+ * @return a (possibly empty) List<Feature>
+ */
+ public abstract List getFeaturesByListOfIds(final List featureIds);
+
+ /**
+ * Return a list of chado features with residues
+ * with residues.
+ * @return the <code>List</code> of <code>Feature</code> objects
+ */
+ public abstract List getResidueFeatures(final Integer organismId);
+
+ /**
+ * Return a list of chado features with residues
+ * with residues.
+ * @param commonName - organism common name
+ * @return the <code>List</code> of <code>Feature</code> objects
+ */
+ public abstract List getResidueFeaturesByOrganismCommonName(final String commonName);
+
+ /**
+ * Get the residues (sub-sequence) for a feature given it's
+ * uniquename
+ * @param uniqueName
+ * @return
+ */
+ public abstract List getResiduesByUniqueName(final String uniqueName);
+
+ /**
+ * Return all the Feature.featureProps for a <code>List</code> of feature_id's.
+ * These are grouped by their feature_id and returned in a <code>List</code>
+ * of Feature's.
+ * @param featureIds
+ * @return a (possibly empty) List<Feature>
+ */
+ public abstract List getFeaturePropByFeatureIds(final List featureIds);
+
+ /**
+ * Return a <code>List</code> of FeatureCvTerm's for all Feature's on
+ * the given srcFeatureId
+ * @param srcFeatureId srcfeature_id
+ * @return
+ */
+ public abstract List getFeatureCvTermsBySrcFeature(Feature srcFeature);
+
+ /**
+ * Return the FeatureCvTremDbXRef's for all Feature's given their srcfeature_id
+ * @param srcFeatureId
+ * @return
+ */
+ public abstract List getFeatureCvTermDbXRefBySrcFeature(Feature srcFeature);
+
+ /**
+ * Return the FeatureCvTermPub's for all Feature's given their srcfeature_id
+ * @param srcfeature_id
+ * @return
+ */
+ public abstract List getFeatureCvTermPubBySrcFeature(Feature srcFeature);
+
+ /**
+ * Return the FeaturePub's for all Feature's given their srcfeature_id
+ * @param srcFeatureId
+ * @return
+ */
+ public abstract List getFeaturePubsBySrcFeature(Feature srcFeature);
+
+ /**
+ * Return the FeaturePub's for a Feature
+ * @param feature
+ * @return
+ */
+ public abstract List getFeaturePubsByFeature(Feature feature);
+
+ /**
+ * Return the FeatureSynonym's for all Feature's given their srcfeature_id
+ * @param srcFeatureId
+ * @return
+ */
+ public abstract List getFeatureSynonymsBySrcFeature(Feature srcFeature);
+
+ /**
+ * Return the FeatureSynonym's for all Feature's given their feature_id's
+ * @param
+ * @return
+ */
+ public abstract List getFeatureSynonymsByFeatureIds(List featureIds);
+
+ /**
+ * Return a list of features that have this particular cvterm
+ * @param cvTermName the CvTerm name
+ * @return a (possibly empty) List<Feature> of children
+ */
+ public List getFeaturesByCvTermNameAndCvName(String cvTermName, String cvName)
+ {
+ return null;
+ }
+
+
+ public List getFeaturesByOrganism(Organism organism)
+ {
+ return null;
+ }
+
+ public List getFeaturesByUniqueNames(List name)
+ {
+ return null;
+ }
+
+ //////
+ ////// SchemaDaoI
+ //////
+ //////
+
+
+ /**
+ * Return the FeatureDbXRef's for all Feature's given their srcfeature_id
+ * @param srcFeatureId
+ * @return
+ */
+ public abstract List getFeatureDbXRefsBySrcFeature(Feature srcFeature);
+
+ public List getFeaturesByAnyNameAndOrganism(String arg0, String arg1, String arg2)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getPossibleMatches(String arg0, CvTerm arg1, int arg2)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public AnalysisFeature getAnalysisFeatureFromFeature(Feature arg0)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ //////
+ ////// GeneralDaoI
+ //////
+ //////
+ /**
+ * Retrieve a database by name
+ *
+ * @param name the name to lookup
+ * @return the corresponding db, or null
+ */
+ public Db getDbByName(String name)
+ {
+ return null;
+ }
+
+ /**
+ * Retrieve the db xref corresponding to a given DB and accession number
+ *
+ * @param db the db the dbxref refers to
+ * @param accession the accession "number" the dbxref refers to
+ * @return the dbxref, or null
+ */
+ public DbXRef getDbXRefByDbAndAcc(Db db, String accession)
+ {
+ return null;
+ }
+
+ public abstract List getDbs();
+
+ //////
+ ////// OrganismDaoI
+ //////
+ //////
+ /**
+ * Get the organism corresponding to this id
+ *
+ * @param id the organism id (primary key) to lookup by
+ * @return the corresponding organism, or null
+ */
+ public Organism getOrganismById(int id)
+ {
+ return null;
+ }
+
+ /**
+ * Get the organism corresponding to this common name
+ *
+ * @param commonName the short name to look up
+ * @return the corresponding organism, or null
+ */
+ public abstract Organism getOrganismByCommonName(String commonName);
+
+ /**
+ * Get a list of the common name of all the organisms.
+ *
+ * @return a (possibly empty) List<String> of all the organisms' common names
+ */
+ public List findAllOrganismCommonNames()
+ {
+ return null;
+ }
+
+
+ //////
+ ////// CvDaoI
+ //////
+ //////
+
+ public abstract List getAllCvs();
+
+ public List getAllTermsInCvWithCount(Cv arg0)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public CvTerm getCvTermByDbXRef(DbXRef arg0)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getPossibleMatches(String arg0, Cv arg1, int arg2)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /**
+ * Get a CV by id
+ *
+ * @param id the cv id (primary key)
+ * @return the corresponding Cv, or null
+ */
+ public Cv getCvById(int id)
+ {
+ return null;
+ }
+
+ // TODO Should this return a list or just one?
+ /**
+ * Retrieve a controlled vocabulary by its name
+ *
+ * @param name the name to lookup
+ * @return the List<Cv> of matches, or null
+ */
+ public List getCvByName(String name)
+ {
+ return null;
+ }
+
+
+
+
+ /**
+ * Retrieve a CvTerm from the Gene Ontology
+ *
+ * @param value the
+ * @return the corresponding CvTerm, or null
+ */
+ public CvTerm getGoCvTermByAcc(String value)
+ {
+ return null;
+ }
+
+
+ /**
+ * Retrieve a CvTerm from the Gene Ontology via it's database entry
+ *
+ * @param id the database name eg GO:123456
+ * @return the corresponding CvTerm, or null
+ */
+ public CvTerm getGoCvTermByAccViaDb(final String id)
+ {
+ return null;
+ }
+
+
+ public List getFeatureCvTermsByFeatureAndCvTermAndNot(Feature arg0, CvTerm arg1, boolean arg2)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public FeatureDbXRef getFeatureDbXRefByFeatureAndDbXRef(Feature arg0, DbXRef arg1)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public boolean existsNameInOntology(String arg0, Cv arg1)
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+
+
+ //
+ //
+ // PubDaoI
+ //
+
+ public Pub getPubByDbXRef(DbXRef arg0)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Pub getPubById(int arg0)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Pub getPubByUniqueName(String arg0)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getPubPropByPubAndCvTerm(Pub arg0, CvTerm arg1)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+
+ //
+ //
+ // Common functions
+ //
+
+
+ /**
+ * Find a dbxref in the database and retrieve the associated
+ * dbxref_id and db_id
+ * @param dbXRef
+ * @return
+ */
+ protected DbXRef loadDbXRef(DbXRef dbXRef)
+ {
+ Integer db_id = getDbId(dbXRef.getDb());
+
+ if(db_id == null)
+ throw new RuntimeException("No database called " +
+ dbXRef.getDb().getName() +
+ " found -check the spelling!");
+
+ dbXRef.getDb().setDbId(db_id.intValue());
+
+ Integer dbxref_id = getDbXRefId(dbXRef);
+ if(dbxref_id == null)
+ {
+ dbXRef.setVersion("1");
+ // create a new accession entry in dbxref
+ insertDbXRef(dbXRef);
+ // now get the new dbxref_id
+ dbxref_id = getDbXRefId(dbXRef);
+ }
+
+ dbXRef.setDbXRefId(dbxref_id.intValue());
+ return dbXRef;
+ }
+
+ /**
+ * Find a Pub if it exists. If the Pub does not exist then create
+ * one.
+ * @param pub
+ * @return
+ */
+ protected Pub loadPub(Pub pub)
+ {
+ Pub pubResult = getPubByUniqueName(pub);
+
+ if(pubResult == null)
+ {
+ // define the pub.type_id
+ //
+ CvTerm cvTerm;
+
+ if(pub.getUniqueName().startsWith("PMID:") ||
+ pub.getUniqueName().startsWith("PubMed:"))
+ cvTerm = DatabaseDocument.getCvTermByCvAndCvTerm("unfetched", "genedb_literature");
+ else
+ cvTerm = DatabaseDocument.getCvTermByCvAndCvTerm("unknown", "genedb_literature");
+ pub.setCvTerm(cvTerm);
+
+ insertPub(pub);
+ pubResult = getPubByUniqueName(pub);
+
+ // add PubDbXRef
+ loadPubDbXRef(pubResult);
+ }
+
+ return pubResult;
+ }
+
+ /**
+ * Create PubDbXRef for new Pub's and to handle links to
+ * links to eg, pubmed.
+ * @param pub
+ */
+ private void loadPubDbXRef(final Pub pub)
+ {
+ try
+ {
+ int index = pub.getUniqueName().indexOf(':');
+ if (index > -1)
+ {
+ DbXRef dbXRef = new DbXRef();
+ dbXRef.setAccession(pub.getUniqueName().substring(index + 1));
+ Db db = new Db();
+ db.setName(pub.getUniqueName().substring(0, index));
+ dbXRef.setDb(db);
+ dbXRef = loadDbXRef(dbXRef);
+
+ PubDbXRef pubDbXRef = new PubDbXRef();
+ pubDbXRef.setDbXRef(dbXRef);
+ pubDbXRef.setPub(pub);
+ insertPubDbXRef(pubDbXRef);
+ }
+ }
+ catch (Exception e)
+ {
+ logger4j.warn("GmodDAO.loadPubDbXRef() :: "+e.getMessage());
+ }
+ }
+
+ /**
+ * Insert a feature_cvterm and associated feature_cvtermprop's,
+ * feature_cvterm_dbxref's and feature_cvterm_pub.
+ * @param feature_cvterm
+ */
+ protected void insertAllFeatureCvTerm(final FeatureCvTerm feature_cvterm)
+ {
+ // get the pub_id and create a new Pub if necessary
+ if(feature_cvterm.getPub() != null)
+ feature_cvterm.setPub( loadPub(feature_cvterm.getPub()) );
+
+ insertFeatureCvTerm(feature_cvterm);
+
+ //
+ // get the current feature_id sequence value
+ int feature_cvterm_id = getCurrval("feature_cvterm_feature_cvterm_id_seq");
+ feature_cvterm.setFeatureCvTermId(feature_cvterm_id);
+
+ if(feature_cvterm.getFeatureCvTermProps() != null)
+ {
+ Collection featureCvTermProps = feature_cvterm.getFeatureCvTermProps();
+ Iterator it = featureCvTermProps.iterator();
+ while(it.hasNext())
+ {
+ FeatureCvTermProp featureCvTermProp = (FeatureCvTermProp)it.next();
+ featureCvTermProp.setFeatureCvTerm(feature_cvterm);
+
+ insertFeatureCvTermProp(featureCvTermProp);
+ }
+ }
+
+ // feature_cvterm_pub's
+ if(feature_cvterm.getFeatureCvTermPubs() != null)
+ {
+ Collection featureCvTermPubs = feature_cvterm.getFeatureCvTermPubs();
+ Iterator it = featureCvTermPubs.iterator();
+ while(it.hasNext())
+ {
+ FeatureCvTermPub featureCvTermPub = (FeatureCvTermPub)it.next();
+ featureCvTermPub.setFeatureCvTerm(feature_cvterm);
+
+ // get the pub_id and create a new Pub if necessary
+ featureCvTermPub.setPub( loadPub(featureCvTermPub.getPub()) );
+
+ insertFeatureCvTermPub(featureCvTermPub);
+ }
+
+ }
+ // feature_cvterm_dbxref's
+ if(feature_cvterm.getFeatureCvTermDbXRefs() != null)
+ {
+ Collection featureCvTermDbXRefs = feature_cvterm.getFeatureCvTermDbXRefs();
+ Iterator it = featureCvTermDbXRefs.iterator();
+ while(it.hasNext())
+ {
+ FeatureCvTermDbXRef featureCvTermDbXRef = (FeatureCvTermDbXRef)it.next();
+ featureCvTermDbXRef.setFeatureCvTerm(feature_cvterm);
+
+ // look for dbxref in the database
+ DbXRef dbxref = loadDbXRef(featureCvTermDbXRef.getDbXRef());
+ insertFeatureCvTermDbXRef(featureCvTermDbXRef);
+ }
+ }
+ }
+
+ protected abstract Integer getDbId(Db db);
+ protected abstract Integer getDbXRefId(DbXRef dbXRef);
+ protected abstract void insertDbXRef(DbXRef dbXRef);
+ protected abstract Pub getPubByUniqueName(Pub pub);
+ protected abstract void insertPub(Pub pub);
+ protected abstract void insertPubDbXRef(PubDbXRef pubDbXRef);
+ protected abstract void insertFeatureCvTerm(final FeatureCvTerm feature_cvterm);
+ protected abstract int getCurrval(String seq_id);
+ protected abstract void insertFeatureCvTermProp(FeatureCvTermProp featureCvTermProp);
+ protected abstract void insertFeatureCvTermPub(FeatureCvTermPub featureCvTermPub);
+ protected abstract void insertFeatureCvTermDbXRef(FeatureCvTermDbXRef featureCvTermDbXRef);
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/chado/Graph.java b/uk/ac/sanger/artemis/chado/Graph.java
new file mode 100644
index 0000000..b59634a
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/Graph.java
@@ -0,0 +1,94 @@
+/* Graph.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.chado;
+
+import java.io.Serializable;
+
+/**
+ * Database graph.
+ */
+public class Graph implements Serializable
+{
+ private static final long serialVersionUID = 1L;
+ // table columns
+ private int graphId;
+ private int featureId;
+ private String name;
+ private String description;
+ private byte[] data;
+
+ public Graph()
+ {
+ }
+
+ public int getGraphId()
+ {
+ return graphId;
+ }
+
+ public void setGraphId(int graphId)
+ {
+ this.graphId = graphId;
+ }
+
+ public int getFeatureId()
+ {
+ return featureId;
+ }
+
+ public void setFeatureId(int featureId)
+ {
+ this.featureId = featureId;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+ public void setDescription(String description)
+ {
+ this.description = description;
+ }
+
+ public byte[] getData()
+ {
+ return data;
+ }
+
+ public void setData(byte[] data)
+ {
+ this.data = data;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/chado/IBatisDAO.java b/uk/ac/sanger/artemis/chado/IBatisDAO.java
new file mode 100644
index 0000000..cebc65d
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/IBatisDAO.java
@@ -0,0 +1,1401 @@
+/* IBatisDAO /* IBatisDAO
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.sql.*;
+
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureCvTermDbXRef;
+import org.gmod.schema.sequence.FeatureCvTermProp;
+import org.gmod.schema.sequence.FeatureCvTermPub;
+import org.gmod.schema.sequence.FeatureDbXRef;
+import org.gmod.schema.sequence.FeatureProp;
+import org.gmod.schema.sequence.FeaturePub;
+import org.gmod.schema.sequence.Synonym;
+import org.gmod.schema.sequence.FeatureLoc;
+import org.gmod.schema.sequence.FeatureRelationship;
+import org.gmod.schema.sequence.FeatureSynonym;
+import org.gmod.schema.general.Db;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.pub.Pub;
+import org.gmod.schema.pub.PubDbXRef;
+import org.gmod.schema.analysis.AnalysisFeature;
+import org.gmod.schema.cv.Cv;
+import org.gmod.schema.cv.CvTerm;
+
+import javax.sql.DataSource;
+import javax.swing.JPasswordField;
+
+/**
+ *
+ * iBATIS implemetation of the <code>DAO</code> data
+ * access interface.
+ *
+ */
+public class IBatisDAO extends GmodDAO
+{
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(IBatisDAO.class);
+ private SqlMapClientWrapper sqlMap;
+ private boolean featureCvTermRank = true;
+ private boolean featureCvTermRankChecked = false;
+
+ /**
+ * Define a iBatis data access object. This uses <code>DbSqlConfig</code>
+ * to read the configuration in. The system property <quote>chado</quote>
+ * can be used to define the database location <i>e.g.</i>
+ * -Dchado=host:port/database?user
+ */
+ public IBatisDAO(final JPasswordField pfield)
+ {
+ SqlMapClientWrapper sqlMap = new SqlMapClientWrapper(pfield);
+
+ this.sqlMap = sqlMap;
+
+/* DbSqlConfig sql_config = new DbSqlConfig();
+ sql_config.init(pfield);
+ this.sqlMap = sql_config.getSqlMapInstance();*/
+ }
+
+ public void close() throws SQLException
+ {
+ sqlMap.close();
+ }
+
+ public DataSource getDataSource() throws SQLException
+ {
+ return sqlMap.getSqlMap().getDataSource();
+ }
+
+ /**
+ * Test to see if this is an old chado database version
+ * without the feature_cvterm.rank column.
+ * @return true if the feature_cvterm.rank column exists.
+ */
+ private boolean isFeatureCvTermRank()
+ {
+ if(featureCvTermRankChecked)
+ return featureCvTermRank;
+
+ String schema = ArtemisUtils.getCurrentSchema();
+ // check column names
+ List list = sqlMap.queryForList("getFeatureCvTermColumnsForASchema", schema);
+ featureCvTermRank = false;
+ for(int i=0; i<list.size(); i++)
+ {
+ if( ((String)list.get(i)).equals("rank") )
+ {
+ featureCvTermRank = true;
+ break;
+ }
+ }
+
+ featureCvTermRankChecked = true;
+ return featureCvTermRank;
+ }
+
+ //////
+ ////// GeneralDaoI
+ //////
+ //////
+ public List getDbs()
+ {
+ return sqlMap.queryForList("getDbs", null);
+ }
+
+ //////
+ ////// SequenceDaoI
+ //////
+ //////
+
+ public Feature getLazyFeatureNoResiduesById(final Integer featureId)
+ {
+ return (Feature)sqlMap.queryForObject("getLazyFeatureNoResiduesById",featureId);
+ }
+
+ public List getFeatureDbXRefsByFeatureId(final List featureIds)
+ {
+ return sqlMap.queryForList("getFeatureDbXRefsByFeatureId",featureIds);
+ }
+
+ public List getResiduesByUniqueName(final String uniqueName)
+ {
+ return sqlMap.queryForList("getResiduesByUniqueName",uniqueName);
+ }
+
+ public List getResidueFeatures(final Integer organismId)
+ {
+
+ CvTerm cvTerm = getCvTermByNameAndCvName("top_level_seq", "genedb_misc");
+ if(cvTerm != null)
+ return sqlMap.queryForList("getTopLevelFeatures",organismId);
+ else
+ return sqlMap.queryForList("getResidueFeatures",organismId);
+ }
+
+ public List getResidueFeaturesByOrganismCommonName(final String commonName)
+ {
+ return sqlMap.queryForList("getResidueFeaturesByOrganismCommonName",commonName);
+ }
+
+ public List getFeatureLocsByListOfIds(final Collection featureIds)
+ {
+ return sqlMap.queryForList("getFeatureLocsByListOfIds",featureIds);
+ }
+
+ public List getParentFeaturesByChildFeatureIds(final List featureIds)
+ {
+ return sqlMap.queryForList("getParentFeaturesByChildFeatureIds",featureIds);
+ }
+
+ public List getFeatureCvTermsBySrcFeature(Feature srcFeature)
+ {
+ if(isFeatureCvTermRank())
+ {
+ logger4j.debug("USE getFeatureCvTermsBySrcFeature()");
+ return sqlMap.queryForList("getFeatureCvTermsBySrcFeature",
+ srcFeature);
+ }
+ else
+ {
+ logger4j.debug("USE getFeatureCvTermsNoRankBySrcFeature()");
+ return sqlMap.queryForList("getFeatureCvTermsNoRankBySrcFeature",
+ srcFeature);
+ }
+ }
+
+ public List getFeaturePubsBySrcFeature(final Feature srcFeature)
+ {
+ return sqlMap.queryForList("getFeaturePubsBySrcFeature",
+ srcFeature);
+ }
+
+
+ public List getFeaturePubsByFeature(final Feature feature)
+ {
+ return sqlMap.queryForList("getFeaturePubsByFeature",
+ feature);
+ }
+
+ public List getClustersByFeatureIds(final List featureIds)
+ {
+ return sqlMap.queryForList("getLazyClustersByFeatureIds", featureIds);
+ }
+
+ /**
+ * Returns matches for all features on a given srcfeature
+ */
+ public List getSimilarityMatches(final Integer srcFeatureId)
+ {
+ return sqlMap.queryForList("getLazySimilarityMatches", srcFeatureId);
+ }
+
+ /**
+ * Returns matches for a list of feature_id's
+ */
+ public List getSimilarityMatchesByFeatureIds(final List featureIds)
+ {
+ return sqlMap.queryForList("getLazySimilarityMatchesByFeatureIds", featureIds);
+ }
+
+ public List getFeaturePropByFeatureIds(final List featureIds)
+ {
+ return sqlMap.queryForList("getFeaturePropByFeatureIds", featureIds);
+ }
+
+ /**
+ * Return the feature corresponding to this feature_id
+ *
+ * @param id the systematic id
+ * @return the Feature, or null
+ */
+ public Feature getFeatureById(int id)
+ {
+ org.gmod.schema.sequence.Feature feature =
+ new org.gmod.schema.sequence.Feature();
+ feature.setFeatureId(id);
+
+ if(isFeatureCvTermRank())
+ return (Feature)sqlMap.queryForObject("getLazyFeature", feature);
+ else
+ return (Feature)sqlMap.queryForObject("getLazyFeatureNoFeatureCvTermRank", feature);
+ }
+
+ public List getFeaturesByListOfIds(final List featureIds)
+ {
+ return sqlMap.queryForList("getFeaturesByListOfIds", featureIds);
+ }
+
+ public List getFeaturesByUniqueName(String uniquename)
+ {
+ org.gmod.schema.sequence.Feature feature =
+ new org.gmod.schema.sequence.Feature();
+ feature.setUniqueName(uniquename);
+
+ if(isFeatureCvTermRank())
+ return sqlMap.queryForList("getLazyFeature", feature);
+ else
+ return sqlMap.queryForList("getLazyFeatureNoFeatureCvTermRank", feature);
+ }
+
+ /**
+ * Return a <code>Feature</code> based on its uniquename and type_id
+ */
+ public Feature getFeatureByUniqueName(String uniquename, String featureType)
+ {
+ org.gmod.schema.sequence.Feature feature =
+ new org.gmod.schema.sequence.Feature();
+ feature.setUniqueName(uniquename);
+
+ CvTerm cvTerm = new CvTerm();
+ cvTerm.setName(featureType);
+ feature.setCvTerm(cvTerm);
+
+ if(isFeatureCvTermRank())
+ return (Feature)sqlMap.queryForObject("getLazyFeatureExact", feature);
+ else
+ return (Feature)sqlMap.queryForObject("getLazyFeatureExactNoFeatureCvTermRank", feature);
+ }
+
+
+ /**
+ * Return a list of features with any current (ie non-obsolete) name or synonym
+ * @param name the lookup name
+ * @return a (possibly empty) List<Feature> of children with this current name
+ */
+ public List getFeaturesByAnyCurrentName(String name)
+ {
+ final Synonym alias = new Synonym();
+ alias.setName(name);
+
+ List feature_synonym_list =
+ sqlMap.queryForList("getFeatureSynonymsByName", alias);
+
+ Feature feature = new Feature();
+ feature.setUniqueName(name);
+ feature.setFeatureSynonyms(feature_synonym_list);
+
+ if(isFeatureCvTermRank())
+ return sqlMap.queryForList("getLazyFeature", feature);
+ else
+ return sqlMap.queryForList("getLazyFeatureNoFeatureCvTermRank", feature);
+ }
+
+ /**
+ * Return a list of features with this name or synonym (including obsolete names)
+ *
+ * @param name the lookup name
+ * @return a (possibly empty) List<Feature> of children with this name
+ */
+ public List getFeaturesByAnyName(String name, String featureType)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of features located on a source Feature, within a given range
+ *
+ * @param min the minimum (interbase) coordinate
+ * @param max the maximum (interbase) coordinate
+ * @param strand
+ * @param parent the source feature
+ * @param type
+ * @return a List<Feature> which ??? this range
+ */
+ public List getFeaturesByRange(int min, int max, int strand,
+ Feature parent, String type)
+ {
+ Feature feature = new Feature();
+ FeatureLoc featureLoc = new FeatureLoc();
+ featureLoc.setFmin(new Integer(min));
+ featureLoc.setFmax(new Integer(max));
+ feature.setFeatureLoc(featureLoc);
+ featureLoc.setFeatureBySrcFeatureId(parent);
+ return sqlMap.queryForList("getFeature", feature);
+ }
+
+ /**
+ * This can be used to get individual features or children.
+ * If Feature.featureloc.srcfeature_id is set this is used
+ * to return the children of that srcfeature_id.
+ * @param feature the feature to query
+ * @return the <code>List</code> of child <code>Feature</code> objects
+ */
+ public List getFeaturesByLocatedOnFeature(final Feature feature)
+ {
+ if(isFeatureCvTermRank())
+ return sqlMap.queryForList("getFeature", feature);
+ else
+ return sqlMap.queryForList("getFeatureNoFeatureCvTermRank", feature);
+ }
+
+ /**
+ * Return the FeatureCvTerm that links a given Feature and CvTerm,
+ * with a given value of 'not'
+ *
+ * @param feature the Feature to test the link for
+ * @param cvTerm the CvTerm to test the link for
+ * @param not test for the not flag in the FeatureCvTerm
+ * @return the Feature, or null
+ */
+ public FeatureCvTerm getFeatureCvTermByFeatureAndCvTerm(
+ Feature feature,
+ CvTerm cvTerm, boolean not)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of FeatureCvterm's for a Feature, or a list
+ * of all FeatureCvTerm's if Feature is null.
+ * @param feature the Feature to retrieve associated FeatureCvTerm's
+ * @return the FeatureCvTerm's
+ */
+ public List getFeatureCvTermsByFeature(Feature feature)
+ {
+ if(isFeatureCvTermRank())
+ return
+ sqlMap.queryForList("getFeatureCvTermsByFeature", feature);
+ else
+ return
+ sqlMap.queryForList("getFeatureCvTermsNoRankByFeature", feature);
+ }
+
+ /**
+ * Return a synonym of the given name and type if it exists
+ * @param name the name to lookup
+ * @param type the type of the Synonym
+ * @return a Synonym, or null
+ */
+ public Synonym getSynonymByNameAndCvTerm(
+ String name, CvTerm type)
+ {
+ Synonym synonym = new Synonym();
+ synonym.setName(name);
+ synonym.setCvTerm(type);
+
+ return (Synonym)sqlMap.queryForObject("getSynonymByNameAndType",
+ synonym);
+ }
+
+ /**
+ * Return a list of FeatureSynonyms which link a given Feature
+ * and Synonym
+ * @param feature the test Feature
+ * @param synonym the test Synonym
+ * @return a (possibly empty) List<FeatureSynonym>
+ */
+ public List getFeatureSynonymsByFeatureAndSynonym(
+ Feature feature, Synonym synonym)
+ {
+ return
+ sqlMap.queryForList("getFeatureSynonymsByName", synonym);
+ }
+
+ public List getFeatureSynonymsBySrcFeature(final Feature srcFeature)
+ {
+ return
+ sqlMap.queryForList("getFeatureSynonymsBySrcFeature", srcFeature);
+ }
+
+ public List getFeatureSynonymsByFeatureIds(final List featuresIds)
+ {
+ return
+ sqlMap.queryForList("getFeatureSynonymsByFeatureIds", featuresIds);
+ }
+
+ /**
+ * Return all the FeatureDbXRefs for a given feature, <b>specified by name</b>, or all if
+ * <code>null</code> is passed
+ *
+ * @param uniqueName the uniquename of a Feature, or null for all FeatureDbXRefs
+ * @return a (possibly empty) List<FeatureDbXRefI>
+ */
+ public List getFeatureDbXRefsByFeatureUniquename(final String uniqueName)
+ {
+ Feature feature = new Feature();
+ feature.setUniqueName(uniqueName);
+
+ return sqlMap.queryForList("getFeatureDbXRef", feature);
+ }
+
+ public List getFeatureDbXRefsBySrcFeature(final Feature srcFeature)
+ {
+ return sqlMap.queryForList("getFeatureDbXRefsBySrcFeature", srcFeature);
+ }
+
+
+ /**
+ * Return the list of FeatureSynonyms for a given Feature, <b>specified by name</b>, or all if
+ * <code>null</code> is passed
+ *
+ * @param uniqueName the uniquename of a Feature, or null for all
+ * @return a (possibly empty) List<FeatureSynonymI> of matching synonyms
+ */
+ public List getFeatureSynonymsByFeatureUniquename(final String uniqueName)
+ {
+ Feature feature = new Feature();
+ feature.setUniqueName(uniqueName);
+
+ return sqlMap.queryForList("getFeatureSynonymsByUniquename", feature);
+ }
+
+ public List getAllFeatureSynonymsAsFeature()
+ {
+ return sqlMap.queryForList("getAllFeatureSynonymsAsFeature", null);
+ }
+
+ /**
+ * Return the list of Features for a given GO number
+ *
+ *
+ * @param go the GO number
+ * @return a (possibly empty) List<Feature> of matching genes
+ */
+ public List getFeatureByGO(final String go)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of features contained in this organisms with this name or synonym (including obsolete names). The
+ * name can contain an SQL wildcard (%)
+ *
+ * @param name the lookup name
+ * @param featureType the type of feature to return eg "gene"
+ * @param organisms the list of organisms
+ * @return a (possibly empty) List<Feature> of children with this name
+ */
+ public List getFeaturesByAnyNameAndOrganism(String nl,List ids,String featureType)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of features that have this particular cvterm
+ *
+ *
+ * @param cvTermName the CvTerm name
+ * @return a (possibly empty) List<Feature> of children
+ */
+ public List getFeaturesByCvTermName(String cvTermName)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of top-level features
+ * @return a (possibly empty) List<Feature> of children
+ */
+ public List getTopLevelFeatures()
+ {
+ return null;
+ }
+
+
+ /**
+ * Get a list of all FeatureCvTermDbXRef's for a Feature, or a list
+ * of all FeatureCvTermDbXRef's if Feature is null.
+ * @param feature the Feature to retrieve associated FeatureCvTermDbXRef's
+ * @return the FeatureCvTermDbXRef's
+ */
+ public List getFeatureCvTermDbXRefByFeature(Feature feature)
+ {
+ return sqlMap.queryForList("getFeatureCvTermDbXRefByFeature", feature);
+ }
+
+ /**
+ * Get a list of all FeatureCvTermDbXRef's for a Feature, or a list
+ * of all FeatureCvTermDbXRef's if Feature is null.
+ * @param feature the Feature to retrieve associated FeatureCvTermDbXRef's
+ * @return the FeatureCvTermDbXRef's
+ */
+ public List getFeatureCvTermDbXRefBySrcFeature(final Feature srcFeature)
+ {
+ return sqlMap.queryForList("getFeatureCvTermDbXRefBySrcFeature",
+ srcFeature);
+ }
+
+
+
+ /**
+ * Get a list of all FeatureCvTermPub's for a Feature, or a list
+ * of all FeatureCvTermPub's if Feature is null.
+ * @param feature the Feature to retrieve associated FeatureCvTermPub's
+ * @return the FeatureCvTermPub's
+ */
+ public List getFeatureCvTermPubByFeature(Feature feature)
+ {
+ return sqlMap.queryForList("getFeatureCvTermPubByFeature", feature);
+ }
+
+
+ /**
+ * Get a list of all FeatureCvTermPub's for a Feature, or a list
+ * of all FeatureCvTermPub's if Feature is null.
+ * @param feature the Feature to retrieve associated FeatureCvTermPub's
+ * @return the FeatureCvTermPub's
+ */
+ public List getFeatureCvTermPubBySrcFeature(final Feature srcFeature)
+ {
+ return sqlMap.queryForList("getFeatureCvTermPubBySrcFeature",
+ srcFeature);
+ }
+
+
+ public List getProducts()
+ {
+ return null;
+ }
+
+
+ //////
+ ////// SchemaDaoI
+ //////
+ //////
+
+ /**
+ * Return a list of chado features with residues
+ * with residues.
+ * @return the <code>List</code> of <code>Feature</code> objects
+ */
+ public List getResidueFeatures()
+ {
+ CvTerm cvTerm = getCvTermByNameAndCvName("top_level_seq", "genedb_misc");
+ if(cvTerm != null)
+ return sqlMap.queryForList("getTopLevelFeatures",null);
+ else
+ return sqlMap.queryForList("getResidueFeatures",null);
+ }
+
+ public List getOrganismsContainingSrcFeatures()
+ {
+ CvTerm cvTerm = getCvTermByNameAndCvName("populated", "genedb_misc");
+ if(cvTerm != null)
+ return sqlMap.queryForList("getTopLevelOrganisms", null);
+ else
+ return sqlMap.queryForList("getOrganismsContainingSrcFeatures", null);
+ }
+
+ /**
+ *
+ * For a schema return the type_id's with residues.
+ * @param schema schema/organism name or null
+ * @return the <code>List</code> of type_id's as <code>String</code>
+ * objects
+ */
+ public List getResidueType(final String schema)
+ {
+ return sqlMap.queryForList("getResidueType", schema);
+ }
+
+ /**
+ *
+ * Get available schemas (as a <code>List</code> of <code>String</code>
+ * objects).
+ * @return the available schemas
+ */
+ public List getSchema()
+ {
+ return sqlMap.queryForList("getSchema", null);
+ }
+
+ //////
+ ////// CvDaoI
+ //////
+ //////
+ public List getAllCvs()
+ {
+ return sqlMap.queryForList("getAllCvs", null);
+ }
+
+ /**
+ * Retrieve a named CvTerm from a given Cv
+ *
+ * @param cvTermName the name of the cvterm
+ * @param cv the controlled vocabulary this cvterm is part of
+ * @return a (possibly empty) list of matching cvterms
+ */
+ public List getCvTermByNameInCv(String cvTermName, Cv cv)
+ {
+ CvTerm cvTerm = new CvTerm();
+ cvTerm.setName(cvTermName);
+ cvTerm.setCv(cv);
+
+ return sqlMap.queryForList("getCvterm", cvTerm);
+ }
+
+ /**
+ * Get the full list of cvterm_id and name as a <code>List</code> of
+ * <code>CvTerm</code> objects.
+ * @return the full list of cvterm_id and name
+ */
+ public List getCvTerms()
+ {
+ return sqlMap.queryForList("getCvterm", null);
+ }
+
+ /**
+ * Retrieve a named CvTerm from a given Cv
+ *
+ * @param cvTermName the name of the cvterm
+ * @param name the controlled vocabulary name this cvterm could be part of
+ * @return a (possibly empty) cvterm
+ */
+ public CvTerm getCvTermByNameAndCvName(String cvTermName, String name)
+ {
+ Cv cv = new Cv();
+ cv.setName(name);
+ CvTerm cvTerm = new CvTerm();
+ cvTerm.setName(cvTermName);
+ cvTerm.setCv(cv);
+
+ return (CvTerm)sqlMap.queryForObject("getCvterm", cvTerm);
+ }
+
+
+ public CvTerm getCvTermById(final int cvTermId)
+ {
+ return (CvTerm)sqlMap.queryForObject("getCvtermByCvTermId", new Integer(cvTermId));
+ }
+
+ //////
+ ////// OrganismDaoI
+ //////
+ //////
+
+ public List getOrganisms()
+ {
+ return sqlMap.queryForList("getOrganism", null);
+ }
+
+ public Organism getOrganismByCommonName(String commonName)
+ {
+ Organism o = new Organism();
+ o.setCommonName(commonName);
+ return (Organism)sqlMap.queryForObject("getOrganism", o);
+ }
+
+ //////
+ ////// PubDaoI
+ //////
+ //////
+
+ /**
+ * Get a list of all PubDbXRef's
+ * @return list of PubDbXRef's
+ */
+ public List getPubDbXRef()
+ {
+ return sqlMap.queryForList("getPubDbXRef", null);
+ }
+
+
+ //
+ // Graph data
+ public Graph getGraph(final Integer graphId)
+ {
+ //return (Graph) sqlMap.queryForObject("getGraph", graphId);
+ return null;
+ }
+
+
+ public List getGraphs(final Integer featureId)
+ {
+ return sqlMap.queryForList("getGraphs", featureId);
+ }
+
+ public List getTableColumns(String tableName)
+ {
+ return sqlMap.queryForList("getTableColumns", tableName);
+ }
+
+//
+// WRITE BACK
+//
+
+ /**
+ * Merge (update) an already persistent object back to the database (at the end of
+ * the current transaction, or depending upon flush mode). This method is defined in
+ * all the DAOs. It's recommended to call it through an appropriate one eg SequenceDaoI
+ * for FeatureI
+ *
+ * @param o The object to merge
+ */
+ public void merge(Object o)
+ {
+ if(o instanceof FeatureLoc)
+ updateFeatureLoc((FeatureLoc)o);
+ else if(o instanceof Feature)
+ {
+ /*Feature f = (Feature)o;
+ if(f.getSeqLen() > 0 &&
+ f.getCvTerm().getName().equals("region"))
+ {
+ // insert sequence region
+ sqlMap.insert("updateRegionSequence", o);
+ } */
+
+ if(o instanceof FeatureForUpdatingResidues)
+ {
+ if(! ((FeatureForUpdatingResidues)o).isResidueUpdate() )
+ sqlMap.update("updateFeatureLocByChangingSequence", o);
+ sqlMap.update("updateFeatureResidues", o);
+ }
+ else
+ sqlMap.update("updateFeature", o);
+ }
+ else if(o instanceof FeatureProp)
+ sqlMap.update("updateFeatureProp", o);
+ else if(o instanceof FeatureRelationship)
+ sqlMap.update("updateFeatureRelationshipsForSubjectId", o);
+ else if(o instanceof FeatureCvTerm)
+ sqlMap.update("updateFeatureCvTerm", o);
+
+ }
+
+
+ /**
+ * Save the object to the database (at the end of the current transaction,
+ * or depending upon flush mode). This method is defined in all the DAOs.
+ * It's recommended to call it through an appropriate one eg SequenceDaoI
+ * for FeatureI
+ * @param o The object to store
+ */
+ public void persist(Object o)
+ {
+ if(o instanceof FeatureProp)
+ sqlMap.insert("insertFeatureProp", o);
+ else if(o instanceof Feature)
+ insertFeature((Feature)o);
+ else if(o instanceof FeatureDbXRef)
+ insertFeatureDbXRef((FeatureDbXRef)o);
+ else if(o instanceof FeatureSynonym)
+ insertFeatureAlias((FeatureSynonym)o);
+ else if(o instanceof FeatureCvTerm)
+ insertAllFeatureCvTerm((FeatureCvTerm)o);
+ else if(o instanceof FeaturePub)
+ insertFeaturePub((FeaturePub)o);
+ else if(o instanceof FeatureRelationship)
+ insertFeatureRelationship((FeatureRelationship)o);
+ else if(o instanceof AnalysisFeature)
+ insertAnalysisFeature((AnalysisFeature)o);
+ else if(o instanceof CvTerm)
+ insertCvTerm((CvTerm)o);
+ }
+
+
+ /**
+ * Remove the object from the database (at the end of the current transaction,
+ * or depending upon flush mode). This method is defined in all the DAOs.
+ * It's recommended to call it through an appropriate one eg SequenceDaoI for
+ * FeatureI
+ * @param o The object to delete
+ */
+ public void delete(Object o)
+ {
+ if(o instanceof Feature)
+ sqlMap.delete("deleteFeature", o);
+ else if(o instanceof FeatureProp)
+ sqlMap.delete("deleteFeatureProp", o);
+ else if(o instanceof FeatureDbXRef)
+ sqlMap.delete("deleteFeatureDbXRef", o);
+ else if(o instanceof FeatureSynonym)
+ deleteFeatureSynonym((FeatureSynonym)o);
+ else if(o instanceof FeatureCvTerm)
+ sqlMap.delete("deleteFeatureCvTerm", o);
+ else if(o instanceof FeaturePub)
+ sqlMap.delete("deleteFeaturePub", o);
+ else if(o instanceof FeatureRelationship)
+ sqlMap.delete("deleteFeatureRelationship", o);
+ else if(o instanceof AnalysisFeature)
+ deleteAnalysisFeature((AnalysisFeature)o);
+ }
+
+ /**
+ * Update a featureLoc. This checks if there are multiple featureLocs
+ * associated with this feature and will update all of them.
+ * @param featureLoc
+ */
+ private void updateFeatureLoc(FeatureLoc featureLoc)
+ {
+ List featureLocs = sqlMap.queryForList("getFeatureLoc",
+ featureLoc.getFeatureByFeatureId().getFeatureId());
+
+ if(featureLocs.size() > 1)
+ {
+ // the feature appears to have multiple featureLocs
+ int srcFeatureId = featureLoc.getFeatureBySrcFeatureId().getFeatureId();
+ int newFmin = featureLoc.getFmin();
+ int newFmax = featureLoc.getFmax();
+ int diffFmin = 0;
+ int diffFmax = 0;
+
+ for(int i=0; i<featureLocs.size(); i++)
+ {
+ FeatureLoc thisFeatureLoc = (FeatureLoc) featureLocs.get(i);
+ int thisSrcFeatureId =
+ thisFeatureLoc.getFeatureBySrcFeatureId().getFeatureId();
+ if(srcFeatureId == thisSrcFeatureId)
+ {
+ diffFmin = newFmin-thisFeatureLoc.getFmin();
+ diffFmax = newFmax-thisFeatureLoc.getFmax();
+ break;
+ }
+ }
+
+ for(int i=0; i<featureLocs.size(); i++)
+ {
+ FeatureLoc thisFeatureLoc = (FeatureLoc) featureLocs.get(i);
+ int thisSrcFeatureId =
+ thisFeatureLoc.getFeatureBySrcFeatureId().getFeatureId();
+
+ if(srcFeatureId != thisSrcFeatureId)
+ {
+ // check to see if the featureloc fmin/fmax are marked as partial
+ // if they are then ignore as they are likely to be the end of
+ // a contig
+ boolean changed = false;
+ if(!thisFeatureLoc.isFminPartial())
+ {
+ thisFeatureLoc.setFmin(thisFeatureLoc.getFmin()+diffFmin);
+ changed = true;
+ }
+ if(!thisFeatureLoc.isFmaxPartial())
+ {
+ thisFeatureLoc.setFmax(thisFeatureLoc.getFmax()+diffFmax);
+ changed = true;
+ }
+ if(changed)
+ sqlMap.update("updateFeatureLoc", thisFeatureLoc);
+ }
+ }
+ }
+ sqlMap.update("updateFeatureLoc", featureLoc);
+ }
+
+ public List getFeatureLocsByFeatureId(int featureId)
+ {
+ return sqlMap.queryForList("getFeatureLoc",featureId);
+ }
+
+ public List getFeatureLocsBySrcFeatureId(int srcFeatureId)
+ {
+ return sqlMap.queryForList("getFeatureLocBySrcFeatureId",
+ srcFeatureId);
+ }
+
+ /**
+ * Insert a feature into the database defined by the <code>Feature</code>.
+ * @param feature the feature to insert
+ */
+ private void insertFeature
+ (final Feature feature)
+ {
+ final Organism organism;
+ if(feature.getFeatureId() > 0 || feature.getFeatureLoc() != null)
+ {
+ Integer organism_id = (Integer)sqlMap.queryForObject(
+ "getOrganismIdBySrcFeatureIdOrFeatureId", feature);
+ organism = new Organism();
+ organism.setOrganismId(organism_id.intValue());
+ }
+ else
+ organism = (Organism)sqlMap.queryForObject("getOrganism", feature.getOrganism());
+
+ feature.setOrganism(organism);
+
+ //
+ // insert feature into feature table
+
+ sqlMap.insert("insertFeature", feature);
+
+ //
+ // get the current feature_id sequence value
+ int feature_id = ((Integer)sqlMap.queryForObject("currval",
+ "feature_feature_id_seq")).intValue();
+
+ //
+ // insert feature location into featureloc
+ if(feature.getFeatureLoc() != null)
+ {
+ feature.setFeatureId(feature_id);
+ FeatureLoc featureLoc = feature.getFeatureLoc();
+ featureLoc.setFeatureByFeatureId(feature);
+ sqlMap.insert("insertFeatureLoc", featureLoc);
+ }
+
+ // insert feature relationships
+ if(feature.getFeatureRelationshipsForSubjectId() != null)
+ {
+ List parents = new Vector(
+ feature.getFeatureRelationshipsForSubjectId());
+
+ for(int i=0; i<parents.size(); i++)
+ {
+ FeatureRelationship feature_relationship =
+ (FeatureRelationship)parents.get(i);
+ sqlMap.insert("insertFeatureRelationship", feature_relationship);
+ }
+ }
+
+ if(feature.getFeatureProps() != null)
+ {
+ List featureProps = new Vector(feature.getFeatureProps());
+
+ for(int i=0; i<featureProps.size(); i++)
+ sqlMap.insert("insertFeatureProp", (FeatureProp)featureProps.get(i));
+ }
+ }
+
+ private void insertFeatureRelationship(FeatureRelationship feature_relationship)
+ {
+ sqlMap.insert("insertFeatureRelationship", feature_relationship);
+ }
+
+ /**
+ * Insert a feature_cvterm and associated feature_cvtermprop's,
+ * feature_cvterm_dbxref's and feature_cvterm_pub.
+ * @param feature_cvterm
+ */
+ private void insertFeaturePub(final FeaturePub featurePub)
+ {
+ // get the pub_id and create a new Pub if necessary
+ if(featurePub.getPub() != null)
+ featurePub.setPub( loadPub(featurePub.getPub()) );
+
+ sqlMap.insert("insertFeaturePub", featurePub);
+ }
+
+ /**
+ * Insert a feature_dbxref for a feature.
+ * @param feature_dbxref the <code>FeatureDbXRef</code>
+ */
+ private void insertFeatureDbXRef(final FeatureDbXRef feature_dbxref)
+ {
+ DbXRef dbXRef = loadDbXRef(feature_dbxref.getDbXRef());
+ feature_dbxref.setDbXRef(dbXRef);
+
+ // get the feature id's
+ List features = getFeaturesByUniqueName(
+ feature_dbxref.getFeature().getUniqueName());
+
+ feature_dbxref.getFeature().setFeatureId(
+ ((Feature)features.get(0)).getFeatureId() );
+
+ sqlMap.insert("insertFeatureDbXRef", feature_dbxref);
+ }
+
+ /**
+ * Insert analysis_feature, match feature and associated query and subject features
+ * @param analysisFeature
+ */
+ private void insertAnalysisFeature(AnalysisFeature analysisFeature)
+ {
+ Feature matchFeature = analysisFeature.getFeature();
+ Feature queryFeature = null;
+ Feature subjectFeature = null;
+ FeatureLoc queryLoc = null;
+ FeatureLoc subjectLoc = null;
+
+ Collection featureLocs = matchFeature.getFeatureLocsForFeatureId();
+ Iterator it = featureLocs.iterator();
+ while(it.hasNext())
+ {
+ FeatureLoc featureLoc = (FeatureLoc)it.next();
+
+ if(featureLoc.getFeatureBySrcFeatureId().getFeatureId() > 0 ||
+ featureLoc.getFeatureBySrcFeatureId().getFeatureId() == -1)
+ {
+ queryFeature = featureLoc.getFeatureBySrcFeatureId();
+ queryLoc = featureLoc;
+ }
+ else
+ {
+ subjectFeature = featureLoc.getFeatureBySrcFeatureId();
+ subjectLoc = featureLoc;
+ }
+ }
+
+ if(queryFeature.getFeatureId() == -1)
+ queryFeature = (Feature)getFeaturesByUniqueName(queryFeature.getUniqueName()).get(0);
+
+ Integer organism_id = (Integer)sqlMap.queryForObject(
+ "getOrganismIdBySrcFeatureIdOrFeatureId", queryFeature);
+ Organism organism = new Organism();
+ organism.setOrganismId(organism_id.intValue());
+ matchFeature.setOrganism(organism);
+ subjectFeature.setOrganism(organism);
+
+ // insert match feature
+ int value = 1;
+ List matches = getFeaturesByUniqueName(matchFeature.getUniqueName()+"_%");
+
+ for(int i=0; i<matches.size(); i++)
+ {
+ String name = ((Feature)matches.get(i)).getUniqueName();
+ int index = name.lastIndexOf('_');
+ name = name.substring(index+1);
+ if( Integer.parseInt(name) >= value )
+ value = Integer.parseInt(name)+1;
+ }
+
+ matchFeature.setUniqueName( matchFeature.getUniqueName()+"_"+value);
+ sqlMap.insert("insertFeature", matchFeature);
+ int matchFeatureId = ((Integer)sqlMap.queryForObject("currval",
+ "feature_feature_id_seq")).intValue();
+ matchFeature.setFeatureId(matchFeatureId);
+
+ // insert primary dbXRef
+ DbXRef primaryDbXRef = subjectFeature.getDbXRef();
+ primaryDbXRef = loadDbXRef(primaryDbXRef);
+ subjectFeature.setDbXRef(primaryDbXRef);
+
+ // insert subject feature
+ Feature f = getFeatureByUniqueName(subjectFeature.getUniqueName(),
+ subjectFeature.getCvTerm().getName());
+ if(f != null)
+ subjectFeature.setFeatureId(f.getFeatureId());
+ else
+ {
+ sqlMap.insert("insertFeature", subjectFeature);
+ int subjectFeatureId = ((Integer)sqlMap.queryForObject("currval",
+ "feature_feature_id_seq")).intValue();
+ subjectFeature.setFeatureId(subjectFeatureId);
+ }
+
+ // insert match featureLocs
+ queryLoc.setFeatureBySrcFeatureId(queryFeature);
+ queryLoc.setFeatureByFeatureId(matchFeature);
+ sqlMap.insert("insertFeatureLoc", queryLoc);
+
+ subjectLoc.setFeatureBySrcFeatureId(subjectFeature);
+ subjectLoc.setFeatureByFeatureId(matchFeature);
+ sqlMap.insert("insertFeatureLoc", subjectLoc);
+
+
+ // insert analysis
+ Integer analysisId = (Integer) sqlMap.insert("insertAnalysis", analysisFeature.getAnalysis());
+ analysisFeature.getAnalysis().setAnalysisId(analysisId.intValue());
+
+ // insert analysis feature
+ matchFeature.setFeatureId(matchFeatureId);
+ analysisFeature.setFeature(matchFeature);
+ sqlMap.insert("insertAnalysisFeature", analysisFeature);
+
+ // if subject feature not inserted add dbXRefs and featureProps
+ if(f == null)
+ {
+ // insert dbXRefs
+ Collection featureCvTermDbXRefs = subjectFeature.getFeatureDbXRefs();
+
+ if(featureCvTermDbXRefs != null)
+ {
+ it = featureCvTermDbXRefs.iterator();
+
+ while(it.hasNext())
+ insertFeatureDbXRef((FeatureDbXRef) it.next());
+ }
+
+ // insert subject featureprops
+ Collection featureProps = subjectFeature.getFeatureProps();
+ if(featureProps != null)
+ {
+ it = featureProps.iterator();
+
+ while(it.hasNext())
+ {
+ FeatureProp featureProp = (FeatureProp) it.next();
+ featureProp.setFeature(subjectFeature);
+ sqlMap.insert("insertFeatureProp", featureProp);
+ }
+ }
+ }
+
+ // insert match featureprops
+ Collection featureProps = matchFeature.getFeatureProps();
+ if(featureProps != null)
+ {
+ it = featureProps.iterator();
+
+ while(it.hasNext())
+ {
+ FeatureProp featureProp = (FeatureProp) it.next();
+ featureProp.setFeature(matchFeature);
+ sqlMap.insert("insertFeatureProp", featureProp);
+ }
+ }
+ }
+
+ /**
+ * Insert a cvterm
+ */
+ private void insertCvTerm(final CvTerm cvTerm)
+ {
+ Cv cv = (Cv)sqlMap.queryForObject("getCvByName", cvTerm.getCv().getName());
+ cvTerm.setCv(cv);
+
+ DbXRef dbXRef = cvTerm.getDbXRef();
+ dbXRef = loadDbXRef(dbXRef);
+ cvTerm.setDbXRef(dbXRef);
+
+ sqlMap.insert("insertCvTerm", cvTerm);
+ }
+
+ /**
+ * Insert a feature_synonym for a feature.
+ * @param feature_synonym the <code>FeatureSynonym</code>
+ */
+ private void insertFeatureAlias(final FeatureSynonym feature_synonym)
+ {
+ Object synonym =
+ sqlMap.queryForObject("getSynonymByNameAndType",
+ feature_synonym.getSynonym());
+
+ if(synonym == null)
+ {
+ logger4j.debug("synonym "+feature_synonym.getSynonym()+" needs inserting");
+ // create a new synonym name
+ sqlMap.insert("insertAlias", feature_synonym);
+
+ synonym =
+ sqlMap.queryForObject("getSynonymByNameAndType",
+ feature_synonym.getSynonym());
+ }
+
+ feature_synonym.setSynonym((Synonym)synonym);
+ sqlMap.insert("insertFeatureAlias", feature_synonym);
+ }
+
+
+ protected void insertFeatureCvTerm(final FeatureCvTerm feature_cvterm)
+ {
+ sqlMap.insert("insertFeatureCvTerm", feature_cvterm);
+ }
+
+ protected int getCurrval(String seq_id)
+ {
+ return ((Integer)sqlMap.queryForObject("currval",
+ seq_id)).intValue();
+ }
+
+ protected void insertFeatureCvTermProp(FeatureCvTermProp featureCvTermProp)
+ {
+ sqlMap.insert("insertFeatureCvTermProp", featureCvTermProp);
+ }
+
+ protected void insertFeatureCvTermPub(FeatureCvTermPub featureCvTermPub)
+ {
+ sqlMap.insert("insertFeatureCvTermPub", featureCvTermPub);
+ }
+
+ protected void insertFeatureCvTermDbXRef(FeatureCvTermDbXRef featureCvTermDbXRef)
+ {
+ sqlMap.insert("insertFeatureCvTermDbXRef", featureCvTermDbXRef);
+ }
+
+ private void deleteAnalysisFeature(AnalysisFeature analysisFeature)
+ {
+ //
+ Feature matchFeature = analysisFeature.getFeature();
+ List featureLocs = new Vector(matchFeature.getFeatureLocsForFeatureId());
+
+ Feature subjectFeature = null;
+ Feature queryFeature = null;
+ int nsubject = 0;
+ int nquery = 0;
+
+ for(int i=0; i<featureLocs.size(); i++)
+ {
+ FeatureLoc featureLoc = (FeatureLoc)featureLocs.get(i);
+
+ if(featureLoc.getFeatureBySrcFeatureId().getFeatureId() > 0 ||
+ featureLoc.getFeatureBySrcFeatureId().getFeatureId() == -1)
+ {
+ queryFeature = featureLoc.getFeatureBySrcFeatureId();
+ nquery = i;
+ }
+ else
+ {
+ subjectFeature = featureLoc.getFeatureBySrcFeatureId();
+ nsubject = i;
+ }
+ }
+
+ if(queryFeature.getFeatureId() == -1)
+ {
+ Feature feat =
+ (Feature)getFeaturesByUniqueName(queryFeature.getUniqueName()).get(0);
+ queryFeature.setFeatureId(feat.getFeatureId());
+ ((FeatureLoc)featureLocs.get(nquery)).setFeatureBySrcFeatureId(queryFeature);
+ }
+
+ /*
+ if(((FeatureLoc)featureLocs.get(0)).getFeatureBySrcFeatureId().getDbXRef() != null)
+ {
+ subjectFeature = ((FeatureLoc)featureLocs.get(0)).getFeatureBySrcFeatureId();
+ nsubject = 0;
+ }
+ else
+ {
+ subjectFeature = ((FeatureLoc)featureLocs.get(1)).getFeatureBySrcFeatureId();
+ nsubject = 1;
+ }
+ */
+
+ // look for SWALL: and UNIPROT:
+ subjectFeature.setUniqueName( "%"+subjectFeature.getDbXRef().getAccession() );
+ List subjectFeatures = sqlMap.queryForList("getLazyFeature", subjectFeature);
+ Integer matchFeatureId = null;
+
+ for(int i=0; i<subjectFeatures.size(); i++)
+ {
+ ((FeatureLoc)featureLocs.get(nsubject)).setFeatureBySrcFeatureId(
+ (Feature)subjectFeatures.get(i));
+ matchFeature.setFeatureLocsForFeatureId(featureLocs);
+ List result = sqlMap.queryForList("getFeatureIdBySrcFeatureId", matchFeature);
+ if(result != null)
+ {
+ matchFeatureId = (Integer)result.get(0);
+ break;
+ }
+ }
+
+ matchFeature.setFeatureId(matchFeatureId.intValue());
+ sqlMap.delete("deleteFeatureById", matchFeature);
+ }
+
+ /**
+ * Delete a feature_synonym for a feature.
+ * @param feature_synonym the <code>FeatureSynonym</code>
+ */
+ private int deleteFeatureSynonym(FeatureSynonym feature_synonym)
+ {
+ List feature_synonym_list =
+ sqlMap.queryForList("getFeatureSynonymsByName", feature_synonym.getSynonym());
+
+ feature_synonym.setSynonym(
+ ((FeatureSynonym)feature_synonym_list.get(0)).getSynonym() );
+
+ // check this name is not used some where else,
+ // i.e. in more than one row
+ if(feature_synonym_list.size() > 1)
+ return sqlMap.delete("deleteFeatureAlias", feature_synonym);
+ else
+ return sqlMap.delete("deleteAlias", feature_synonym);
+ }
+
+ protected Integer getDbId(Db db)
+ {
+ Integer dbId = (Integer)sqlMap.queryForObject("getDbId", db);
+
+ if(dbId == null)
+ {
+ List dbIds = sqlMap.queryForList("getDbIdIgnoreCase", db);
+ if(dbIds.size() > 0)
+ dbId = (Integer)dbIds.get(0);
+ }
+
+ return dbId;
+ }
+
+ protected Integer getDbXRefId(DbXRef dbXRef)
+ {
+ return (Integer)sqlMap.queryForObject("getDbXRefId", dbXRef);
+ }
+
+ protected void insertDbXRef(DbXRef dbXRef)
+ {
+ sqlMap.insert("insertDbXRef", dbXRef);
+ }
+
+ protected Pub getPubByUniqueName(Pub pub)
+ {
+ return (Pub)sqlMap.queryForObject("getPubByUniqueName", pub);
+ }
+
+ protected void insertPub(Pub pub)
+ {
+ sqlMap.insert("insertPub", pub);
+ }
+
+ protected void insertPubDbXRef(PubDbXRef pubDbXRef)
+ {
+ sqlMap.insert("insertPubDbXRef", pubDbXRef);
+ }
+
+ public void startTransaction() throws SQLException
+ {
+ sqlMap.startTransaction();
+ }
+
+ public void endTransaction() throws SQLException
+ {
+ sqlMap.endTransaction();
+ }
+
+ public void commitTransaction() throws SQLException
+ {
+ sqlMap.commitTransaction();
+ }
+
+
+ /**
+ * Takes a list and creates a <code>Hashtable</code> with the keys
+ * being the feature_id and the value a <code>Vector</code> of the dbxrefs.
+ * @param list a <code>List</code> of <code>DbXRef</code> objects.
+ * @return a <code>Hashtable</code> of dbxrefs.
+ */
+ public static Hashtable<Integer, List<String>> mergeDbXRef(final List<FeatureDbXRef> list)
+ {
+ Hashtable<Integer, List<String>> dbxrefHash = new Hashtable<Integer, List<String>>();
+ for(FeatureDbXRef dbxref : list)
+ {
+ Integer feature_id = new Integer(dbxref.getFeature().getFeatureId());
+ String value = dbxref.getDbXRef().getDb().getName() + ":" +
+ dbxref.getDbXRef().getAccession();
+ if(dbxrefHash.containsKey(feature_id))
+ {
+ List<String> v = dbxrefHash.get(feature_id);
+ v.add(value);
+ dbxrefHash.put(feature_id, v);
+ }
+ else
+ {
+ List<String> v = new Vector<String>();
+ v.add(value);
+ dbxrefHash.put(feature_id, v);
+ }
+ }
+ return dbxrefHash;
+ }
+}
diff --git a/uk/ac/sanger/artemis/chado/JdbcDAO.java b/uk/ac/sanger/artemis/chado/JdbcDAO.java
new file mode 100644
index 0000000..8f72e27
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/JdbcDAO.java
@@ -0,0 +1,2334 @@
+/*
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import javax.swing.JPasswordField;
+
+import java.sql.*;
+import java.io.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Vector;
+
+import uk.ac.sanger.artemis.util.DatabaseLocationParser;
+
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureCvTermProp;
+import org.gmod.schema.sequence.FeatureDbXRef;
+import org.gmod.schema.sequence.FeatureProp;
+import org.gmod.schema.sequence.Synonym;
+import org.gmod.schema.sequence.FeatureLoc;
+import org.gmod.schema.sequence.FeatureRelationship;
+import org.gmod.schema.sequence.FeatureSynonym;
+import org.gmod.schema.sequence.FeatureCvTermDbXRef;
+import org.gmod.schema.sequence.FeatureCvTermPub;
+import org.gmod.schema.general.Db;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.cv.Cv;
+import org.gmod.schema.pub.Pub;
+import org.gmod.schema.pub.PubDbXRef;
+
+
+
+/**
+ *
+ * Java Database Connectivity (JDBC) implemetation of the
+ * <code>ChadoDAO</code> data access interface.
+ *
+ */
+public class JdbcDAO extends GmodDAO
+{
+
+ private String sqlLog = System.getProperty("user.home") +
+ System.getProperty("file.separator") +
+ "art_sql_debug.log";
+ private Connection conn;
+
+ /**
+ * Define a JDBC data access object and establish a <code>Connection</code>.
+ * @param location the database location <i>e.g.</i>
+ * jdbc:postgresql://localhost:2997/chado?user=tjc
+ * @param pfield the password for this connection
+ * @throws SQLException
+ */
+ public JdbcDAO(final String location, final JPasswordField pfield)
+ throws java.sql.SQLException, java.net.ConnectException
+ {
+ if(pfield == null || pfield.getPassword().length == 0)
+ conn = DriverManager.getConnection(location);
+
+ // assume we have a password
+ DatabaseLocationParser dlp = new DatabaseLocationParser(location);
+ conn = DriverManager.getConnection(dlp.getConnectionString(),
+ dlp.getUsername(),
+ new String(pfield.getPassword()));
+ }
+
+ //////
+ ////// GeneralDaoI
+ //////
+ //////
+ public List getDbs()
+ {
+ return null;
+ }
+
+ //////
+ ////// SequenceDaoI
+ //////
+ //////
+
+ public List getOrganismsContainingSrcFeatures()
+ {
+ return null;
+ }
+
+ public Feature getLazyFeatureNoResiduesById(final Integer featureId)
+ {
+ return null;
+ }
+
+ public List getClustersByFeatureIds(List featureIds)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeatureDbXRefsByFeatureId(List featureIds)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeaturePropByFeatureIds(List featureIds)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeaturesByListOfIds(List featureIds)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeatureDbXRefsBySrcFeature(Feature srcFeature)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeatureSynonymsBySrcFeature(Feature srcFeature)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeatureSynonymsByFeatureIds(final List featuresIds)
+ {
+ return null;
+ }
+
+ public List getFeatureLocsByFeatureId(int featureId)
+ {
+ return null;
+ }
+
+ public List getFeatureLocsBySrcFeatureId(int srcFeatureId)
+ {
+ return null;
+ }
+
+ public List getFeatureLocsByListOfIds(final Collection featureIds)
+ {
+ return null;
+ }
+
+ public List getResiduesByUniqueName(String uniqueName)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeaturePubsBySrcFeature(Feature srcFeature)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeaturePubsByFeature(final Feature feature)
+ {
+ return null;
+ }
+
+ public List getSimilarityMatches(final Integer srcFeatureId)
+ {
+ return null;
+ }
+
+ public List getSimilarityMatchesByFeatureIds(List featureIds)
+ {
+ return null;
+ }
+
+ public List getFeatureCvTermDbXRefBySrcFeature(Feature srcFeature)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeatureCvTermPubBySrcFeature(Feature srcFeature)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeatureCvTermsBySrcFeature(Feature srcFeature)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getResidueFeatures(Integer organismId)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getResidueFeaturesByOrganismCommonName(final String commonName)
+ {
+ return null;
+ }
+
+ public List getParentFeaturesByChildFeatureIds(final List featureIds)
+ {
+ return null;
+ }
+
+ /**
+ * Return the feature corresponding to this feature_id
+ *
+ * @param id the systematic id
+ * @return the Feature, or null
+ */
+ public Feature getFeatureById(int id)
+ {
+ Feature feature = new Feature();
+ feature.setFeatureId(id);
+ return getLazyFeature(feature);
+ }
+
+ /**
+ * Return a features with this systematic id
+ *
+ * @param name the systematic id
+ * @return the Feature, or null
+ */
+ public Feature getFeatureByUniqueName(final String uniquename, final String featureType)
+ {
+ Feature feature = new Feature();
+ feature.setUniqueName(uniquename);
+ feature.setFeatureId(-1);
+
+ CvTerm cvTerm = new CvTerm();
+ cvTerm.setName(featureType);
+ feature.setCvTerm(cvTerm);
+
+ return getLazyFeature(feature);
+ }
+
+ /**
+ * Return a features with this systematic id
+ *
+ * @param name the systematic id
+ * @return the Feature, or null
+ */
+ public List getFeaturesByUniqueName(String uniquename)
+ {
+ return getFeatureQuery(uniquename, -1, -1, null);
+ }
+
+ /**
+ * Return a list of features with any current (ie non-obsolete) name or synonym
+ *
+ * @param name the lookup name
+ * @return a (possibly empty) List<Feature> of children with this current name
+ */
+ public List getFeaturesByAnyCurrentName(String name)
+ {
+ Feature feature = new Feature();
+ feature.setUniqueName(name);
+
+ // getFeatureSynonymsByName() needs implementing
+ //List feature_synonym_list = getFeatureSynonymsByName();
+
+ return getFeatureQuery(name, -1, -1, null);
+ }
+
+ /**
+ * Return a list of features with this name or synonym (including obsolete names)
+ *
+ * @param name the lookup name
+ * @return a (possibly empty) List<Feature> of children with this name
+ */
+ public List getFeaturesByAnyName(String name, String featureType)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of features located on a source Feature, within a given range
+ *
+ * @param min the minimum (interbase) coordinate
+ * @param max the maximum (interbase) coordinate
+ * @param strand
+ * @param parent the source feature
+ * @param type
+ * @return a List<Feature> which ??? this range
+ */
+ public List getFeaturesByRange(int min, int max, int strand,
+ org.gmod.schema.sequence.Feature parent, String type)
+ {
+ return null;
+ }
+
+
+ /**
+ * This can be used to get individual features or children.
+ * If Feature.featureloc.srcfeature_id is set this is used
+ * to return the children of that srcfeature_id.
+ * @param feature the feature to query
+ * @return the <code>List</code> of child <code>Feature</code> objects
+ */
+ public List getFeaturesByLocatedOnFeature(final Feature feature)
+ {
+ return getFeatureQuery(null,
+ feature.getFeatureLoc().getFeatureBySrcFeatureId().getFeatureId(), -1, null);
+ }
+
+ /**
+ * Return the FeatureCvTerm that links a given Feature and CvTerm,
+ * with a given value of 'not'
+ *
+ * @param feature the Feature to test the link for
+ * @param cvTerm the CvTerm to test the link for
+ * @param not test for the not flag in the FeatureCvTerm
+ * @return the Feature, or null
+ */
+ public FeatureCvTerm getFeatureCvTermByFeatureAndCvTerm(
+ Feature feature,
+ CvTerm cvTerm, boolean not)
+ {
+ return null;
+ }
+
+ public List getFeatureCvTermsByFeature(Feature feature)
+ {
+ String sqlTest = "SELECT pg_attribute.attname "+
+ "FROM pg_attribute, pg_class, pg_namespace "+
+ "WHERE pg_namespace.oid=pg_class.relnamespace AND "+
+ "attrelid=pg_class.oid AND "+
+ "relname='feature_cvterm' AND "+
+ "attnum > 0 AND "+
+ "nspname='"+ArtemisUtils.getCurrentSchema()+"'";
+ appendToLogFile(sqlTest, sqlLog);
+
+ boolean fcRank = false;
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sqlTest);
+ while(rs.next())
+ {
+ if(rs.getString("attname").equals("rank"))
+ fcRank = true;
+ }
+
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ String sql = "SELECT fc.*, "+
+ "fcp.type_id, fcp.value, fcp.rank AS fcp_rank, "+
+ "cvterm.name AS cvterm_name, cv.name AS cv_name, "+
+ "pub.pub_id, pub.uniquename, "+
+ "db.name, dbxref.accession "+
+ "FROM feature_cvterm fc "+
+ "LEFT JOIN feature_cvtermprop fcp ON fc.feature_cvterm_id=fcp.feature_cvterm_id "+
+ "LEFT JOIN cvterm ON cvterm.cvterm_id=fc.cvterm_id "+
+ "LEFT JOIN cv ON cvterm.cv_id=cv.cv_id "+
+ "LEFT JOIN pub ON fc.pub_id=pub.pub_id "+
+ "LEFT JOIN dbxref ON cvterm.dbxref_id=dbxref.dbxref_id "+
+ "LEFT JOIN db ON dbxref.db_id=db.db_id ";
+
+
+ if(feature != null && feature.getUniqueName() != null)
+ sql = sql + " WHERE "+
+ "feature_id=(SELECT feature_id FROM feature WHERE uniquename='"+
+ feature.getUniqueName()+"')";
+
+ if(fcRank)
+ sql = sql + " ORDER BY fc.feature_cvterm_id, fc.rank, type_id, fcp_rank";
+ else
+ sql = sql + " ORDER BY fc.feature_cvterm_id, type_id, fcp_rank";
+
+ appendToLogFile(sql, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+ ResultSet.CONCUR_UPDATABLE);
+ ResultSet rs = st.executeQuery(sql);
+ List featureCvTerms = new Vector();
+
+ while(rs.next())
+ {
+ int feature_id = rs.getInt("feature_id");
+ Feature this_feature = new Feature();
+ this_feature.setFeatureId(feature_id);
+
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(rs.getInt("cvterm_id"));
+ cvterm.setName(rs.getString("cvterm_name"));
+ Cv cv = new Cv();
+ cv.setName(rs.getString("cv_name"));
+ cvterm.setCv(cv);
+
+ DbXRef dbxref = new DbXRef();
+ dbxref.setAccession(rs.getString("accession"));
+ Db db = new Db();
+ db.setName(rs.getString("name"));
+ dbxref.setDb(db);
+
+ cvterm.setDbXRef(dbxref);
+
+ Pub pub = new Pub();
+ pub.setPubId(rs.getInt("pub_id"));
+ pub.setUniqueName(rs.getString("uniquename"));
+
+
+ int fc_rank = 0;
+
+ if(fcRank)
+ fc_rank = rs.getInt("rank");
+
+ FeatureCvTerm feature_cvterm =
+ new FeatureCvTerm(cvterm, this_feature, pub, rs.getBoolean("is_not"), fc_rank);
+
+ // feature_cvtermprop's group by feature_cvterm_id
+ List featureCvTermProps = new Vector();
+ int next_fc_rank = -1;
+ int next_feature_cvterm_id = -1;
+ int feature_cvterm_id = rs.getInt("feature_cvterm_id");
+ feature_cvterm.setFeatureCvTermId(feature_cvterm_id);
+
+ do
+ {
+ FeatureCvTermProp featureProp = new FeatureCvTermProp();
+ CvTerm featurePropCvTerm = new CvTerm();
+ featurePropCvTerm.setCvTermId(rs.getInt("type_id"));
+ featureProp.setCvTerm(featurePropCvTerm);
+ featureProp.setValue(rs.getString("value"));
+ featureProp.setRank(rs.getInt("fcp_rank"));
+
+ featureCvTermProps.add(featureProp);
+
+ if(rs.next())
+ {
+ next_feature_cvterm_id = rs.getInt("feature_cvterm_id");
+ next_fc_rank = 0;
+
+ if(fcRank)
+ next_fc_rank = rs.getInt("rank");
+ if(feature_cvterm_id != next_feature_cvterm_id ||
+ fc_rank != next_fc_rank)
+ rs.previous();
+ }
+ else
+ next_feature_cvterm_id = -1;
+ } while(feature_cvterm_id == next_feature_cvterm_id &&
+ fc_rank == next_fc_rank);
+
+ feature_cvterm.setFeatureCvTermProps(featureCvTermProps);
+
+ featureCvTerms.add(feature_cvterm);
+ }
+
+ return featureCvTerms;
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ public Synonym getSynonymByNameAndCvTerm(
+ String name, CvTerm cvTerm)
+ {
+ String sql = "SELECT * FROM synonym WHERE ";
+
+ if(name != null)
+ sql = sql + "name="+name+" AND ";
+ if(cvTerm != null)
+ sql = sql + "type_id="+cvTerm.getCvTermId()+" AND ";
+
+ sql = sql + "synonym_id > 0";
+
+ appendToLogFile(sql, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(rs.getInt("type_id"));
+
+ Synonym synonym = new Synonym(cvterm, rs.getString("name"),
+ rs.getString("synonym_sgml"));
+ synonym.setSynonymId(rs.getInt("synonym_id"));
+ return synonym;
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ }
+
+
+ public List getFeatureSynonymsByFeatureAndSynonym(
+ Feature feature, Synonym synonym)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of FeatureSynonyms which link a given Feature
+ * and Synonym
+ * @param feature the test Feature
+ * @param synonym the test Synonym
+ * @return a (possibly empty) List<FeatureSynonym>
+ */
+ public List getFeatureSynonymsByFeatureUniquename(
+ Feature feature, Synonym synonym)
+ {
+ return null;
+ }
+
+ /**
+ * Return all the FeatureDbXRefs for a given feature, <b>specified by name</b>, or all if
+ * <code>null</code> is passed
+ *
+ * @param uniqueName the uniquename of a Feature, or null for all FeatureDbXRefs
+ * @return a (possibly empty) List<FeatureDbXRefI>
+ */
+ public List getFeatureDbXRefsByFeatureUniquename(final String uniqueName)
+ {
+ String sql = "SELECT db.name, dbx.accession, dbx.version, dbx.description, "
+ + "dbx_f.feature_id, dbx_f.is_current FROM "
+ + "feature_dbxref dbx_f "
+ + "LEFT JOIN dbxref dbx ON dbx.dbxref_id=dbx_f.dbxref_id "
+ + "LEFT JOIN db ON db.db_id=dbx.db_id "
+ + "LEFT JOIN feature f ON dbx_f.feature_id=f.feature_id ";
+
+ if(uniqueName != null)
+ sql = sql + "WHERE f.uniquename='" + uniqueName + "'";
+
+ sql = sql + " ORDER BY f.type_id, uniquename";
+
+ appendToLogFile(sql, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ List dbxrefs = new Vector();
+
+ while(rs.next())
+ {
+ FeatureDbXRef feature_dbxref = new FeatureDbXRef();
+ DbXRef dbxref = new DbXRef();
+ Db db = new Db();
+ db.setName(rs.getString("name"));
+ dbxref.setAccession(rs.getString("accession"));
+ dbxref.setVersion(rs.getString("version"));
+ dbxref.setDescription(rs.getString("description"));
+ dbxref.setDb(db);
+ Feature feat = new Feature();
+ feat.setFeatureId(rs.getInt("feature_id"));
+ feature_dbxref.setDbXRef(dbxref);
+ feature_dbxref.setFeature(feat);
+ feature_dbxref.setCurrent(rs.getBoolean("is_current"));
+ dbxrefs.add(feature_dbxref);
+ }
+
+ return dbxrefs;
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Return the list of FeatureSynonyms for a given Feature, <b>specified by name</b>, or all if
+ * <code>null</code> is passed
+ *
+ * @param uniqueName the uniquename of a Feature, or null for all
+ * @return a (possibly empty) List<FeatureSynonymI> of matching synonyms
+ */
+ public List getFeatureSynonymsByFeatureUniquename(final String uniqueName)
+ {
+ String sql = "SELECT fs.*, s.name, s.type_id FROM "+
+ "feature_synonym fs "+
+ "LEFT JOIN feature f ON f.feature_id=fs.feature_id "+
+ "LEFT JOIN synonym s ON fs.synonym_id=s.synonym_id ";
+
+ if(uniqueName != null)
+ sql = sql + " WHERE uniquename='" + uniqueName + "'";
+
+ appendToLogFile(sql, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ List synonym = new Vector();
+ FeatureSynonym alias;
+ while(rs.next())
+ {
+ alias = new FeatureSynonym();
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(rs.getInt("type_id"));
+ Synonym syn = new Synonym();
+ syn.setName(rs.getString("name"));
+ syn.setCvTerm(cvterm);
+ Feature feat = new Feature();
+ feat.setFeatureId(rs.getInt("feature_id"));
+
+ alias.setSynonym(syn);
+ alias.setFeature(feat);
+
+ Pub pub = new Pub();
+ pub.setPubId(rs.getInt("pub_id"));
+ alias.setPub(pub);
+ alias.setInternal(rs.getBoolean("is_internal"));
+ alias.setCurrent(rs.getBoolean("is_current"));
+ synonym.add(alias);
+ }
+
+ return synonym;
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+
+ public List getAllFeatureSynonymsAsFeature()
+ {
+ String sql = "SELECT fs.*, s.name, s.type_id , s.synonym_id FROM "+
+ "feature_synonym fs "+
+ "LEFT JOIN synonym s ON fs.synonym_id=s.synonym_id ORDER BY feature_id";
+
+ appendToLogFile(sql, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+ ResultSet.CONCUR_UPDATABLE);
+ ResultSet rs = st.executeQuery(sql);
+ List features = new Vector();
+ FeatureSynonym alias;
+ while(rs.next())
+ {
+ int feature_id = rs.getInt("feature_id");
+
+ Feature feature = new Feature();
+ feature.setFeatureId(feature_id);
+ java.util.Collection synonyms = feature.getFeatureSynonyms();
+ if(synonyms == null || synonyms.size() == 0)
+ synonyms = new Vector();
+ int next_feature_id = -1;
+
+ do
+ {
+ alias = new FeatureSynonym();
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(rs.getInt("type_id"));
+ Synonym syn = new Synonym();
+ syn.setName(rs.getString("name"));
+ syn.setCvTerm(cvterm);
+ Feature feat = new Feature();
+ feat.setFeatureId(rs.getInt("feature_id"));
+
+ alias.setSynonym(syn);
+ alias.setFeature(feat);
+
+ Pub pub = new Pub();
+ pub.setPubId(rs.getInt("pub_id"));
+ alias.setPub(pub);
+ alias.setInternal(rs.getBoolean("is_internal"));
+ alias.setCurrent(rs.getBoolean("is_current"));
+ synonyms.add(alias);
+
+ if(rs.next())
+ {
+ next_feature_id = rs.getInt("feature_id");
+ if(feature_id != next_feature_id)
+ rs.previous();
+ }
+ else
+ next_feature_id = -1;
+ feature.setFeatureSynonyms(synonyms);
+ }
+ while(feature_id == next_feature_id);
+
+
+ features.add(feature);
+ }
+
+ return features;
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Return the list of Features for a given GO number
+ *
+ *
+ * @param go the GO number
+ * @return a (possibly empty) List<Feature> of matching genes
+ */
+ public List getFeatureByGO(final String go)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of features contained in this organisms with this name or synonym (including obsolete names). The
+ * name can contain an SQL wildcard (%)
+ *
+ * @param name the lookup name
+ * @param featureType the type of feature to return eg "gene"
+ * @param organisms the list of organisms
+ * @return a (possibly empty) List<Feature> of children with this name
+ */
+ public List getFeaturesByAnyNameAndOrganism(String nl,List ids,String featureType)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of features that have this particular cvterm
+ *
+ *
+ * @param cvTermName the CvTerm name
+ * @return a (possibly empty) List<Feature> of children
+ */
+ public List getFeaturesByCvTermName(String cvTermName)
+ {
+ return null;
+ }
+
+ /**
+ * Return a list of top-level features
+ *
+ *
+ * @return a (possibly empty) List<Feature> of children
+ */
+ public List getTopLevelFeatures()
+ {
+ return null;
+ }
+
+ public List getFeatureCvTermDbXRefByFeature(Feature feature)
+ {
+ String sql = "SELECT fcd.feature_cvterm_id, dbx.*, db.name "+
+ "FROM feature_cvterm_dbxref fcd "+
+ "LEFT JOIN dbxref dbx ON dbx.dbxref_id=fcd.dbxref_id "+
+ "LEFT JOIN db ON db.db_id=dbx.db_id";
+
+ if(feature != null && feature.getUniqueName() != null)
+ sql = sql+ " " +
+ "LEFT JOIN feature_cvterm fc ON fcd.feature_cvterm_id=fc.feature_cvterm_id "+
+ "WHERE feature_id=(SELECT feature_id FROM feature WHERE uniquename='"+
+ feature.getUniqueName()+"')";
+
+ appendToLogFile(sql, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ List featureCvTermDbXRefs = new Vector();
+ FeatureCvTermDbXRef featureCvTermDbXRef;
+ while(rs.next())
+ {
+ featureCvTermDbXRef = new FeatureCvTermDbXRef();
+ FeatureCvTerm featureCvTerm = new FeatureCvTerm();
+ featureCvTerm.setFeatureCvTermId( rs.getInt("feature_cvterm_id") );
+ featureCvTermDbXRef.setFeatureCvTerm(featureCvTerm);
+
+ DbXRef dbXRef = new DbXRef();
+ dbXRef.setAccession( rs.getString("accession") );
+ dbXRef.setDescription( rs.getString("description") );
+ dbXRef.setVersion( rs.getString("version") );
+
+ Db db = new Db();
+ db.setName( rs.getString("name") );
+
+ dbXRef.setDb(db);
+
+ featureCvTermDbXRef.setDbXRef(dbXRef);
+ featureCvTermDbXRefs.add(featureCvTermDbXRef);
+ }
+
+ return featureCvTermDbXRefs;
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ public List getFeatureCvTermPubByFeature(Feature feature)
+ {
+ String sql = "SELECT fcp.feature_cvterm_id, pub.* " +
+ "FROM feature_cvterm_pub fcp " +
+ "LEFT JOIN pub ON fcp.pub_id=pub.pub_id ";
+
+ if(feature != null && feature.getUniqueName() != null)
+ sql = sql+ " " +
+ "LEFT JOIN feature_cvterm fc ON fcp.feature_cvterm_id=fc.feature_cvterm_id "+
+ "WHERE feature_id=(SELECT feature_id FROM feature WHERE uniquename='"+
+ feature.getUniqueName()+"')";
+
+ appendToLogFile(sql, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ List featureCvTermPubs = new Vector();
+ FeatureCvTermPub featureCvTermPub;
+ while(rs.next())
+ {
+ featureCvTermPub = new FeatureCvTermPub();
+ FeatureCvTerm featureCvTerm = new FeatureCvTerm();
+ featureCvTerm.setFeatureCvTermId( rs.getInt("feature_cvterm_id") );
+ featureCvTermPub.setFeatureCvTerm(featureCvTerm);
+
+ Pub pub = new Pub();
+ pub.setUniqueName(rs.getString("uniquename"));
+
+ featureCvTermPub.setPub(pub);
+ featureCvTermPubs.add(featureCvTermPub);
+ }
+
+ return featureCvTermPubs;
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ public List getProducts()
+ {
+ return null;
+ }
+
+ //////
+ //////
+
+
+ /**
+ * Get the properties of a feature.
+ * @param uniquename the unique name of the feature
+ * @return the <code>List</code> of <code>Feature</code>
+ */
+ private Feature getLazyFeature(final Feature feature)
+ {
+ List list = getFeatureQuery(feature.getUniqueName(),
+ -1, feature.getFeatureId(),
+ feature.getCvTerm());
+ if(list == null || list.size() < 1)
+ return null;
+
+ return (Feature)list.get(0);
+ }
+
+ /**
+ * Get the properties of a feature.
+ * @param uniquename the unique name of the feature
+ * @param parentFeatureID the id of parent feature to query
+ * @return the <code>List</code> of <code>Feature</code>
+ */
+ private List getFeatureQuery(final String uniquename,
+ final int parentFeatureID,
+ final int feature_id,
+ final CvTerm cvTerm)
+ {
+ final List list = new Vector();
+ try
+ {
+ Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+ ResultSet.CONCUR_UPDATABLE);
+
+ String sql = "SELECT timelastmodified, f.feature_id, residues,"
+ + " fl.strand, fmin, fmax, uniquename, f.type_id,"
+ + " fp.type_id AS prop_type_id, fp.value, fl.phase,"
+ + " f.organism_id, abbreviation, genus, species, common_name, comment,"
+ + " fr.object_id, fr.type_id AS relation_type_id, fr.rank"
+ + " FROM feature f" + " LEFT JOIN feature_relationship fr ON "
+ + "fr.subject_id=" + "f.feature_id" + " LEFT JOIN featureprop fp ON "
+ + "fp.feature_id=" + "f.feature_id" + " LEFT JOIN featureloc fl ON "
+ + "f.feature_id=" + "fl.feature_id"
+ + " LEFT JOIN organism ON organism.organism_id=f.organism_id ";
+
+ if(cvTerm != null && cvTerm.getName() != null)
+ sql = sql + "LEFT JOIN cvterm ON f.type_id=cvterm.cvterm_id ";
+
+ sql = sql + " WHERE ";
+
+ if(uniquename != null)
+ sql = sql + "uniquename LIKE '" + uniquename + "'";
+
+ if(parentFeatureID > -1)
+ sql = sql + "srcfeature_id = " + parentFeatureID;
+
+ if(feature_id > -1)
+ sql = sql + "f.feature_id = " + feature_id;
+
+ if(cvTerm != null && cvTerm.getName() != null)
+ sql = sql + " AND cvterm.name="+cvTerm.getName();
+
+ sql = sql
+ + " ORDER BY f.type_id, uniquename";
+
+ appendToLogFile(sql, sqlLog);
+ ResultSet rs = st.executeQuery(sql);
+
+ while(rs.next())
+ {
+ int feat_id = rs.getInt("feature_id");
+
+ Feature feature = new Feature();
+
+ FeatureLoc featureloc = new FeatureLoc();
+ featureloc.setFmin(new Integer(rs.getInt("fmin")));
+ featureloc.setFmax(new Integer(rs.getInt("fmax")));
+ featureloc.setStrand(new Short(rs.getShort("strand")));
+
+ int phase = rs.getInt("phase");
+ if(rs.wasNull())
+ featureloc.setPhase(null);
+ else
+ featureloc.setPhase(new Integer(phase));
+
+ feature.setResidues(rs.getBytes("residues"));
+
+ feature.setFeatureLoc(featureloc);
+ feature.setCvTerm(new CvTerm());
+ feature.getCvTerm().setCvTermId(rs.getInt("type_id"));
+ feature.setUniqueName(rs.getString("uniquename"));
+ feature.setTimeLastModified(rs.getTimestamp("timelastmodified"));
+ feature.setFeatureId(rs.getInt("feature_id"));
+
+ // feature organism
+ Organism organism = new Organism();
+ organism.setAbbreviation(rs.getString("abbreviation"));
+ organism.setComment(rs.getString("comment"));
+ organism.setCommonName(rs.getString("common_name"));
+ organism.setGenus(rs.getString("genus"));
+ organism.setOrganismId(rs.getInt("organism_id"));
+ organism.setSpecies(rs.getString("species"));
+ feature.setOrganism(organism);
+
+ boolean next = false;
+ do
+ {
+ // feature properties
+ int prop_type_id = rs.getInt("prop_type_id");
+
+ if(prop_type_id != 0)
+ {
+ FeatureProp featureprop = new FeatureProp();
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(prop_type_id);
+ featureprop.setCvTerm(cvterm);
+ featureprop.setValue(rs.getString("value"));
+
+ if(feature.getFeatureProps() == null
+ || feature.getFeatureProps().size() == 0)
+ feature.setFeatureProps(new Vector());
+ feature.addFeatureProp(featureprop);
+ }
+ else
+ feature.setFeatureProps(new Vector(0));
+
+ // feature relationship
+ FeatureRelationship feature_relationship = new FeatureRelationship();
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(rs.getInt("relation_type_id"));
+ feature_relationship.setCvTerm(cvterm);
+
+ int obj_id = rs.getInt("object_id");
+
+ if(obj_id != 0)
+ {
+ Feature object = new Feature();
+ object.setFeatureId(obj_id);
+ feature_relationship.setFeatureByObjectId(object);
+
+ if(feature.getFeatureRelationshipsForSubjectId() == null
+ || feature.getFeatureRelationshipsForSubjectId().size() == 0)
+ feature.setFeatureRelationshipsForSubjectId(new Vector());
+
+ feature.addFeatureRelationshipsForSubjectId(feature_relationship);
+ }
+ else
+ feature.setFeatureRelationshipsForSubjectId(new Vector(0));
+
+ if(!rs.isLast())
+ {
+ rs.next();
+ if(rs.getInt("feature_id") == feat_id)
+ next = true;
+ else
+ {
+ rs.previous();
+ next = false;
+ }
+ }
+ else
+ next = false;
+ }while(next);
+
+
+ list.add(feature);
+ }
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ // merge same features in the list
+ //return mergeList(list);
+ return list;
+ }
+
+ //////
+ ////// SchemaDaoI
+ //////
+ //////
+
+ /**
+ * Return a list of chado features with residues.
+ * @return the <code>List</code> of <code>Feature</code> objects
+ */
+ public List getResidueFeatures()
+ {
+ String sql = new String(
+ "SELECT uniquename, name, feature_id, type_id FROM ");
+
+ sql = sql + "feature WHERE residues notnull ";
+
+ appendToLogFile(sql, sqlLog);
+
+ List list = new Vector();
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ while(rs.next())
+ {
+ Feature feature = new Feature();
+
+ feature.setFeatureId(rs.getInt("feature_id"));
+ feature.setName(rs.getString("name"));
+ feature.setUniqueName(rs.getString("uniquename"));
+ feature.setCvTerm(new CvTerm());
+ feature.getCvTerm().setCvTermId(rs.getInt("type_id"));
+
+ list.add(feature);
+ }
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ return list;
+ }
+
+
+ /**
+ * For a schema return the type_id's with residues.
+ * @param schema schema/organism name or null
+ * @return the <code>List</code> of type_id's as <code>String</code>
+ * objects
+ */
+ public List getResidueType(final String schema)
+ {
+ String sql = "SELECT DISTINCT type_id FROM ";
+
+ if(schema != null || !schema.equals(""))
+ sql = sql + schema +"." ;
+ sql = sql + "feature WHERE residues notnull";
+ appendToLogFile(sql, sqlLog);
+
+ List cvterm_ids = new Vector();
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+
+ while(rs.next())
+ cvterm_ids.add(rs.getString("type_id"));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ return cvterm_ids;
+ }
+
+ /**
+ * Get available schemas (as a <code>List</code> of <code>String</code>
+ * objects).
+ * @return the available schemas
+ */
+ public List getSchema()
+ {
+ List schemas = new Vector();
+ try
+ {
+ Statement st = conn.createStatement();
+
+ String query = "SELECT schema_name FROM information_schema.schemata "
+ + "WHERE schema_name=schema_owner ORDER BY schema_name";
+ appendToLogFile(query, sqlLog);
+
+ ResultSet rs = st.executeQuery(query);
+
+ while(rs.next())
+ schemas.add(rs.getString("schema_name"));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ return schemas;
+ }
+
+ //////
+ ////// CvDaoI
+ //////
+ //////
+
+ public List getCvTermByNameInCv(String cvTermName, Cv cv)
+ {
+ return null;
+ }
+
+ public List getAllCvs()
+ {
+ return null;
+ }
+
+ /**
+ * Get the full list of cvterm_id and name as a <code>List</code> of
+ * <code>CvTerm</code> objects.
+ * @return the full list of cvterm_id and name
+ */
+ public List getCvTerms()
+ {
+ String sql = "SELECT cvterm.cvterm_id, cvterm.name as cvterm_name, cv.NAME as cv_name, accession " +
+ "FROM cvterm " +
+ "LEFT JOIN dbxref ON dbxref.dbxref_id=cvterm.dbxref_id LEFT JOIN cv ON cv.cv_id = cvterm.cv_id";
+
+ appendToLogFile(sql, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ List cvterms = new Vector();
+
+ while(rs.next())
+ {
+ CvTerm cvterm = new CvTerm();
+ cvterm.setCvTermId(rs.getInt("cvterm_id"));
+ cvterm.setName(rs.getString("cvterm_name"));
+ Cv cv = new Cv();
+ cv.setName(rs.getString("cv_name"));
+ cvterm.setCv(cv);
+ DbXRef dbXRef = new DbXRef();
+ dbXRef.setAccession(rs.getString("accession"));
+ cvterm.setDbXRef(dbXRef);
+
+ cvterms.add(cvterm);
+ }
+ return cvterms;
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ }
+
+ /**
+ * Retrieve a named CvTerm from a given Cv
+ *
+ * @param cvTermName the name of the cvterm
+ * @param name the controlled vocabulary name this cvterm could be part of
+ * @return a (possibly empty) cvterm
+ */
+ public CvTerm getCvTermByNameAndCvName(String cvTermName, String name)
+ {
+ return null;
+ }
+
+ public CvTerm getCvTermById(final int cvTermId)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ //////
+ ////// OrganismDaoI
+ //////
+ //////
+
+ public List getOrganisms()
+ {
+ String sql = "SELECT organism_id AS organismId, abbreviation, "+
+ "genus, species, common_name AS commonName, comment "+
+ "FROM organism ORDER BY commonName";
+
+ appendToLogFile(sql, sqlLog);
+ List organisms = new Vector();
+
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+
+ while(rs.next())
+ {
+ Organism organism = new Organism();
+ organism.setOrganismId(rs.getInt("organismId"));
+ organism.setAbbreviation(rs.getString("abbreviation"));
+ organism.setGenus(rs.getString("genus"));
+ organism.setSpecies(rs.getString("species"));
+ organism.setCommonName(rs.getString("commonName"));
+ organism.setComment(rs.getString("comment"));
+ organisms.add(organism);
+ }
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ return organisms;
+ }
+
+ //////
+ ////// PubDaoI
+ //////
+ //////
+
+ public List getPubDbXRef()
+ {
+ String sql = "SELECT pub_id, pub_dbxref.dbxref_id, "+
+ "accession, version, dbx.description AS dbx_description, "+
+ "db.name, db.description, db.urlprefix, db.url FROM pub_dbxref "+
+ "LEFT JOIN dbxref dbx ON pub_dbxref.dbxref_id=dbx.dbxref_id "+
+ "LEFT JOIN db ON db.db_id=dbx.db_id";
+
+ appendToLogFile(sql, sqlLog);
+ List pubDbXRefs = new Vector();
+
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+
+ PubDbXRef pubDbXRef;
+ while(rs.next())
+ {
+ pubDbXRef = new PubDbXRef();
+ Pub pub = new Pub();
+ pub.setPubId(rs.getInt("pub_id"));
+ pubDbXRef.setPub(pub);
+
+ DbXRef dbXRef = new DbXRef();
+ dbXRef.setAccession(rs.getString("accession"));
+ dbXRef.setDescription(rs.getString("description"));
+ dbXRef.setVersion(rs.getString("version"));
+
+ Db db = new Db();
+ db.setName(rs.getString("name"));
+ db.setDescription(rs.getString("description"));
+ db.setUrl(rs.getString("url"));
+ db.setUrlPrefix(rs.getString("urlPrefix"));
+
+ dbXRef.setDb(db);
+ pubDbXRef.setDbXRef(dbXRef);
+ pubDbXRefs.add(pubDbXRef);
+ }
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ return pubDbXRefs;
+ }
+
+//
+// WRITE
+//
+
+
+ /**
+ * Merge (update) an already persistent object back to the database
+ * (at the end of the current transaction, or depending upon flush mode).
+ * This method is defined in all the DAOs. It's recommended to call it
+ * through an appropriate one eg SequenceDaoI for FeatureI
+ * @param o The object to merge
+ */
+ public void merge(Object o)
+ {
+ if(o instanceof FeatureLoc)
+ updateFeatureLoc((FeatureLoc)o);
+ else if(o instanceof Feature)
+ {
+ if(o instanceof FeatureForUpdatingResidues)
+ updateFeatureResidues((FeatureForUpdatingResidues)o);
+ else
+ updateFeature((Feature)o);
+ }
+ else if(o instanceof FeatureProp)
+ updateFeatureProp((FeatureProp)o);
+ else if(o instanceof FeatureRelationship)
+ updateFeatureRelationship((FeatureRelationship)o);
+ }
+
+ /**
+ * Save the object to the database (at the end of the current transaction,
+ * or depending upon flush mode). This method is defined in all the DAOs.
+ * It's recommended to call it through an appropriate one eg SequenceDaoI
+ * for FeatureI
+ * @param o The object to store
+ */
+ public void persist(Object o)
+ {
+ if(o instanceof FeatureProp)
+ insertFeatureProp((FeatureProp)o);
+ else if(o instanceof Feature)
+ insertFeature((Feature)o);
+ else if(o instanceof FeatureDbXRef)
+ insertFeatureDbXRef((FeatureDbXRef)o);
+ else if(o instanceof FeatureSynonym)
+ insertFeatureAlias((FeatureSynonym)o);
+ else if(o instanceof FeatureCvTerm)
+ insertAllFeatureCvTerm((FeatureCvTerm)o);
+ }
+
+ /**
+ * Remove the object from the database (at the end of the current transaction,
+ * or depending upon flush mode). This method is defined in all the DAOs.
+ * It's recommended to call it through an appropriate one eg SequenceDaoI for
+ * FeatureI
+ * @param o The object to delete
+ */
+ public void delete(Object o)
+ {
+ if(o instanceof Feature)
+ deleteFeature((Feature)o);
+ else if(o instanceof FeatureProp)
+ deleteFeatureProp((FeatureProp)o);
+ else if(o instanceof FeatureDbXRef)
+ deleteFeatureDbXRef((FeatureDbXRef)o);
+ else if(o instanceof FeatureSynonym)
+ deleteFeatureSynonym((FeatureSynonym)o);
+ else if(o instanceof FeatureCvTerm)
+ deleteFeatureCvTerm((FeatureCvTerm)o);
+ }
+
+
+ /**
+ * Update a feature location with the give <code>FeatureLoc</code>
+ * object.
+ * @param featureloc the new <code>FeatureLoc</code> object.
+ */
+ private void updateFeatureLoc(FeatureLoc featureloc)
+ {
+ final String sql = "UPDATE featureloc SET fmin=?, fmax=?, rank=?, strand=?, phase=? "+
+ "WHERE feature_id=(SELECT feature_id FROM feature WHERE uniquename=?)";
+ try
+ {
+ PreparedStatement pstmt = conn.prepareStatement(sql);
+ pstmt.setInt(1, featureloc.getFmin().intValue());
+ pstmt.setInt(2, featureloc.getFmax().intValue());
+ pstmt.setInt(3, featureloc.getRank());
+ pstmt.setShort(4, featureloc.getStrand().shortValue());
+
+ if(featureloc.getPhase() != null)
+ pstmt.setInt(5, featureloc.getPhase().intValue());
+ else
+ pstmt.setNull(5, Types.INTEGER);
+
+ pstmt.setString(6, featureloc.getFeatureByFeatureId().getUniqueName());
+ appendToLogFile(sql, sqlLog);
+
+ pstmt.executeUpdate();
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Update a feature with a given <code>Feature</code> object.
+ * @param feature the new <code>Feature</code> object.
+ */
+ private void updateFeature(Feature feature)
+ {
+ String sql = "UPDATE feature SET uniquename=?";
+
+ if(feature.getCvTerm() != null)
+ sql = sql+", type_id=?";
+
+ if(feature.getTimeLastModified() != null)
+ sql = sql+", timelastmodified=?";
+
+ sql = sql+"WHERE feature_id=?";
+
+ try
+ {
+ PreparedStatement pstmt = conn.prepareStatement(sql);
+ pstmt.setString(1, feature.getUniqueName());
+ int param = 2;
+ if(feature.getCvTerm() != null)
+ {
+ pstmt.setLong(param, feature.getCvTerm().getCvTermId());
+ param++;
+ }
+
+ if(feature.getTimeLastModified() != null)
+ {
+ pstmt.setTimestamp(param, feature.getTimeLastModified());
+ param++;
+ }
+
+ pstmt.setInt(param, feature.getFeatureId());
+ appendToLogFile(sql, sqlLog);
+ pstmt.executeUpdate();
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+
+ /**
+ * Update feature residues (inserting or deleting a dna sequence).
+ * @param feature the new <code>FeatureForUpdatingResidues</code> object.
+ */
+ private void updateFeatureResidues(FeatureForUpdatingResidues feature)
+ {
+ String sql1 =
+ "UPDATE featureloc SET ";
+
+ if(feature.getNewSubSequence() != null)
+ sql1 = sql1 + "fmin=fmin+" + feature.getLength() +
+ " , fmax=fmax+" + feature.getLength();
+ else
+ sql1 = sql1 + "fmin=fmin-" + feature.getLength() +
+ " , fmax=fmax-" + feature.getLength();
+
+ sql1 = sql1 + " WHERE fmin >= " + feature.getStartBase() +
+ " AND srcfeature_id="+feature.getFeatureId();
+ appendToLogFile(sql1, sqlLog);
+
+ String sql2 = " UPDATE feature SET "+
+ "residues=substring(residues from 1 for "+ feature.getStartBase() + ") || ";
+
+ if(feature.getNewSubSequence() != null)
+ sql2 = sql2 + "'" + feature.getNewSubSequence() + "' || ";
+
+ sql2 = sql2 + "substring(residues from "+ feature.getEndBase() +
+ " for "+ feature.getSeqLen() + "), "+
+ "seqlen=" + feature.getSeqLen() +
+ " WHERE feature_id="+feature.getFeatureId();
+
+ appendToLogFile(sql2, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement();
+ st.executeUpdate(sql1);
+ st.executeUpdate(sql2);
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Update a feature property with a given <code>FeatureProp</code>
+ * object.
+ * @param featureprop the new <code>FeatureProp</code> object.
+ */
+ private void updateFeatureProp(FeatureProp featureprop)
+ {
+ String sql = "UPDATE featureprop SET value=? "+
+ "WHERE rank=? AND type_id=? AND "+
+ "feature_id=(SELECT feature_id FROM feature WHERE uniquename=?)";
+ try
+ {
+ PreparedStatement pstmt = conn.prepareStatement(sql);
+ pstmt.setString(1, featureprop.getValue());
+ pstmt.setInt(2, featureprop.getRank());
+ pstmt.setLong(3, featureprop.getCvTerm().getCvTermId());
+ pstmt.setString(4, featureprop.getFeature().getUniqueName());
+
+ appendToLogFile(sql, sqlLog);
+ pstmt.executeUpdate();
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+
+ /**
+ * Insert attributes defined by the <code>FeatureProp</code>.
+ * @param featureprop the new <code>FeatureProp</code>
+ */
+ private void insertFeatureProp
+ (final FeatureProp featureprop)
+ {
+ StringBuffer sqlBuff = new StringBuffer();
+
+ sqlBuff.append("INSERT INTO featureprop");
+ sqlBuff.append(" ( feature_id, type_id, value, rank ) ");
+ sqlBuff.append("VALUES ");
+
+ sqlBuff.append("( (SELECT feature_id FROM feature WHERE uniquename=");
+ sqlBuff.append("'"+ featureprop.getFeature().getUniqueName()+"')," );
+ sqlBuff.append(featureprop.getCvTerm().getCvTermId()+", '");
+ sqlBuff.append(featureprop.getValue()+"',");
+ sqlBuff.append(featureprop.getRank()+" )");
+
+ appendToLogFile(new String(sqlBuff), sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement();
+ int rowCount = st.executeUpdate(new String(sqlBuff));
+ System.out.println(rowCount + " row(s) inserted");
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Delete attributes defined by the <code>FeatureProp</code>.
+ * @param featureprop the new <code>FeatureProp</code>
+ */
+ private void deleteFeatureProp
+ (final FeatureProp featureprop)
+ {
+ StringBuffer sqlBuff = new StringBuffer();
+ String uniquename = featureprop.getFeature().getUniqueName();
+
+ sqlBuff.append("DELETE FROM featureprop WHERE ");
+
+ if(uniquename != null)
+ sqlBuff.append("feature_id="+
+ "(SELECT feature_id FROM feature WHERE uniquename='"+
+ uniquename+"') AND ");
+
+ if(featureprop.getRank() > -1)
+ sqlBuff.append("rank="+featureprop.getRank()+" AND ");
+
+ if(featureprop.getValue() != null)
+ sqlBuff.append("value="+featureprop.getValue()+" AND ");
+
+ sqlBuff.append("type_id="+featureprop.getCvTerm().getCvTermId());
+
+ appendToLogFile(new String(sqlBuff), sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement();
+ int rowCount = st.executeUpdate(new String(sqlBuff));
+ System.out.println(rowCount+" row(s) deleted");
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+
+ /**
+ * Insert a feature into the database defined by the <code>Feature</code>.
+ * @param feature the new <code>Feature</code>
+ */
+ private void insertFeature
+ (final Feature feature)
+ {
+ try
+ {
+ //
+ // get the organism_id
+ Statement st = conn.createStatement();
+ String sql = "SELECT organism_id from " + "feature where feature_id = '"
+ + feature.getFeatureLoc().getFeatureBySrcFeatureId().getFeatureId() + "'";
+
+ appendToLogFile(sql, sqlLog);
+ ResultSet rs = st.executeQuery(sql);
+ rs.next();
+
+ final int organism_id = rs.getInt("organism_id");
+
+ // insert new feature into feature table
+ StringBuffer sql_buff = new StringBuffer();
+ sql_buff.append("INSERT INTO feature (");
+ sql_buff.append(" feature_id ,");
+ sql_buff.append(" organism_id ,");
+ sql_buff.append(" name ,");
+ sql_buff.append(" uniquename ,");
+ sql_buff.append(" type_id");
+ sql_buff.append(" ) VALUES ( ");
+ sql_buff.append("nextval('feature_feature_id_seq') , ");
+ sql_buff.append(organism_id + " , ");
+ sql_buff.append("'" + feature.getName() + "'" + " , ");
+ sql_buff.append("'" + feature.getUniqueName() + "'" + " , ");
+ sql_buff.append(Long.toString(feature.getCvTerm().getCvTermId()));
+ sql_buff.append(" )");
+
+ sql = new String(sql_buff);
+ appendToLogFile(sql, sqlLog);
+ st = conn.createStatement();
+ int rowCount = st.executeUpdate(sql);
+
+ //
+ // get the current feature_id sequence value
+ final int feature_id = getCurrval("feature_feature_id_seq");
+
+ //
+ // insert feature location into featureloc
+ sql_buff = new StringBuffer();
+ sql_buff.append("INSERT INTO featureloc (");
+ sql_buff.append(" featureloc_id ,");
+ sql_buff.append(" feature_id ,");
+ sql_buff.append(" srcfeature_id ,");
+ sql_buff.append(" fmin ,");
+ sql_buff.append(" fmax ,");
+ sql_buff.append(" strand ");
+
+ if(feature.getFeatureLoc().getPhase() != null)
+ sql_buff.append(" , phase ");
+
+ sql_buff.append(" ) VALUES ( ");
+ sql_buff.append("nextval('featureloc_featureloc_id_seq') , ");
+ sql_buff.append(feature_id + " , ");
+ sql_buff.append(feature.getFeatureLoc().getFeatureBySrcFeatureId().getFeatureId() + " , ");
+ sql_buff.append(feature.getFeatureLoc().getFmin() + " , ");
+ sql_buff.append(feature.getFeatureLoc().getFmax() + " , ");
+ sql_buff.append(feature.getFeatureLoc().getStrand());
+
+ if(feature.getFeatureLoc().getPhase() != null)
+ sql_buff.append(" , "+feature.getFeatureLoc().getPhase());
+
+ sql_buff.append(" )");
+
+ sql = new String(sql_buff);
+ appendToLogFile(sql, sqlLog);
+ st = conn.createStatement();
+ rowCount = st.executeUpdate(sql);
+
+ // insert feature relationships
+ if(feature.getFeatureRelationshipsForSubjectId() != null)
+ {
+ List parents = (List)feature.getFeatureRelationshipsForSubjectId();
+ for(int i = 0; i < parents.size(); i++)
+ {
+ // insert feature_relationship
+ FeatureRelationship feature_relationship = (FeatureRelationship) parents
+ .get(i);
+
+ sql_buff = new StringBuffer();
+ sql_buff.append("INSERT INTO feature_relationship ");
+ sql_buff.append("( subject_id, object_id, type_id ) ");
+ sql_buff.append("VALUES ");
+ sql_buff
+ .append("( (SELECT feature_id FROM feature WHERE uniquename='");
+ sql_buff.append(feature_relationship.getFeatureBySubjectId().getUniqueName()
+ + "'), ");
+ sql_buff.append("(SELECT feature_id FROM feature WHERE uniquename='");
+ sql_buff.append(feature_relationship.getFeatureByObjectId().getUniqueName()
+ + "'), ");
+ sql_buff.append(feature_relationship.getCvTerm().getCvTermId() + ")");
+
+ sql = new String(sql_buff);
+ appendToLogFile(sql, sqlLog);
+ st = conn.createStatement();
+ rowCount = st.executeUpdate(sql);
+
+ }
+ }
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ }
+
+ /**
+ * Delete a feature from the database defined by the
+ * <code>Feature</code>.
+ * @param feature the new <code>Feature</code>
+ */
+ private int deleteFeature
+ (final Feature feature)
+ {
+ try
+ {
+ String sql = "DELETE FROM feature WHERE uniquename='"
+ + feature.getUniqueName() + "'";
+ appendToLogFile(sql, sqlLog);
+
+ Statement st = conn.createStatement();
+ return st.executeUpdate(sql);
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Insert a feature_dbxref for a feature.
+ * @param feature_dbxref the new <code>FeatureDbXRef</code>
+ */
+ private void insertFeatureDbXRef(final FeatureDbXRef feature_dbxref)
+ {
+ // find database id
+ String sql = "SELECT db_id FROM db WHERE name='"+
+ feature_dbxref.getDbXRef().getDb().getName()+"'";
+
+ try
+ {
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ boolean exists = rs.next();
+
+ if(!exists)
+ throw new SQLException("No database called "
+ + feature_dbxref.getDbXRef().getDb().getName() + " found (for "
+ + feature_dbxref.getFeature().getUniqueName()
+ + ") check the spelling!");
+
+ final int db_id = rs.getInt("db_id");
+ // find if accession exists already
+ String sqlDbXRefId = "SELECT dbxref_id FROM dbxref WHERE accession='"
+ + feature_dbxref.getDbXRef().getAccession() + "' AND db_id=" + db_id;
+
+ appendToLogFile(sqlDbXRefId, sqlLog);
+ rs = st.executeQuery(sqlDbXRefId);
+ exists = rs.next();
+
+ if(!exists)
+ {
+ // create a new accession entry in dbxref
+ sql = "INSERT INTO dbxref ( db_id, accession ) " + "VALUES (" + db_id
+ + ", '" + feature_dbxref.getDbXRef().getAccession() + "' )";
+
+ appendToLogFile(sql, sqlLog);
+ int rowCount = st.executeUpdate(new String(sql));
+
+ // now get the new dbxref_id
+ appendToLogFile(sqlDbXRefId, sqlLog);
+ rs = st.executeQuery(sqlDbXRefId);
+ rs.next();
+ }
+
+ final int dbxref_id = rs.getInt("dbxref_id");
+ sql = "INSERT INTO feature_dbxref "
+ + "(feature_id, dbxref_id, is_current)" + " VALUES "
+ + "( (SELECT feature_id FROM " + "feature WHERE uniquename='"
+ + feature_dbxref.getFeature().getUniqueName() + "'), " + dbxref_id
+ + ", " + Boolean.toString(feature_dbxref.isCurrent()) + ")";
+ System.out.println(sql);
+ appendToLogFile(sql, sqlLog);
+ st.executeUpdate(new String(sql));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Delete a feature_dbxref for a feature.
+ * @param feature_dbxref the new <code>FeatureDbXRef</code>
+ */
+ private void deleteFeatureDbXRef(final FeatureDbXRef feature_dbxref)
+ {
+ final String uniquename = feature_dbxref.getFeature().getUniqueName();
+
+ final String sql =
+ "DELETE FROM feature_dbxref "+
+ "WHERE dbxref_id="+
+ "(SELECT dbxref_id FROM dbxref WHERE accession='"+
+ feature_dbxref.getDbXRef().getAccession()+"' "+
+ "AND db_id=(SELECT db_id FROM db WHERE name='"+
+ feature_dbxref.getDbXRef().getDb().getName()+"'))"+
+ "AND feature_id=(SELECT feature_id FROM "+
+ "feature WHERE uniquename='"+uniquename+"')";
+
+ try
+ {
+ Statement st = conn.createStatement();
+ st.executeUpdate(sql);
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Insert a feature_synonym for a feature.
+ * @param feature_synonym the new <code>FeatureSynonym</code>
+ */
+ private void insertFeatureAlias(final FeatureSynonym feature_synonym)
+ {
+ final String uniquename = feature_synonym.getFeature().getUniqueName();
+ final String synonym_name = feature_synonym.getSynonym().getName();
+
+ String sql;
+
+ String sqlAliasId = "SELECT synonym_id FROM "+
+ "synonym WHERE synonym.name='"+synonym_name+"'";
+
+ appendToLogFile(sqlAliasId, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
+ ResultSet.CONCUR_UPDATABLE);
+ ResultSet rs = st.executeQuery(sqlAliasId);
+ boolean exists = rs.next();
+
+ if(!exists)
+ {
+ // create a new synonym name
+ String type_id = Long.toString(feature_synonym.getSynonym().getCvTerm()
+ .getCvTermId());
+
+ sql = "INSERT INTO "
+ + "synonym (name, type_id, synonym_sgml) values ( '" + synonym_name
+ + "'," + type_id + ",'" + synonym_name + "')";
+
+ st.executeUpdate(sql);
+ appendToLogFile(sql, sqlLog);
+
+ rs = st.executeQuery(sqlAliasId);
+ rs.next();
+ appendToLogFile(sqlAliasId, sqlLog);
+ }
+
+ final int synonym_id = rs.getInt("synonym_id");
+ sql = "INSERT INTO "
+ + "feature_synonym ( synonym_id, feature_id, pub_id )" + " values ( "
+ + synonym_id + " ," + "(SELECT feature_id FROM "
+ + "feature WHERE uniquename='" + uniquename + "'), " + " 1)";
+
+ appendToLogFile(sql, sqlLog);
+ st.executeUpdate(sql);
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Delete a feature_synonym for a feature.
+ * @param feature_synonym the new <code>FeatureSynonym</code>
+ * @return number of rows changed
+ */
+ private void deleteFeatureSynonym(final FeatureSynonym feature_synonym)
+ {
+ final String uniquename = feature_synonym.getFeature().getUniqueName();
+ final String synonym_name = feature_synonym.getSynonym().getName();
+ String sql = "SELECT synonym_id FROM synonym WHERE "+
+ "synonym.name='"+synonym_name+"'";
+
+ appendToLogFile(sql, sqlLog);
+
+ try
+ {
+ Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+ ResultSet.CONCUR_READ_ONLY);
+ ResultSet rs = st.executeQuery(sql);
+ rs.last();
+ int nrows = rs.getRow();
+ final int synonym_id = rs.getInt("synonym_id");
+
+ // check this name is not used some where else,
+ // i.e. in more than one row
+ if(nrows > 1)
+ {
+ sql = "DELETE FROM feature_synonym WHERE " + "synonym_id=" + synonym_id
+ + " AND " + "feature_id=(SELECT feature_id FROM "
+ + "feature WHERE uniquename='" + uniquename + "')";
+ }
+ else
+ sql = "DELETE FROM synonym WHERE synonym_id=" + synonym_id;
+
+ st = conn.createStatement();
+ st.executeUpdate(sql);
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Delete featureCvTerm and update associated feature_cvterm.rank's
+ * if appropriate
+ * @param featureCvTerm
+ */
+ private void deleteFeatureCvTerm(FeatureCvTerm feature_cvterm)
+ {
+ final String sql = "DELETE FROM feature_cvterm WHERE feature_cvterm_id="+
+ feature_cvterm.getFeatureCvTermId();
+
+ try
+ {
+ Statement st = conn.createStatement();
+ st.executeUpdate(sql);
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ /**
+ * Update feature_relationship for a feature.
+ * @param feature_relationship the <code>FeatureRelationship</code>
+ */
+ private void updateFeatureRelationship(
+ final FeatureRelationship feature_relationship)
+ {
+ StringBuffer sqlBuff = new StringBuffer();
+ sqlBuff.append("UPDATE feature_relationship ");
+ sqlBuff.append(" SET ");
+ sqlBuff.append(" rank="+feature_relationship.getRank()+", ");
+ sqlBuff.append(" type_id="+feature_relationship.getCvTerm().getCvTermId());
+ sqlBuff.append(" WHERE ");
+ sqlBuff.append("subject_id=");
+ sqlBuff.append("( SELECT feature_id FROM feature WHERE uniquename='");
+ sqlBuff.append(feature_relationship.getFeatureBySubjectId().getUniqueName()+"' ) ");
+ sqlBuff.append("AND ");
+ sqlBuff.append("object_id=");
+ sqlBuff.append("( SELECT feature_id FROM feature WHERE uniquename='");
+ sqlBuff.append(feature_relationship.getFeatureByObjectId().getUniqueName()+"' ) ");
+
+ String sql = sqlBuff.toString();
+
+ System.out.println(sql);
+ appendToLogFile(sql, sqlLog);
+ try
+ {
+ Statement st = conn.createStatement();
+ st.executeUpdate(sql);
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+
+ /**
+ * Appends a log entry to the log file
+ * @param logEntry entry to add to log file
+ * @param logFileName log file name
+ */
+ private void appendToLogFile(String logEntry, String logFileName)
+ {
+ if(System.getProperty("debug") == null)
+ return;
+
+ BufferedWriter bw = null;
+ try
+ {
+ String dat = new java.util.Date().toString();
+ bw = new BufferedWriter(new FileWriter(logFileName, true));
+ bw.write(dat + ":: " + logEntry);
+ bw.newLine();
+ bw.flush();
+ }
+ catch(Exception ioe)
+ {
+ System.out.println("Error writing to log file " + logFileName);
+ ioe.printStackTrace();
+ }
+ finally
+ // always close the file
+ {
+ if(bw != null)
+ try
+ {
+ bw.close();
+ }
+ catch(IOException ioe2){}
+ }
+ }
+
+ protected int getCurrval(String seq_id)
+ {
+ int currval;
+ try
+ {
+ //
+ // get the current feature_id sequence value
+ String sql = "SELECT currval('"+seq_id+"')";
+ appendToLogFile(sql, sqlLog);
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ rs.next();
+ currval = rs.getInt("currval");
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ return currval;
+ }
+
+ protected Integer getDbId(Db db)
+ {
+ Integer db_id;
+ try
+ {
+ String sql = "SELECT db_id FROM db WHERE name='"+db.getName()+"'";
+ appendToLogFile(sql, sqlLog);
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ rs.next();
+ db_id = new Integer(rs.getInt("db_id"));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ return db_id;
+ }
+
+ protected Integer getDbXRefId(DbXRef dbXRef)
+ {
+ Integer dbxref_id;
+ try
+ {
+ String sql = "SELECT dbxref_id FROM dbxref WHERE accession='"+
+ dbXRef.getAccession()+"' AND db_id="+dbXRef.getDb().getDbId();
+ appendToLogFile(sql, sqlLog);
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ rs.next();
+ dbxref_id = new Integer(rs.getInt("dbxref_id"));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ return dbxref_id;
+ }
+
+ protected Pub getPubByUniqueName(Pub pub)
+ {
+ try
+ {
+ String sql = "SELECT * FROM pub WHERE uniquename='"+pub.getUniqueName()+"'";
+ appendToLogFile(sql, sqlLog);
+ Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(sql);
+ rs.next();
+
+ pub.setPubId( rs.getInt("pub_id") );
+ //
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+
+ return pub;
+ }
+
+ protected void insertDbXRef(DbXRef dbXRef)
+ {
+ try
+ {
+ String sql = "INSERT INTO dbxref ( db_id, accession, version ) VALUES ("+
+ dbXRef.getDb().getDbId() +", '"+
+ dbXRef.getAccession() +"', "+
+ dbXRef.getVersion()+")";
+ Statement st = conn.createStatement();
+ appendToLogFile(sql, sqlLog);
+ st.executeUpdate(new String(sql));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ protected void insertFeatureCvTerm(FeatureCvTerm feature_cvterm)
+ {
+ try
+ {
+ String uniqueName = feature_cvterm.getFeature().getUniqueName();
+ final String pubIdStr;
+
+ if(feature_cvterm.getPub() != null)
+ {
+ if(feature_cvterm.getPub().getPubId() == 0)
+ pubIdStr = "(SELECT pub_id FROM pub WHERE uniquename="+
+ feature_cvterm.getPub().getUniqueName()+")";
+ else
+ pubIdStr = Integer.toString(feature_cvterm.getPub().getPubId());
+ }
+ else
+ pubIdStr = "0";
+
+ String sql = "INSERT INTO feature_cvterm "+
+ "( feature_cvterm_id, feature_id, cvterm_id, pub_id, rank, is_not ) "+
+ "VALUES "+
+ "( nextval('feature_cvterm_feature_cvterm_id_seq'), "+
+ "(SELECT feature_id FROM feature WHERE uniquename='"+ uniqueName + "'), "+
+ feature_cvterm.getCvTerm().getCvTermId() + " , "+
+ pubIdStr + " , "+
+ feature_cvterm.getRank() + " , "+
+ feature_cvterm.isNot() + " ) ";
+ appendToLogFile(sql, sqlLog);
+ Statement st = conn.createStatement();
+ st.executeUpdate(new String(sql));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ protected void insertFeatureCvTermDbXRef(FeatureCvTermDbXRef featureCvTermDbXRef)
+ {
+ try
+ {
+ String sql = "INSERT INTO feature_cvterm_dbxref "+
+ "( feature_cvterm_id, dbxref_id ) "+
+ "VALUES ( "+
+ featureCvTermDbXRef.getFeatureCvTerm().getFeatureCvTermId() + ", " +
+ featureCvTermDbXRef.getDbXRef().getDbXRefId()+" )";
+ appendToLogFile(sql, sqlLog);
+ Statement st = conn.createStatement();
+ st.executeUpdate(new String(sql));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ protected void insertFeatureCvTermProp(FeatureCvTermProp featureCvTermProp)
+ {
+ try
+ {
+ String sql = "INSERT INTO feature_cvtermprop "+
+ "( feature_cvtermprop_id, feature_cvterm_id, type_id, value, rank ) "+
+ "VALUES ( "+
+ "nextval('feature_cvtermprop_feature_cvtermprop_id_seq'), "+
+ featureCvTermProp.getFeatureCvTerm().getFeatureCvTermId() + ", "+
+ featureCvTermProp.getCvTerm().getCvTermId() + ", '"+
+ featureCvTermProp.getValue() +"', "+
+ featureCvTermProp.getRank() +")";
+ appendToLogFile(sql, sqlLog);
+ Statement st = conn.createStatement();
+ st.executeUpdate(new String(sql));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ protected void insertFeatureCvTermPub(FeatureCvTermPub featureCvTermPub)
+ {
+ try
+ {
+ String sql = "INSERT INTO feature_cvterm_pub "+
+ "( feature_cvterm_id, pub_id ) "+
+ "VALUES ( "+
+ featureCvTermPub.getFeatureCvTerm().getFeatureCvTermId() + ", "+
+ featureCvTermPub.getPub().getPubId() + ")";
+ appendToLogFile(sql, sqlLog);
+ Statement st = conn.createStatement();
+ st.executeUpdate(new String(sql));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ protected void insertPub(Pub pub)
+ {
+ try
+ {
+ String sql = "INSERT INTO pub ( ";
+
+ if(pub.getTitle() != null)
+ sql = sql + "title, ";
+
+ /*if(pub.getVolumeTitle() != null)
+ sql = sql + "volumetitle, ";
+
+ if(pub.getVolume() != null)
+ sql = sql + "volume, ";
+
+ if(pub.getSeriesName() != null)
+ sql = sql + "series_name, ";
+
+ if(pub.getIssue() != null)
+ sql = sql + "issue, ";
+
+ if(pub.getPyear() != null)
+ sql = sql + "pyear, ";
+
+ if(pub.getPages() != null)
+ sql = sql + "pages, ";
+
+ if(pub.getMiniRef() != null)
+ sql = sql + "miniref, ";
+
+ sql = sql + "uniquename, type_id ";
+
+ if(pub.isObsolete() != null)
+ sql = sql + " , is_obsolete ";
+
+ if(pub.getPublisher() != null)
+ sql = sql + " , publisher ";
+
+ if(pub.getPubPlace() != null)
+ sql = sql + " , pubplace ";*/
+
+ sql = sql + ") VALUES (";
+
+ if(pub.getTitle() != null)
+ sql = sql + pub.getTitle() + ", ";
+
+ // FIX THIS
+ //sql = sql + "'" + pub.getUniqueName() + "'," +
+ // pub.getCvTerm().getCvTermId()+ ")";
+
+ appendToLogFile(sql, sqlLog);
+ Statement st = conn.createStatement();
+ st.executeUpdate(new String(sql));
+ }
+ catch(SQLException sqle)
+ {
+ throw new RuntimeException(sqle);
+ }
+ }
+
+ protected void insertPubDbXRef(PubDbXRef pubDbXRef)
+ {
+
+ }
+
+ public Organism getOrganismByCommonName(String commonName)
+ {
+ return null;
+ }
+
+ public Graph getGraph(Integer graphId)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getGraphs(Integer featureId)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getFeaturesByUniqueNames(List arg0)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public List getTableColumns(String tableName)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
diff --git a/uk/ac/sanger/artemis/chado/SqlMapClientWrapper.java b/uk/ac/sanger/artemis/chado/SqlMapClientWrapper.java
new file mode 100644
index 0000000..35d0196
--- /dev/null
+++ b/uk/ac/sanger/artemis/chado/SqlMapClientWrapper.java
@@ -0,0 +1,154 @@
+/* SqlMapClientWrapper /* IBatisDAO
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.chado;
+
+import javax.swing.JPasswordField;
+
+import java.sql.SQLException;
+import java.util.List;
+import com.ibatis.sqlmap.client.SqlMapClient;
+
+/**
+ * Wrapper for com.ibatis.sqlmap.client.SqlMapClient, so that
+ * RuntimeExceptions are thrown rather than SQLExceptions.
+ */
+public class SqlMapClientWrapper
+{
+
+ private SqlMapClient sqlMap;
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(SqlMapClientWrapper.class);
+
+ public SqlMapClientWrapper(final JPasswordField pfield)
+ {
+ DbSqlConfig sql_config = new DbSqlConfig();
+ sql_config.init(pfield);
+ this.sqlMap = sql_config.getSqlMapInstance();
+ }
+
+ /**
+ * Close the current connection and return it to the pool
+ * @throws SQLException
+ */
+ protected void close() throws SQLException
+ {
+ if(sqlMap != null)
+ {
+ if(sqlMap.getCurrentConnection() != null)
+ if(!sqlMap.getCurrentConnection().isClosed())
+ sqlMap.getCurrentConnection().close();
+
+ sqlMap = null;
+ }
+ }
+
+ protected List queryForList(final String method, final Object arg)
+ {
+ try
+ {
+ return sqlMap.queryForList(method, arg);
+ }
+ catch(SQLException e)
+ {
+ logger4j.error("queryForList() "+method+" \n"+
+ System.getProperty("chado")+"\n"+e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected Object queryForObject(final String method, final Object arg)
+ {
+ try
+ {
+ return sqlMap.queryForObject(method, arg);
+ }
+ catch(SQLException e)
+ {
+ logger4j.error("queryForObject() "+method+"\n"+
+ System.getProperty("chado")+"\n"+e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected Object insert(final String method, final Object arg)
+ {
+ try
+ {
+ return sqlMap.insert(method, arg);
+ }
+ catch(SQLException e)
+ {
+ logger4j.error(e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected int delete(final String method, final Object arg)
+ {
+ try
+ {
+ return sqlMap.delete(method, arg);
+ }
+ catch(SQLException e)
+ {
+ logger4j.error(e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected int update(final String method, final Object arg)
+ {
+ try
+ {
+ return sqlMap.update(method, arg);
+ }
+ catch(SQLException e)
+ {
+ logger4j.error(e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected void startTransaction() throws SQLException
+ {
+ sqlMap.startTransaction();
+ }
+
+ protected void endTransaction() throws SQLException
+ {
+ if(sqlMap != null)
+ sqlMap.endTransaction();
+ }
+
+ protected void commitTransaction() throws SQLException
+ {
+ sqlMap.commitTransaction();
+ }
+
+ protected SqlMapClient getSqlMap()
+ {
+ return sqlMap;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/Block.java b/uk/ac/sanger/artemis/circular/Block.java
new file mode 100644
index 0000000..0529ef3
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/Block.java
@@ -0,0 +1,953 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import uk.ac.sanger.artemis.Feature;
+
+import javax.swing.*;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.event.*;
+import java.io.IOException;
+import java.util.Vector;
+
+import javax.swing.event.*;
+import java.awt.geom.Arc2D;
+
+public class Block implements Transferable
+{
+ private static final long serialVersionUID = 1L;
+ private DNADraw current_dna;
+ private double angStart;
+ private double angEnd;
+
+ private Vector rect;
+ private boolean drawLabel = false;
+ final public static DataFlavor BLOCK =
+ new DataFlavor(Block.class, "Block");
+ static DataFlavor blockFlavors[] = { BLOCK };
+
+ private String label;
+ private int bstart;
+ private int bend;
+ private Feature feature;
+ private Color colour;
+ private float strokeSize;
+ private boolean arrowHead;
+ private boolean arrowTail;
+
+ /** track to place the block on - as a fraction of the radii*/
+ private Track track;
+
+
+ public Block(final String label,
+ int bstart,
+ int bend,
+ Color colour,
+ float strokeSize,
+ Track track)
+ {
+ super();
+ this.label = label;
+ this.bstart = bstart;
+ this.bend = bend;
+ this.colour = colour;
+ this.strokeSize = strokeSize;
+ this.track = track;
+ }
+
+
+ public Block(final String label,
+ int bstart,
+ int bend,
+ Color colour,
+ float strokeSize,
+ Track fracRadii,
+ DNADraw current_dna)
+ {
+ this(label, bstart, bend, colour, strokeSize, fracRadii);
+ this.current_dna = current_dna;
+ }
+
+
+ protected void draw(Graphics2D g2)
+ {
+ if(current_dna.isCircular())
+ drawCircular(g2);
+ else
+ drawLinear(g2);
+ }
+
+
+ protected void showProperties(final JFrame f, final DNADraw draw,
+ JButton delete)
+ {
+// final JFrame f = new JFrame("Properties");
+//set up menu bar
+ JMenuBar menuBar = new JMenuBar();
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.setMnemonic(KeyEvent.VK_F);
+ menuBar.add(fileMenu);
+
+ JMenuItem closeMenu = new JMenuItem("Close");
+ closeMenu.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+
+ closeMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+ fileMenu.add(closeMenu);
+
+//set up window
+ f.setJMenuBar(menuBar);
+ JPanel pane = (JPanel)f.getContentPane();
+ String markerLabel = getLabel();
+ int bstart = getBstart();
+ int bend = getBend();
+ Color colour = getColour();
+ float strokeSize = getStrokeSize();
+ boolean arrowHead = isArrowHead();
+ boolean arrowTail = isArrowTail();
+
+ Box bdown = Box.createVerticalBox();
+ pane.add(bdown);
+
+ bdown.add(Box.createVerticalStrut(4));
+ Dimension d = new Dimension(200,30);
+ Box bacross = Box.createHorizontalBox();
+ final JTextField annotation = new JTextField();
+ annotation.setPreferredSize(d);
+ annotation.setMaximumSize(d);
+ annotation.setText(markerLabel);
+ annotation.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setLabel(annotation.getText());
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ bacross.add(annotation);
+ bacross.add(new JLabel(" Label"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ d = new Dimension(65,30);
+ bacross = Box.createHorizontalBox();
+ final TextFieldInt start = new TextFieldInt();
+ start.setPreferredSize(d);
+ start.setMaximumSize(d);
+ start.setValue(bstart);
+ start.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setBstart(start.getValue());
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ bacross.add(start);
+ bacross.add(new JLabel(" Start"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ final TextFieldInt end = new TextFieldInt();
+ end.setPreferredSize(d);
+ end.setMaximumSize(d);
+ end.setValue(bend);
+ end.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setBend(end.getValue());
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ bacross.add(end);
+ bacross.add(new JLabel(" End"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+//Set up the dialog that the button brings up.
+ final JButton colourButton = GeneticMarker.setUpColorButton(colour);
+ final JButton applyButton = new JButton("Apply");
+
+ applyButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setColour(colourButton.getBackground());
+ setLabel(annotation.getText());
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+
+ bacross = Box.createHorizontalBox();
+ bacross.add(new JLabel("Pick a Colour: "));
+ bacross.add(colourButton);
+ bacross.add(applyButton);
+
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ final JSlider slider;
+
+ if(strokeSize <= 25)
+ slider = new JSlider(1,25,(int)strokeSize);
+ else
+ slider = new JSlider(1,25+(int)strokeSize,(int)strokeSize);
+
+ bacross.add(slider);
+ bacross.add(new JLabel(" Line width"));
+ bacross.add(Box.createHorizontalGlue());
+ slider.addChangeListener(new ChangeListener()
+ {
+ public void stateChanged(ChangeEvent e)
+ {
+ setStrokeSize((float)slider.getValue());
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ bacross.add(new JLabel("Arrow :"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ final JRadioButton head = new JRadioButton("Head");
+ final JRadioButton tail = new JRadioButton("Tail");
+ final JRadioButton none = new JRadioButton("None");
+ head.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(head.isSelected())
+ {
+ setArrowHead(true);
+ setArrowTail(false);
+ }
+ else
+ {
+ setArrowHead(false);
+ setArrowTail(true);
+ }
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ bacross.add(head);
+
+ tail.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(tail.isSelected())
+ {
+ setArrowTail(true);
+ setArrowHead(false);
+ }
+ else
+ {
+ setArrowTail(false);
+ setArrowHead(true);
+ }
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ bacross.add(tail);
+
+ none.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(none.isSelected())
+ {
+ setArrowTail(false);
+ setArrowHead(false);
+ }
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ bacross.add(none);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ final JCheckBox label = new JCheckBox("Show Label", drawLabel);
+ label.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ drawLabel = label.isSelected();
+ setLabel(annotation.getText());
+ draw.repaint();
+ }
+ });
+ bacross.add(label);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+
+ bacross = Box.createHorizontalBox();
+ bacross.add(delete);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ ButtonGroup group = new ButtonGroup();
+ group.add(head);
+ group.add(tail);
+ group.add(none);
+ if(arrowHead)
+ head.setSelected(true);
+ else if(arrowTail)
+ tail.setSelected(true);
+
+ f.pack();
+ f.setVisible(true);
+ }
+
+ protected void drawLinear(final Graphics2D g2)
+ {
+ RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ qualityHints.put(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2.setRenderingHints(qualityHints);
+
+ String markerLabel = getLabel();
+ int bstart = getBstart();
+ int bend = getBend();
+
+
+ FontMetrics fm = g2.getFontMetrics();
+ g2.setColor(getColour());
+
+ //int shift = (int)getTrack().getPosition();
+
+ int basesPerLine = current_dna.getBasesPerLine();
+ int lineNumberStart = Math.round((float)(bstart-1)/((float)basesPerLine) + 0.5f)-1;
+ int lineNumberEnd = Math.round((float)(bend-1)/((float)basesPerLine) + 0.5f)-1;
+ float singleBaseWidth = current_dna.getSingleBaseWidth();
+
+ int borderWidth2 = current_dna.getBorderWidth2();
+ int borderHeight2 = current_dna.getBorderHeight2();
+
+ int ypos = (int) ( (lineNumberStart*current_dna.getLineHeight()) - (strokeSize/2.f) -
+ ((1-track.getPosition())*current_dna.getLineHeight()) +
+ borderHeight2+current_dna.getLineHeight());
+
+ int xstart = (int) ((bstart-(lineNumberStart*basesPerLine))*singleBaseWidth)+borderWidth2;
+ int xend = (int) ((bend-(lineNumberEnd*basesPerLine))*singleBaseWidth)+borderWidth2;
+
+ if(rect == null)
+ rect = new Vector();
+
+ BasicStroke basicstroke;
+ if(bstart == bend)
+ {
+ // SNP / variation feature
+ basicstroke = new BasicStroke(
+ 1.f,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+ g2.setStroke(basicstroke);
+ g2.drawLine(xstart,ypos,xstart,(int) (ypos+strokeSize));
+ Rectangle r = new Rectangle();
+ r.setLocation(xstart,ypos);
+ r.setSize(current_dna.getWidth()-borderWidth2-xstart,2);
+ rect.add(r);
+ return;
+ }
+
+ basicstroke = new BasicStroke(
+ strokeSize,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+ g2.setStroke(basicstroke);
+
+ if(arrowTail)
+ {
+ int[] xPoints = {xstart+(int)strokeSize,xstart+(int)strokeSize,xstart};
+ int[] yPoints = {ypos+(int)strokeSize,ypos-(int)strokeSize,ypos};
+ xstart+=strokeSize/2;
+ g2.fillPolygon(xPoints,yPoints,3);
+ }
+
+
+ Rectangle r;
+
+ if(lineNumberEnd > lineNumberStart)
+ {
+ g2.drawLine(xstart,ypos,current_dna.getWidth()-borderWidth2,ypos);
+ r = new Rectangle();
+ r.setLocation(xstart,ypos);
+ r.setSize(current_dna.getWidth()-borderWidth2-xstart,(int)strokeSize);
+ rect.add(r);
+
+ for(int i=lineNumberStart+1; i<lineNumberEnd; i++)
+ {
+ ypos = (int) ( (i*current_dna.getLineHeight()) - (strokeSize/2.f) -
+ ((1-track.getPosition())*current_dna.getLineHeight()) +
+ borderHeight2+current_dna.getLineHeight());
+ g2.drawLine(borderWidth2,ypos,current_dna.getWidth()-borderWidth2,ypos);
+
+ r = new Rectangle();
+ r.setLocation(borderWidth2,ypos);
+ r.setSize(current_dna.getWidth()-borderWidth2-borderWidth2,(int)strokeSize);
+ rect.add(r);
+ }
+
+ ypos = (int) ( (lineNumberEnd*current_dna.getLineHeight()) - (strokeSize/2.f) -
+ ((1-track.getPosition())*current_dna.getLineHeight()) +
+ borderHeight2+current_dna.getLineHeight());
+ xstart = borderWidth2;
+ }
+
+ if(arrowHead)
+ {
+ int[] xPoints = {xend-(int)strokeSize,xend-(int)strokeSize,xend};
+ int[] yPoints = {ypos+(int)strokeSize,ypos-(int)strokeSize,ypos};
+ xend-=strokeSize/2;
+ g2.fillPolygon(xPoints,yPoints,3);
+ }
+
+ g2.drawLine(xstart,ypos,xend,ypos);
+
+ r = new Rectangle();
+ r.setLocation(xstart,ypos);
+ r.setSize(xend-xstart,(int)strokeSize);
+ rect.add(r);
+
+
+ if(drawLabel)
+ {
+ int xmid = xstart+(int)(((xend-xstart)/2.)-(fm.stringWidth(markerLabel)/2.));
+ g2.drawString(markerLabel,xmid,ypos-strokeSize);
+ }
+ }
+
+
+ protected void drawCircular(Graphics2D g2)
+ {
+ RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ qualityHints.put(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2.setRenderingHints(qualityHints);
+
+
+ int bstart = getBstart();
+ int bend = getBend();
+ Color colour = getColour();
+
+ FontMetrics fm = g2.getFontMetrics();
+ double hgt = fm.getAscent();
+ g2.setColor(colour);
+
+ final double ddiameter = current_dna.getDiameter();
+ final double widthPanel = current_dna.getWidth();
+ final double heightPanel = current_dna.getHeight();
+ final double dradii = ddiameter/2.d;
+ final double rad = 360.d;
+ Point location = current_dna.getLocationPoint();
+
+ AffineTransform origin = g2.getTransform();
+ AffineTransform newOrig = (AffineTransform)(origin.clone());
+
+ angStart = current_dna.getAngleFromPosition(bstart,rad) ;
+ angEnd = current_dna.getAngleFromPosition(bend,rad) - angStart;
+
+ double shift = dradii*(1.d-getTrack().getPosition());
+ double bdiameter = ddiameter*getTrack().getPosition();
+
+ //
+ float stroke = getStrokeSize();
+ float stroke2 = getStrokeSize()/2.f;
+ int strokeInt = (int)stroke;
+
+ double d2 = (bdiameter/2.d);
+ double d = d2*2.d;
+
+ double locx = location.getX()+shift;
+ double locy = location.getY()+shift;
+
+ if(arrowHead)
+ {
+ newOrig.rotate(Math.toRadians(-angStart-angEnd),
+ locx+d2,locy+d2);
+
+ int xmid = (int)(locx+d);
+ int ymid = location.y+(int)(dradii);
+ int[] xPoints = {xmid-strokeInt,xmid+strokeInt,xmid};
+ int[] yPoints = {ymid-strokeInt,ymid-strokeInt,ymid};
+ g2.setTransform(newOrig);
+ g2.fillPolygon(xPoints,yPoints,3);
+ angEnd += Math.toDegrees(Math.atan(((float)(strokeInt)*0.8f)/(float)d2));
+ }
+ else if(arrowTail)
+ {
+ newOrig.rotate(Math.toRadians(-angStart),
+ locx+d2,locy+d2);
+
+ int xmid = (int)(locx+d);
+ int ymid = location.y+(int)(dradii);
+ int[] xPoints = {xmid-strokeInt,xmid+strokeInt,xmid};
+ int[] yPoints = {ymid+strokeInt,ymid+strokeInt,ymid};
+ g2.setTransform(newOrig);
+ g2.fillPolygon(xPoints,yPoints,3);
+
+ double adj = Math.toDegrees(Math.atan(((float)strokeInt*0.8f)/(float)d2));
+ angStart -= adj;
+ angEnd += adj;
+ }
+
+
+ g2.setTransform(origin);
+ // double arcLength = Math.toRadians(angEnd)*(bdiameter/2.);
+
+ // if too small draw arc as a line
+ if(Math.abs(angEnd) < 0.4d)
+ {
+ newOrig = (AffineTransform) (origin.clone());
+
+ double xLine = d2-stroke2 ;
+ newOrig.translate(locx+d2,locy+d2);
+ newOrig.rotate(Math.toRadians(-angStart));
+
+ BasicStroke basicstroke = new BasicStroke(
+ 1,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+
+ g2.setStroke(basicstroke);
+ g2.setTransform(newOrig);
+ //g2.drawLine((int)xLine, 0, (int)(xLine+strokeInt), 0);
+ g2.draw(new Line2D.Double(xLine, 0.d, xLine+stroke, 0.d));
+ }
+ else
+ {
+ BasicStroke basicstroke = new BasicStroke(
+ stroke,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+
+ g2.setStroke(basicstroke);
+ /*g2.drawArc(locx,locy, d, d,
+ Math.round(Math.round(angStart)),
+ Math.round(Math.round(angEnd)));*/
+
+ g2.draw(new Arc2D.Double(locx,locy, d, d, angStart, angEnd,
+ Arc2D.OPEN));
+ }
+
+ g2.setTransform(origin);
+
+ if(drawLabel)
+ {
+ newOrig = (AffineTransform)(origin.clone());
+ newOrig.rotate(Math.toRadians(-angStart-(angEnd/2.d)),
+ widthPanel/2.d,heightPanel/2.d);
+
+ int xblock = 0;
+ int yblock = 0;
+
+ int widMarker = fm.stringWidth(getLabel())/2;
+ xblock = (int)( dradii - (shift/2.d) + (newOrig.getScaleX()*
+ (dradii - 3 - widMarker - ((shift+getStrokeSize())/2.d))) -
+ widMarker );
+
+ xblock = (int)( dradii + (newOrig.getScaleX()*
+ (dradii - shift - 3 - widMarker - (getStrokeSize()/2.d))) -
+ widMarker );
+
+ yblock = (int)( dradii + (newOrig.getShearY()*
+ (dradii - shift - 3 - ((getStrokeSize() + hgt)/2.d))) +
+ hgt/2.d );
+
+ newOrig = (AffineTransform)(origin.clone());
+
+
+ AffineTransform labelTransform = (AffineTransform)(origin.clone());
+ labelTransform.translate(location.x+dradii, location.y+dradii);
+ labelTransform.rotate(Math.toRadians(-DNADraw.THETA));
+ labelTransform.translate(-(location.x+dradii), -(location.y+dradii));
+ g2.setTransform(labelTransform);
+
+ g2.drawString(getLabel(),
+ location.x+xblock,
+ location.y+yblock);
+ g2.setTransform(origin);
+ }
+ }
+
+
+ /**
+ * Routine to set the position of the block
+ * @param x x position
+ * @param y y position
+ */
+ public void setBlockLocation(int x, int y, final TrackManager viewer)
+ {
+ if(current_dna.isCircular())
+ {
+ double dradii = current_dna.getDiameter()/2.d;
+ Point location = current_dna.getLocationPoint();
+
+ double x_origin = location.x+dradii;
+ double y_origin = location.y+dradii+DNADraw.Y_SHIFT;
+ double len = Math.sqrt(Math.pow((y_origin-y),2)+
+ Math.pow((x_origin-x),2));
+ getTrack().setPosition(len/dradii);
+ }
+ else
+ getTrack().setPosition(y);
+ viewer.refresh();
+ }
+
+ protected double getMidAngle()
+ {
+ return Math.abs(getAngStart() + (getAngEnd()/2.d));
+ }
+
+ protected Point[] getLinePoints(final int extraLength)
+ {
+ if(current_dna.isCircular())
+ {
+ double dradii = current_dna.getDiameter()/2.d;
+ Point location = current_dna.getLocationPoint();
+
+ double x_origin = location.x+dradii;
+ double y_origin = location.y+dradii+DNADraw.Y_SHIFT;
+
+ double midAngle = getMidAngle();
+ double trackPos = (track.getPosition()*dradii)+(track.getSize()/2.f);
+
+ double x = 0.d;
+ double y = 0.d;
+ double x2 = 0.d;
+ double y2 = 0.d;
+ double x3 = 0.d;
+ int lineLength = 100+extraLength;
+
+ if(midAngle <= 90.d)
+ {
+ x = (Math.sin(Math.toRadians(midAngle)) * trackPos)+x_origin;
+ y = y_origin-(Math.cos(Math.toRadians(midAngle)) * trackPos);
+
+ x2 = (Math.sin(Math.toRadians(midAngle)) * (trackPos+lineLength))+x_origin;
+ y2 = y_origin-(Math.cos(Math.toRadians(midAngle)) * (trackPos+lineLength));
+
+ x3 = x2+10;
+ }
+ else if(midAngle <= 180.d)
+ {
+ midAngle-=90;
+ x = (Math.cos(Math.toRadians(midAngle)) * trackPos)+x_origin;
+ y = (Math.sin(Math.toRadians(midAngle)) * trackPos)+y_origin;
+
+ x2 = (Math.cos(Math.toRadians(midAngle)) * (trackPos+lineLength))+x_origin;
+ y2 = (Math.sin(Math.toRadians(midAngle)) * (trackPos+lineLength))+y_origin;
+
+ x3 = x2+10;
+ }
+ else if(midAngle <= 270.d)
+ {
+ midAngle-=180;
+ x = x_origin-(Math.sin(Math.toRadians(midAngle)) * trackPos);
+ y = (Math.cos(Math.toRadians(midAngle)) * trackPos)+y_origin;
+
+ x2 = x_origin-(Math.sin(Math.toRadians(midAngle)) * (trackPos+lineLength));
+ y2 = (Math.cos(Math.toRadians(midAngle)) * (trackPos+lineLength))+y_origin;
+
+ x3 = x2-10;
+ }
+ else if(midAngle <= 360.d)
+ {
+ midAngle-=270;
+ x = x_origin-(Math.cos(Math.toRadians(midAngle)) * trackPos);
+ y = y_origin-(Math.sin(Math.toRadians(midAngle)) * trackPos);
+
+ x2 = x_origin-(Math.cos(Math.toRadians(midAngle)) * (trackPos+lineLength));
+ y2 = y_origin-(Math.sin(Math.toRadians(midAngle)) * (trackPos+lineLength));
+
+ x3 = x2-10;
+ }
+
+ final Point[] line = new Point[3];
+ line[0] = new Point((int)x, (int)y);
+ line[1] = new Point((int)x2, (int)y2);
+ line[2] = new Point((int)x3, (int)y2);
+
+ return line;
+ }
+ else
+ return null;
+ }
+
+ /**
+ *
+ * Routine to check whether a given location is over
+ * the drawn block.
+ * @param x x position
+ * @param y y position
+ *
+ */
+ public boolean isOverMe(int x, int y)
+ {
+ if(current_dna.isCircular())
+ {
+ double dradii = current_dna.getDiameter()/2.d;
+ Point location = current_dna.getLocationPoint();
+
+ double x_origin = location.x+dradii;
+ double y_origin = location.y+dradii+DNADraw.Y_SHIFT;
+
+ double ttheta = Math.abs(y_origin-y)/Math.abs(x_origin-x);
+ double angDegrees = Math.toDegrees(Math.atan(ttheta));
+
+ if(x < x_origin && y > y_origin)
+ angDegrees = (180 - angDegrees);
+ else if(x < x_origin && y < y_origin)
+ angDegrees = (angDegrees + 180);
+ else if(x > x_origin && y < y_origin)
+ angDegrees = (360 - angDegrees);
+
+ angDegrees = angDegrees - DNADraw.THETA;
+ if(angDegrees > 360)
+ angDegrees-=360;
+
+ if( angDegrees >= -angStart-.05 && angDegrees <= -(angStart+angEnd-0.05) )
+ {
+ double len = Math.sqrt(Math.pow((y_origin - y), 2)
+ + Math.pow((x_origin - x), 2));
+ //double rat = (len / dradii);
+
+ /*System.out.println(getLabel() + " " + getFeature().getIDString());
+ System.out.println("ANGLE " + angDegrees );
+ System.out.println("START " + angStart + " " + angStart);
+ System.out.println("END " + (angStart + angEnd) + " " + angEnd);*/
+ double trackPos = track.getPosition()*dradii;
+
+ if(len <= trackPos+(track.getSize()/2.f) &&
+ len >= trackPos-(track.getSize()/2.f))
+ return true;
+ }
+ }
+ else
+ {
+ for(int i=0; i<rect.size(); i++)
+ {
+ Rectangle r = (Rectangle)rect.get(i);
+ if(r.contains(x,y))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Transferable
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return blockFlavors;
+ }
+
+ public boolean isDataFlavorSupported(DataFlavor f)
+ {
+ if(f.equals(BLOCK))
+ return true;
+ return false;
+ }
+
+ public Object getTransferData(DataFlavor d)
+ throws UnsupportedFlavorException, IOException
+ {
+ if(d.equals(BLOCK))
+ return this;
+ else throw new UnsupportedFlavorException(d);
+ }
+
+
+ public Track getTrack()
+ {
+ return track;
+ }
+
+
+ public void setTrack(Track track)
+ {
+ this.track = track;
+ }
+
+
+ public double getAngEnd()
+ {
+ return angEnd;
+ }
+
+
+ public void setAngEnd(double angEnd)
+ {
+ this.angEnd = angEnd;
+ }
+
+
+ public double getAngStart()
+ {
+ return angStart;
+ }
+
+
+ public void setAngStart(double angStart)
+ {
+ this.angStart = angStart;
+ }
+
+
+ public boolean isArrowHead()
+ {
+ return arrowHead;
+ }
+
+
+ public void setArrowHead(boolean arrowHead)
+ {
+ this.arrowHead = arrowHead;
+ }
+
+
+ public boolean isArrowTail()
+ {
+ return arrowTail;
+ }
+
+
+ public void setArrowTail(boolean arrowTail)
+ {
+ this.arrowTail = arrowTail;
+ }
+
+
+ public int getBend()
+ {
+ return bend;
+ }
+
+
+ public void setBend(int bend)
+ {
+ this.bend = bend;
+ }
+
+
+ public int getBstart()
+ {
+ return bstart;
+ }
+
+
+ public void setBstart(int bstart)
+ {
+ this.bstart = bstart;
+ }
+
+
+ public Color getColour()
+ {
+ if(track.getColour() != null)
+ return track.getColour();
+ return colour;
+ }
+
+
+ public void setColour(Color colour)
+ {
+ this.colour = colour;
+ }
+
+
+ public String getLabel()
+ {
+ return label;
+ }
+
+ public void setLabel(String label)
+ {
+ this.label = label;
+ }
+
+
+ public float getStrokeSize()
+ {
+ return strokeSize;
+ }
+
+
+ public void setStrokeSize(float strokeSize)
+ {
+ this.strokeSize = strokeSize;
+ }
+
+
+ public Feature getFeature()
+ {
+ return feature;
+ }
+
+
+ public void setFeature(Feature feature)
+ {
+ this.feature = feature;
+ }
+
+
+ public boolean isDrawLabel()
+ {
+ return drawLabel;
+ }
+
+
+ public void setDrawLabel(boolean drawLabel)
+ {
+ this.drawLabel = drawLabel;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/BlockComparator.java b/uk/ac/sanger/artemis/circular/BlockComparator.java
new file mode 100644
index 0000000..ac03131
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/BlockComparator.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.util.Comparator;
+
+class BlockComparator implements Comparator
+{
+
+ public int compare(Object o1, Object o2)
+ {
+ double m1 = ((Block)o1).getMidAngle();
+ double m2 = ((Block)o2).getMidAngle();
+
+ if(m1 < m2)
+ return -1;
+ else if(m1 > m2)
+ return 1;
+ return 0;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/DNADraw.java b/uk/ac/sanger/artemis/circular/DNADraw.java
new file mode 100644
index 0000000..52b9905
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/DNADraw.java
@@ -0,0 +1,1964 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.event.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Vector;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.DragSourceDragEvent;
+import java.awt.dnd.DragSourceDropEvent;
+import java.awt.dnd.DragSourceEvent;
+import java.awt.dnd.DragSourceListener;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.editor.BrowserControl;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Bases;
+
+
+public class DNADraw extends ScrollPanel
+ implements Printable, DragGestureListener,
+ DragSourceListener, DropTargetListener
+{
+ private static final long serialVersionUID = 1L;
+ public static JScrollPane jsp = new JScrollPane();
+ private DNADraw current_dna;
+ private JFrame mainFrame;
+
+ private Point location = new Point();
+ private Dimension border = new Dimension(150,100);
+ private Dimension panelSize = new Dimension(690,690);
+ private Dimension linearPanelSize = new Dimension(800,350);
+ private Hashtable lineAttr;
+ private Vector minorTicks;
+ private Vector majorTicks;
+ private Vector block;
+ private Vector restrictionEnzyme;
+
+ private int startTick = 0;
+ private int minorTick = 100;
+ private int majorTick = 500;
+
+//
+// store the tick positions -- there appears to be
+// a bug in AffineTransform when it comes to using
+// elements from the matrix when printing
+//
+ private int[] tickMajorXPositions;
+ private int[] tickMajorYPositions;
+
+ private int[] tickMinorXPositions;
+ private int[] tickMinorYPositions;
+
+ private boolean labelTicks = true;
+
+ private int[] reXPositions;
+ private int[] reYPositions;
+ private boolean close = false;
+ private EntryGroup artemisEntryGroup;
+ private Bases bases;
+ private Graph gcGraph;
+ private Graph gcSkewGraph;
+ private Vector<Graph> userGraphs = new Vector<Graph>();
+
+ final JMenu userDraw = new JMenu("Draw");
+ final JMenu userOptions = new JMenu("Options");
+
+ protected static int Y_SHIFT = -35;
+ protected static int THETA = -90;
+ private TrackManager trackManager;
+ private AffineTransform original;
+
+ // linear plot variables
+ private JMenuItem linearPlotOptions = new JMenuItem("Linear plot...");;
+ private int numberOfLines;
+ private int basesPerLine = 20000;
+ private float lineHeight = 200.f;
+ private float singleBaseWidth;
+ private int borderWidth2;
+ private int borderHeight2;
+
+ public DNADraw()
+ {
+ super(new BorderLayout());
+ location.setLocation(75.d, 75.d);
+
+ current_dna = this;
+ setBackground(Color.white);
+ setPreferredSize(panelSize);
+ setOpaque(false);
+ setToolTipText("");
+
+ if( System.getProperty("java.awt.headless") == null ||
+ !System.getProperty("java.awt.headless").equals("true") )
+ {
+ DragSource dragSource = DragSource.getDefaultDragSource();
+ dragSource.createDefaultDragGestureRecognizer(
+ this, // component where drag originates
+ DnDConstants.ACTION_COPY_OR_MOVE, // actions
+ this);
+ setDropTarget(new DropTarget(this,this));
+ }
+ lineAttr = new Hashtable();
+ lineAttr.put("start",new Integer(0));
+ lineAttr.put("end",new Integer(4000));
+ lineAttr.put("lsize",new Integer(5));
+ lineAttr.put("circular",new Boolean(true));
+
+ MouseListener mouseListener = new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent me)
+ {
+ if(me.getClickCount() == 2 &&
+ !me.isPopupTrigger())
+ {
+ final Block b = getBlockAtLocation(me.getPoint());
+ if(b != null)
+ {
+ final JFrame f = new JFrame("Properties");
+ JButton butt = new JButton("Delete");
+ butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ block.remove(b);
+ current_dna.repaint();
+ f.setVisible(false);
+ f.dispose();
+ }
+ });
+ b.showProperties(f, DNADraw.this, butt);
+ }
+ }
+ }
+ };
+ this.addMouseListener(mouseListener);
+ }
+
+ public String getVersion()
+ {
+ final ClassLoader cl = this.getClass().getClassLoader();
+ try
+ {
+ String line;
+ InputStream in = cl.getResourceAsStream("etc/versions");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ while((line = reader.readLine()) != null)
+ {
+ if(line.startsWith("DNAPlotter"))
+ return line.substring( "DNAPlotter".length() ).trim();
+ }
+ reader.close();
+ in.close();
+ }
+ catch (Exception ex)
+ {
+ }
+ return null;
+ }
+
+ /**
+ * Add list of features to a track
+ * @param features
+ * @param track
+ * @param drawLabel
+ */
+ public void addFeaturesToTrack(final List features,
+ final Track track,
+ final boolean drawLabel)
+ {
+ for(int i=0; i<features.size(); i++)
+ {
+ addFeatureToTrack((uk.ac.sanger.artemis.circular.Feature)features.get(i),
+ track, drawLabel);
+ }
+ }
+
+ /**
+ * Add a feature to a track
+ * @param tmpf
+ * @param track
+ * @param drawLabel
+ */
+ public void addFeatureToTrack(final uk.ac.sanger.artemis.circular.Feature tmpf,
+ final Track track,
+ final boolean drawLabel)
+ {
+ final uk.ac.sanger.artemis.Feature f = tmpf.getArtemisFeature();
+ Vector ranges = f.getLocation().getRanges();
+
+ for(int j=0; j<ranges.size(); j++)
+ {
+ Range range = (Range) ranges.get(j);
+
+ Block drawBlock = new Block(f.getIDString(),
+ range.getStart(),
+ range.getEnd(),
+ f.getColour(),
+ 10.f,
+ track, this);
+ drawBlock.setDrawLabel(drawLabel);
+ drawBlock.setFeature(f);
+ addBlock(drawBlock);
+ }
+ }
+
+ public String getToolTipText(MouseEvent me)
+ {
+ Block b = getBlockAtLocation(me.getPoint());
+ if(b != null)
+ {
+ if(b.getFeature() != null)
+ return b.getLabel()+" "+b.getFeature().getLocation().toStringShort();
+
+ return b.getLabel()+" "+b.getBstart()+".."+b.getBend();
+ }
+ return null;
+ }
+
+ public DNADraw(Vector minorTicks, Vector majorTicks,
+ Vector block,
+ Vector restrictionEnzyme)
+ {
+ this();
+ this.minorTicks = minorTicks;
+ this.block = block;
+ this.restrictionEnzyme = restrictionEnzyme;
+ }
+
+
+ public DNADraw(Vector block, Vector restrictionEnzyme,
+ Hashtable lineAttr, int startTick,
+ int minorTick, int majorTick)
+ {
+ this();
+ this.block = block;
+ this.restrictionEnzyme = restrictionEnzyme;
+ this.lineAttr = lineAttr;
+ this.startTick = startTick;
+ this.minorTick = minorTick;
+ this.majorTick = majorTick;
+
+ if(!isCircular())
+ setPreferredSize(linearPanelSize);
+
+ basesPerLine = majorTick;
+ calculateTickPosistions();
+ }
+
+
+ /**
+ *
+ * Get the width/diameter of the DNA map
+ *
+ */
+ protected double getDiameter()
+ {
+ return ((double)getWidth())-border.getWidth();
+ }
+
+
+ protected Point getLocationPoint()
+ {
+ return location;
+ }
+
+
+ protected void zoomIn()
+ {
+ int wid = getWidth();
+ wid = wid+(int)(wid*0.1);
+ int hgt = getHeight();
+ if(isCircular())
+ hgt = hgt+(int)(hgt*0.1);
+ zoom(wid,hgt);
+ }
+
+
+ protected void zoomOut()
+ {
+ int wid = getWidth();
+ wid = wid-(int)(wid*0.1);
+ int hgt = getHeight();
+ if(isCircular())
+ hgt = hgt-(int)(hgt*0.1);
+ zoom(wid,hgt);
+ }
+
+
+ private void zoom(int wid, int hgt)
+ {
+ if(isCircular())
+ {
+ panelSize = new Dimension(wid,hgt);
+ setPreferredSize(panelSize);
+ setSize(panelSize);
+ }
+ else
+ {
+ linearPanelSize = new Dimension(wid,hgt);
+ setPreferredSize(linearPanelSize);
+ setSize(linearPanelSize);
+ }
+ repaint();
+ }
+
+
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+
+ if(isCircular())
+ drawCircularPanel(g2,true);
+ else
+ drawLinearPanel(g2);
+ }
+
+
+ protected boolean isCircular()
+ {
+ return ((Boolean)lineAttr.get("circular")).booleanValue();
+ }
+
+
+ protected void addBlock(Block b)
+ {
+ if(getGeneticMarker() == null)
+ setGeneticMarker(new Vector());
+ this.getGeneticMarker().add(b);
+ validate();
+ }
+
+
+ protected void drawLinearPanel(Graphics2D g2)
+ {
+ RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ qualityHints.put(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2.setRenderingHints(qualityHints);
+
+ FontMetrics fm = g2.getFontMetrics();
+ double hgt = fm.getAscent();
+ g2.setColor(Color.black);
+ double widDash = 4;
+
+ int lineSize = 5;
+ try
+ {
+ lineSize = getLineSize();
+ }
+ catch(NullPointerException npe)
+ {
+ System.out.println("No line size specified using default!");
+ }
+
+ borderWidth2 = border.width/2;
+ borderHeight2 = border.height/2;
+
+ BasicStroke basicstroke = new BasicStroke(
+ lineSize,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+ g2.setStroke(basicstroke);
+
+ int lastBase = getEnd();
+ numberOfLines = Math.round( (lastBase/basesPerLine) + 0.5f);
+
+ int height = (int) ((numberOfLines * lineHeight)+border.height);
+ Dimension size = new Dimension(getWidth(),height);
+ setPreferredSize(size);
+ setSize(size);
+
+ singleBaseWidth = (float)(getWidth()-border.width)/(float)basesPerLine;
+
+ for(int i=0; i<numberOfLines; i++)
+ {
+ int ypos = (int) (borderHeight2+lineHeight+(lineHeight*i));
+
+ if(i<numberOfLines-1)
+ g2.drawLine(borderWidth2,ypos,getWidth()-borderWidth2,ypos);
+ else
+ {
+ int lastLineWidth = (int) ((lastBase-(i*basesPerLine))*singleBaseWidth)+borderWidth2;
+ g2.drawLine(borderWidth2,ypos,lastLineWidth,ypos);
+ }
+ }
+
+ g2.setColor(Color.black);
+ g2.setStroke(new BasicStroke(1.f));
+
+ if(majorTicks == null || minorTicks == null)
+ calculateTickPosistions();
+
+ Enumeration enumTk = minorTicks.elements();
+ while(enumTk.hasMoreElements())
+ {
+ int tick = ((Integer)enumTk.nextElement()).intValue();
+ int lineNumber = Math.round((float)(tick-1)/((float)basesPerLine) + 0.5f)-1;
+
+ int ypos = (int) (borderHeight2+lineHeight+(lineHeight*lineNumber));
+ int xpos = (int) ((tick-(lineNumber*basesPerLine))*singleBaseWidth)+borderWidth2;
+ int y = ypos+(int)((lineSize+widDash)/2);
+
+ g2.drawLine(xpos,ypos,xpos,y);
+ }
+
+ enumTk = majorTicks.elements();
+ while(enumTk.hasMoreElements())
+ {
+ int tick = ((Integer)enumTk.nextElement()).intValue();
+ int lineNumber = Math.round((float)(tick-1)/((float)basesPerLine) + 0.5f)-1;
+
+ if(lineNumber < 0)
+ lineNumber = 0;
+
+ int ypos = (int) (borderHeight2+lineHeight+(lineHeight*lineNumber));
+ int xpos = (int) ((tick-(lineNumber*basesPerLine))*singleBaseWidth)+borderWidth2;
+ int y = ypos+(int)((lineSize+widDash)/2);
+
+ g2.drawLine(xpos,ypos,xpos,y);
+
+ String label = Integer.toString(tick);
+ xpos-=(fm.stringWidth(label)/2);
+ y+=hgt;
+ g2.drawString(label,xpos,y);
+ }
+
+ /*if(restrictionEnzyme != null)
+ {
+ enumTk = restrictionEnzyme.elements();
+ while(enumTk.hasMoreElements())
+ {
+ Vector re = (Vector)enumTk.nextElement();
+ String reLabel = (String)re.elementAt(0);
+ int pos = ((Integer)re.elementAt(1)).intValue();
+ g2.setColor((Color)re.elementAt(2));
+ int x = ((diameter-location.x)*(pos-start)/(end-start))+location.x;
+ int y = ymid-(lineSize/2)-(int)widDash;
+ g2.drawLine(x,ymid,x,y);
+ x-=(fm.stringWidth(reLabel)/2);
+ y-=hgt;
+ g2.drawString(reLabel,x,y);
+ }
+ }*/
+
+ // draw features
+ Vector markers = getGeneticMarker();
+ for(int i=0; i<markers.size(); i++)
+ {
+ Block b = (Block)markers.get(i);
+ b.drawLinear(g2);
+ }
+ }
+
+ protected void drawCircularPanel(Graphics2D g2, boolean record)
+ {
+ RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ qualityHints.put(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2.setRenderingHints(qualityHints);
+ g2.setColor(Color.black);
+
+ FontMetrics fm = g2.getFontMetrics();
+ double hgt = fm.getAscent();
+ final double widthPanel = getWidth();
+ final double heightPanel = getHeight();
+
+ double rad = 360.d;
+ double pi = Math.PI;
+ double widDash = 4;
+
+ double ddiameter = widthPanel-border.getWidth();
+ double ddiameter2 = ddiameter/2.d;
+ int diameter = (int)ddiameter;
+
+ int lineSize = 5;
+ try
+ {
+ lineSize = getLineSize();
+ }
+ catch(NullPointerException npe)
+ {
+ System.out.println("No line size specified using default!");
+ }
+
+ original = g2.getTransform();
+ AffineTransform origin = (AffineTransform) original.clone();
+
+ origin.translate(0, Y_SHIFT);
+
+ // rotate so that origin is at the top
+ origin.translate(widthPanel/2, heightPanel/2);
+ origin.rotate(Math.toRadians(THETA));
+ origin.translate(-widthPanel/2, -heightPanel/2);
+
+ g2.setTransform(origin);
+
+ g2.setStroke(new BasicStroke((float)lineSize));
+ g2.drawArc(location.x,location.y,
+ diameter,diameter,0,360);
+
+ /* draw track circle
+ * int shift = (int)(diameter*(1.d-0.9d)/2);
+ g2.drawArc(location.x+shift,location.y+shift,
+ (int)(diameter*0.9),(int)(diameter*0.9),0,360);*/
+
+ AffineTransform newOrig;
+
+ if(restrictionEnzyme != null)
+ {
+ if(record)
+ {
+ int nsize = restrictionEnzyme.size();
+ reXPositions = new int[nsize];
+ reYPositions = new int[nsize];
+ }
+ Enumeration enumRes = restrictionEnzyme.elements();
+ while(enumRes.hasMoreElements())
+ {
+ Vector re = (Vector)enumRes.nextElement();
+ String reLabel = (String)re.elementAt(0);
+ int pos = ((Integer)re.elementAt(1)).intValue();
+ g2.setColor((Color)re.elementAt(2));
+ double ang = getAngleFromPosition(pos,rad);
+
+ newOrig = (AffineTransform)(origin.clone());
+ newOrig.rotate(Math.toRadians(-ang),
+ widthPanel/2.d,heightPanel/2.d);
+
+ int widLabel = (lineSize+fm.stringWidth(reLabel))/2;
+ int widREDash = (int)(widDash+widDash+lineSize)+widLabel;
+
+ int x = 0;
+ int y = 0;
+ if(record)
+ {
+ x = (int)( ddiameter2 + (newOrig.getScaleX()*
+ (ddiameter2 + 10 + widLabel + widREDash) ) -
+ widLabel );
+ y = (int)( ddiameter2 + (newOrig.getShearY()*
+ (ddiameter2 + 10 + widREDash + (hgt/2.d)) ) +
+ hgt/2.d );
+
+ int index = restrictionEnzyme.indexOf(re);
+ reXPositions[index] = x;
+ reYPositions[index] = y;
+ }
+ else
+ {
+ int index = restrictionEnzyme.indexOf(re);
+ x = reXPositions[index];
+ y = reYPositions[index];
+ }
+
+ AffineTransform labelTransform = (AffineTransform)(origin.clone());
+ labelTransform.translate(location.x+ddiameter2, location.y+ddiameter2);
+ labelTransform.rotate(Math.toRadians(-DNADraw.THETA));
+ labelTransform.translate(-(location.x+ddiameter2), -(location.y+ddiameter2));
+ g2.setTransform(labelTransform);
+
+
+ g2.drawString(reLabel,location.x+x,location.y+y);
+ g2.setTransform(newOrig);
+ g2.setStroke(new BasicStroke(1.f));
+ int xLine = location.x+(int)(ddiameter);
+ int yLine = location.y+(int)(ddiameter/2.d);
+ g2.drawLine(xLine,yLine,(int)(xLine+widREDash),yLine);
+ g2.setTransform(origin);
+ }
+ }
+
+ if(majorTicks == null || minorTicks == null)
+ {
+ calculateTickPosistions();
+ if(majorTick>0)
+ basesPerLine = majorTick;
+ }
+ //major ticks
+ drawCircularTicks(g2,ddiameter,ddiameter2,diameter,origin,
+ widthPanel,heightPanel,rad,pi,widDash,fm,
+ lineSize,record,majorTicks,false);
+
+ //minor ticks
+ drawCircularTicks(g2,ddiameter,ddiameter2,diameter,origin,
+ widthPanel,heightPanel,rad,pi,widDash/2,fm,
+ lineSize,record,minorTicks,true);
+
+ // draw features
+ Vector markers = getGeneticMarker();
+ for(int i=0; i<markers.size(); i++)
+ {
+ Block b = (Block)markers.get(i);
+ b.drawCircular(g2);
+ }
+
+ }
+
+
+ private void drawCircularTicks(Graphics2D g2, double ddiameter,
+ double ddiameter2, int diameter, AffineTransform origin,
+ double widthPanel,double heightPanel, double rad, double pi,
+ double widDash, FontMetrics fm, int lineSize,
+ boolean record, Vector ticks, final boolean smallTicks)
+ {
+
+ double hgt = fm.getAscent();
+
+ g2.setColor(Color.black);
+ if(record)
+ {
+ int nsize = ticks.size();
+
+ if(smallTicks)
+ {
+ tickMinorXPositions = new int[nsize];
+ tickMinorYPositions = new int[nsize];
+ }
+ else
+ {
+ tickMajorXPositions = new int[nsize];
+ tickMajorYPositions = new int[nsize];
+ }
+ }
+
+ AffineTransform newOrig;
+ Enumeration enumTk = ticks.elements();
+ while(enumTk.hasMoreElements())
+ {
+ int tick = ((Integer)enumTk.nextElement()).intValue();
+ double theta = Math.toRadians(-getAngleFromPosition(tick,rad));
+ if(theta > pi)
+ theta = theta - pi*2.d;
+
+ newOrig = (AffineTransform)(origin.clone());
+
+ // rotate and add tick mark
+ newOrig.rotate(theta,widthPanel/2.d,heightPanel/2.d);
+ String label = Integer.toString(tick);
+ double wid = fm.stringWidth(label);
+
+ int x = 0;
+ int y = 0;
+ if(record)
+ {
+ x = (int)( (ddiameter2) + (newOrig.getScaleX()*
+ (widDash+lineSize+3+(diameter+wid)/2.d)) - (wid/2.d));
+
+ y = (int)( (ddiameter2) + (newOrig.getShearY()*
+ (widDash+lineSize+3+(diameter+hgt)/2.d)) + (hgt/2.d));
+
+ int index = ticks.indexOf(new Integer(tick));
+
+ if(smallTicks)
+ {
+ tickMinorXPositions[index] = x;
+ tickMinorYPositions[index] = y;
+ }
+ else
+ {
+ tickMajorXPositions[index] = x;
+ tickMajorYPositions[index] = y;
+ }
+ }
+ else // use stored positions for printing
+ {
+ int index = ticks.indexOf(new Integer(tick));
+ if(smallTicks)
+ {
+ x = tickMinorXPositions[index];
+ y = tickMinorYPositions[index];
+ }
+ else
+ {
+ x = tickMajorXPositions[index];
+ y = tickMajorYPositions[index];
+ }
+ }
+
+ if(labelTicks && !smallTicks) // add tick label
+ {
+ AffineTransform labelTransform = (AffineTransform)(origin.clone());
+ labelTransform.translate(location.x+ddiameter2, location.y+ddiameter2);
+ labelTransform.rotate(Math.toRadians(-THETA));
+ labelTransform.translate(-(location.x+ddiameter2), -(location.y+ddiameter2));
+ g2.setTransform(labelTransform);
+ g2.drawString(label,
+ location.x+x,
+ location.y+y);
+ g2.setTransform(origin);
+ }
+
+ g2.setTransform(newOrig);
+
+ g2.setStroke(new BasicStroke(1.f));
+ int xLine = location.x+(int)(ddiameter);
+ int yLine = location.y+(int)(ddiameter/2.d);
+ g2.drawLine(xLine,yLine,(int)(xLine+lineSize+widDash),yLine);
+
+/*
+ System.out.println("THETA "+Math.toDegrees(theta));
+ System.out.println("m00 "+newOrig.getScaleX()+
+ " m01 "+newOrig.getShearX()+
+ " m02 "+newOrig.getTranslateX());
+ System.out.println("m10 "+newOrig.getScaleY()+
+ " m12 "+newOrig.getTranslateY());
+*/
+ g2.setTransform(origin);
+ }
+
+ return;
+ }
+
+
+ /**
+ *
+ * Calculate the tick marks to be drawn
+ *
+ */
+ protected void calculateTickPosistions()
+ {
+ minorTicks = new Vector();
+ majorTicks = new Vector();
+ int start = getStart();
+ int end = getEnd();
+
+ if(majorTick == 0)
+ return;
+
+ for(int i=startTick; i<end; i+=majorTick)
+ if(i >= start)
+ majorTicks.add(new Integer(i));
+
+ if(minorTick == 0)
+ return;
+
+ for(int i=startTick; i<end; i+=minorTick)
+ {
+ Integer tk = new Integer(i);
+ if(i >= start && !majorTicks.contains(tk))
+ minorTicks.add(tk);
+ }
+ }
+
+
+ /**
+ *
+ * Return the position tick marks start at
+ *
+ */
+ protected int getStartTick()
+ {
+ return startTick;
+ }
+
+
+ /**
+ *
+ * Set the position tick marks start at
+ *
+ */
+ protected boolean setStartTick(int startTick)
+ {
+ this.startTick = startTick;
+ if((startTick >= getStart()) && (startTick < getEnd()))
+ return true;
+
+ return false;
+ }
+
+
+ /**
+ *
+ * Return the interval for the tick marks
+ *
+ */
+ protected int getTickInterval()
+ {
+ return majorTick;
+ }
+
+
+ /**
+ *
+ * Set the interval for the tick marks
+ *
+ */
+ public boolean setTickInterval(int majorTick)
+ {
+ if(majorTick < (getEnd()-getStart()))
+ {
+ this.majorTick = majorTick;
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ *
+ * Return the interval for the tick marks
+ *
+ */
+ protected int getMinorTickInterval()
+ {
+ return minorTick;
+ }
+
+
+ /**
+ *
+ * Set the interval for the tick marks
+ *
+ */
+ public boolean setMinorTickInterval(int minorTick)
+ {
+ if(minorTick < (getEnd()-getStart()))
+ {
+ this.minorTick = minorTick;
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ *
+ * Return an angle in degrees
+ *
+ */
+ protected double getAngleFromPosition(int pos,double rad)
+ {
+ int start = getStart();
+ int end = getEnd();
+ return - ((pos-start)*rad)/(end-start);
+ }
+
+
+ /**
+ *
+ * The method @print@ must be implemented for @Printable@ interface.
+ * Parameters are supplied by system.
+ *
+ */
+ public int print(Graphics g, PageFormat pf, int pageIndex)
+ throws PrinterException
+ {
+ Graphics2D g2 = (Graphics2D)g;
+ g2.setColor(Color.black); //set default foreground color to black
+
+ //RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);
+ Dimension d = this.getSize(); //get size of document
+ double panelWidth = d.width; //width in pixels
+ double panelHeight = d.height; //height in pixels
+ double pageHeight = pf.getImageableHeight(); //height of printer page
+ double pageWidth = pf.getImageableWidth(); //width of printer page
+ double scale = pageWidth/panelWidth;
+ int totalNumPages = (int)Math.ceil(scale * panelHeight / pageHeight);
+
+ // Make sure not print empty pages
+ if(pageIndex >= totalNumPages)
+ {
+ System.out.println("NO SUCH PAGE "+pageIndex);
+ return Printable.NO_SUCH_PAGE;
+ }
+
+ // Shift Graphic to line up with beginning of print-imageable region
+ g2.translate(pf.getImageableX(), pf.getImageableY());
+ // Shift Graphic to line up with beginning of next page to print
+ g2.translate(0f, -pageIndex*pageHeight);
+ // Scale the page so the width fits...
+ g2.scale(scale, scale);
+
+ try
+ {
+ drawAll(g2,false);
+ }
+ catch(Exception e){ e.printStackTrace(); }
+ return Printable.PAGE_EXISTS;
+ }
+
+ public void drawAll(Graphics2D g2, boolean l)
+ {
+ if(((Boolean)lineAttr.get("circular")).booleanValue())
+ drawCircularPanel(g2,l); //repaint the page for printing
+ else
+ drawLinearPanel(g2);
+
+ if(gcGraph != null && containsGraph(gcGraph))
+ {
+ if(isCircular())
+ gcGraph.draw(g2);
+ else
+ gcGraph.drawLinear(g2);
+ }
+ if(gcSkewGraph != null && containsGraph(gcSkewGraph))
+ {
+ if(isCircular())
+ gcSkewGraph.draw(g2);
+ else
+ gcSkewGraph.drawLinear(g2);
+ }
+ if(userGraphs.size() > 0)
+ {
+ for(int i=0; i<userGraphs.size(); i++)
+ {
+ Graph userGraph = userGraphs.get(i);
+ if(containsGraph(userGraph))
+ {
+ if(isCircular())
+ userGraph.draw(g2);
+ else
+ userGraph.drawLinear(g2);
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if a Graph object is contained by the panel.
+ * @param g
+ * @return
+ */
+ protected boolean containsGraph(Graph g)
+ {
+ int ncomponents = getComponentCount();
+ for(int i=0; i<ncomponents; i++)
+ if(getComponent(i).equals(g))
+ return true;
+
+ return false;
+ }
+
+ public void doPrintActions()
+ {
+ final PrinterJob pj=PrinterJob.getPrinterJob();
+ pj.setPrintable(this);
+ pj.printDialog();
+ try
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ pj.print();
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ catch (Exception pe) { pe.printStackTrace(); }
+ }
+
+
+ public void setRestrictionEnzyme(Vector restrictionEnzyme)
+ {
+ this.restrictionEnzyme = restrictionEnzyme;
+ }
+
+ public void setGeneticMarker(Vector block)
+ {
+ this.block = block;
+ }
+
+ protected Hashtable getLineAttributes()
+ {
+ return lineAttr;
+ }
+
+ public Hashtable getFeaturePoints()
+ {
+ Collections.sort(block, new BlockComparator());
+ final Hashtable h = new Hashtable(block.size());
+ int len = 0;
+ double lastAngle = -10.d;
+
+ for(int i=0; i<block.size(); i++)
+ {
+ Block b = (Block)block.get(i);
+ double thisAngle = b.getMidAngle();
+
+ if( Math.abs(thisAngle - lastAngle ) < 2 )
+ len = len+10;
+ else
+ len = 0;
+
+ h.put(b.getFeature().getIDString()+";"+b.getBstart()+".."+b.getBend(),
+ b.getLinePoints( len ));
+
+ lastAngle = thisAngle;
+ }
+ return h;
+ }
+
+ public void setLineAttributes(Hashtable lineAttr)
+ {
+ this.lineAttr = lineAttr;
+
+ if(isCircular())
+ {
+ setSize(panelSize);
+ setPreferredSize(panelSize);
+ }
+
+ linearPlotOptions.setEnabled(!isCircular());
+ revalidate();
+ repaint();
+ }
+
+
+ protected void setLineSize(int lineSize)
+ {
+ lineAttr.put("lsize",new Integer(lineSize));
+ }
+
+
+ protected int getLineSize()
+ {
+ return ((Integer)lineAttr.get("lsize")).intValue();
+ }
+
+ public void setPlasmidLocation(int x,int y)
+ {
+ location.setLocation(x,y);
+ }
+
+
+ public JMenuBar createMenuBar()
+ {
+ JMenuBar menuBar = new JMenuBar();
+
+// file menu
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.setMnemonic(KeyEvent.VK_F);
+ menuBar.add(fileMenu);
+
+ final JMenuItem openMenu = new JMenuItem("Open");
+ openMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Wizard.getDNADrawFromFile(DNADraw.this);
+ majorTicks = null;
+
+ if(gcGraph != null)
+ gcGraph = new GCGraph(DNADraw.this);
+ if(gcSkewGraph != null)
+ gcSkewGraph = new GCSkewGraph(DNADraw.this);
+
+ repaint();
+
+ /*EmbossCirdnaReader dnaRead = new EmbossCirdnaReader();
+ block = dnaRead.getBlock();
+ restrictionEnzyme = dnaRead.getRestrictionEnzyme();
+
+ lineAttr.put("start",new Integer(dnaRead.getStart()));
+ lineAttr.put("end",new Integer(dnaRead.getEnd()));
+
+ current_dna = new DNADraw(block,restrictionEnzyme,
+ lineAttr,0,100,100);
+ jsp.setViewportView(current_dna);*/
+ }
+ });
+ fileMenu.add(openMenu);
+ fileMenu.add(new JSeparator());
+
+ final JMenuItem readInEntry = new JMenuItem("Read In Entry on Separate Track...");
+ readInEntry.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Wizard.readEntry(DNADraw.this, getBases());
+ trackManager.refresh();
+ repaint();
+ }
+ });
+ fileMenu.add(readInEntry);
+
+// print
+ final JMenuItem printMenu = new JMenuItem("Print...");
+ fileMenu.add(printMenu);
+
+ printMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ doPrintActions();
+ }
+ });
+
+ JMenuItem printImage = new JMenuItem("Save As jpeg/png Image...");
+ printImage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintDNAImage pdnai = new PrintDNAImage(current_dna);
+ pdnai.printAsSinglePage();
+ }
+ });
+ fileMenu.add(printImage);
+
+// print preview
+ JMenuItem printPreview = new JMenuItem("Print Preview...");
+ printPreview.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintDNAImage pdnai = new PrintDNAImage(current_dna);
+ pdnai.printPreview();
+ }
+ });
+ fileMenu.add(printPreview);
+ fileMenu.add(new JSeparator());
+
+ fileMenu.add(TrackManager.getExportTrackTemplateMenuItem(null, this));
+
+ if(trackManager == null)
+ trackManager = new TrackManager(DNADraw.this);
+ fileMenu.add(trackManager.getImportTrackTemplateMenuItem(null));
+ fileMenu.add(new JSeparator());
+
+ final JMenuItem fileMenuExit;
+ if(!close)
+ fileMenuExit = new JMenuItem("Exit");
+ else
+ fileMenuExit = new JMenuItem("Close");
+ fileMenuExit.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(!close)
+ System.exit(0);
+ else
+ {
+ mainFrame.setVisible(false);
+ mainFrame.dispose();
+ }
+ }
+ });
+ fileMenu.add(fileMenuExit);
+
+// view menu
+ JMenu viewMenu = new JMenu("View");
+ menuBar.add(viewMenu);
+
+ JMenuItem zoomIn = new JMenuItem("Zoom In");
+ zoomIn.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_I, ActionEvent.CTRL_MASK));
+ zoomIn.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ zoomIn();
+ }
+ });
+ viewMenu.add(zoomIn);
+
+ JMenuItem zoomOut = new JMenuItem("Zoom Out");
+ zoomOut.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_O, ActionEvent.CTRL_MASK));
+ zoomOut.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ zoomOut();
+ }
+ });
+ viewMenu.add(zoomOut);
+
+
+ JMenu graph_menu = new JMenu("Graph");
+ graph_menu.setMnemonic(KeyEvent.VK_G);
+ menuBar.add(graph_menu);
+
+ JMenu gc = new JMenu("GC plot");
+ graph_menu.add(gc);
+
+ final JCheckBoxMenuItem gcPlot = new JCheckBoxMenuItem("Draw");
+
+ if(gcGraph == null)
+ gcPlot.setState(false);
+ else
+ gcPlot.setState(true);
+ gcPlot.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ if(gcPlot.isSelected())
+ {
+ if(gcGraph == null)
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ gcGraph = new GCGraph(DNADraw.this);
+ gcGraph.setTrack(0.4);
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ add(gcGraph);
+ revalidate();
+ }
+ else
+ remove(gcGraph);
+ repaint();
+ }
+ });
+ gc.add(gcPlot);
+
+ JMenuItem gcOptions = new JMenuItem("Options...");
+ gc.add(gcOptions);
+ gcOptions.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ gcGraph.showOptions();
+ }
+ });
+
+ JMenu gcSkew = new JMenu("GC Skew");
+ graph_menu.add(gcSkew);
+
+ final JCheckBoxMenuItem gcSkewPlot = new JCheckBoxMenuItem("Draw");
+ if(gcSkewGraph == null)
+ gcSkewPlot.setState(false);
+ else
+ gcSkewPlot.setState(true);
+
+ gcSkewPlot.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ if(gcSkewPlot.isSelected())
+ {
+ if(gcSkewGraph == null)
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ gcSkewGraph = new GCSkewGraph(DNADraw.this);
+ gcSkewGraph.setTrack(0.2);
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ add(gcSkewGraph);
+ revalidate();
+ }
+ else
+ remove(gcSkewGraph);
+ repaint();
+ }
+ });
+ gcSkew.add(gcSkewPlot);
+
+ JMenuItem gcSkewOptions = new JMenuItem("Options...");
+ gcSkew.add(gcSkewOptions);
+ gcSkewOptions.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ gcSkewGraph.showOptions();
+ }
+ });
+
+
+ //
+ // User graph
+
+ JMenu user = new JMenu("User");
+ graph_menu.add(user);
+
+ final JMenuItem userPlot = new JMenuItem("Add");
+ userPlot.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent ae)
+ {
+ final uk.ac.sanger.artemis.components.StickyFileChooser dialog =
+ new uk.ac.sanger.artemis.components.StickyFileChooser ();
+
+ dialog.setDialogTitle ("Select a data file name ...");
+ dialog.setDialogType (JFileChooser.OPEN_DIALOG);
+
+ final int status = dialog.showOpenDialog (null);
+
+ if(status != JFileChooser.APPROVE_OPTION ||
+ dialog.getSelectedFile () == null)
+ {
+ return;
+ }
+
+ final java.io.File file =
+ new java.io.File (dialog.getCurrentDirectory (),
+ dialog.getSelectedFile ().getName ());
+
+ if(file.length() == 0)
+ return;
+
+ //final uk.ac.sanger.artemis.util.Document document =
+ // new uk.ac.sanger.artemis.util.FileDocument (file);
+
+ UserGraph userGraph;
+ try
+ {
+ userGraph =
+ new UserGraph(DNADraw.this, file.getAbsolutePath());
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ return;
+ }
+
+ if(userGraph == null)
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ try
+ {
+ userGraph = new UserGraph(DNADraw.this, file.getAbsolutePath());
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ userGraph.setTrack(0.2);
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+
+ setUserGraph(userGraph);
+ add(userGraph);
+
+ revalidate();
+ repaint();
+ }
+ });
+ user.add(userPlot);
+ user.add(userDraw);
+ user.add(userOptions);
+
+
+// options menu
+ JMenu optionMenu = new JMenu("Options");
+ menuBar.add(optionMenu);
+
+
+ JMenuItem wizard = new JMenuItem("DNA Wizard...");
+ wizard.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ new Wizard(current_dna);
+ }
+ });
+ optionMenu.add(wizard);
+ optionMenu.add(new JSeparator());
+
+ JMenuItem tracksMenu = new JMenuItem("Track Manager...");
+
+ if(getArtemisEntryGroup() != null)
+ {
+ if(trackManager == null)
+ trackManager = new TrackManager(DNADraw.this);
+ tracksMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ trackManager.setVisible(true);
+ }
+ });
+ }
+ else
+ tracksMenu.setEnabled(false);
+
+ optionMenu.add(tracksMenu);
+
+
+ /*JMenuItem line = new JMenuItem("DNA Properties...");
+ line.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ JFrame f = new JFrame("DNA Properties");
+ LineAttribute la = new LineAttribute(current_dna);
+ JScrollPane laScroll = new JScrollPane(la);
+ JPanel laPane = (JPanel)f.getContentPane();
+ laPane.add(laScroll,BorderLayout.CENTER);
+ f.setJMenuBar(la.createMenuBar(f));
+ f.pack();
+ f.setVisible(true);
+ }
+ });
+ optionMenu.add(line);*/
+
+
+ JMenuItem tickMarks = new JMenuItem("Tick marks...");
+ tickMarks.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ JFrame f = new JFrame("Tick Marks");
+ Ticks tk = new Ticks(current_dna,true);
+ JScrollPane tkScroll = new JScrollPane(tk);
+ JPanel tkPane = (JPanel)f.getContentPane();
+ tkPane.add(tkScroll,BorderLayout.CENTER);
+ f.setJMenuBar(tk.createMenuBar(f));
+ f.pack();
+ f.setVisible(true);
+ }
+ });
+ optionMenu.add(tickMarks);
+
+
+ JMenuItem gmarker = new JMenuItem("Features...");
+ gmarker.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ JFrame f = new JFrame("Features");
+ GeneticMarker gm = new GeneticMarker(current_dna,
+ block);
+ JScrollPane gmScroll = new JScrollPane(gm);
+ JPanel gmPane = (JPanel)f.getContentPane();
+ gmPane.add(gmScroll,BorderLayout.CENTER);
+ f.setJMenuBar(gm.createMenuBar(f));
+ f.pack();
+ f.setVisible(true);
+ }
+ });
+ optionMenu.add(gmarker);
+
+ JMenuItem reSites = new JMenuItem("Restriction Enzyme...");
+ reSites.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ JFrame f = new JFrame("Restriction Enzyme");
+ RestrictionEnzyme re = new RestrictionEnzyme(current_dna,
+ restrictionEnzyme);
+ JScrollPane reScroll = new JScrollPane(re);
+ JPanel rePane = (JPanel)f.getContentPane();
+ rePane.add(reScroll,BorderLayout.CENTER);
+ f.setJMenuBar(re.createMenuBar(f));
+ f.pack();
+ f.setVisible(true);
+ }
+ });
+ optionMenu.add(reSites);
+
+ linearPlotOptions.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ showLinearPlotOptions();
+ }
+ });
+ linearPlotOptions.setEnabled(!isCircular());
+ optionMenu.add(linearPlotOptions);
+ optionMenu.addSeparator();
+
+ final JCheckBoxMenuItem labelTick = new JCheckBoxMenuItem("Show tick numbers", labelTicks);
+ labelTick.addItemListener(new ItemListener()
+ {
+
+ public void itemStateChanged(ItemEvent e)
+ {
+ labelTicks = labelTick.isSelected();
+ repaint();
+ }
+ });
+ optionMenu.add(labelTick);
+
+ final JCheckBoxMenuItem asCircular = new JCheckBoxMenuItem("Display as Circular Plot", isCircular());
+ asCircular.addItemListener(new ItemListener()
+ {
+
+ public void itemStateChanged(ItemEvent e)
+ {
+ lineAttr.put("circular",new Boolean(asCircular.isSelected()));
+ setLineAttributes(lineAttr);
+ linearPlotOptions.setEnabled(!isCircular());
+ }
+ });
+ optionMenu.add(asCircular);
+
+// help manu
+ JMenu helpMenu = new JMenu("Help");
+ menuBar.add(helpMenu);
+
+ JMenuItem aboutMenu = new JMenuItem("About");
+ helpMenu.add(aboutMenu);
+ aboutMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ /*ClassLoader cl = DNADraw.this.getClass().getClassLoader();
+ URL url = cl.getResource("etc/readmeDNADraw.html");*/
+ BrowserControl.displayURL("http://www.sanger.ac.uk/Software/Artemis/circular/");
+ }
+ });
+
+ return menuBar;
+ }
+
+ /**
+ * Provide options for the linear plot
+ */
+ private void showLinearPlotOptions()
+ {
+ GridBagLayout grid = new GridBagLayout();
+ GridBagConstraints c = new GridBagConstraints();
+ c.ipady = 3;
+ c.ipadx = 5;
+
+ JPanel optionBox = new JPanel(grid);
+ c.gridx = 1;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.WEST;
+ TextFieldFloat lineHeightField = new TextFieldFloat();
+ lineHeightField.setValue(getLineHeight());
+ optionBox.add(lineHeightField, c);
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ optionBox.add(new JLabel("Line Height"), c);
+
+ c.gridx = 1;
+ c.gridy = 1;
+ c.anchor = GridBagConstraints.WEST;
+ TextFieldInt basesPerLineField = new TextFieldInt();
+ basesPerLineField.setValue(getBasesPerLine());
+ optionBox.add(basesPerLineField, c);
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ optionBox.add(new JLabel("Bases Per Line"), c);
+
+ JOptionPane.showMessageDialog(null, optionBox,
+ "Linear Plot Options", JOptionPane.QUESTION_MESSAGE);
+
+ setLineHeight( (float) lineHeightField.getValue() );
+ setBasesPerLine(basesPerLineField.getValue());
+ revalidate();
+ repaint();
+ }
+
+ public void setCloseAndDispose(boolean close, JFrame mainFrame)
+ {
+ this.mainFrame = mainFrame;
+ this.close = close;
+ }
+
+ protected Vector getGeneticMarker()
+ {
+ return block;
+ }
+
+
+ protected Vector getRestrictionEnzyme()
+ {
+ return restrictionEnzyme;
+ }
+
+
+ protected int getStart()
+ {
+ return ((Integer)lineAttr.get("start")).intValue();
+ }
+
+
+ protected int getEnd()
+ {
+ return ((Integer)lineAttr.get("end")).intValue();
+ }
+
+
+ protected void setStart(int start)
+ {
+ lineAttr.put("start",new Integer(start));
+ calculateTickPosistions();
+ }
+
+
+ protected void setEnd(int end)
+ {
+ lineAttr.put("end",new Integer(end));
+ calculateTickPosistions();
+ }
+
+ public Block getBlockAtLocation(Point loc)
+ {
+ for(int i=0; i<block.size(); i++)
+ {
+ Block b = (Block) block.get(i);
+ if(b.isOverMe(loc.x, loc.y))
+ return b;
+ }
+ return null;
+ }
+
+ public Block getBlockAtBasePosition(int bend)
+ {
+ for(int i=0; i<block.size(); i++)
+ {
+ Block b = (Block) block.get(i);
+ if(b.getBend() == bend)
+ return b;
+ }
+ return null;
+ }
+
+////////////////////
+// DRAG AND DROP
+////////////////////
+ public RenderedImage createImage(Block b)
+ {
+ // Create a buffered image in which to draw
+ BufferedImage bufferedImage = new BufferedImage(getWidth(), getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+
+ // Create a graphics contents on the buffered image
+ Graphics2D g2d = bufferedImage.createGraphics();
+ RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ qualityHints.put(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHints(qualityHints);
+ g2d.setTransform( ((Graphics2D)getGraphics()).getTransform() );
+ // Draw graphics
+ //b.drawCircular(g2d);
+
+ g2d.setStroke(new BasicStroke(2.f));
+ g2d.setColor(b.getColour());
+ g2d.drawArc(0, 0, 10, 10, 0, 360);
+ g2d.dispose();
+
+ return bufferedImage;
+ }
+
+// drag source
+ public void dragGestureRecognized(DragGestureEvent e)
+ {
+ // ignore if mouse popup trigger
+ InputEvent ie = e.getTriggerEvent();
+ if(ie instanceof MouseEvent)
+ if(((MouseEvent)ie).isPopupTrigger())
+ return;
+
+ Point loc = e.getDragOrigin();
+ Block b = getBlockAtLocation(loc);
+
+ if(b != null)
+ {
+ Image image = (Image)createImage(b);
+ e.startDrag(DragSource.DefaultCopyDrop, // cursor
+ image, new Point(-1, -1),
+ (Transferable)b, // transferable data
+ this);
+ }
+ }
+
+ public void dragDropEnd(DragSourceDropEvent e) {}
+ public void dragEnter(DragSourceDragEvent e) {}
+ public void dragExit(DragSourceEvent e) {}
+ public void dragOver(DragSourceDragEvent e) {}
+ public void dropActionChanged(DragSourceDragEvent e) {}
+
+// drop sink
+ public void dragEnter(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(Block.BLOCK))
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+
+ public void drop(DropTargetDropEvent e)
+ {
+ Transferable t = e.getTransferable();
+ if(t.isDataFlavorSupported(Block.BLOCK))
+ {
+ try
+ {
+ Point loc = e.getLocation();
+ Block b = (Block)t.getTransferData(Block.BLOCK);
+ b.setBlockLocation(loc.x,loc.y,trackManager);
+ DNADraw.this.repaint();
+ }
+ catch(Exception ufe){}
+ }
+ }
+
+ public void dragOver(DropTargetDragEvent e)
+ {
+ }
+
+ public void dropActionChanged(DropTargetDragEvent e) {}
+ public void dragExit(DropTargetEvent e){}
+
+ public EntryGroup getArtemisEntryGroup()
+ {
+ return artemisEntryGroup;
+ }
+
+ public void setArtemisEntryGroup(EntryGroup artemisEntryGroup)
+ {
+ this.artemisEntryGroup = artemisEntryGroup;
+ }
+
+ public Bases getBases()
+ {
+ if(getArtemisEntryGroup() != null)
+ return getArtemisEntryGroup().getSequenceEntry().getBases();
+ return this.bases;
+ }
+
+ public void setBases(Bases bases)
+ {
+ this.bases = bases;
+ }
+
+ protected Vector getBlock()
+ {
+ return block;
+ }
+
+
+ public int getBasesPerLine()
+ {
+ return basesPerLine;
+ }
+
+
+ public void setBasesPerLine(int basesPerLine)
+ {
+ this.basesPerLine = basesPerLine;
+ }
+
+
+ public int getNumberOfLines()
+ {
+ return numberOfLines;
+ }
+
+
+ public void setNumberOfLines(int numberOfLines)
+ {
+ this.numberOfLines = numberOfLines;
+ }
+
+
+ public float getLineHeight()
+ {
+ return lineHeight;
+ }
+
+
+ public void setLineHeight(float lineHeight)
+ {
+ this.lineHeight = lineHeight;
+ }
+
+
+ public float getSingleBaseWidth()
+ {
+ return singleBaseWidth;
+ }
+
+
+ public void setSingleBaseWidth(float singleBaseWidth)
+ {
+ this.singleBaseWidth = singleBaseWidth;
+ }
+
+
+ public int getBorderWidth2()
+ {
+ return borderWidth2;
+ }
+
+
+ public void setBorderWidth2(int borderWidth2)
+ {
+ this.borderWidth2 = borderWidth2;
+ }
+
+
+ public int getBorderHeight2()
+ {
+ return borderHeight2;
+ }
+
+
+ public void setBorderHeight2(int borderHeight2)
+ {
+ this.borderHeight2 = borderHeight2;
+ }
+
+ public TrackManager getTrackManager()
+ {
+ return trackManager;
+ }
+
+ public void setTrackManager(TrackManager trackManager)
+ {
+ this.trackManager = trackManager;
+ }
+
+ public Graph getGcGraph()
+ {
+ return gcGraph;
+ }
+
+ public Vector<Graph> getUserGraphs()
+ {
+ return userGraphs;
+ }
+
+ public void setGcGraph(Graph gcGraph)
+ {
+ this.gcGraph = gcGraph;
+ }
+
+ public Graph getGcSkewGraph()
+ {
+ return gcSkewGraph;
+ }
+
+ public void setGcSkewGraph(Graph gcSkewGraph)
+ {
+ this.gcSkewGraph = gcSkewGraph;
+ }
+
+
+ protected void setUserGraph(final UserGraph thisUserGraph)
+ {
+ userGraphs.add(thisUserGraph);
+
+ JMenuItem thisGraphOptions = new JMenuItem(thisUserGraph.getFileName());
+ thisGraphOptions.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ thisUserGraph.showOptions();
+ }
+ });
+ userOptions.add(thisGraphOptions);
+
+ final JCheckBoxMenuItem thisGraphDraw =
+ new JCheckBoxMenuItem(thisUserGraph.getFileName());
+ thisGraphDraw.setSelected(true);
+ thisGraphDraw.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(thisGraphDraw.isSelected())
+ thisUserGraph.setVisible(true);
+ else
+ thisUserGraph.setVisible(false);
+ }
+ });
+ userDraw.add(thisGraphDraw);
+ }
+
+ public static void main(final String arg[])
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ final Wizard wiz;
+
+ if(arg.length > 0 && arg[0].equals("-t"))
+ wiz = new Wizard(arg[1]);
+ else
+ wiz = new Wizard((DNADraw)null);
+ final DNADraw dna = wiz.getDNADraw();
+
+ //
+ final String version = dna.getVersion();
+ final JFrame f = new JFrame();
+ if(version == null)
+ f.setTitle("DNAPlotter");
+ else
+ f.setTitle("DNAPlotter :: "+version);
+
+ Dimension d = f.getToolkit().getScreenSize();
+
+ jsp.setViewportView(dna);
+ jsp.getViewport().setBackground(Color.white);
+ f.getContentPane().add(jsp);
+ f.setJMenuBar(dna.createMenuBar());
+
+ //dna.add(new Graph(dna));
+ f.pack();
+ f.setLocation(((int)d.getWidth()-f.getWidth())/4,
+ ((int)d.getHeight()-f.getHeight())/2);
+
+
+ //dna.add(dna.new BlockPanel(dna));
+ f.setVisible(true);
+
+ if(wiz.getWorkerGraph() != null)
+ wiz.getWorkerGraph().start();
+ }
+ });
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/EmbossCirdnaReader.java b/uk/ac/sanger/artemis/circular/EmbossCirdnaReader.java
new file mode 100644
index 0000000..813d51b
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/EmbossCirdnaReader.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import javax.swing.*;
+import java.awt.Color;
+import java.io.*;
+import java.util.Vector;
+import java.util.StringTokenizer;
+
+/**
+*
+*
+*/
+public class EmbossCirdnaReader
+{
+
+ /** cirdna file */
+ private File cirdnaFile;
+ /** true if read ok */
+ private boolean reading = false;
+ /** */
+ private Vector restrictionEnzyme = new Vector();
+ /** genetic markers */
+ private Vector block = new Vector();
+ /** emboss colour scheme for cirdna */
+ private Color[] embossColor = {
+ Color.black,
+ Color.red,
+ Color.yellow,
+ Color.green,
+ Color.decode("#99CCFF"), //AQUAMARINE
+ Color.decode("#FFCCCC"), //pink
+ Color.decode("#FFFFCC"), //wheat
+ Color.gray,
+ Color.decode("#993300"), //brown
+ Color.blue,
+ Color.decode("#9933FF"), //blueviolet
+ Color.cyan,
+ Color.decode("#33FFCC"), //turqoise
+ Color.decode("#FF00FF"), //magenta
+ Color.decode("#FF9966"), //salmon
+ Color.white
+ };
+ int start = 0;
+ int end = 0;
+
+
+ public EmbossCirdnaReader()
+ {
+ SecurityManager sm = System.getSecurityManager();
+ System.setSecurityManager(null);
+ JFileChooser fc = new JFileChooser(System.getProperty("user.home"));
+ System.setSecurityManager(sm);
+
+ int returnVal = fc.showOpenDialog(fc);
+
+ if(returnVal == JFileChooser.APPROVE_OPTION)
+ {
+ cirdnaFile = fc.getSelectedFile();
+ readFile();
+ reading = true;
+ }
+ }
+
+ /**
+ *
+ * @param cirdnaFile cirdna file
+ *
+ */
+ public EmbossCirdnaReader(File cirdnaFile)
+ {
+ this.cirdnaFile = cirdnaFile;
+ readFile();
+ reading = true;
+ }
+
+
+ /**
+ *
+ *
+ */
+ public boolean isReading()
+ {
+ return reading;
+ }
+
+ /**
+ *
+ * Read a cirdna file
+ *
+ */
+ public Vector readFile()
+ {
+ BufferedReader in = null;
+ try
+ {
+ in = new BufferedReader(new FileReader(cirdnaFile));
+ String line;
+ while((line = in.readLine()) != null )
+ {
+ line = line.trim().toLowerCase();
+ if(!line.equals(""))
+ {
+ StringTokenizer stok = new StringTokenizer(line," ");
+ if(line.startsWith("start "))
+ {
+ stok.nextElement();
+ start = Integer.parseInt((String)stok.nextElement());
+ }
+ else if(line.startsWith("end "))
+ {
+ stok.nextElement();
+ end = Integer.parseInt((String)stok.nextElement());
+ }
+ else if(line.startsWith("group"))
+ {
+ while((line = in.readLine()) != null )
+ {
+ line = line.trim().toLowerCase();
+ if(line.startsWith("endgroup"))
+ break;
+ else if(line.startsWith("block "))
+ {
+ stok = new StringTokenizer(line," ");
+ Vector marker = new Vector();
+ stok.nextElement();
+ Integer bstart = new Integer((String)stok.nextElement());
+ Integer bend = new Integer((String)stok.nextElement());
+ Color col = Color.red;
+
+ if(stok.hasMoreTokens())
+ col = embossColor[Integer.parseInt((String)stok.nextElement())];
+ String name = in.readLine().trim();
+ if(name.equals("endlabel"))
+ name = "";
+
+ marker.add(name);
+ marker.add(bstart);
+ marker.add(bend);
+ marker.add(col);
+ marker.add(new Float(10.f));
+ marker.add(new Boolean(false));
+ marker.add(new Boolean(false));
+ block.add(marker);
+ }
+ else if(line.startsWith("tick"))
+ {
+ stok = new StringTokenizer(line," ");
+
+ stok.nextElement();
+ Integer pos = new Integer((String)stok.nextElement());
+ Color col = Color.red;
+ if(stok.hasMoreTokens())
+ col = embossColor[Integer.parseInt((String)stok.nextElement())];
+ String name = in.readLine();
+ if(line.equals("endlabel"))
+ name = "";
+
+ Vector re = new Vector();
+ re.add(name);
+ re.add(pos);
+ re.add(col);
+ restrictionEnzyme.add(re);
+ }
+ }
+ }
+
+
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ System.out.println("SequenceReader Error");
+ }
+
+ System.out.println("Start : "+start);
+ System.out.println("End : "+end);
+
+ return null;
+ }
+
+
+ protected Vector getRestrictionEnzyme()
+ {
+ return restrictionEnzyme;
+ }
+
+
+ protected Vector getBlock()
+ {
+ return block;
+ }
+
+ protected int getStart()
+ {
+ return start;
+ }
+
+ protected int getEnd()
+ {
+ return end;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/Feature.java b/uk/ac/sanger/artemis/circular/Feature.java
new file mode 100644
index 0000000..7b65b6d
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/Feature.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+public class Feature
+{
+ private int colour;
+ private int start;
+ private int end;
+ private String emblKey;
+
+ public Feature(final String emblKey,
+ final int start,
+ final int end,
+ final int colour)
+ {
+ this.emblKey = emblKey;
+ this.start = start;
+ this.end = end;
+ this.colour = colour;
+ }
+
+ public int getEnd()
+ {
+ return end;
+ }
+
+ public void setEnd(int end)
+ {
+ this.end = end;
+ }
+
+ public int getStart()
+ {
+ return start;
+ }
+
+ public void setStart(int start)
+ {
+ this.start = start;
+ }
+
+ public String getEmblKey()
+ {
+ return emblKey;
+ }
+
+ public void setEmblKey(String emblKey)
+ {
+ this.emblKey = emblKey;
+ }
+
+ public int getColour()
+ {
+ return colour;
+ }
+
+ public void setColour(int colour)
+ {
+ this.colour = colour;
+ }
+
+ public uk.ac.sanger.artemis.Feature getArtemisFeature()
+ {
+ Key key = new Key(emblKey);
+
+ QualifierVector qualifiers = new QualifierVector();
+ Qualifier qualifier = new Qualifier("colour", Integer.toString(getColour()));
+ qualifiers.add(qualifier);
+
+ uk.ac.sanger.artemis.io.Feature f = null;
+ try
+ {
+ Range r = new Range(getStart(), getEnd());
+ Location location = new Location(r);
+
+ f = new uk.ac.sanger.artemis.io.EmblStreamFeature(key, location, qualifiers);
+ return new uk.ac.sanger.artemis.Feature(f);
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/GCGraph.java b/uk/ac/sanger/artemis/circular/GCGraph.java
new file mode 100644
index 0000000..c64e5dd
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/GCGraph.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import uk.ac.sanger.artemis.io.Sequence;
+
+public class GCGraph extends Graph
+{
+ private static final long serialVersionUID = 1L;
+
+ public GCGraph(DNADraw currentDna)
+ {
+ super(currentDna);
+ }
+
+ /**
+ * Recalculate the values in value_array_array, step_size, min_value and
+ * max_value.
+ **/
+ protected float calculateValue(int start, int end)
+ {
+ char[] sequence;
+ if(end<=getBases().getLength())
+ sequence =
+ getBases().getSequence().getCharSubSequence(start, end);
+ else
+ {
+ final Sequence s = getBases().getSequence();
+ char[] seq1 = s.getCharSubSequence(start, getBases().getLength());
+ char[] seq2 = s.getCharSubSequence(1, getWindowSize()-(getBases().getLength()-start));
+ sequence = new char[seq1.length+seq2.length];
+ System.arraycopy(seq1, 0, sequence, 0, seq1.length);
+ System.arraycopy(seq2, 0, sequence, seq1.length-1, seq2.length);
+ }
+
+ float gc_count = 0;
+
+ for (int i = 0 ; i < sequence.length ; ++i)
+ {
+ final char this_char = sequence[i];
+ if (this_char == 'g' || this_char == 'c')
+ ++gc_count;
+ }
+ return gc_count/sequence.length * 100;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/GCSkewGraph.java b/uk/ac/sanger/artemis/circular/GCSkewGraph.java
new file mode 100644
index 0000000..047c889
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/GCSkewGraph.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import uk.ac.sanger.artemis.io.Sequence;
+
+public class GCSkewGraph extends Graph
+{
+ private static final long serialVersionUID = 1L;
+
+ public GCSkewGraph(DNADraw currentDna)
+ {
+ super(currentDna);
+ }
+
+ /**
+ * Return the value of (G content - C content)/(G content + C content)
+ * between the given pair of bases.
+ */
+ protected float calculateValue(int start, int end)
+ {
+ char[] sequence;
+ if(end<=getBases().getLength())
+ sequence =
+ getBases().getSequence().getCharSubSequence(start, end);
+ else
+ {
+ final Sequence s = getBases().getSequence();
+ char[] seq1 = s.getCharSubSequence(start, getBases().getLength());
+ char[] seq2 = s.getCharSubSequence(1, getWindowSize()-(getBases().getLength()-start));
+ sequence = new char[seq1.length+seq2.length];
+ System.arraycopy(seq1, 0, sequence, 0, seq1.length);
+ System.arraycopy(seq2, 0, sequence, seq1.length-1, seq2.length);
+ }
+
+
+ float g_count = 0;
+ float c_count = 0;
+
+ for (int i = 0 ; i < sequence.length ; ++i)
+ {
+ final char this_char = sequence[i];
+
+ if (this_char == 'g')
+ ++g_count;
+
+ if (this_char == 'c')
+ ++c_count;
+ }
+
+ return (g_count - c_count) / (g_count + c_count);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/GeneticMarker.java b/uk/ac/sanger/artemis/circular/GeneticMarker.java
new file mode 100644
index 0000000..4b48460
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/GeneticMarker.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.border.Border;
+import java.awt.event.*;
+import java.awt.*;
+import java.util.Vector;
+import java.awt.Dimension;
+
+
+public class GeneticMarker extends JPanel
+ implements TableModelListener
+{
+ private static final long serialVersionUID = 1L;
+ private DNADraw draw;
+ private JTable markerTable;
+ private MarkerTableModel markerModel;
+
+ public GeneticMarker(final DNADraw draw,final Vector block)
+ {
+ super();
+ this.draw = draw;
+
+ Box bdown = Box.createVerticalBox();
+ Dimension d = new Dimension(100,25);
+ bdown.add(Box.createVerticalStrut(4));
+
+ Vector columnNames = new Vector();
+ columnNames.add("Label");
+ columnNames.add("Start");
+ columnNames.add("End");
+ columnNames.add("Colour");
+ columnNames.add("Line width");
+ columnNames.add("Arrow head");
+ columnNames.add("Arrow tail");
+
+ markerModel =
+ new MarkerTableModel(block,columnNames);
+ markerTable = new JTable(markerModel);
+ markerTable.getModel().addTableModelListener(this);
+
+ setUpColorRenderer(markerTable);
+ setUpColorEditor(markerTable);
+
+// set number of clicks to one
+ DefaultCellEditor edFloat = (DefaultCellEditor)
+ markerTable.getDefaultEditor(Float.class);
+ edFloat.setClickCountToStart(1);
+
+ JScrollPane scrollMarker = new JScrollPane(markerTable);
+ scrollMarker.setPreferredSize(new Dimension(200,150));
+ scrollMarker.getViewport().setBackground(Color.white);
+ bdown.add(scrollMarker);
+
+ bdown.add(Box.createVerticalStrut(4));
+ bdown.add(new JSeparator());
+
+// Data entry
+ bdown.add(Box.createVerticalStrut(4));
+ Box bacross = Box.createHorizontalBox();
+ final TextFieldInt start = new TextFieldInt();
+ start.setMaximumSize(d);
+ start.setPreferredSize(d);
+ bacross.add(start);
+ bacross.add(new JLabel("start"));
+ bacross.add(Box.createHorizontalStrut(4));
+
+ final TextFieldInt end = new TextFieldInt();
+ end.setMaximumSize(d);
+ end.setPreferredSize(d);
+ bacross.add(end);
+ bacross.add(new JLabel("stop"));
+ bacross.add(Box.createHorizontalStrut(4));
+
+// final ColourPanel markerColour = new ColourPanel("Feature Colour",
+// Color.red);
+ final JButton markerColour = setUpColorButton(Color.red);
+ bacross.add(markerColour);
+
+ bacross.add(Box.createHorizontalStrut(4));
+
+ final TextFieldFloat lineSize = new TextFieldFloat();
+
+ int lsize = 10;
+ if(draw != null)
+ lsize = draw.getLineSize();
+
+ final JSlider slider = new JSlider(1,25,lsize);
+ lineSize.setPreferredSize(d);
+ lineSize.setMaximumSize(d);
+ lineSize.setValue((float)lsize);
+ bacross.add(lineSize);
+ bacross.add(new JLabel(" line width"));
+ bacross.add(Box.createHorizontalStrut(4));
+ // change line size on carriage return
+ lineSize.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ float size = (float)lineSize.getValue();
+ slider.setValue((int)size);
+ }
+ });
+
+ slider.addChangeListener(new ChangeListener()
+ {
+ public void stateChanged(ChangeEvent e)
+ {
+ int size = slider.getValue();
+ lineSize.setValue((float)size);
+ }
+ });
+ bacross.add(slider);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ JButton addMarker = new JButton("Add Feature");
+ addMarker.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Track track = new Track(0.7, null);
+ Block drawBlock = new Block(new String("CDS"),
+ start.getValue(),
+ end.getValue(),
+ markerColour.getBackground(),
+ 10.f,
+ track, draw);
+ draw.addBlock(drawBlock);
+ markerModel.addRow(drawBlock);
+ }
+ });
+ bacross.add(addMarker);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ add(bdown);
+ }
+
+ public void tableChanged(TableModelEvent e)
+ {
+ if(draw != null)
+ draw.repaint();
+ }
+
+ protected JMenuBar createMenuBar(final JFrame f)
+ {
+ JMenuBar menuBar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.setMnemonic(KeyEvent.VK_F);
+ menuBar.add(fileMenu);
+
+ JMenuItem closeMenu = new JMenuItem("Close");
+ closeMenu.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+
+ closeMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+ fileMenu.add(closeMenu);
+
+ JMenu toolMenu = new JMenu("Tools");
+ menuBar.add(toolMenu);
+
+ JMenuItem deleteRow = new JMenuItem("Delete Selected Row");
+ deleteRow.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_DELETE, ActionEvent.CTRL_MASK));
+
+ deleteRow.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int row = markerTable.getSelectedRow();
+ Block drawBlock = (Block)markerModel.getValueAt(row,7);
+ draw.getGeneticMarker().remove(drawBlock);
+ markerModel.deleteRow(row);
+ }
+ });
+ toolMenu.add(deleteRow);
+ return menuBar;
+ }
+
+
+ class ColorRenderer extends JLabel
+ implements TableCellRenderer
+ {
+ private static final long serialVersionUID = 1L;
+ Border unselectedBorder = null;
+ Border selectedBorder = null;
+ boolean isBordered = true;
+
+ public ColorRenderer(boolean isBordered)
+ {
+ super();
+ this.isBordered = isBordered;
+ setOpaque(true); //MUST do this for background to show up.
+ }
+
+ public Component getTableCellRendererComponent(
+ JTable table, Object color,
+ boolean isSelected, boolean hasFocus,
+ int row, int column)
+ {
+ setBackground((Color)color);
+ if(isBordered)
+ {
+ if(isSelected)
+ {
+ if(selectedBorder == null)
+ selectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
+ table.getSelectionBackground());
+ setBorder(selectedBorder);
+ }
+ else
+ {
+ if(unselectedBorder == null)
+ unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
+ table.getBackground());
+ setBorder(unselectedBorder);
+ }
+ }
+ return this;
+ }
+ }
+
+
+ private void setUpColorRenderer(JTable table)
+ {
+ table.setDefaultRenderer(Color.class,
+ new ColorRenderer(true));
+ }
+
+
+ private void setUpColorEditor(JTable table)
+ {
+ //First, set up the button that brings up the dialog.
+ final JButton button = new JButton("")
+ {
+ private static final long serialVersionUID = 1L;
+
+ public void setText(String s) {
+ //Button never shows text -- only color.
+ }
+ };
+ button.setBackground(Color.white);
+ button.setBorderPainted(false);
+ button.setMargin(new Insets(0,0,0,0));
+
+ //Now create an editor to encapsulate the button, and
+ //set it up as the editor for all Color cells.
+ final ColorEditor colorEditor = new ColorEditor(button);
+ table.setDefaultEditor(Color.class, colorEditor);
+
+ //Set up the dialog that the button brings up.
+ final JColorChooser colorChooser = new JColorChooser();
+ ActionListener okListener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ colorEditor.currentColor = colorChooser.getColor();
+ }
+ };
+ final JDialog dialog = JColorChooser.createDialog(button,
+ "Pick a Color",
+ true,
+ colorChooser,
+ okListener,
+ null); //XXXDoublecheck this is OK
+
+ //Here's the code that brings up the dialog.
+ button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ button.setBackground(colorEditor.currentColor);
+ colorChooser.setColor(colorEditor.currentColor);
+ dialog.show();
+ }
+ });
+ }
+
+
+ protected static JButton setUpColorButton(Color col)
+ {
+
+ //First, set up the button that brings up the dialog.
+ final JButton button = new JButton("")
+ {
+ private static final long serialVersionUID = 1L;
+
+ public void setText(String s) {
+ //Button never shows text -- only color.
+ }
+ };
+ button.setBackground(col);
+ button.setBorderPainted(false);
+ button.setOpaque(true);
+ button.setMargin(new Insets(0,0,0,0));
+ Dimension d = new Dimension(25,25);
+ button.setPreferredSize(d);
+ button.setMinimumSize(d);
+ button.setMaximumSize(d);
+
+ //Set up the dialog that the button brings up.
+ final JColorChooser colorChooser = new JColorChooser();
+ ActionListener okListener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ button.setBackground(colorChooser.getColor());
+ }
+ };
+ final JDialog dialog = JColorChooser.createDialog(button,
+ "Pick a Color",
+ true,
+ colorChooser,
+ okListener,
+ null); //XXXDoublecheck this is OK
+
+ //Here's the code that brings up the dialog.
+ button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+// button.setBackground(colorChooser.getColor());
+ dialog.show();
+ }
+ });
+ return button;
+ }
+
+ /*
+ * The editor button that brings up the dialog.
+ * We extend DefaultCellEditor for convenience,
+ * even though it mean we have to create a dummy
+ * check box. Another approach would be to copy
+ * the implementation of TableCellEditor methods
+ * from the source code for DefaultCellEditor.
+ */
+ class ColorEditor extends DefaultCellEditor
+ {
+ private static final long serialVersionUID = 1L;
+ Color currentColor = null;
+
+ public ColorEditor(JButton b)
+ {
+ super(new JCheckBox()); //Unfortunately, the constructor
+ //expects a check box, combo box,
+ //or text field.
+ editorComponent = b;
+ setClickCountToStart(1); //This is usually 1 or 2.
+
+ //Must do this so that editing stops when appropriate.
+ b.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ fireEditingStopped();
+ }
+ });
+ }
+
+ protected void fireEditingStopped()
+ {
+ super.fireEditingStopped();
+ }
+
+ public Object getCellEditorValue()
+ {
+ return currentColor;
+ }
+
+ public Component getTableCellEditorComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ int row,
+ int column)
+ {
+ ((JButton)editorComponent).setText(value.toString());
+ currentColor = (Color)value;
+ return editorComponent;
+ }
+ }
+
+ class MarkerTableModel extends AbstractTableModel
+ {
+ private static final long serialVersionUID = 1L;
+ private Vector rowData = new Vector();
+ private Vector columnNames;
+
+ public MarkerTableModel(Vector origRowData,Vector columnNames)
+ {
+ super();
+ this.columnNames = columnNames;
+
+ for(int i=0; i<origRowData.size(); i++)
+ {
+ Block b = (Block)origRowData.get(i);
+ addBlock(b);
+ }
+
+ }
+
+ public int getColumnCount()
+ {
+ return columnNames.size();
+ }
+
+ public int getRowCount()
+ {
+ return rowData.size();
+ }
+
+ public String getColumnName(int col)
+ {
+ return (String)columnNames.elementAt(col);
+ }
+
+ public Object getValueAt(int row, int col)
+ {
+ return ((Vector)rowData.elementAt(row)).elementAt(col);
+ }
+
+ /*
+ * JTable uses this method to determine the default renderer/
+ * editor for each cell. If we didn't implement this method,
+ * then the last column would contain text ("true"/"false"),
+ * rather than a check box.
+ */
+ public Class getColumnClass(int c)
+ {
+ return getValueAt(0,c).getClass();
+ }
+
+ /*
+ * Don't need to implement this method unless your table's
+ * editable.
+ */
+ public boolean isCellEditable(int row, int col)
+ {
+ return true;
+ }
+
+ /*
+ * Don't need to implement this method unless your table's
+ * data can change.
+ */
+ public void setValueAt(Object value, int row, int col)
+ {
+ Vector vrow = (Vector)rowData.elementAt(row);
+ vrow.setElementAt(value,col);
+
+ Block b = (Block)vrow.get(7);
+ switch (col)
+ {
+ case 0: b.setLabel((String) value); break;
+ case 1: b.setBstart(((Integer) value).intValue()); break;
+ case 2: b.setBend(((Integer) value).intValue()); break;
+ case 3: b.setColour((Color) value); break;
+ case 4: b.setStrokeSize(((Float) value).floatValue()); break;
+ case 5: b.setArrowHead(((Boolean) value).booleanValue()); break;
+ case 6: b.setArrowTail(((Boolean) value).booleanValue()); break;
+ }
+
+ rowData.setElementAt(vrow,row);
+ fireTableCellUpdated(row, col);
+ }
+
+
+ public void addRow(Block b)
+ {
+ addBlock(b);
+ fireTableRowsInserted(rowData.size(),rowData.size());
+ }
+
+ private void addBlock(final Block b)
+ {
+ Vector arow = new Vector();
+
+ arow.add(b.getLabel());
+ arow.add(new Integer(b.getBstart()));
+ arow.add(new Integer(b.getBend()));
+ arow.add(b.getColour());
+ arow.add(new Float(b.getStrokeSize()));
+ arow.add(new Boolean(b.isArrowHead()));
+ arow.add(new Boolean(b.isArrowTail()));
+ arow.add(b);
+
+ rowData.add(arow);
+ }
+
+
+ /**
+ *
+ * Delete a row from the table
+ * @param row number to delete
+ * @return true if deleted
+ *
+ */
+ public boolean deleteRow(int row)
+ {
+ if(row < 0 || row>=rowData.size())
+ return false;
+ rowData.remove(row);
+ fireTableRowsDeleted(row,row);
+
+ return true;
+ }
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/Graph.java b/uk/ac/sanger/artemis/circular/Graph.java
new file mode 100644
index 0000000..5e3c39d
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/Graph.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.AffineTransform;
+
+import javax.swing.*;
+
+import uk.ac.sanger.artemis.sequence.Bases;
+
+public abstract class Graph extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+
+ private DNADraw currentDna;
+ private int windowSize = 10000;
+ private int baseStepSize = 200;
+ private float[] value_array = null;
+ private float gcAverage = 0;
+ private float maxValue = Float.MIN_VALUE;
+ private float minValue = Float.MAX_VALUE;
+ private float graphHeight = 0.2f;
+ private int strokeSize = 1;
+ /** track to place the graph on - as a fraction of the radii*/
+ private double track = .4d;
+ private Color minusColour = new Color(0.6f, 0.f, 0.6f);
+ private Color plusColour = new Color(0.7f, 0.7f, 0.1f);
+
+ public Graph(DNADraw currentDna)
+ {
+ this.currentDna = currentDna;
+ setOpaque(false);
+ setPreferredSize(currentDna.getPreferredSize());
+ }
+
+
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+ if(getCurrentDna().isCircular())
+ draw(g2);
+ else
+ drawLinear(g2);
+ }
+
+ /**
+ * Recalculate the values in value_array_array, step_size, min_value and
+ * max_value.
+ **/
+ protected abstract float calculateValue(int start, int end);
+
+ public void draw(Graphics2D g2)
+ {
+ RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ qualityHints.put(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2.setRenderingHints(qualityHints);
+
+ AffineTransform origin = g2.getTransform();
+ AffineTransform newOrig = (AffineTransform)(origin.clone());
+ newOrig = (AffineTransform) (origin.clone());
+
+ double ddiameter = getCurrentDna().getDiameter();
+ double widthPanel = getCurrentDna().getWidth();
+ double heightPanel = getCurrentDna().getHeight();
+
+ Bases bases = getBases();
+ /*if(gcAverage < 1.f)
+ gcAverage = calculateValue(1,bases.getLength()); */
+
+ int nvalues = bases.getLength()/getBaseStepSize();
+
+ if(value_array == null)
+ calcGraphValues();
+
+ int minPos = (int)((ddiameter/2.d)*getTrack());
+ int maxPos = minPos + (int)((ddiameter/2.d)*getGraphHeight());
+
+ BasicStroke basicstroke = new BasicStroke(
+ getStrokeSize(),
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+
+ g2.setStroke(basicstroke);
+
+ newOrig.translate(widthPanel/2.d,heightPanel/2.d);
+ g2.setTransform(newOrig);
+
+ basicstroke = new BasicStroke(
+ getStrokeSize(),
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+
+ g2.setStroke(basicstroke);
+
+ float theta = ((float)getBaseStepSize()*360.f)/((float)bases.getLength());
+
+ int avVal = minPos + (int)( ((gcAverage-minValue)/(maxValue-minValue))*(maxPos-minPos) );
+ for(int i=0;i<nvalues-1; i++)
+ {
+ //int midBase = (i*baseStepSize)+(getWindowSize()/2);
+ newOrig.rotate(Math.toRadians(theta));
+ g2.setTransform(newOrig);
+
+ float xA = value_array[i];
+ if(xA == 0.f)
+ continue;
+ if(value_array[i] >= gcAverage)
+ g2.setColor(getPlusColour());
+ else
+ g2.setColor(getMinusColour());
+
+ int val = minPos + (int)( ((xA-minValue)/(maxValue-minValue))*(maxPos-minPos) );
+ g2.drawLine(avVal, 0, val, 0);
+ }
+ g2.setTransform(origin);
+ }
+
+
+ public void drawLinear(Graphics2D g2)
+ {
+ RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ qualityHints.put(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2.setRenderingHints(qualityHints);
+
+ int basesPerLine = getCurrentDna().getBasesPerLine();
+ float lineHeight = getCurrentDna().getLineHeight();
+ float singleBaseWidth = getCurrentDna().getSingleBaseWidth();
+ int borderWidth2 = getCurrentDna().getBorderWidth2();
+ int borderHeight2 = getCurrentDna().getBorderHeight2();
+
+ Bases bases = getBases();
+ int nvalues = bases.getLength()/getBaseStepSize();
+
+ if(value_array == null)
+ calcGraphValues();
+
+ int minPos = (int)(lineHeight*(1-getTrack()));
+ int maxPos = minPos + (int)(lineHeight*getGraphHeight());
+
+ BasicStroke basicstroke = new BasicStroke(
+ getStrokeSize(),
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+ g2.setStroke(basicstroke);
+
+ int avVal = minPos - (int)( ((gcAverage-minValue)/(maxValue-minValue))*(maxPos-minPos) );
+ for(int i=0;i<nvalues-1; i++)
+ {
+ float xA = value_array[i];
+
+ if(value_array[i] >= gcAverage)
+ g2.setColor(getPlusColour());
+ else
+ g2.setColor(getMinusColour());
+
+ int basePos = (i*getBaseStepSize())+1;
+ int lineNumber = Math.round((float)(basePos-1)/((float)basesPerLine) + 0.5f)-1;
+
+ int ypos = (int)(borderHeight2 + (lineNumber*lineHeight)
+ + (lineHeight*getGraphHeight()));
+
+ int val = minPos - (int)( ((xA-minValue)/(maxValue-minValue))*(maxPos-minPos) );
+ int xpos = (int) ((basePos-(lineNumber*basesPerLine))*singleBaseWidth)+borderWidth2;
+
+ g2.drawLine(xpos, avVal+ypos, xpos, val+ypos);
+ }
+ }
+
+ protected void calcGraphValues()
+ {
+ Bases bases = getBases();
+ int nvalues = bases.getLength()/getBaseStepSize();
+ value_array = new float [nvalues];
+ gcAverage = 0;
+ for(int i=0; i<nvalues; i++)
+ {
+ int start = (i*getBaseStepSize())+1;
+ int end = start+getWindowSize();
+
+ if(end > bases.getLength())
+ end = bases.getLength();
+
+ value_array[i] = calculateValue(start, end);
+ if(value_array[i] > maxValue)
+ maxValue = value_array[i];
+ if(value_array[i] < minValue)
+ minValue = value_array[i];
+ gcAverage += value_array[i];
+ }
+ gcAverage = gcAverage/nvalues;
+ }
+
+ protected int getWindowSize()
+ {
+ return windowSize;
+ }
+
+ protected void setWindowSize(int windowSize)
+ {
+ value_array = null;
+ this.windowSize = windowSize;
+ }
+
+ protected Bases getBases()
+ {
+ return currentDna.getBases();
+ }
+
+ public int getStrokeSize()
+ {
+ return strokeSize;
+ }
+
+ public void setStrokeSize(int strokeSize)
+ {
+ this.strokeSize = strokeSize;
+ }
+
+ public double getTrack()
+ {
+ return track;
+ }
+
+ public void setTrack(double track)
+ {
+ this.track = track;
+ }
+
+ public int getBaseStepSize()
+ {
+ return baseStepSize;
+ }
+
+ public void setBaseStepSize(int baseStepSize)
+ {
+ value_array = null;
+ this.baseStepSize = baseStepSize;
+ }
+
+ public DNADraw getCurrentDna()
+ {
+ return currentDna;
+ }
+
+
+ public float getMaxValue()
+ {
+ return maxValue;
+ }
+
+
+ public void setMaxValue(float maxValue)
+ {
+ this.maxValue = maxValue;
+ }
+
+
+ public float getMinValue()
+ {
+ return minValue;
+ }
+
+
+ public void setMinValue(float minValue)
+ {
+ this.minValue = minValue;
+ }
+
+
+ public float getGraphHeight()
+ {
+ return graphHeight;
+ }
+
+
+ public void setGraphHeight(float graphHeight)
+ {
+ this.graphHeight = graphHeight;
+ }
+
+
+ public Color getMinusColour()
+ {
+ return minusColour;
+ }
+
+
+ public void setMinusColour(Color minusColour)
+ {
+ this.minusColour = minusColour;
+ }
+
+
+ public Color getPlusColour()
+ {
+ return plusColour;
+ }
+
+
+ public void setPlusColour(Color plusColour)
+ {
+ this.plusColour = plusColour;
+ }
+
+
+ /**
+ * Used to change the graph default options
+ */
+ protected void showOptions()
+ {
+ GridBagLayout grid = new GridBagLayout();
+ GridBagConstraints c = new GridBagConstraints();
+ c.ipady = 3;
+ c.ipadx = 5;
+
+ JPanel optionBox = new JPanel(grid);
+
+ // GRAPH HEIGHT
+ c.gridx = 0;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.EAST;
+
+ optionBox.add(new JLabel("Graph Height"), c);
+
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ //final JSlider slider = new JSlider(1,60,(int)(getGraphHeight()*100));
+ final TextFieldFloat graphHeightField = new TextFieldFloat();
+ graphHeightField.setValue(getGraphHeight());
+ graphHeightField.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setGraphHeight((float) graphHeightField.getValue());
+ repaint();
+ }
+ });
+ optionBox.add(graphHeightField, c);
+
+
+ // WINDOW SIZE
+ c.gridx = 0;
+ c.gridy = 1;
+ c.anchor = GridBagConstraints.EAST;
+ optionBox.add(new JLabel("Window Size"), c);
+
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ final TextFieldInt winSize = new TextFieldInt();
+ winSize.setValue(getWindowSize());
+
+ winSize.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setWindowSize(winSize.getValue());
+ repaint();
+ }
+ });
+ optionBox.add(winSize, c);
+
+
+ // STEP SIZE
+ c.gridx = 0;
+ c.gridy = 2;
+ c.anchor = GridBagConstraints.EAST;
+ optionBox.add(new JLabel("Step Size"), c);
+
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ final TextFieldInt stepSize = new TextFieldInt();
+ stepSize.setValue(getBaseStepSize());
+
+ stepSize.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setBaseStepSize(stepSize.getValue());
+ repaint();
+ }
+ });
+ optionBox.add(stepSize, c);
+
+ // TRACK POSITION
+ c.gridx = 0;
+ c.gridy = 4;
+ c.anchor = GridBagConstraints.EAST;
+ optionBox.add(new JLabel("Track"), c);
+
+
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ final TextFieldFloat trackField = new TextFieldFloat();
+ trackField.setValue(getTrack());
+
+ trackField.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setTrack(trackField.getValue());
+ repaint();
+ }
+ });
+ optionBox.add(trackField, c);
+
+ // COLOUR
+ final JButton button = new JButton();
+ final JColorChooser colorChooser = new JColorChooser();
+
+ ActionListener okListener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ button.setBackground(colorChooser.getColor());
+ setMinusColour(colorChooser.getColor());
+ repaint();
+ }
+ };
+ setColorButton(button, getMinusColour(), okListener, colorChooser);
+ c.gridx = 0;
+ c.gridy = 5;
+ c.anchor = GridBagConstraints.EAST;
+ optionBox.add(new JLabel("Below Average"), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ optionBox.add(button, c);
+
+
+ // COLOUR
+ final JButton button2 = new JButton();
+ final JColorChooser colorChooser2 = new JColorChooser();
+
+ ActionListener okListener2 = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ button2.setBackground(colorChooser2.getColor());
+ setPlusColour(colorChooser2.getColor());
+ repaint();
+ }
+ };
+ setColorButton(button2, getPlusColour(), okListener2, colorChooser2);
+ c.gridx = 0;
+ c.gridy = 6;
+ c.anchor = GridBagConstraints.EAST;
+ optionBox.add(new JLabel("Above Average"), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ optionBox.add(button2, c);
+
+ JOptionPane.showMessageDialog(null,
+ optionBox, "Graph Options",
+ JOptionPane.PLAIN_MESSAGE);
+
+ try
+ {
+ setGraphHeight((float) graphHeightField.getValue());
+ setWindowSize(winSize.getValue());
+ setBaseStepSize(stepSize.getValue());
+ setTrack(trackField.getValue());
+ //setMinusColour(colorChooser.getColor());
+ //setPlusColour(colorChooser2.getColor());
+ }
+ catch(Exception e){ e.printStackTrace(); }
+ repaint();
+ }
+
+ /**
+ * Used to write out options to template file
+ * @return
+ */
+ protected String getOptionsStr()
+ {
+ return "height="+getGraphHeight()+" window_size="+getWindowSize()+
+ " base_step_size="+getBaseStepSize()+" track="+getTrack()+
+ " minus_colour="+getMinusColour().getRed()+":"+
+ getMinusColour().getGreen()+":"+
+ getMinusColour().getBlue()+
+ " plus_colour="+getPlusColour().getRed()+":"+
+ getPlusColour().getGreen()+":"+
+ getPlusColour().getBlue();
+ }
+
+ /**
+ * Used when reading in a template file
+ * @param options
+ */
+ protected void setOptionsStr(final String options[])
+ {
+ for(int i=0; i<options.length; i++)
+ {
+ if(options[i].startsWith("height"))
+ setGraphHeight(Float.parseFloat(options[i+1]));
+ else if(options[i].startsWith("window_size"))
+ setWindowSize(Integer.parseInt(options[i+1]));
+ else if(options[i].startsWith("base_step_size"))
+ setBaseStepSize(Integer.parseInt(options[i+1]));
+ else if(options[i].startsWith("track"))
+ setTrack(Double.parseDouble(options[i+1]));
+ else if(options[i].startsWith("minus_colour"))
+ {
+ String col[] = options[i+1].split(":");
+ setMinusColour(new Color(Integer.parseInt(col[0]),
+ Integer.parseInt(col[1]),
+ Integer.parseInt(col[2])));
+ }
+ else if(options[i].startsWith("plus_colour"))
+ {
+ String col[] = options[i+1].split(":");
+ setPlusColour(new Color(Integer.parseInt(col[0]),
+ Integer.parseInt(col[1]),
+ Integer.parseInt(col[2])));
+ }
+ }
+ }
+
+ private void setColorButton(final JButton button,
+ final Color col,
+ final ActionListener okListener,
+ final JColorChooser colorChooser)
+ {
+ button.setOpaque(true);
+ button.setBackground(col);
+ button.setBorderPainted(false);
+ button.setMargin(new Insets(0,0,0,0));
+ Dimension d = new Dimension(25,25);
+ button.setPreferredSize(d);
+
+ //Set up the dialog that the button brings up.
+ final JDialog dialog = JColorChooser.createDialog(button,
+ "Pick a Color",
+ true,
+ colorChooser,
+ okListener,
+ null);
+
+ button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ dialog.setVisible(true);
+ }
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/LineAttribute.java b/uk/ac/sanger/artemis/circular/LineAttribute.java
new file mode 100644
index 0000000..ae0e3b0
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/LineAttribute.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+
+package uk.ac.sanger.artemis.circular;
+
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JSlider;
+import javax.swing.KeyStroke;
+import javax.swing.event.*;
+import java.awt.event.*;
+import java.awt.Dimension;
+import java.util.Hashtable;
+
+public class LineAttribute extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ private Hashtable lineAttr = new Hashtable();
+ private TextFieldInt start;
+ private TextFieldInt end;
+ private TextFieldInt lineSize;
+
+ public LineAttribute(final DNADraw draw)
+ {
+ super();
+
+ Dimension d = new Dimension(100,25);
+ Box bdown = Box.createVerticalBox();
+ bdown.add(Box.createVerticalStrut(4));
+ Box bacross = Box.createHorizontalBox();
+
+ // circular or linear dna
+ ButtonGroup group = new ButtonGroup();
+ final JRadioButton jcirc = new JRadioButton("Circular");
+ jcirc.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ lineAttr.put("circular",new Boolean(jcirc.isSelected()));
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ group.add(jcirc);
+ JRadioButton jline = new JRadioButton("Linear");
+ jline.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ lineAttr.put("circular",new Boolean(jcirc.isSelected()));
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ group.add(jline);
+ jcirc.setSelected( draw.isCircular() );
+ jline.setSelected( !draw.isCircular() );
+ lineAttr.put("circular",new Boolean(draw.isCircular()));
+ bacross.add(jcirc);
+ bacross.add(jline);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ // start position
+ bdown.add(Box.createVerticalStrut(4));
+ bacross = Box.createHorizontalBox();
+ start = new TextFieldInt();
+ start.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int s = getStart();
+ lineAttr.put("start",new Integer(s));
+ draw.setStart(s);
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+
+ if(draw != null)
+ start.setValue(draw.getStart());
+ else
+ start.setValue(1);
+
+ start.setPreferredSize(d);
+ start.setMaximumSize(d);
+ bacross.add(start);
+ bacross.add(new JLabel(" start"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ // end position
+ bacross = Box.createHorizontalBox();
+ end = new TextFieldInt();
+ end.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent ev)
+ {
+ int e = getEnd();
+ lineAttr.put("end",new Integer(e));
+ draw.setEnd(e);
+ if(draw != null)
+ draw.repaint();
+ }
+ });
+ end.setPreferredSize(d);
+ end.setMaximumSize(d);
+
+ if(draw != null)
+ end.setValue(draw.getEnd());
+ else
+ end.setValue(1000);
+
+ bacross.add(end);
+ bacross.add(new JLabel(" stop"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ lineSize = new TextFieldInt();
+
+ // line size
+ int lsize = 5;
+ if(draw != null)
+ lsize = draw.getLineSize();
+ lineAttr.put("lsize",new Integer(lsize));
+
+ final JSlider slider = new JSlider(1,20,lsize);
+ lineSize.setPreferredSize(d);
+ lineSize.setMaximumSize(d);
+ lineSize.setValue(lsize);
+ bacross.add(lineSize);
+ bacross.add(new JLabel(" line width"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+ // change line size on carriage return
+ lineSize.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int size = lineSize.getValue();
+ slider.setValue(size);
+ lineAttr.put("lsize",new Integer(size));
+ if(draw != null)
+ {
+ draw.setLineSize(size);
+ draw.repaint();
+ }
+ }
+ });
+
+ bacross = Box.createHorizontalBox();
+ slider.addChangeListener(new ChangeListener()
+ {
+ public void stateChanged(ChangeEvent e)
+ {
+ int size = slider.getValue();
+ lineSize.setValue(size);
+ lineAttr.put("lsize",new Integer(size));
+ if(draw != null)
+ {
+ draw.setLineSize(size);
+ draw.repaint();
+ }
+ }
+ });
+
+ bacross.add(slider);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ add(bdown);
+
+ }
+
+ protected JMenuBar createMenuBar(final JFrame f)
+ {
+ JMenuBar menuBar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.setMnemonic(KeyEvent.VK_F);
+ menuBar.add(fileMenu);
+
+ JMenuItem closeMenu = new JMenuItem("Close");
+ closeMenu.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+
+ closeMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+ fileMenu.add(closeMenu);
+ return menuBar;
+ }
+
+ protected Hashtable getLineAttr()
+ {
+ lineAttr.put("start",new Integer(start.getValue()));
+ lineAttr.put("end",new Integer(end.getValue()));
+ lineAttr.put("lsize",new Integer(lineSize.getValue()));
+
+ return lineAttr;
+ }
+
+ protected int getStart()
+ {
+ return start.getValue();
+ }
+
+
+ protected int getEnd()
+ {
+ return end.getValue();
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/MemoryComboBox.java b/uk/ac/sanger/artemis/circular/MemoryComboBox.java
new file mode 100644
index 0000000..ee36bce
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/MemoryComboBox.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.util.Vector;
+import java.awt.Font;
+import java.net.URL;
+import javax.swing.JComboBox;
+
+/**
+*
+* Extends <code>JComboBox<code> to add and store new
+* elements
+*
+*/
+public class MemoryComboBox extends JComboBox
+{
+ public static final int MAX_MEM_LEN = 30;
+ private Vector order;
+
+ public MemoryComboBox(Vector v)
+ {
+ super(v);
+ setEditable(true);
+
+ order = new Vector();
+ for(int i=0;i<v.size();i++)
+ order.add(v.get(i));
+
+ setFont(new Font(null,Font.PLAIN,12));
+ }
+
+
+ /**
+ *
+ * Add an element to the combobox
+ * @param item element to add
+ *
+ */
+ public void addURL(Object item)
+ {
+ removeItem(item);
+ insertItemAt(item, 0);
+ setSelectedItem(item);
+ if(getItemCount() > MAX_MEM_LEN)
+ removeItemAt(getItemCount()-1);
+
+ order.add(item);
+ }
+
+
+ /**
+ *
+ * Ensure order is changed
+ *
+ */
+ public void setLastIndex(Object anObject)
+ {
+ if(order.contains(anObject))
+ {
+ order.remove(anObject);
+ order.trimToSize();
+ order.add(anObject);
+ }
+ }
+
+
+ /**
+ *
+ * Determine if a return page is stored
+ * @return true if a return page available
+ *
+ */
+ protected boolean isBackPage()
+ {
+ int currentIndex = order.indexOf(getSelectedItem());
+ if(order.size() <= 1 || currentIndex < 1)
+ return false;
+ else
+ return true;
+ }
+
+
+ /**
+ *
+ * Determine if a forward page is stored
+ * @return true if a forward page available
+ *
+ */
+ protected boolean isForwardPage()
+ {
+ int currentIndex = order.indexOf(getSelectedItem());
+ if(currentIndex < order.size()-1)
+ return true;
+ else
+ return false;
+ }
+
+
+ /**
+ *
+ * Determine if item is in the JComboBox
+ * @param item object to test existance of
+ *
+ */
+ public boolean isItem(Object item)
+ {
+ int nitems = getItemCount();
+ for(int i=0;i<nitems;i++)
+ if(item.equals(getItemAt(i)))
+ return true;
+ return false;
+ }
+
+
+ public int getIndexOf(Object item)
+ {
+ return order.indexOf(item);
+ }
+
+
+ public URL getURLAt(int index)
+ {
+ return (URL)order.get(index);
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/PrintDNAImage.java b/uk/ac/sanger/artemis/circular/PrintDNAImage.java
new file mode 100644
index 0000000..74005d9
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/PrintDNAImage.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.print.PageFormat;
+import java.awt.print.Paper;
+import java.awt.print.PrinterJob;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.border.Border;
+
+import org.apache.batik.dom.GenericDOMImplementation;
+import org.apache.batik.svggen.SVGGeneratorContext;
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.batik.svggen.SVGGraphics2DIOException;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.PrintArtemis;
+import uk.ac.sanger.artemis.components.StickyFileChooser;
+
+
+
+
+/**
+*
+* Print png/jpeg image and print preview.
+* Java 1.4 or higher is required for the imageio package
+* which is used here to create jpeg and png images of the
+* DNA diagram.
+*
+*/
+public class PrintDNAImage extends ScrollPanel
+{
+ private static final long serialVersionUID = 1L;
+
+ /** page format */
+ private PageFormat format = null;
+
+ /** alignment sequence panel */
+ private DNADraw dna;
+ /** status field for print preview */
+ private JTextField statusField = new JTextField("");
+ /** type (jpeg/png) */
+ private String type;
+
+ /**
+ *
+ * @param dna dna panel
+ *
+ */
+ public PrintDNAImage(DNADraw dna)
+ {
+ super();
+ this.dna = dna;
+
+ setBackground(Color.white);
+ }
+
+
+ /**
+ *
+ * Override this method to draw the sequences
+ * @return Graphics g
+ *
+ */
+ public void paintComponent(Graphics g)
+ {
+// let UI delegate paint first (incl. background filling)
+ super.paintComponent(g);
+ Graphics2D g2d = (Graphics2D) g.create();
+ dna.drawAll(g2d,true);
+ }
+
+ /**
+ *
+ * Get a default page format
+ * @return page format
+ *
+ */
+ protected PageFormat getFormatDialog()
+ {
+ PrinterJob printerJob = PrinterJob.getPrinterJob();
+ format = new PageFormat();
+ format = printerJob.pageDialog(format);
+ return format;
+ }
+
+
+ /**
+ *
+ * Returns a generated image
+ * @param pageIndex page number
+ * @return image
+ *
+ */
+ private RenderedImage createDNAImage(int pageIndex)
+ {
+ int width = (int)format.getWidth();
+ int height = (int)format.getHeight();
+ // Create a buffered image in which to draw
+ BufferedImage bufferedImage = new BufferedImage(
+ width,height,
+ BufferedImage.TYPE_INT_RGB);
+
+ // Create a graphics contents on the buffered image
+ Graphics2D g2d = bufferedImage.createGraphics();
+ g2d.setColor(Color.white);
+ g2d.fillRect(0,0,width,height);
+ // Draw graphics
+ dna.drawAll(g2d,true);
+
+ return bufferedImage;
+ }
+
+
+ /**
+ *
+ * Display a print preview page
+ *
+ */
+ protected void printPreview()
+ {
+ Border loweredbevel = BorderFactory.createLoweredBevelBorder();
+ Border raisedbevel = BorderFactory.createRaisedBevelBorder();
+ Border compound = BorderFactory.createCompoundBorder(raisedbevel,loweredbevel);
+ statusField.setBorder(compound);
+ statusField.setEditable(false);
+
+ if(format == null)
+ format = getFormatDialog();
+
+ statusField.setText("DNA map");
+ final JFrame f = new JFrame("Print Preview");
+ JPanel jpane = (JPanel)f.getContentPane();
+ JScrollPane scrollPane = new JScrollPane(this);
+ jpane.setLayout(new BorderLayout());
+ jpane.add(scrollPane,BorderLayout.CENTER);
+ jpane.add(statusField,BorderLayout.SOUTH);
+
+ Dimension d = new Dimension((int)format.getWidth(),
+ (int)format.getHeight());
+ setPreferredSize(d);
+ f.setSize(d);
+
+ JMenuBar menuBar = new JMenuBar();
+ JMenu filemenu = new JMenu("File");
+ menuBar.add(filemenu);
+
+// print postscript
+ JMenu printMenu = new JMenu("Print");
+ filemenu.add(printMenu);
+
+ JMenuItem print = new JMenuItem("Print Postscript...");
+ print.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ dna.doPrintActions();
+ }
+ });
+ printMenu.add(print);
+
+// print png/jpeg
+ JMenuItem printImage = new JMenuItem("Print png/jpeg Image...");
+ printImage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ printAsSinglePage();
+ }
+ });
+ printMenu.add(printImage);
+
+// close
+ filemenu.add(new JSeparator());
+ JMenuItem menuClose = new JMenuItem("Close");
+ menuClose.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+
+ filemenu.add(menuClose);
+ menuClose.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+
+ f.setJMenuBar(menuBar);
+ f.setVisible(true);
+ }
+
+ /**
+ * Provide some options for the image created
+ */
+ private File showOptions()
+ {
+ final StickyFileChooser fc = new StickyFileChooser();
+ File fselect = new File(fc.getCurrentDirectory()+
+ System.getProperty("file.separator")+
+ "dnaplotter.png");
+ fc.setSelectedFile(fselect);
+
+// file name prefix
+ Box bdown = Box.createVerticalBox();
+ bdown.add(Box.createVerticalGlue());
+
+ JLabel labFormat = new JLabel("Select Format:");
+ Font font = labFormat.getFont();
+ labFormat.setFont(font.deriveFont(Font.BOLD));
+
+ bdown.add(labFormat);
+
+ Box bacross = Box.createHorizontalBox();
+ final JComboBox formatSelect = new JComboBox(PrintArtemis.getImageFormats());
+ formatSelect.setSelectedItem("png");
+ formatSelect.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ String selected;
+ if(fc.getSelectedFile() != null)
+ {
+ selected = fc.getSelectedFile().getAbsolutePath();
+ String fmts[] = PrintArtemis.getImageFormats();
+ for(int i=0; i<fmts.length; i++)
+ selected = selected.replaceAll("."+fmts[i]+"$", "");
+ }
+ else
+ selected = "dnaplotter";
+
+ fc.setSelectedFile(new File(selected+"."+
+ formatSelect.getSelectedItem()));
+ }
+ });
+
+ Dimension d = formatSelect.getPreferredSize();
+ formatSelect.setMaximumSize(d);
+ bacross.add(Box.createHorizontalGlue());
+ bacross.add(formatSelect);
+ bdown.add(bacross);
+
+// file prefix & format options
+ fc.setAccessory(bdown);
+ int n = fc.showSaveDialog(null);
+ if(n == JFileChooser.CANCEL_OPTION)
+ return null;
+
+ type = (String)formatSelect.getSelectedItem();
+ return fc.getSelectedFile();
+ }
+
+
+ /**
+ *
+ * Write out the image
+ * @param image image
+ * @param file file to write image to
+ * @param type type of image
+ *
+ */
+ private void writeImageToFile(RenderedImage image,
+ File file, String type)
+ {
+ try
+ {
+ javax.imageio.ImageIO.write(image,type,file);
+ }
+ catch (IOException e)
+ {
+ System.out.println("Java 1.4+ is required");
+ }
+ }
+
+ /**
+ * Print to one jpeg or png file
+ */
+ public void printAsSinglePage()
+ {
+ //PrinterJob printerJob = PrinterJob.getPrinterJob();
+ format = new PageFormat();
+
+ File file = showOptions();
+ if(file == null)
+ return;
+
+ Dimension d = dna.getSize();
+ double imageWidth = d.getWidth();
+ double imageHeight = d.getHeight();
+
+ if(type.equals("svg"))
+ {
+ createSVG(file, d);
+ return;
+ }
+
+ Paper paper = format.getPaper();
+ paper.setSize(imageWidth,imageHeight);
+ paper.setImageableArea(0,0,
+ imageWidth,imageHeight+imageHeight);
+ format.setPaper(paper);
+
+ try
+ {
+ RenderedImage rendImage = createDNAImage(0);
+ writeImageToFile(rendImage,file,type);
+ }
+ catch(NoClassDefFoundError ex)
+ {
+ JOptionPane.showMessageDialog(this,
+ "This option requires Java 1.4 or higher.");
+ }
+ }
+
+ private void createSVG(final File fout, Dimension d)
+ {
+ final DOMImplementation domImpl =
+ GenericDOMImplementation.getDOMImplementation();
+ final Document doc = domImpl.createDocument(
+ "http://www.w3.org/2000/svg", "svg", null);
+
+ SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(doc);
+ ctx.setComment("Generated by DNAPlotter with Batik SVG Generator");
+ final SVGGraphics2D svgG = new SVGGraphics2D(ctx, true);
+ svgG.setFont(Options.getOptions().getFont());
+ svgG.setSVGCanvasSize(d);
+ paintComponent(svgG);
+
+ try
+ {
+ final Writer out = new OutputStreamWriter(
+ new FileOutputStream(fout), "UTF-8");
+ svgG.stream(out, true);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ e.printStackTrace();
+ }
+ catch (SVGGraphics2DIOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+
+ return;
+ }
+
+}
+
+
diff --git a/uk/ac/sanger/artemis/circular/ProgressFrame.java b/uk/ac/sanger/artemis/circular/ProgressFrame.java
new file mode 100644
index 0000000..b5409a7
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/ProgressFrame.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+
+import uk.ac.sanger.artemis.components.Utilities;
+
+
+class ProgressFrame extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private JProgressBar progress;
+
+ public ProgressFrame()
+ {
+ setUndecorated(true);
+ JPanel panel = (JPanel) getContentPane();
+ progress = new JProgressBar(1,10);
+ progress.setStringPainted(true);
+ progress.setValue(2);
+ progress.setPreferredSize(new Dimension(450, progress.getPreferredSize().height));
+ panel.add(progress, BorderLayout.CENTER);
+ pack();
+ Utilities.centreFrame(this);
+ setVisible(true);
+ }
+
+ protected void setString(String s)
+ {
+ toFront();
+ progress.setString(s);
+ }
+
+ protected void setValue(int i)
+ {
+ progress.setValue(i);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/RestrictionEnzyme.java b/uk/ac/sanger/artemis/circular/RestrictionEnzyme.java
new file mode 100644
index 0000000..cc46c3d
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/RestrictionEnzyme.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.DefaultCellEditor;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JColorChooser;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.event.*;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.border.Border;
+import java.awt.event.*;
+import java.awt.*;
+import java.util.Vector;
+import java.awt.Dimension;
+
+
+public class RestrictionEnzyme extends JPanel
+ implements TableModelListener
+{
+
+ private DNADraw dna;
+
+ public RestrictionEnzyme(final DNADraw dna, final Vector restrictionEnzyme)
+ {
+ super();
+ this.dna = dna;
+
+ Box bdown = Box.createVerticalBox();
+ Dimension d = new Dimension(100,25);
+ bdown.add(Box.createVerticalStrut(4));
+
+ Vector columnNames = new Vector();
+ columnNames.add("Label");
+ columnNames.add("Position");
+ columnNames.add("Colour");
+
+ final RETableModel reModel =
+ new RETableModel(restrictionEnzyme,columnNames);
+ final JTable reTable = new JTable(reModel);
+ reTable.getModel().addTableModelListener(this);
+ setUpColorRenderer(reTable);
+ setUpColorEditor(reTable);
+
+ JScrollPane scrollRE = new JScrollPane(reTable);
+ scrollRE.setPreferredSize(new Dimension(300,150));
+ scrollRE.getViewport().setBackground(Color.white);
+ bdown.add(scrollRE);
+
+ bdown.add(Box.createVerticalStrut(4));
+ bdown.add(new JSeparator());
+
+// Data entry
+ bdown.add(Box.createVerticalStrut(4));
+ Box bacross = Box.createHorizontalBox();
+
+ final JTextField reLabel = new JTextField("");
+ reLabel.setMaximumSize(d);
+ reLabel.setPreferredSize(d);
+ bacross.add(reLabel);
+ bacross.add(new JLabel("Label"));
+ bacross.add(Box.createHorizontalStrut(4));
+
+ final TextFieldInt pos = new TextFieldInt();
+ pos.setMaximumSize(d);
+ pos.setPreferredSize(d);
+ bacross.add(pos);
+ bacross.add(new JLabel("Position"));
+ bacross.add(Box.createHorizontalStrut(4));
+
+ final JButton reColour = setUpColorButton(Color.blue);
+ bacross.add(reColour);
+ bacross.add(Box.createHorizontalStrut(4));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ JButton addRE = new JButton("Add RE");
+ addRE.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Vector re = new Vector();
+ re.add(reLabel.getText());
+ re.add(new Integer(pos.getValue()));
+ re.add(reColour.getBackground());
+ restrictionEnzyme.add(re);
+ reModel.addRow();
+ }
+ });
+ bacross.add(addRE);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ add(bdown);
+ }
+
+ public void tableChanged(TableModelEvent e)
+ {
+ if(dna != null)
+ dna.repaint();
+ }
+
+ protected JMenuBar createMenuBar(final JFrame f)
+ {
+ JMenuBar menuBar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.setMnemonic(KeyEvent.VK_F);
+ menuBar.add(fileMenu);
+
+ JMenuItem closeMenu = new JMenuItem("Close");
+ closeMenu.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+
+ closeMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+ fileMenu.add(closeMenu);
+
+ return menuBar;
+ }
+
+
+ class ColorRenderer extends JLabel
+ implements TableCellRenderer
+ {
+ Border unselectedBorder = null;
+ Border selectedBorder = null;
+ boolean isBordered = true;
+
+ public ColorRenderer(boolean isBordered)
+ {
+ super();
+ this.isBordered = isBordered;
+ setOpaque(true); //MUST do this for background to show up.
+ }
+
+ public Component getTableCellRendererComponent(
+ JTable table, Object color,
+ boolean isSelected, boolean hasFocus,
+ int row, int column)
+ {
+ setBackground((Color)color);
+ if(isBordered)
+ {
+ if(isSelected)
+ {
+ if(selectedBorder == null)
+ selectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
+ table.getSelectionBackground());
+ setBorder(selectedBorder);
+ }
+ else
+ {
+ if(unselectedBorder == null)
+ unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
+ table.getBackground());
+ setBorder(unselectedBorder);
+ }
+ }
+ return this;
+ }
+ }
+
+
+ private void setUpColorRenderer(JTable table)
+ {
+ table.setDefaultRenderer(Color.class,
+ new ColorRenderer(true));
+ }
+
+
+ private void setUpColorEditor(JTable table)
+ {
+ //First, set up the button that brings up the dialog.
+ final JButton button = new JButton("")
+ {
+ public void setText(String s) {
+ //Button never shows text -- only color.
+ }
+ };
+ button.setBackground(Color.white);
+ button.setBorderPainted(false);
+ button.setMargin(new Insets(0,0,0,0));
+
+ //Now create an editor to encapsulate the button, and
+ //set it up as the editor for all Color cells.
+ final ColorEditor colorEditor = new ColorEditor(button);
+ table.setDefaultEditor(Color.class, colorEditor);
+
+ //Set up the dialog that the button brings up.
+ final JColorChooser colorChooser = new JColorChooser();
+ ActionListener okListener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ colorEditor.currentColor = colorChooser.getColor();
+ }
+ };
+ final JDialog dialog = JColorChooser.createDialog(button,
+ "Pick a Color",
+ true,
+ colorChooser,
+ okListener,
+ null); //XXXDoublecheck this is OK
+
+ //Here's the code that brings up the dialog.
+ button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ button.setBackground(colorEditor.currentColor);
+ colorChooser.setColor(colorEditor.currentColor);
+ dialog.show();
+ }
+ });
+ }
+
+
+ private JButton setUpColorButton(Color col)
+ {
+
+ //First, set up the button that brings up the dialog.
+ final JButton button = new JButton("")
+ {
+ public void setText(String s) {
+ //Button never shows text -- only color.
+ }
+ };
+ button.setBackground(col);
+ button.setBorderPainted(false);
+ button.setMargin(new Insets(0,0,0,0));
+ Dimension d = new Dimension(25,25);
+ button.setPreferredSize(d);
+ button.setMinimumSize(d);
+ button.setMaximumSize(d);
+
+ //Set up the dialog that the button brings up.
+ final JColorChooser colorChooser = new JColorChooser();
+ ActionListener okListener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ button.setBackground(colorChooser.getColor());
+ }
+ };
+ final JDialog dialog = JColorChooser.createDialog(button,
+ "Pick a Color",
+ true,
+ colorChooser,
+ okListener,
+ null); //XXXDoublecheck this is OK
+
+ //Here's the code that brings up the dialog.
+ button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+// button.setBackground(colorChooser.getColor());
+ dialog.show();
+ }
+ });
+ return button;
+ }
+
+ /*
+ * The editor button that brings up the dialog.
+ * We extend DefaultCellEditor for convenience,
+ * even though it mean we have to create a dummy
+ * check box. Another approach would be to copy
+ * the implementation of TableCellEditor methods
+ * from the source code for DefaultCellEditor.
+ */
+ class ColorEditor extends DefaultCellEditor
+ {
+ Color currentColor = null;
+
+ public ColorEditor(JButton b)
+ {
+ super(new JCheckBox()); //Unfortunately, the constructor
+ //expects a check box, combo box,
+ //or text field.
+ editorComponent = b;
+ setClickCountToStart(1); //This is usually 1 or 2.
+
+ //Must do this so that editing stops when appropriate.
+ b.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ fireEditingStopped();
+ }
+ });
+ }
+
+ protected void fireEditingStopped()
+ {
+ super.fireEditingStopped();
+ }
+
+ public Object getCellEditorValue()
+ {
+ return currentColor;
+ }
+
+ public Component getTableCellEditorComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ int row,
+ int column)
+ {
+ ((JButton)editorComponent).setText(value.toString());
+ currentColor = (Color)value;
+ return editorComponent;
+ }
+ }
+
+ class RETableModel extends AbstractTableModel
+ {
+
+ private Vector rowData;
+ private Vector columnNames;
+
+ public RETableModel(Vector rowData,Vector columnNames)
+ {
+ super();
+ this.columnNames = columnNames;
+ this.rowData = rowData;
+ }
+
+ public int getColumnCount()
+ {
+ return columnNames.size();
+ }
+
+ public int getRowCount()
+ {
+ return rowData.size();
+ }
+
+ public String getColumnName(int col)
+ {
+ return (String)columnNames.elementAt(col);
+ }
+
+ public Object getValueAt(int row, int col)
+ {
+ return ((Vector)rowData.elementAt(row)).elementAt(col);
+ }
+
+ /*
+ * JTable uses this method to determine the default renderer/
+ * editor for each cell. If we didn't implement this method,
+ * then the last column would contain text ("true"/"false"),
+ * rather than a check box.
+ */
+ public Class getColumnClass(int c)
+ {
+ return getValueAt(0,c).getClass();
+ }
+
+ /*
+ * Don't need to implement this method unless your table's
+ * editable.
+ */
+ public boolean isCellEditable(int row, int col)
+ {
+ return true;
+ }
+
+ /*
+ * Don't need to implement this method unless your table's
+ * data can change.
+ */
+ public void setValueAt(Object value, int row, int col)
+ {
+ Vector vrow = (Vector)rowData.elementAt(row);
+ vrow.setElementAt(value,col);
+ rowData.setElementAt(vrow,row);
+ fireTableCellUpdated(row, col);
+ }
+
+
+ public void addRow()
+ {
+ fireTableRowsInserted(rowData.size(),rowData.size());
+ }
+
+ /**
+ *
+ * Delete a row from the table
+ * @param row number to delete
+ * @return true if deleted
+ *
+ */
+ public boolean deleteRow(int row)
+ {
+ if(row < 0 || row>=rowData.size())
+ return false;
+ rowData.remove(row);
+ fireTableRowsDeleted(row,row);
+
+ return true;
+ }
+
+
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/ScrollPanel.java b/uk/ac/sanger/artemis/circular/ScrollPanel.java
new file mode 100644
index 0000000..9f4c318
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/ScrollPanel.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.LayoutManager;
+import java.awt.Rectangle;
+import java.awt.Dimension;
+import javax.swing.JPanel;
+import javax.swing.Scrollable;
+
+/**
+*
+* Extends JPanel to implement Scrollable to speed scroll pane
+* scrolling
+*
+*/
+public class ScrollPanel extends JPanel implements Scrollable
+{
+ public ScrollPanel()
+ {
+ super();
+ }
+
+ public ScrollPanel(LayoutManager l)
+ {
+ super(l);
+ }
+
+ public Dimension getPreferredScrollableViewportSize()
+ {
+ return getPreferredSize();
+ }
+
+ public boolean getScrollableTracksViewportHeight()
+ {
+ return false;
+ }
+
+ public boolean getScrollableTracksViewportWidth()
+ {
+ return false;
+ }
+
+ public int getScrollableBlockIncrement(Rectangle r,
+ int orientation, int direction)
+ {
+ return 60;
+ }
+
+ public int getScrollableUnitIncrement(Rectangle r,
+ int orientation, int direction)
+ {
+ return 10;
+ }
+
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/SequenceData.java b/uk/ac/sanger/artemis/circular/SequenceData.java
new file mode 100644
index 0000000..fb611d1
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/SequenceData.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.*;
+
+/**
+*
+* Object to represent the content of each row in the DragJTable
+* of a SequenceList.
+*
+*/
+public class SequenceData implements Transferable, Serializable
+{
+ /** sequence file/database */
+ public String s_name;
+ /** sequence start */
+ public String s_beg;
+ /** sequence end */
+ public String s_end;
+ /** use as the default */
+ public Boolean s_default;
+ /** file on remote file system */
+ public Boolean s_remote;
+ /** sequence list file */
+ public Boolean s_listFile;
+
+ /** dataflavor for drag and drop transfers */
+ public static DataFlavor SEQUENCEDATA =
+ new DataFlavor(SequenceData.class, "Sequence data");
+
+ /** available dataflavors */
+ static DataFlavor flavors[] = { SEQUENCEDATA };
+
+ public SequenceData()
+ {
+ s_name = new String();
+ s_beg = new String();
+ s_end = new String();
+ s_default = new Boolean(false);
+ s_remote = new Boolean(false);
+ s_listFile = new Boolean(false);
+ }
+
+ public SequenceData(String name, String beg, String end,
+ Boolean lis, Boolean def, Boolean remote)
+ {
+ s_name = name;
+ s_beg = beg;
+ s_end = end;
+ s_default = def;
+ s_listFile = lis;
+ s_remote = remote;
+ }
+
+/**
+*
+* Returns the sequence name
+* @return s_name sequence name
+*
+*/
+ public String getSequenceName()
+ {
+ return s_name;
+ }
+
+// Transferable
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return flavors;
+ }
+
+ public boolean isDataFlavorSupported(DataFlavor f)
+ {
+ if(f.equals(SEQUENCEDATA))
+ return true;
+ return false;
+ }
+
+ public Object getTransferData(DataFlavor d)
+ throws UnsupportedFlavorException, IOException
+ {
+ if(d.equals(SEQUENCEDATA))
+ return this;
+ else throw new UnsupportedFlavorException(d);
+ }
+
+// Serializable
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException
+ {
+ out.defaultWriteObject();
+ }
+
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/TextFieldFloat.java b/uk/ac/sanger/artemis/circular/TextFieldFloat.java
new file mode 100644
index 0000000..d0eb71b
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/TextFieldFloat.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.Toolkit;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.PlainDocument;
+
+/**
+*
+* JTextfield for float fields in the EMBOSS form
+*
+*/
+public class TextFieldFloat extends TextFieldSink
+{
+
+ private Toolkit toolkit;
+ private NumberFormat decimalFormatter;
+
+ public TextFieldFloat()
+ {
+ super();
+ toolkit = Toolkit.getDefaultToolkit();
+ decimalFormatter = NumberFormat.getNumberInstance(Locale.UK);
+ }
+
+ public double getValue()
+ {
+ double retVal = 0;
+ try
+ {
+ retVal = decimalFormatter.parse(getText()).doubleValue();
+ }
+ catch (ParseException e)
+ {
+ // This should never happen because insertString allows
+ // only properly formatted data to get in the field.
+ toolkit.beep();
+// System.err.println("TextFieldFloat getValue: " + retVal);
+ }
+ return retVal;
+ }
+
+ public void setValue(double value)
+ {
+ setText(decimalFormatter.format(value));
+ }
+
+ protected Document createDefaultModel()
+ {
+ return new DecimalNumberDocument();
+ }
+
+ protected class DecimalNumberDocument extends PlainDocument
+ {
+ public void insertString(int offs,
+ String str,
+ AttributeSet a)
+ throws BadLocationException
+ {
+ char[] source = str.toCharArray();
+ char[] result = new char[source.length];
+ int j = 0;
+
+ for (int i = 0; i < result.length; i++)
+ {
+ if (Character.isDigit(source[i]) ||
+ source[i] == '.' || source[i] == '-')
+ result[j++] = source[i];
+ else
+ {
+ if(source[i] != ',')
+ toolkit.beep();
+// System.err.println("insertString: " + source[i]);
+ }
+ }
+ super.insertString(offs, new String(result, 0, j), a);
+ }
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/circular/TextFieldInt.java b/uk/ac/sanger/artemis/circular/TextFieldInt.java
new file mode 100644
index 0000000..65284d3
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/TextFieldInt.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.Toolkit;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.PlainDocument;
+
+/**
+*
+* JTextfield for int fields in the EMBOSS form
+*
+*/
+public class TextFieldInt extends TextFieldSink
+{
+
+ private Toolkit toolkit;
+ private NumberFormat integerFormatter;
+
+ public TextFieldInt()
+ {
+ super();
+ toolkit = Toolkit.getDefaultToolkit();
+ integerFormatter = NumberFormat.getNumberInstance(Locale.UK);
+ integerFormatter.setParseIntegerOnly(true);
+ }
+
+ public int getValue()
+ {
+ int retVal = 0;
+ try
+ {
+ retVal = integerFormatter.parse(getText()).intValue();
+ }
+ catch (ParseException e)
+ {
+ // This should never happen because insertString allows
+ // only properly formatted data to get in the field.
+ //toolkit.beep();
+ }
+ return retVal;
+ }
+
+ public void setValue(int value)
+ {
+ setText(integerFormatter.format(value));
+ }
+
+ protected Document createDefaultModel()
+ {
+ return new WholeNumberDocument();
+ }
+
+ protected class WholeNumberDocument extends PlainDocument
+ {
+ public void insertString(int offs,
+ String str,
+ AttributeSet a)
+ throws BadLocationException
+ {
+ char[] source = str.toCharArray();
+ char[] result = new char[source.length];
+ int j = 0;
+
+ for (int i = 0; i < result.length; i++)
+ {
+ if (Character.isDigit(source[i]) ||
+ source[i] == '-')
+ result[j++] = source[i];
+ else
+ {
+ if(source[i] != ',')
+ {
+ toolkit.beep();
+ System.err.println("insertString: " + source[i]);
+ }
+ }
+ }
+ super.insertString(offs, new String(result, 0, j), a);
+ }
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/circular/TextFieldSink.java b/uk/ac/sanger/artemis/circular/TextFieldSink.java
new file mode 100644
index 0000000..bd6bc65
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/TextFieldSink.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.Color;
+import java.awt.datatransfer.*;
+
+import javax.swing.BorderFactory;
+import javax.swing.JTextField;
+import javax.swing.border.*;
+import java.awt.dnd.*;
+import java.io.IOException;
+
+
+/**
+*
+* Extends JTextField to enable pasting & drag and drop into it.
+*
+*/
+public class TextFieldSink extends JTextField implements DropTargetListener
+{
+
+ public TextFieldSink()
+ {
+
+//pasting not really needed?
+// addMouseListener(new MouseAdapter()
+// {
+// public void mouseClicked(MouseEvent e)
+// {
+// if(e.getClickCount() == 2)
+// {
+// pasteText();
+// e.consume();
+// };
+// }
+// });
+
+ setDropTarget(new DropTarget(this,this));
+
+ }
+
+ public void pasteText()
+ {
+ Clipboard c = this.getToolkit().getSystemClipboard();
+
+ Transferable t = c.getContents(this);
+ if(t==null)
+ {
+ this.getToolkit().beep();
+ return;
+ }
+ try
+ {
+ if(t.isDataFlavorSupported(DataFlavor.stringFlavor))
+ {
+ String s = (String) t.getTransferData(DataFlavor.stringFlavor);
+ this.replaceSelection(s);
+ }
+ else
+ this.getToolkit().beep();
+ }
+ catch (UnsupportedFlavorException ex) { this.getToolkit().beep(); }
+ catch (IOException ex) { this.getToolkit().beep(); }
+
+ }
+
+ protected static Border dropBorder = new BevelBorder(BevelBorder.LOWERED);
+ protected static Border endBorder =
+ BorderFactory.createLineBorder(Color.black);
+
+ public void dragEnter(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(DataFlavor.stringFlavor) ||
+ e.isDataFlavorSupported(SequenceData.SEQUENCEDATA) )
+ {
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ this.setBorder(dropBorder);
+ }
+ }
+
+ public void dragExit(DropTargetEvent e)
+ {
+ this.setBorder(endBorder);
+ }
+
+ public void drop(DropTargetDropEvent e)
+ {
+ this.setBorder(endBorder);
+ Transferable t = e.getTransferable();
+ if(t.isDataFlavorSupported(DataFlavor.stringFlavor))
+ {
+ e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+ try
+ {
+ String dropS = (String) t.getTransferData(DataFlavor.stringFlavor);
+ this.replaceSelection(dropS);
+ e.dropComplete(true);
+ }
+ catch (Exception ex) {}
+
+ }
+ else if(t.isDataFlavorSupported(SequenceData.SEQUENCEDATA))
+ {
+ e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+ try
+ {
+ SequenceData seqData = (SequenceData)
+ t.getTransferData(SequenceData.SEQUENCEDATA);
+ String seq = seqData.s_name;
+ if(seqData.s_listFile.booleanValue())
+ seq = "@".concat(seq);
+
+ this.replaceSelection(seq);
+ e.dropComplete(true);
+ }
+ catch (Exception ex) {}
+ }
+ else
+ {
+ e.rejectDrop();
+ return;
+ }
+ return;
+ }
+
+ public void dragOver(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(DataFlavor.stringFlavor) ||
+ e.isDataFlavorSupported(SequenceData.SEQUENCEDATA) )
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+
+ public void dropActionChanged(DropTargetDragEvent e) {}
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/Ticks.java b/uk/ac/sanger/artemis/circular/Ticks.java
new file mode 100644
index 0000000..82d387e
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/Ticks.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.KeyStroke;
+
+
+public class Ticks extends JPanel
+{
+
+ private TextFieldInt interval;
+ private TextFieldInt minor;
+ private TextFieldInt startTick;
+
+
+ public Ticks(final DNADraw draw, boolean setTicks)
+ {
+ super();
+
+ Box bdown = Box.createVerticalBox();
+ Dimension d = new Dimension(100,25);
+ bdown.add(Box.createVerticalStrut(4));
+ Box bacross = Box.createHorizontalBox();
+
+//tick interval
+ bacross = Box.createHorizontalBox();
+ interval = new TextFieldInt();
+ if(draw != null)
+ interval.setValue(draw.getTickInterval());
+ else
+ interval.setValue(200);
+ interval.setMaximumSize(d);
+ interval.setPreferredSize(d);
+ bacross.add(interval);
+ bacross.add(new JLabel(" Tick Interval"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+//minor ticks
+ bacross = Box.createHorizontalBox();
+ minor = new TextFieldInt();
+ if(draw != null)
+ minor.setValue(draw.getMinorTickInterval());
+ else
+ minor.setValue(100);
+ minor.setMaximumSize(d);
+ minor.setPreferredSize(d);
+ bacross.add(minor);
+ bacross.add(new JLabel(" Minor Tick Interval"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+//start ticks
+ bacross = Box.createHorizontalBox();
+ startTick = new TextFieldInt();
+ if(draw != null)
+ startTick.setValue(draw.getStartTick());
+ else
+ startTick.setValue(0);
+ startTick.setMaximumSize(d);
+ startTick.setPreferredSize(d);
+ bacross.add(startTick);
+ bacross.add(new JLabel(" Start Tick"));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ if(setTicks)
+ {
+ bacross = Box.createHorizontalBox();
+ JButton setTick = new JButton("Set");
+ setTick.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(draw != null)
+ {
+ if(!draw.setStartTick(startTick.getValue()))
+ {
+ JOptionPane.showMessageDialog(null,
+ "Start tick is out of range for this sequence",
+ "Out of Range",JOptionPane.ERROR_MESSAGE);
+ }
+
+ if(!draw.setTickInterval(interval.getValue()))
+ {
+ JOptionPane.showMessageDialog(null,
+ "Tick interval is out of range",
+ "Out of Range",JOptionPane.ERROR_MESSAGE);
+ }
+
+ if(!draw.setMinorTickInterval(minor.getValue()))
+ {
+ JOptionPane.showMessageDialog(null,
+ "Minor Tick interval is out of range",
+ "Out of Range",JOptionPane.ERROR_MESSAGE);
+ }
+
+ draw.calculateTickPosistions();
+ draw.repaint();
+ }
+ }
+ });
+ bacross.add(setTick);
+ bdown.add(bacross);
+ }
+
+ add(bdown);
+ }
+
+ protected JMenuBar createMenuBar(final JFrame f)
+ {
+ final JMenuBar menuBar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.setMnemonic(KeyEvent.VK_F);
+ menuBar.add(fileMenu);
+
+ JMenuItem closeMenu = new JMenuItem("Close");
+ closeMenu.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+
+ closeMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+ fileMenu.add(closeMenu);
+
+ return menuBar;
+ }
+
+
+ /**
+ *
+ * Return the position tick marks start at
+ *
+ */
+ protected int getStartTick()
+ {
+ return startTick.getValue();
+ }
+
+
+ /**
+ *
+ * Return the interval for the tick marks
+ *
+ */
+ protected int getTickInterval()
+ {
+ return interval.getValue();
+ }
+
+
+ /**
+ *
+ * Return the interval for the minor tick marks
+ *
+ */
+ protected int getMinorTickInterval()
+ {
+ return minor.getValue();
+ }
+
+
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/Track.java b/uk/ac/sanger/artemis/circular/Track.java
new file mode 100644
index 0000000..1b1d432
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/Track.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Vector;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureKeyPredicate;
+import uk.ac.sanger.artemis.FeatureKeyQualifierPredicate;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.io.Key;
+
+public class Track
+{
+ private double position = .9d;
+ private float size = 10.f;
+ private FeaturePredicate featurePredicate;
+ private boolean showForward = true;
+ private boolean showReverse = true;
+ private boolean notQualifier = false;
+ private boolean any = false;
+ private String keyStr;
+ private String qualifier;
+ private String qualifierValue;
+ private Entry entry;
+ private Color colour;
+
+ public Track(double position, Entry entry)
+ {
+ this.position = position;
+ this.entry = entry;
+ }
+
+ public Track(double position,
+ String keyStr,
+ String qualifier,
+ boolean notQualifier,
+ boolean showForward,
+ boolean showReverse,
+ Entry entry)
+ {
+ this.position = position;
+ this.showForward = showForward;
+ this.showReverse = showReverse;
+ this.keyStr = keyStr;
+ this.qualifier = qualifier;
+ this.notQualifier = notQualifier;
+ this.entry = entry;
+
+ if(keyStr != null)
+ {
+ final Key key = new Key(keyStr);
+ if(qualifier != null)
+ featurePredicate =
+ new FeatureKeyQualifierPredicate(key, qualifier, isNotQualifier());
+ else
+ featurePredicate = new FeatureKeyPredicate(key);
+ }
+
+ if(featurePredicate == null)
+ any = true;
+ }
+
+
+ public Track(double position,
+ String keyStr,
+ boolean showForward,
+ boolean showReverse,
+ Entry entry)
+ {
+ this(position, keyStr, null, true, showForward, showReverse, entry);
+ }
+
+
+ public double getPosition()
+ {
+ return position;
+ }
+
+ public void setPosition(double position)
+ {
+ this.position = position;
+ }
+
+ /**
+ * Test if this feature is drawn on this track
+ * @param this_feature
+ * @return
+ */
+ public boolean isOnTrack(Feature this_feature)
+ {
+ if(getEntry() != null && !getEntry().contains(this_feature))
+ return false;
+
+ if(isAny())
+ return true;
+
+ if(featurePredicate == null)
+ return false;
+
+ if(featurePredicate.testPredicate(this_feature))
+ {
+ if( (this_feature.isForwardFeature() && isShowForward()) ||
+ (!this_feature.isForwardFeature() && isShowReverse()) )
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isShowForward()
+ {
+ return showForward;
+ }
+
+ public void setShowForward(boolean showForward)
+ {
+ this.showForward = showForward;
+ }
+
+ public boolean isShowReverse()
+ {
+ return showReverse;
+ }
+
+ public void setShowReverse(boolean showReverse)
+ {
+ this.showReverse = showReverse;
+ }
+
+ public boolean isAny()
+ {
+ return any;
+ }
+
+ public void setAny(boolean any)
+ {
+ this.any = any;
+ }
+
+ public FeaturePredicate getFeaturePredicate()
+ {
+ return featurePredicate;
+ }
+
+ public void setFeaturePredicate(FeaturePredicate featurePredicate)
+ {
+ this.featurePredicate = featurePredicate;
+ }
+
+ public String getKeyStr()
+ {
+ return keyStr;
+ }
+
+ public void setKeyStr(String keyStr)
+ {
+ this.keyStr = keyStr;
+ }
+
+ public String getQualifier()
+ {
+ return qualifier;
+ }
+
+ public void setQualifier(String qualifier)
+ {
+ this.qualifier = qualifier;
+ }
+
+ public boolean isNotQualifier()
+ {
+ return notQualifier;
+ }
+
+ public void setNotQualifier(boolean notQualifier)
+ {
+ this.notQualifier = notQualifier;
+ }
+
+ public String getQualifierValue()
+ {
+ return qualifierValue;
+ }
+
+ public void setQualifierValue(String qualifierValue)
+ {
+ this.qualifierValue = qualifierValue;
+ }
+
+ public float getSize()
+ {
+ return size;
+ }
+
+ public void setSize(float size)
+ {
+ this.size = size;
+ }
+
+ public Entry getEntry()
+ {
+ return entry;
+ }
+
+ public void setEntry(Entry entry)
+ {
+ this.entry = entry;
+ }
+
+ public Color getColour()
+ {
+ return colour;
+ }
+
+ public void setColour(Color colour)
+ {
+ this.colour = colour;
+ }
+
+ protected void setPropertiesFromTemplate(final String line)
+ {
+ final String properties[] = line.split("\t");
+
+ setPosition(Double.parseDouble(properties[0]));
+ setSize(Float.parseFloat(properties[1]));
+ setShowForward(Boolean.parseBoolean(properties[2]));
+ setShowReverse(Boolean.parseBoolean(properties[3]));
+ setNotQualifier(Boolean.parseBoolean(properties[4]));
+ setAny(Boolean.parseBoolean(properties[5]));
+
+ if(properties[6].equals("null"))
+ setKeyStr(null);
+ else
+ setKeyStr(properties[6]);
+ if(properties[7].equals("null"))
+ setQualifier(null);
+ else
+ setQualifier(properties[7]);
+ if(properties[8].equals("null"))
+ setQualifierValue(null);
+ else
+ setQualifierValue(properties[8]);
+ if(properties[9].equals("null"))
+ setColour(null);
+ else
+ {
+ String colourRGB[] = properties[9].split(":");
+ Color colour = new Color(Integer.parseInt(colourRGB[0]),
+ Integer.parseInt(colourRGB[1]),
+ Integer.parseInt(colourRGB[2]));
+ setColour(colour);
+ }
+ }
+
+ /**
+ * Write the track properties out
+ * @param writer
+ * @throws IOException
+ */
+ protected void write(final Writer writer) throws IOException
+ {
+ writer.write(position+"\t"+
+ size+"\t"+
+ showForward+"\t"+
+ showReverse+"\t"+
+ isNotQualifier()+"\t"+
+ any+"\t"+
+ keyStr+"\t"+
+ qualifier+"\t"+
+ qualifierValue+"\t"+
+ ( colour != null ?
+ colour.getRed()+":"+colour.getGreen()+":"+colour.getBlue() : null)+"\t"+
+ ( entry != null ? entry.getName()+"\t"+entry.getRootDocument() : null )+
+ "\n");
+ }
+
+ /**
+ * Write a header line for the track properties
+ * @param writer
+ * @throws IOException
+ */
+ protected static void writeHeader(final Writer writer, final DNADraw dna) throws IOException
+ {
+ DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
+ writer.write("## DNA Plot :: track template (created: "+
+ dateFormat.format(new Date())+")\n");
+ writer.write("# line attributes: start="+dna.getStart()+
+ " end="+dna.getEnd()+" " +
+ "line_size="+dna.getLineSize()+
+ " circular="+dna.isCircular());
+
+ if(!dna.isCircular())
+ writer.write(" line_height="+dna.getLineHeight()+
+ " bases_per_line="+dna.getBasesPerLine());
+
+ writer.write("\n# tick marks: major="+dna.getTickInterval()+
+ " minor="+dna.getMinorTickInterval()+"\n");
+
+ // graphs
+ Vector<Graph> userGraphs = dna.getUserGraphs();
+ for(int i=0; i<userGraphs.size(); i++)
+ {
+ Graph userGraph = userGraphs.get(i);
+ if(dna.containsGraph(userGraph))
+ {
+ String fileName = ((UserGraph)userGraph).getFileName();
+ writer.write("# User Graph: "+userGraph.getOptionsStr()+
+ " file_name="+fileName+"\n");
+ }
+ }
+
+ if(dna.getGcGraph() != null && dna.containsGraph(dna.getGcGraph()))
+ writer.write("# GC Graph: "+dna.getGcGraph().getOptionsStr()+"\n");
+ if(dna.getGcSkewGraph() != null && dna.containsGraph(dna.getGcSkewGraph()))
+ writer.write("# GC Skew Graph: "+dna.getGcSkewGraph().getOptionsStr()+"\n");
+
+ writer.write(
+ "# Columns are:\n"+
+ "# POS - track position\n"+
+ "# SIZE - track size\n"+
+ "# FWD - show forward strand features\n"+
+ "# REV - show reverse strand features\n"+
+ "# NOT - use NOT\n"+
+ "# ANY - show any features\n"+
+ "# KEY - show features of this key\n"+
+ "# QUAL - show features with this qualifier\n"+
+ "# VAL - show features with this qualifier value(s)\n"+
+ "# COL - colour for this track e.g. 255:0:0 (R:G:B) or NULL\n"+
+ "# NAME - file entry name or null\n"+
+ "# DIR - root directory for this file\n#\n");
+ writer.write(
+ "#POS\t"+
+ "SIZE\t"+
+ "FWD\t"+
+ "REV\t"+
+ "NOT \t"+
+ "ANY\t"+
+ "KEY\t"+
+ "QUAL\t"+
+ "VAL\t"+
+ "COL\t"+
+ "NAME\t"+
+ "DIR\n");
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/TrackManager.java b/uk/ac/sanger/artemis/circular/TrackManager.java
new file mode 100644
index 0000000..effd189
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/TrackManager.java
@@ -0,0 +1,675 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureKeyPredicate;
+import uk.ac.sanger.artemis.FeatureKeyQualifierPredicate;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.FeaturePredicateConjunction;
+import uk.ac.sanger.artemis.FeaturePredicateVector;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.components.KeyChoice;
+import uk.ac.sanger.artemis.components.QualifierChoice;
+import uk.ac.sanger.artemis.components.StickyFileChooser;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+
+public class TrackManager extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private DNADraw dnaDraw;
+
+ private KeyChoice keyChoice[];
+ private QualifierChoice qualifierChoice[];
+ private JTextField qualifierValue[];
+ private JCheckBox notQualifier[];
+ private JCheckBox showForward[];
+ private JCheckBox showReverse[];
+ private JCheckBox showAny[];
+ private TextFieldFloat trackSize[];
+ private TextFieldFloat trackPosition[];
+
+ public TrackManager(final DNADraw dnaDraw)
+ {
+ super("Track Manager");
+ this.dnaDraw = dnaDraw;
+ setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+
+ createMenu();
+ JScrollPane jsp = new JScrollPane(getPanelComponents());
+ getContentPane().add(jsp);
+ pack();
+ }
+
+ /**
+ * Create a menu for the track manager.
+ */
+ private void createMenu()
+ {
+ final JMenu menuFile = new JMenu("File");
+ final JMenuBar menuBar = new JMenuBar();
+ setJMenuBar(menuBar);
+ menuBar.add(menuFile);
+ menuFile.add(getExportTrackTemplateMenuItem(this, dnaDraw));
+ menuFile.add(getImportTrackTemplateMenuItem(this));
+ final JMenuItem closeMenu = new JMenuItem("Close");
+ closeMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ TrackManager.this.setVisible(false);
+ }
+ });
+ menuFile.add(closeMenu);
+ }
+
+ protected JMenuItem getImportTrackTemplateMenuItem(final JFrame f)
+ {
+ final JMenuItem readTemplate = new JMenuItem("Import Track Template...");
+ readTemplate.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ StickyFileChooser fileDialog = new StickyFileChooser();
+ int status = fileDialog.showOpenDialog(f);
+ if(status == JFileChooser.CANCEL_OPTION)
+ return;
+
+ final File fileRead = fileDialog.getSelectedFile();
+ if(!fileRead.exists())
+ {
+ JOptionPane.showMessageDialog(f,
+ fileRead.getName()+" not found.",
+ "Problem Reading File",
+ JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ try
+ {
+ final FileReader reader = new FileReader(fileRead);
+ BufferedReader inputStream = new BufferedReader(reader);
+ String inLine = null;
+
+ Track[] tracks = Wizard.getTracks();
+ int trackCount = 0;
+
+ while ((inLine = inputStream.readLine()) != null)
+ {
+ if(inLine.startsWith("#") || inLine.trim().equals(""))
+ continue;
+
+ if(trackCount >= tracks.length)
+ {
+ addTrack();
+ tracks = Wizard.getTracks();
+ }
+ tracks[trackCount].setPropertiesFromTemplate(inLine);
+ trackCount++;
+ }
+ inputStream.close();
+ reader.close();
+ refresh();
+ }
+ catch(FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ });
+ return readTemplate;
+ }
+
+ /**
+ * Menu Item with associated ActionListener for exporting the properties
+ * of this track.
+ * @param f
+ * @return
+ */
+ protected static JMenuItem getExportTrackTemplateMenuItem(final JFrame f,
+ final DNADraw dnaDraw)
+ {
+ final JMenuItem saveTemplate = new JMenuItem("Export Track Template...");
+ saveTemplate.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ StickyFileChooser fileDialog = new StickyFileChooser();
+ int status = fileDialog.showSaveDialog(f);
+ if(status == JFileChooser.CANCEL_OPTION)
+ return;
+
+ final File fileWrite = fileDialog.getSelectedFile();
+ if(fileWrite.exists())
+ {
+ status = JOptionPane.showConfirmDialog(f, fileWrite.getName()+
+ " exists. Overwrite?",
+ "Selected File Exists",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(status == JFileChooser.CANCEL_OPTION)
+ return;
+ }
+
+ final Track[] tracks = Wizard.getTracks();
+ try
+ {
+ final Writer writer = new FileWriter(fileWrite);
+ Track.writeHeader(writer, dnaDraw);
+ for(int i=0; i<tracks.length; i++)
+ tracks[i].write(writer);
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ });
+ return saveTemplate;
+ }
+
+ private JPanel getPanelComponents()
+ {
+ final Track[] tracks = Wizard.getTracks();
+ GridBagLayout grid = new GridBagLayout();
+ final GridBagConstraints c = new GridBagConstraints();
+ c.ipady = 3;
+ c.ipadx = 5;
+
+ final JPanel optionBox = new JPanel(grid);
+ keyChoice = new KeyChoice[tracks.length];
+ qualifierChoice = new QualifierChoice[tracks.length];
+ qualifierValue = new JTextField[tracks.length];
+ notQualifier = new JCheckBox[tracks.length];
+ showForward = new JCheckBox[tracks.length];
+ showReverse = new JCheckBox[tracks.length];
+ showAny = new JCheckBox[tracks.length];
+ trackSize = new TextFieldFloat[tracks.length];
+ trackPosition = new TextFieldFloat[tracks.length];
+
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 0;
+ optionBox.add(new JLabel("Track"), c);
+ c.gridx = 1;
+ optionBox.add(new JLabel("Key"), c);
+ c.gridx = 2;
+ c.gridwidth = 4;
+ optionBox.add(new JLabel("Qualifier"), c);
+ c.gridx = 6;
+ c.gridwidth = 1;
+ optionBox.add(new JLabel("Strand"), c);
+ c.gridx = 9;
+ optionBox.add(new JLabel("Size"), c);
+ c.gridx = 10;
+ optionBox.add(new JLabel("Position"), c);
+
+ for(int i = 0; i < tracks.length; i++)
+ {
+ final Track track = tracks[i];
+ c.gridx = 0;
+ c.gridy = i+1;
+ c.anchor = GridBagConstraints.EAST;
+
+ optionBox.add(new JLabel(Integer.toString(i+1)+" "+track.getEntry().getName()), c);
+
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+
+ final Key key;
+ if(track.getKeyStr() != null)
+ key = new Key(track.getKeyStr());
+ else
+ key = new Key("-");
+
+ Entry entry = dnaDraw.getArtemisEntryGroup().getDefaultEntry();
+ if(entry == null)
+ entry = dnaDraw.getArtemisEntryGroup().getSequenceEntry();
+ keyChoice[i] = new KeyChoice(
+ entry.getEntryInformation(),key);
+
+ optionBox.add(keyChoice[i], c);
+
+ c.gridx = 2;
+ notQualifier[i] = new JCheckBox("Not", !track.isNotQualifier());
+ optionBox.add(notQualifier[i], c);
+
+ c.gridx = 3;
+ String qualifier = track.getQualifier();
+ final int n = i;
+ final JButton addQualifier = new JButton("ADD QUALIFIER");
+ addQualifier.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ c.gridx = 3;
+ c.gridy = n+1;
+ qualifierChoice[n] = new QualifierChoice(
+ dnaDraw.getArtemisEntryGroup().getDefaultEntry().getEntryInformation(),key, null, false);
+ optionBox.add(qualifierChoice[n], c);
+
+ c.gridx = 4;
+ qualifierValue[n] = new JTextField(track.getQualifierValue(), 10);
+ optionBox.add(qualifierValue[n], c);
+
+ optionBox.remove(addQualifier);
+ optionBox.repaint();
+ optionBox.revalidate();
+ }
+ });
+
+ if(qualifier == null)
+ optionBox.add(addQualifier, c);
+ else
+ {
+ qualifierChoice[i] = new QualifierChoice(
+ entry.getEntryInformation(),key, qualifier, false);
+ optionBox.add(qualifierChoice[i], c);
+ c.gridx = 4;
+ qualifierValue[i] = new JTextField(track.getQualifierValue(), 10);
+ optionBox.add(qualifierValue[i], c);
+ }
+
+ final JButton removeButton = new JButton("X");
+ Font font = dnaDraw.getFont().deriveFont(Font.BOLD);
+ removeButton.setFont(font);
+ removeButton.setToolTipText("REMOVE QUALIFIER");
+ c.gridx = 5;
+ optionBox.add(removeButton, c);
+ removeButton.setForeground(new Color(139,35,35));
+ removeButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(qualifierChoice[n] != null)
+ {
+ optionBox.remove(qualifierChoice[n]);
+ optionBox.remove(qualifierValue[n]);
+ }
+
+ c.gridx = 3;
+ c.gridy = n+1;
+ optionBox.add(addQualifier, c);
+ qualifierChoice[n] = null;
+ optionBox.repaint();
+ optionBox.revalidate();
+ }
+ });
+ removeButton.setPreferredSize(new Dimension(35,
+ removeButton.getPreferredSize().height));
+
+ c.gridx = 6;
+ showForward[i] = new JCheckBox("Forward", track.isShowForward());
+ optionBox.add(showForward[i], c);
+
+ c.gridx = 7;
+ showReverse[i] = new JCheckBox("Reverse", track.isShowReverse());
+ optionBox.add(showReverse[i], c);
+
+ c.gridx = 8;
+ showAny[i] = new JCheckBox("Any", track.isAny());
+ optionBox.add(showAny[i], c);
+
+ c.gridx = 9;
+ trackSize[i] = new TextFieldFloat();
+ trackSize[i].setValue(track.getSize());
+ trackSize[i].setColumns(4);
+ optionBox.add(trackSize[i], c);
+
+ c.gridx = 10;
+ trackPosition[i] = new TextFieldFloat();
+ trackPosition[i].setValue(track.getPosition());
+ trackPosition[i].setColumns(4);
+ optionBox.add(trackPosition[i], c);
+
+
+ c.gridx = 11;
+ final JButton colourSelection = new JButton("COLOUR");
+
+ colourSelection.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final JFrame frameColour = new JFrame("Track "+(n+1)+" Colour");
+ GridBagLayout grid = new GridBagLayout();
+ final GridBagConstraints c = new GridBagConstraints();
+ c.ipady = 3;
+ c.ipadx = 5;
+ c.anchor = GridBagConstraints.WEST;
+
+ final JPanel optionBox = new JPanel(grid);
+ frameColour.getContentPane().add(optionBox);
+
+ Color col = track.getColour();
+ if(col == null)
+ col = Color.red;
+
+ final JButton colourButton = GeneticMarker.setUpColorButton(col);
+ c.gridx = 0;
+ c.gridy = 0;
+ optionBox.add(new JLabel("Pick a Colour:"),c);
+ c.gridx = 1;
+ c.gridy = 0;
+ optionBox.add(colourButton,c);
+
+ final JCheckBox colourQualifier = new JCheckBox("Use colour qualifier");
+ if(track.getColour() == null)
+ colourQualifier.setSelected(true);
+ else
+ colourQualifier.setSelected(false);
+
+ colourQualifier.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(colourQualifier.isSelected())
+ track.setColour(null);
+ else
+ track.setColour(colourButton.getBackground());
+ dnaDraw.repaint();
+ }
+ });
+
+ final JButton ok = new JButton("Apply Colour to All");
+ ok.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ track.setColour(colourButton.getBackground());
+ colourQualifier.setSelected(false);
+ dnaDraw.repaint();
+ }
+ });
+ c.gridx = 2;
+ c.gridy = 0;
+ optionBox.add(ok,c);
+
+ c.gridx = 1;
+ c.gridy = 1;
+ c.gridwidth = 2;
+ optionBox.add(colourQualifier, c);
+
+ c.gridx = 1;
+ c.gridy = 2;
+ final JButton close = new JButton("Close");
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ frameColour.dispose();
+ }
+ });
+ optionBox.add(close,c);
+
+ frameColour.pack();
+ frameColour.setVisible(true);
+ }
+ });
+ optionBox.add(colourSelection, c);
+
+
+ c.gridx = 12;
+ final int trackIndex = i;
+ final JButton deleteTrack = new JButton("DELETE");
+ deleteTrack.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int val = JOptionPane.showConfirmDialog(dnaDraw,
+ "Delete track "+(trackIndex+1)+"?",
+ "Confirm", JOptionPane.OK_CANCEL_OPTION);
+ if(val == JOptionPane.CANCEL_OPTION)
+ return;
+
+ Wizard.deleteTrack(trackIndex);
+ getContentPane().removeAll();
+
+ JScrollPane jsp = new JScrollPane(getPanelComponents());
+ getContentPane().add(jsp);
+ pack();
+
+ setVisible(true);
+ update(Wizard.getTracks());
+ }
+ });
+ optionBox.add(deleteTrack, c);
+ }
+
+ c.gridx = 0;
+ c.gridy = tracks.length+1;
+ c.gridwidth = 2;
+
+ JButton updateButton = new JButton("UPDATE TRACKS");
+ optionBox.add(updateButton,c);
+ updateButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ update(tracks);
+ }
+ });
+
+ c.gridx = 2;
+ c.gridy = tracks.length+1;
+ c.gridwidth = 2;
+
+ JButton addTrackButton = new JButton("ADD TRACK");
+ optionBox.add(addTrackButton,c);
+ addTrackButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ addTrack();
+ }
+ });
+
+ return optionBox;
+ }
+
+ protected void refresh()
+ {
+ getContentPane().removeAll();
+ JScrollPane jsp = new JScrollPane(getPanelComponents());
+ getContentPane().add(jsp);
+ pack();
+ }
+
+ private void addTrack()
+ {
+ Entry entry;
+ if(Wizard.getTracks().length > 0)
+ entry = Wizard.getTracks()[0].getEntry();
+ else
+ entry = dnaDraw.getArtemisEntryGroup().elementAt(0);
+
+ Wizard.addTrack( entry );
+ refresh();
+ setVisible(true);
+ }
+
+ /**
+ * Update the tracks based on the Track Manager settings
+ * @param tracks
+ */
+ public void update(final Track[] tracks)
+ {
+ // update tracks
+ for(int i=0; i<tracks.length; i++)
+ {
+ if(keyChoice[i].getSelectedItem().getKeyString().equals("-"))
+ {
+ tracks[i].setFeaturePredicate(null);
+ tracks[i].setAny(showAny[i].isSelected());
+ tracks[i].setKeyStr("-");
+ }
+ else
+ {
+ tracks[i].setKeyStr(keyChoice[i].getSelectedItem().getKeyString());
+ if(qualifierChoice[i] == null)
+ {
+ tracks[i].setFeaturePredicate(new FeatureKeyPredicate(keyChoice[i].getSelectedItem()));
+ tracks[i].setQualifier(null);
+ }
+ else
+ {
+ if(qualifierValue[i].getText().trim().equals(""))
+ {
+ tracks[i].setFeaturePredicate(
+ new FeatureKeyQualifierPredicate(keyChoice[i].getSelectedItem(),
+ (String)qualifierChoice[i].getSelectedItem(), !notQualifier[i].isSelected()));
+ }
+ else
+ {
+
+ final FeaturePredicateVector temp_predicates =
+ new FeaturePredicateVector();
+
+ //final StringVector words =
+ // StringVector.getStrings(search_text, " ");
+
+ final StringTokenizer tok = new StringTokenizer(qualifierValue[i].getText(), " \n");
+
+ while(tok.hasMoreTokens())
+ {
+ final String this_word = tok.nextToken().trim();
+ final FeaturePredicate new_predicate =
+ new FeatureKeyQualifierPredicate(keyChoice[i].getSelectedItem(),
+ (String)qualifierChoice[i].getSelectedItem(),
+ this_word,
+ false,
+ true);
+
+ temp_predicates.add(new_predicate);
+ }
+
+ FeaturePredicateConjunction key_and_qualifier_predicate =
+ new FeaturePredicateConjunction(temp_predicates,
+ FeaturePredicateConjunction.OR);
+
+
+ tracks[i].setFeaturePredicate(key_and_qualifier_predicate);
+ }
+ tracks[i].setQualifier((String)qualifierChoice[i].getSelectedItem());
+ tracks[i].setQualifierValue(qualifierValue[i].getText());
+ }
+ tracks[i].setAny(false);
+ }
+ tracks[i].setShowForward(showForward[i].isSelected());
+ tracks[i].setShowReverse(showReverse[i].isSelected());
+ tracks[i].setNotQualifier(!notQualifier[i].isSelected());
+ tracks[i].setSize((float) trackSize[i].getValue());
+ tracks[i].setPosition(trackPosition[i].getValue());
+ }
+
+ // update viewer
+ updateDNADraw(dnaDraw, tracks);
+ }
+
+
+ private static void updateDNADraw(final DNADraw dnaDraw, final Track[] tracks)
+ {
+ dnaDraw.getBlock().removeAll(dnaDraw.getBlock());
+ final FeatureVector features = dnaDraw.getArtemisEntryGroup().getAllFeatures();
+
+ for(int i=0; i<features.size(); i++)
+ {
+ Feature f = features.elementAt(i);
+ Vector myTracks = new Vector();
+
+ for(int j=0; j<tracks.length; j++)
+ {
+ if(tracks[j].isOnTrack(f))
+ myTracks.add(tracks[j]);
+ }
+
+ if(myTracks.size() < 1)
+ continue;
+
+ Color col = f.getColour();
+ if(col == null || col.equals(Color.white))
+ col = Color.lightGray;
+
+ RangeVector ranges = f.getLocation().getRanges();
+
+ for(int j=0; j<ranges.size(); j++)
+ {
+ Range range = (Range) ranges.get(j);
+
+ for(int k=0; k<myTracks.size(); k++)
+ {
+ Track myTrack = (Track)myTracks.get(k);
+ Block drawBlock = new Block(f.getIDString(),
+ range.getStart(),
+ range.getEnd(),
+ col,
+ myTrack.getSize(),
+ myTrack, dnaDraw);
+
+ drawBlock.setFeature(f);
+ dnaDraw.getBlock().add(drawBlock);
+ }
+ }
+ }
+
+ dnaDraw.repaint();
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/circular/UserGraph.java b/uk/ac/sanger/artemis/circular/UserGraph.java
new file mode 100644
index 0000000..d2a5fe8
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/UserGraph.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+
+package uk.ac.sanger.artemis.circular;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.regex.Pattern;
+
+import javax.swing.JOptionPane;
+
+import uk.ac.sanger.artemis.io.ReadFormatException;
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class UserGraph extends Graph
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The data that was read by the constructor.
+ **/
+ private float data[] = null;
+
+ /**
+ * The maximum value in the data array.
+ **/
+ private float data_max = Float.MIN_VALUE;
+
+ /**
+ * The minimum value in the data array.
+ **/
+ private float data_min = Float.MAX_VALUE;
+
+ /**
+ * The average calculated by readData ().
+ **/
+ private float average_value = 0;
+
+ private String fileName;
+
+
+ public UserGraph(DNADraw currentDna, final String fileName)
+ throws IOException
+ {
+ super(currentDna);
+ this.fileName = fileName;
+
+ final Reader document_reader = Wizard.getReader(fileName);
+ LinePushBackReader pushback_reader =
+ new LinePushBackReader(document_reader);
+
+ final String first_line = pushback_reader.readLine ();
+ final StringVector tokens = StringVector.getStrings (first_line, " ");
+
+ if (tokens.size () < 1)
+ throw new ReadFormatException ("unknown file type");
+
+ pushback_reader.pushBack (first_line);
+ data = new float [getBases().getLength()];
+ readData (pushback_reader);
+ pushback_reader.close();
+ }
+
+ /**
+ * Return the value between the given pair of bases.
+ */
+ protected float calculateValue(int start, int end)
+ {
+ float value = 0.f;
+
+ for (int base = start ; base <= end ; ++base)
+ value += data[base - 1] / (end - start + 1);
+
+ return value;
+ }
+
+ /**
+ * Read all from buffered_reader into data.
+ **/
+ private void readData (final LinePushBackReader pushback_reader)
+ throws IOException
+ {
+ String line = null;
+ int count = 0;
+ final int seqLength = getBases().getLength();
+ final Pattern patt = Pattern.compile("\\s+");
+
+ while ((line = pushback_reader.readLine ()) != null)
+ {
+ if (count >= seqLength)
+ throw new ReadFormatException ("too many values in input file");
+
+ String tokens[] = patt.split(line);
+ if (tokens.length == 1)
+ {
+ for (int i = 0 ; i < tokens.length ; ++i)
+ {
+ try
+ {
+ float value = Float.parseFloat(tokens[i]);
+
+ if (value > data_max)
+ data_max = value;
+
+ if (value < data_min)
+ data_min = value;
+
+ data[count] = value;
+
+ average_value += value;
+ }
+ catch (NumberFormatException e)
+ {
+ throw new ReadFormatException ("cannot understand this number: " +
+ tokens[i] + " - " +
+ e.getMessage ());
+ }
+ }
+ }
+ else
+ {
+ JOptionPane.showMessageDialog(null,
+ "Line has the wrong number of fields:\n"+line+
+ "\nOnly one column allowed.",
+ "Data Columns", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ ++count;
+ }
+ average_value /= seqLength;
+ }
+
+ protected String getFileName()
+ {
+ return fileName;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/Wizard.java b/uk/ac/sanger/artemis/circular/Wizard.java
new file mode 100644
index 0000000..f148b4d
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/Wizard.java
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author: Tim Carver
+ */
+
+package uk.ac.sanger.artemis.circular;
+
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+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.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Vector;
+import java.util.Hashtable;
+
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.EntryVector;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.components.StickyFileChooser;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.MSPcrunchDocumentEntry;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.WorkingGZIPInputStream;
+
+
+/**
+* DNA draw wizard
+*/
+public class Wizard
+{
+ private DNADraw dna = null;
+
+ public static Track TRACK_1 = new Track(0.95d, "CDS", "pseudo", false, true, false, null);
+ public static Track TRACK_2 = new Track(0.9d, "CDS", "pseudo", false, false, true, null);
+ public static Track TRACK_3 = new Track(0.85d, "CDS", "pseudo", true, true, true, null);
+ public static Track TRACK_4 = new Track(0.8d, "misc_feature", true, true, null);
+ public static Track TRACK_5 = new Track(0.75d, null, true, true, null);
+
+ private SwingWorker workerGraph;
+ public static Track[] tracks = { TRACK_1, TRACK_2, TRACK_3, TRACK_4, TRACK_5 };
+
+ public Wizard(DNADraw dna_current)
+ {
+ int n = getOption(dna_current); // option 0 - read data file
+ // option 1 - edit existing dna
+ // option 2 - read template
+ if(n == 0)
+ dna = getDNADrawFromFile(dna_current);
+ else if(n == 2)
+ {
+ StickyFileChooser chooser = new StickyFileChooser();
+ chooser.showOpenDialog(null);
+
+ File fileTemplate = chooser.getSelectedFile();
+ if(!fileTemplate.exists())
+ JOptionPane.showMessageDialog(null,
+ fileTemplate.getName()+" cannot be found!",
+ "Missing File", JOptionPane.WARNING_MESSAGE);
+ loadTemplate(chooser.getSelectedFile());
+ }
+ else if(n == 1)
+ {
+ Vector block = new Vector();
+ Vector restrictionEnzyme = new Vector();
+ if(dna_current == null)
+ dna = new DNADraw();
+ else
+ {
+ dna = dna_current;
+ block = dna_current.getGeneticMarker();
+ restrictionEnzyme = dna_current.getRestrictionEnzyme();
+ }
+
+ LineAttribute la = new LineAttribute(dna);
+
+ GeneticMarker gm;
+ if(dna_current != null)
+ gm = new GeneticMarker(dna_current,block);
+ else
+ gm = new GeneticMarker(dna,block);
+
+ RestrictionEnzyme re;
+ if(dna_current != null)
+ re = new RestrictionEnzyme(dna_current,restrictionEnzyme);
+ else
+ re = new RestrictionEnzyme(dna,restrictionEnzyme);
+
+ Ticks tk = new Ticks(dna_current,false);
+
+ la.setMinimumSize(la.getPreferredSize());
+ la.setMaximumSize(la.getPreferredSize());
+
+ re.setMinimumSize(re.getPreferredSize());
+ re.setMaximumSize(re.getPreferredSize());
+
+ ScrollPanel pane = new ScrollPanel(new BorderLayout());
+ Box bdown = Box.createVerticalBox();
+ bdown.add(new JLabel("Properties"));
+ Box bacross = Box.createHorizontalBox();
+ bacross.add(la);
+ bacross.add(tk);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bdown.add(new JSeparator());
+ bdown.add(Box.createVerticalStrut(10));
+ bdown.add(new JLabel("Features"));
+ bacross = Box.createHorizontalBox();
+ bacross.add(gm);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+ bdown.add(new JSeparator());
+ bdown.add(Box.createVerticalStrut(10));
+ bdown.add(new JLabel("Restriction Enzymes"));
+ bacross = Box.createHorizontalBox();
+ bacross.add(re);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+ pane.add(bdown,BorderLayout.CENTER);
+
+ JScrollPane createWizScroll = new JScrollPane(pane);
+
+ Dimension dscreen = createWizScroll.getToolkit().getScreenSize();
+ int wid = (int)dscreen.getWidth();
+ if(wid > 700)
+ wid = 700;
+
+ int hgt = (int)dscreen.getHeight();
+ if(hgt > 750)
+ hgt = 700;
+ hgt-=50;
+
+ Dimension d = new Dimension(wid,hgt);
+ createWizScroll.setPreferredSize(d);
+
+ JOptionPane.showMessageDialog(null,
+ createWizScroll, "DNA Wizard",
+ JOptionPane.PLAIN_MESSAGE);
+
+ dna.setGeneticMarker(block);
+ dna.setRestrictionEnzyme(restrictionEnzyme);
+ dna.setLineAttributes(la.getLineAttr());
+ dna.setStartTick(tk.getStartTick());
+ dna.setMinorTickInterval(tk.getMinorTickInterval());
+ dna.setTickInterval(tk.getTickInterval());
+
+ int s = la.getStart();
+ dna.setStart(s);
+
+ s = la.getEnd();
+ dna.setEnd(s);
+ }
+ }
+
+ /**
+ * Open a DNA plot based on a template file
+ * @param template
+ */
+ public Wizard(final String templateName)
+ {
+ loadTemplate(templateName);
+ }
+
+ /**
+ * Load from a template file
+ * @param template
+ */
+ private void loadTemplate(final String templateName)
+ {
+ final ProgressFrame progress = new ProgressFrame();
+ progress.setString("Reading from "+templateName+" ");
+ progress.setValue(2);
+
+ if(dna == null)
+ dna = new DNADraw();
+ Options.getOptions();
+ final BufferedReader inputStream = getReader(templateName);
+ loadTemplate(inputStream, templateName, progress);
+ }
+
+ /**
+ * Load from a template file
+ * @param template
+ */
+ private void loadTemplate(final File templateFile)
+ {
+ final ProgressFrame progress = new ProgressFrame();
+ progress.setString("Reading from "+templateFile.getName()+" ");
+ progress.setValue(2);
+
+ if(dna == null)
+ dna = new DNADraw();
+ Options.getOptions();
+ FileReader reader;
+ try
+ {
+ reader = new FileReader(templateFile);
+ final BufferedReader inputStream = new BufferedReader(reader);
+ loadTemplate(inputStream, templateFile.getName(), progress);
+ }
+ catch(FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void loadTemplate(final BufferedReader inputStream,
+ final String templateName,
+ final ProgressFrame progress)
+ {
+ try
+ {
+ final EntryGroup entryGroup = new SimpleEntryGroup();
+ final Hashtable fileEntrys = new Hashtable();
+ Vector v_tracks = new Vector();
+ String inLine = null;
+ String lineAttrStr[] = null;
+ String tickMarksStr[] = null;
+ String gcGraphStr[] = null;
+ String gcSkewGraphStr[] = null;
+ String userGraphStr[] = null;
+
+ String lineAttrStart = "# line attributes:";
+ String tickMarksStart = "# tick marks:";
+ String gcGraphStart = "# GC Graph:";
+ String gcSkewGraphStart = "# GC Skew Graph:";
+ String userGraphStart = "# User Graph:";
+ String mergeBlastFeatures = "# merge.blast";
+
+ while((inLine = inputStream.readLine()) != null)
+ {
+ if(inLine.startsWith("#") || inLine.trim().equals(""))
+ {
+ if(inLine.startsWith(lineAttrStart))
+ lineAttrStr = inLine.substring(lineAttrStart.length()).trim().split("[=\\s]");
+ else if(inLine.startsWith(tickMarksStart))
+ tickMarksStr = inLine.substring(tickMarksStart.length()).trim().split("[=\\s]");
+ else if(inLine.startsWith(gcGraphStart))
+ gcGraphStr = inLine.substring(gcGraphStart.length()).trim().split("[=\\s]");
+ else if(inLine.startsWith(gcSkewGraphStart))
+ gcSkewGraphStr = inLine.substring(gcSkewGraphStart.length()).trim().split("[=\\s]");
+ else if(inLine.startsWith(userGraphStart))
+ userGraphStr = inLine.substring(userGraphStart.length()).trim().split("[=\\s]");
+ else if(inLine.startsWith(mergeBlastFeatures))
+ System.setProperty("merge.blast", "true");
+ continue;
+ }
+
+ String properties[] = inLine.split("\t");
+
+ final String separator;
+ if (properties[11].indexOf ("://") != -1)
+ separator = "/";
+ else
+ separator = File.separator;
+ String fileName = properties[11] + separator + properties[10];
+ Entry entry;
+ if(!fileEntrys.containsKey(fileName))
+ {
+ progress.setString("Reading "+properties[10]);
+ progress.setValue(4);
+ entry = getEntry(fileName, entryGroup);
+ if(entry == null)
+ continue;
+ fileEntrys.put(fileName, entry);
+ }
+ else
+ {
+ entry = (Entry)fileEntrys.get(fileName);
+ }
+
+ Track track = new Track(.9,entry);
+ track.setPropertiesFromTemplate(inLine);
+ v_tracks.add(track);
+ }
+ inputStream.close();
+
+ progress.setString("Read template "+templateName);
+ progress.setValue(7);
+ Track[] newTracks = new Track[v_tracks.size()];
+ for(int i=0; i<v_tracks.size(); i++)
+ newTracks[i] = (Track) v_tracks.get(i);
+
+ Wizard.tracks = newTracks;
+
+ dna.setArtemisEntryGroup(entryGroup);
+
+ int sequenceLength = entryGroup.getSequenceEntry().getBases().getLength();
+
+ Hashtable lineAttr = new Hashtable();
+ lineAttr.put("lsize", new Integer(1));
+ lineAttr.put("circular", new Boolean(true));
+ lineAttr.put("start", new Integer(0));
+ lineAttr.put("end", new Integer(sequenceLength));
+ if(lineAttrStr != null)
+ {
+ for(int i=0; i<lineAttrStr.length; i++)
+ {
+ if(lineAttrStr[i].startsWith("line_size"))
+ lineAttr.put("lsize", new Integer(lineAttrStr[i+1]));
+ else if(lineAttrStr[i].startsWith("circular"))
+ lineAttr.put("circular", new Boolean(lineAttrStr[i+1]));
+ else if(lineAttrStr[i].startsWith("line_height"))
+ dna.setLineHeight(Float.parseFloat(lineAttrStr[i+1]));
+ else if(lineAttrStr[i].startsWith("bases_per_line"))
+ dna.setBasesPerLine(Integer.parseInt(lineAttrStr[i+1]));
+ }
+ }
+ dna.setLineAttributes(lineAttr);
+
+ final int div;
+ if(sequenceLength < 1000)
+ div = 100;
+ else if(sequenceLength < 10000)
+ div = 1000;
+ else if(sequenceLength < 100000)
+ div = 10000;
+ else
+ div = 100000;
+ int tick = sequenceLength / div;
+ tick = tick * (div / 10);
+ int tick2 = tick / 2;
+ tick = tick2 * 2;
+
+ if(tickMarksStr != null)
+ {
+ for(int i=0; i<tickMarksStr.length; i++)
+ {
+ if(tickMarksStr[i].startsWith("major"))
+ tick = Integer.parseInt(tickMarksStr[i+1]);
+ else if(tickMarksStr[i].startsWith("minor"))
+ tick2 = Integer.parseInt(tickMarksStr[i+1]);
+ }
+ }
+
+ dna.setGeneticMarker(new Vector());
+ dna.setRestrictionEnzyme(new Vector());
+ dna.setMinorTickInterval(tick2);
+ dna.setTickInterval(tick);
+
+ TrackManager trackManager = dna.getTrackManager();
+ if(trackManager == null)
+ {
+ trackManager = new TrackManager(dna);
+ dna.setTrackManager(trackManager);
+ }
+ trackManager.update(tracks);
+
+ final String[] this_gcGraphStr = gcGraphStr;
+ final String[] this_gcSkewGraphStr = gcSkewGraphStr;
+ final String[] this_userGraphStr = userGraphStr;
+ workerGraph = new SwingWorker()
+ {
+ public Object construct()
+ {
+ loadGraphs(this_gcGraphStr, this_gcSkewGraphStr, this_userGraphStr, dna, progress);
+ return null;
+ }
+ };
+ }
+ catch(FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ catch(NoSequenceException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Load graphs using template file details
+ * @param gcGraphStr
+ * @param gcSkewGraphStr
+ * @param userGraphStr
+ * @param dna
+ */
+ private void loadGraphs(final String gcGraphStr[],
+ final String gcSkewGraphStr[],
+ final String userGraphStr[],
+ final DNADraw dna,
+ final ProgressFrame progress)
+ {
+
+ if(gcGraphStr != null)
+ {
+ if(progress != null)
+ {
+ progress.setString("Calculating GC graph points");
+ }
+ GCGraph gcGraph = new GCGraph(dna);
+ gcGraph.setOptionsStr(gcGraphStr);
+ dna.setGcGraph(gcGraph);
+ gcGraph.calcGraphValues();
+ dna.add(gcGraph);
+ dna.repaint();
+ dna.revalidate();
+ }
+ if(gcSkewGraphStr != null)
+ {
+ if(progress != null)
+ {
+ progress.setString("Calculating GC Skew graph points");
+ progress.setValue(8);
+ }
+ GCSkewGraph gcSkewGraph = new GCSkewGraph(dna);
+ gcSkewGraph.setOptionsStr(gcSkewGraphStr);
+ dna.setGcSkewGraph(gcSkewGraph);
+ gcSkewGraph.calcGraphValues();
+ dna.add(gcSkewGraph);
+ dna.repaint();
+ dna.revalidate();
+ }
+ if(userGraphStr != null)
+ {
+ String fileName = null;
+ for(int i=0;i<userGraphStr.length; i++)
+ {
+ if(userGraphStr[i].startsWith("file_name"))
+ fileName = userGraphStr[i+1];
+ }
+ //final uk.ac.sanger.artemis.util.Document document =
+ // new uk.ac.sanger.artemis.util.FileDocument(new File(fileName));
+ try
+ {
+ if(progress != null)
+ {
+ progress.setString("Calculating user graph points");
+ progress.setValue(9);
+ }
+ UserGraph userGraph = new UserGraph(dna, fileName);
+ userGraph.setOptionsStr(userGraphStr);
+ dna.setUserGraph(userGraph);
+ userGraph.calcGraphValues();
+ dna.add(userGraph);
+ dna.repaint();
+ dna.revalidate();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ return;
+ }
+ }
+ progress.dispose();
+ }
+
+ /**
+ * Return an Artemis entry from a file
+ * @param entryFileName
+ * @param entryGroup
+ * @return
+ * @throws NoSequenceException
+ */
+ private Entry getEntry(final String entryFileName, final EntryGroup entryGroup)
+ throws NoSequenceException
+ {
+ final Document entry_document = DocumentFactory.makeDocument(entryFileName);
+ final EntryInformation artemis_entry_information =
+ Options.getArtemisEntryInformation();
+
+ uk.ac.sanger.artemis.io.Entry new_embl_entry =
+ EntryFileDialog.getEntryFromFile(null, entry_document,
+ artemis_entry_information,
+ false);
+
+ if(new_embl_entry == null) // the read failed
+ return null;
+
+ new_embl_entry = mergeOption(new_embl_entry);
+
+ Entry entry = null;
+ try
+ {
+ Bases bases = null;
+ if(entryGroup.getSequenceEntry() != null)
+ bases = entryGroup.getSequenceEntry().getBases();
+ if(bases == null)
+ entry = new Entry(new_embl_entry);
+ else
+ entry = new Entry(bases,new_embl_entry);
+
+ entryGroup.add(entry);
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(null, "read failed: one of the features in " +
+ entryFileName + " has an out of range " +
+ "location: " + e.getMessage());
+ }
+ return entry;
+ }
+
+
+ private static uk.ac.sanger.artemis.io.Entry mergeOption(uk.ac.sanger.artemis.io.Entry embl_entry)
+ {
+ if(embl_entry instanceof uk.ac.sanger.artemis.io.MSPcrunchDocumentEntry &&
+ System.getProperty("merge.blast") == null)
+ {
+ int status = JOptionPane.showConfirmDialog(null,
+ "This looks like a BLAST file. Do you want to merge\n"+
+ "overlapping BLAST hits to improve performance?", "Read BLAST",
+ JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
+ if(status == JOptionPane.OK_OPTION)
+ System.setProperty("merge.blast", "");
+ else
+ System.setProperty("merge.blast", "false");
+ }
+
+ if( embl_entry instanceof uk.ac.sanger.artemis.io.MSPcrunchDocumentEntry &&
+ System.getProperty("merge.blast") != null &&
+ !System.getProperty("merge.blast").equals("false") )
+ embl_entry = mergeOverlappingFeatures(embl_entry);
+ return embl_entry;
+ }
+
+ /**
+ * Merge overlapping BLAST hit features to improve the performance of
+ * DNAPlotter.
+ * @param embl_entry
+ * @return
+ */
+ private static uk.ac.sanger.artemis.io.Entry mergeOverlappingFeatures(uk.ac.sanger.artemis.io.Entry embl_entry)
+ {
+ final String name = embl_entry.getName();
+ final RangeVector ranges = new RangeVector();
+ uk.ac.sanger.artemis.io.FeatureVector features = embl_entry.getAllFeatures();
+ System.out.print("Number of features: before merge = "+features.size());
+
+ for(int i=0; i<features.size(); i++)
+ {
+ Range ri = ((uk.ac.sanger.artemis.io.Feature) features.elementAt(i)).
+ getLocation().getTotalRange();
+ boolean overlaps = false;
+ Range rjOld = null;
+ Range rj = null;
+ int j;
+ for(j=0; j<ranges.size(); j++)
+ {
+ rjOld = (Range) ranges.get(j);
+ if(ri.overlaps(rjOld))
+ {
+ int start = rjOld.getStart();
+ int end = rjOld.getEnd();
+ if(start > ri.getStart())
+ start = ri.getStart();
+ if(end < ri.getEnd())
+ end = ri.getEnd();
+
+ try
+ {
+ rj = new Range(start, end);
+ overlaps = true;
+ break;
+ }
+ catch (OutOfRangeException e){}
+ }
+ }
+ if(!overlaps)
+ ranges.add(ri);
+ else
+ {
+ ranges.remove(rjOld);
+ ranges.add(rj);
+ }
+ }
+
+ embl_entry.dispose();
+ embl_entry = new MSPcrunchDocumentEntry(
+ Options.getOptions().getArtemisEntryInformation())
+ {
+ public boolean isReadOnly () {
+ return false;
+ }
+ };
+ embl_entry.setName(name);
+
+ final Key key = new Key("CRUNCH_D");
+ for(int i=0; i<ranges.size(); i++)
+ {
+ Range r = (Range)ranges.get(i);
+ try
+ {
+ Feature f = new uk.ac.sanger.artemis.Feature(
+ new uk.ac.sanger.artemis.io.EmblStreamFeature(key, new Location(r), null));
+ embl_entry.add(f.getEmblFeature());
+ }
+ catch (ReadOnlyException e){}
+ catch (EntryInformationException e){}
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ System.out.println(" after merge = "+ranges.size());
+ return embl_entry;
+ }
+
+ /**
+ * Create a DNADraw panel from a file
+ * @param dna_current
+ * @return
+ */
+ protected static DNADraw getDNADrawFromFile(DNADraw dna_current)
+ {
+ Options.getOptions();
+ uk.ac.sanger.artemis.components.FileDialogEntrySource entrySource =
+ new uk.ac.sanger.artemis.components.FileDialogEntrySource(null, null);
+
+ final EntryGroup entryGroup = new SimpleEntryGroup();
+ Entry entry;
+ try
+ {
+ entry = entrySource.getEntry(true);
+ entryGroup.add(entry);
+ return getDNADrawFromArtemisEntry(dna_current, entryGroup, entry);
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ catch(NoSequenceException e)
+ {
+ JOptionPane.showMessageDialog(null, "No sequence found!",
+ "Sequence Missing", JOptionPane.WARNING_MESSAGE);
+ }
+ return null;
+ }
+
+ /**
+ * Create a DNADraw panel from an entry
+ * @param dna_current
+ * @param entryGroup
+ * @param entry
+ * @return
+ */
+ public static DNADraw getDNADrawFromArtemisEntry(DNADraw dna_current,
+ final EntryGroup entryGroup,
+ final Entry entry)
+ {
+ for(int i=0; i<tracks.length; i++)
+ tracks[i].setEntry(entry);
+
+ FeatureVector features = entry.getAllFeatures();
+ Vector block = new Vector();
+
+ if(dna_current == null)
+ dna_current = new DNADraw();
+
+ dna_current.setArtemisEntryGroup(entryGroup);
+
+ Hashtable lineAttr = new Hashtable();
+ lineAttr.put("lsize", new Integer(1));
+ lineAttr.put("circular", new Boolean(true));
+ lineAttr.put("start", new Integer(0));
+ lineAttr.put("end", new Integer(entry.getBases().getLength()));
+
+ dna_current.setLineAttributes(lineAttr);
+
+ for(int i = 0; i < features.size(); i++)
+ {
+ Feature f = features.elementAt(i);
+
+ RangeVector ranges = f.getLocation().getRanges();
+
+ for(int j = 0; j < ranges.size(); j++)
+ {
+ Range range = (Range) ranges.get(j);
+
+ Color col = f.getColour();
+ if(col == null || col.equals(Color.white))
+ col = Color.lightGray;
+
+ Track track;
+
+ if(TRACK_1.isOnTrack(f))
+ track = TRACK_1;
+ else if(TRACK_2.isOnTrack(f))
+ track = TRACK_2;
+ else if(TRACK_3.isOnTrack(f))
+ track = TRACK_3;
+ else if(TRACK_4.isOnTrack(f))
+ track = TRACK_4;
+ else
+ track = TRACK_5;
+
+ Block drawBlock = new Block(f.getIDString(), range.getStart(), range
+ .getEnd(), col, 10.f, track, dna_current);
+
+ drawBlock.setFeature(f);
+ block.add(drawBlock);
+ }
+ }
+
+ int div;
+ if(entry.getBases().getLength() < 1000)
+ div = 100;
+ else if(entry.getBases().getLength() < 10000)
+ div = 1000;
+ else if(entry.getBases().getLength() < 100000)
+ div = 10000;
+ else
+ div = 100000;
+ int tick = entry.getBases().getLength() / div;
+ tick = tick * (div / 10);
+ int tick2 = tick / 2;
+ tick = tick2 * 2;
+
+ dna_current.setGeneticMarker(block);
+ dna_current.setRestrictionEnzyme(new Vector());
+ dna_current.setMinorTickInterval(tick2);
+ dna_current.setTickInterval(tick);
+
+ EntryVector entries = entryGroup.getActiveEntries();
+ for(int i=0; i<entries.size(); i++)
+ {
+ Entry this_entry = entries.elementAt(i);
+ if(!this_entry.getName().equals(entry.getName()))
+ addFeaturesFromEntry(this_entry, dna_current);
+ }
+ return dna_current;
+ }
+
+ /**
+ * Read a new entry from a file
+ * @param dna_current
+ * @param bases
+ * @return
+ */
+ public static DNADraw readEntry(final DNADraw dna_current,
+ final Bases bases)
+ {
+ Options.getOptions();
+ uk.ac.sanger.artemis.components.FileDialogEntrySource entrySource =
+ new uk.ac.sanger.artemis.components.FileDialogEntrySource(null, null);
+
+ try
+ {
+ Entry entry = entrySource.getEntry(bases,true);
+
+ if(entry.getEMBLEntry() instanceof uk.ac.sanger.artemis.io.MSPcrunchDocumentEntry)
+ {
+ uk.ac.sanger.artemis.io.Entry new_embl_entry =
+ mergeOption(entry.getEMBLEntry());
+ entry = new Entry(bases, new_embl_entry);
+ }
+
+ dna_current.getArtemisEntryGroup().add(entry);
+
+ addFeaturesFromEntry(entry, dna_current);
+ }
+ catch(OutOfRangeException e)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Feature found out of range:\n"+
+ e.getMessage(),"Out of Range",
+ JOptionPane.WARNING_MESSAGE);
+ }
+
+ return dna_current;
+ }
+
+ /**
+ * Add features from an entry to a new track
+ * @param entry
+ * @param dna_current
+ */
+ private static void addFeaturesFromEntry(final Entry entry,
+ final DNADraw dna_current)
+ {
+ FeatureVector features = entry.getAllFeatures();
+
+ Track track = addTrack(entry);
+ track.setAny(true);
+ for(int i=0; i<features.size(); i++)
+ {
+ Feature f = features.elementAt(i);
+ RangeVector ranges = f.getLocation().getRanges();
+
+ for(int j=0; j<ranges.size(); j++)
+ {
+ Range range = (Range) ranges.get(j);
+
+ Color col = f.getColour();
+ if(col == null || col.equals(Color.white))
+ col = Color.lightGray;
+
+ Block drawBlock = new Block(f.getIDString(),
+ range.getStart(),
+ range.getEnd(),
+ col,
+ 10.f,
+ track, dna_current);
+
+ drawBlock.setFeature(f);
+ dna_current.getGeneticMarker().add(drawBlock);
+ }
+ }
+ }
+
+
+
+ public DNADraw getDNADraw()
+ {
+ return dna;
+ }
+
+
+ private int getOption(DNADraw dna_current)
+ {
+ Box bdown = Box.createVerticalBox();
+
+ JRadioButton[] radioButtons;
+
+ radioButtons = new JRadioButton[2];
+
+ final ButtonGroup group = new ButtonGroup();
+ radioButtons[0] = new JRadioButton("Read in sequence file");
+ group.add(radioButtons[0]);
+
+ radioButtons[0].setSelected(true);
+ bdown.add(radioButtons[0]);
+
+
+ radioButtons[0].setSelected(true);
+ if(dna_current != null)
+ {
+ radioButtons[1] = new JRadioButton("Edit current dna display");
+ group.add(radioButtons[1]);
+ radioButtons[1].setSelected(true);
+ }
+ else
+ {
+ radioButtons[1] = new JRadioButton("Read template file");
+ group.add(radioButtons[1]);
+ }
+ bdown.add(radioButtons[1]);
+
+ JPanel pane = new JPanel(new BorderLayout());
+ pane.add(bdown);
+ JOptionPane.showMessageDialog(null,
+ pane, "DNA Viewer Wizard",
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(radioButtons[0].isSelected())
+ return 0;
+ else if(radioButtons[1].isSelected() && dna_current != null)
+ return 1;
+ else if(radioButtons[1].isSelected())
+ return 2;
+
+ return 1;
+ }
+
+ protected static Track[] getTracks()
+ {
+ return tracks;
+ }
+
+
+ protected static Track addTrack(Entry entry)
+ {
+ Track[] tracks = getTracks();
+ Track[] newTracks = new Track[tracks.length+1];
+ for(int i=0; i<tracks.length; i++)
+ newTracks[i] = tracks[i];
+
+ final double position;
+ if(tracks.length > 1)
+ position = tracks[tracks.length-1].getPosition()-0.05;
+ else
+ position = 0.95;
+ Track newTrack = new Track(position, entry);
+ newTracks[tracks.length] = newTrack;
+ Wizard.tracks = newTracks;
+ return newTrack;
+ }
+
+ protected static void deleteTrack(int trackIndex)
+ {
+ Track[] tracks = getTracks();
+ Track[] newTracks = new Track[tracks.length-1];
+ int count = 0;
+ for(int i=0; i<tracks.length; i++)
+ {
+ if(i == trackIndex)
+ continue;
+
+ newTracks[count] = tracks[i];
+ count++;
+ }
+
+ Wizard.tracks = newTracks;
+ }
+
+ /**
+ * Return reader for a file or a URL
+ * @param templateName
+ * @return
+ */
+ protected static BufferedReader getReader(final String templateName)
+ {
+ final File fileTemplate = new File(templateName);
+ BufferedReader inputStream = null;
+ if(!fileTemplate.exists())
+ {
+ if (templateName.indexOf ("://") != -1)
+ {
+ URL template;
+ try
+ {
+ template = new URL(templateName);
+ inputStream = new BufferedReader(
+ new InputStreamReader(template.openStream()));
+ }
+ catch(MalformedURLException e)
+ {
+ e.printStackTrace();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ else
+ {
+ try
+ {
+ if(templateName.endsWith(".gz"))
+ inputStream = new BufferedReader(
+ new InputStreamReader(new WorkingGZIPInputStream(
+ new FileInputStream(fileTemplate) )));
+ else
+ inputStream = new BufferedReader(new FileReader(fileTemplate));
+ }
+ catch(FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return inputStream;
+ }
+
+ public SwingWorker getWorkerGraph()
+ {
+ return workerGraph;
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/circular/digest/CircularGenomeController.java b/uk/ac/sanger/artemis/circular/digest/CircularGenomeController.java
new file mode 100644
index 0000000..f68a980
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/digest/CircularGenomeController.java
@@ -0,0 +1,694 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.circular.Block;
+import uk.ac.sanger.artemis.circular.DNADraw;
+import uk.ac.sanger.artemis.components.ArtemisMain;
+import uk.ac.sanger.artemis.components.EntryEdit;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.components.StickyFileChooser;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+
+/**
+ *
+ */
+public class CircularGenomeController
+{
+ private Block lastBlock = null;
+ private JPanel gelPanel;
+ private int hgt;
+ private JFrame frame = new JFrame();
+ private boolean methylation = false;
+
+ public CircularGenomeController()
+ {
+ }
+
+ /**
+ * Create in-silico Pulse Field Gel Electrophoresis from a restriction enzyme
+ * digest and draw alongside DNAPlotter
+ * @param enzymes
+ * @param sequenceFiles
+ * @param restrictOutputs
+ * @param methylation if true then RE recognition sites will not match methylated bases
+ * @throws Exception
+ */
+ protected void setup(String enzymes,
+ List<File> sequenceFiles,
+ List<File> restrictOutputs,
+ boolean methylation)
+ throws Exception
+ {
+ this.methylation = methylation;
+ // add each sequence file to a different entry group
+ List<EntryGroup> entries = new Vector<EntryGroup>();
+ if (sequenceFiles != null && sequenceFiles.size() > 0)
+ {
+ for (int i = 0; i < sequenceFiles.size(); i++)
+ {
+ EntryGroup entryGroup = getEntryGroupFromFile(sequenceFiles.get(i));
+ entries.add(entryGroup);
+ }
+ }
+ else
+ {
+ EntryGroup entryGroup = getEntryGroupFromFile(null);
+ File sequenceFile = new File(((File) entryGroup.getSequenceEntry()
+ .getRootDocument().getLocation()).getAbsolutePath()
+ + File.separator + entryGroup.getSequenceEntry().getName());
+
+ if(sequenceFiles == null)
+ sequenceFiles = new Vector<File>();
+ sequenceFiles.add(sequenceFile);
+ entries.add(entryGroup);
+ }
+
+ if (enzymes == null)
+ enzymes = promptForEnzymes();
+
+ // run restrict
+ if(restrictOutputs == null)
+ {
+ restrictOutputs = new Vector<File>(sequenceFiles.size());
+ for (int i = 0; i < sequenceFiles.size(); i++)
+ {
+ File sequenceFile = sequenceFiles.get(i);
+ File restrictOutput = File.createTempFile("restrict_"
+ + sequenceFile.getName(), ".txt");
+ restrictOutputs.add(restrictOutput);
+ runEmbossRestrict(sequenceFile.getAbsolutePath(), enzymes,
+ restrictOutput);
+ }
+ }
+ drawResults(restrictOutputs, entries, sequenceFiles, enzymes);
+ }
+
+ /**
+ * Run the EMBOSS application restrict. This uses the EMBOSS_ROOT property to
+ * define the location of EMBOSS.
+ * @param fileName
+ * @param cgcb
+ * @param restrictOutput
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ private void runEmbossRestrict(final String fileName,
+ final String enzymes,
+ final File restrictOutput) throws IOException, InterruptedException
+ {
+ String[] args = {
+ System.getProperty("EMBOSS_ROOT") + "/bin/restrict", fileName, "-auto",
+ "-limit", "y", "-enzymes", enzymes,
+ methylation ? "-methylation" : "",
+ "-out",
+ restrictOutput.getCanonicalPath() };
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+ pb.redirectErrorStream(true);
+ Process p = pb.start();
+ System.err.print("** Running restrict");
+ try
+ {
+ InputStream is = p.getInputStream();
+ int inchar;
+ while ((inchar = is.read()) != -1)
+ {
+ char c = (char) inchar;
+ System.err.print(c);
+ }
+ System.err.println("**");
+ p.waitFor();
+ System.err.println("Process exited with '" + p.exitValue() + "'");
+ }
+ catch (InterruptedException exp)
+ {
+ exp.printStackTrace();
+ }
+ p.waitFor();
+ }
+
+ /**
+ * Display the result in DNAPlotter and the virtual digest.
+ *
+ * @param restrictOutput
+ * @param entryGroup
+ * @param fileName
+ * @param cgcb
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ private void drawResults(final List<File> restrictOutputs,
+ final List<EntryGroup> entries, final List<File> sequenceFiles,
+ final String enzymes) throws FileNotFoundException, IOException
+ {
+ JTabbedPane tabbedPane = new JTabbedPane();
+ gelPanel= new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
+ Dimension preferredSize = null;
+ for (int i = 0; i < entries.size(); i++)
+ {
+ final ReportDetails rd = Utils.findCutSitesFromEmbossReport(
+ new FileReader(restrictOutputs.get(i).getCanonicalPath()));
+
+ if(rd.cutSites.size() == 0)
+ {
+ JOptionPane.showMessageDialog(null,
+ "No cut site found for "+sequenceFiles.get(i).getName(),
+ "RE Digest Results", JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ final DNADraw dna = Utils.createDNADrawFromReportDetails(
+ rd, entries.get(i));
+ tabbedPane.add(sequenceFiles.get(i).getName(), dna);
+
+ hgt = dna.getHeight();
+ final InSilicoGelPanel inSilicoGelPanel = new InSilicoGelPanel(rd.length,
+ rd.cutSites, hgt, restrictOutputs.get(i),
+ sequenceFiles.get(i).getName());
+ gelPanel.add(inSilicoGelPanel);
+ preferredSize = inSilicoGelPanel.getPreferredSize();
+ addMouseListener(rd, dna, inSilicoGelPanel);
+ }
+
+ frame.setTitle ("Sandpiper :: "+enzymes);
+ addMenuBar(frame);
+ Dimension d = frame.getToolkit().getScreenSize();
+
+ JScrollPane jspGel = new JScrollPane(gelPanel);
+
+ Dimension dgel = new Dimension(
+ preferredSize.width* (entries.size()>1 ? 2 : 1), preferredSize.height);
+ jspGel.setPreferredSize(dgel);
+ gelPanel.setMinimumSize(dgel);
+ gelPanel.setBackground(Color.white);
+
+ JScrollPane jspTabbedPane = new JScrollPane(tabbedPane);
+ JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false,
+ jspGel, jspTabbedPane);
+ mainPanel.setBackground(Color.white);
+
+ JScrollPane jsp = new JScrollPane(mainPanel);
+ jsp.getViewport().setBackground(Color.white);
+ frame.getContentPane().add(jsp);
+
+ frame.pack();
+ frame.setLocation(((int) d.getWidth() - frame.getWidth()) / 4,
+ ((int) d.getHeight() - frame.getHeight()) / 2);
+ frame.setVisible(true);
+ }
+
+ /**
+ * Add the menu bar
+ *
+ * @param f
+ */
+ private void addMenuBar(final JFrame f)
+ {
+ f.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ exitApp(f);
+ }
+ });
+
+ JMenuBar menuBar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+ menuBar.add(fileMenu);
+
+ JMenuItem loadExpData = new JMenuItem("Load experimental data...");
+ loadExpData.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ StickyFileChooser fileChooser = new StickyFileChooser();
+ int ret = fileChooser.showOpenDialog(null);
+ if(ret == StickyFileChooser.CANCEL_OPTION)
+ return;
+
+ File expFile = fileChooser.getSelectedFile();
+ FileReader reader;
+ try
+ {
+ reader = new FileReader(expFile);
+ final List<FragmentBand> bands = Utils.findCutSitesFromExperiment(reader);
+ final InSilicoGelPanel inSilicoGelPanel = new InSilicoGelPanel(
+ bands, hgt, expFile, expFile.getName());
+ gelPanel.add(inSilicoGelPanel);
+ frame.validate();
+ }
+ catch (FileNotFoundException e1)
+ {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ catch (IOException e2)
+ {
+ // TODO Auto-generated catch block
+ e2.printStackTrace();
+ }
+ }
+ });
+ fileMenu.add(loadExpData);
+ fileMenu.addSeparator();
+
+ JMenuItem exitMenu = new JMenuItem("Exit");
+ exitMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ exitApp(f);
+ }
+ });
+ fileMenu.add(exitMenu);
+
+ f.setJMenuBar(menuBar);
+ }
+
+ /**
+ *
+ * @param f
+ */
+ private void exitApp(JFrame f)
+ {
+ f.dispose();
+ System.exit(0);
+ }
+
+ /**
+ * Add mouse lister to highlight bands on the virtual digest when the mouse is
+ * over the corresponding feature in the circular plot.
+ *
+ * @param rd
+ * @param dna
+ * @param inSilicoGelPanel
+ */
+ private void addMouseListener(final ReportDetails rd, final DNADraw dna,
+ final InSilicoGelPanel inSilicoGelPanel)
+ {
+ final JPopupMenu popup = new JPopupMenu();
+ final JMenuBar menuBar = dna.createMenuBar();
+
+ JMenu[] menus = new JMenu[menuBar.getMenuCount()];
+ for (int i = 0; i < menuBar.getMenuCount(); i++)
+ menus[i] = menuBar.getMenu(i);
+
+ for (int i = 0; i < menus.length; i++)
+ popup.add(menus[i]);
+
+ final JMenuItem openArtemis = new JMenuItem("Open in Artemis...");
+ openArtemis.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Range range = null;
+ try
+ {
+ if (lastBlock == null)
+ range = new Range(1, dna.getArtemisEntryGroup().getBases()
+ .getLength());
+ else
+ range = new Range(lastBlock.getBstart(), lastBlock.getBend());
+ }
+ catch (OutOfRangeException e1)
+ {
+ e1.printStackTrace();
+ return;
+ }
+ final ArtemisMain main_window = new ArtemisMain(null);
+ main_window.setVisible(false);
+ final EntryGroup entryGroup = dna.getArtemisEntryGroup().truncate(
+ range);
+ final EntryEdit entryEdit = new EntryEdit(entryGroup);
+ entryEdit.setVisible(true);
+ }
+ });
+
+ popup.add(openArtemis);
+
+ MouseMotionListener mouseMotionListener = new MouseMotionAdapter()
+ {
+ public void mouseMoved(MouseEvent me)
+ {
+ List<CutSite> cutSites = rd.cutSites;
+ final Block b = dna.getBlockAtLocation(me.getPoint());
+ if (b != null && b.isOverMe(me.getX(), me.getY()))
+ {
+ int bend = b.getBend();
+ // int bstart = b.getBstart();
+
+ if (bend == rd.length)
+ {
+ ((CutSite) cutSites.get(0)).setHighlighted(true);
+ for (int i = 1; i < cutSites.size(); i++)
+ ((CutSite) cutSites.get(i)).setHighlighted(false);
+ }
+ else
+ {
+ for (int i = 0; i < cutSites.size(); i++)
+ {
+ CutSite cutSite = (CutSite) cutSites.get(i);
+ if (bend == cutSite.getFivePrime())
+ cutSite.setHighlighted(true);
+ else
+ cutSite.setHighlighted(false);
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < cutSites.size(); i++)
+ {
+ CutSite cutSite = (CutSite) cutSites.get(i);
+ cutSite.setHighlighted(false);
+ }
+ }
+ inSilicoGelPanel.repaint();
+ }
+ };
+ dna.addMouseMotionListener(mouseMotionListener);
+
+ MouseListener popupListener = new MouseAdapter()
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if (e.isPopupTrigger())
+ {
+ lastBlock = dna.getBlockAtLocation(e.getPoint());
+
+ if (lastBlock == null)
+ openArtemis.setText("Open in Artemis...");
+ else
+ openArtemis.setText("Open in Artemis [" + lastBlock.getBstart()
+ + ".." + lastBlock.getBend() + "]...");
+
+ popup.show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+ };
+ dna.addMouseListener(popupListener);
+
+ MouseMotionListener gelMouseMotionListener = new MouseMotionAdapter()
+ {
+ Block lastBlock = null;
+ Color lastBlockColour = null;
+ public void mouseMoved(MouseEvent me)
+ {
+ final FragmentBand band = inSilicoGelPanel.getBandAtLocation(me.getPoint());
+ if(lastBlock != null)
+ {
+ lastBlock.setColour(lastBlockColour);
+ dna.repaint();
+ }
+ if(band != null)
+ {
+ CutSite cs = band.bandCutSite;
+ lastBlock = dna.getBlockAtBasePosition(cs.getFivePrime());
+ lastBlockColour = lastBlock.getColour();
+ lastBlock.setColour(Color.GREEN);
+ dna.repaint();
+ }
+ else
+ lastBlock = null;
+ }
+ };
+ inSilicoGelPanel.addMouseMotionListener(gelMouseMotionListener);
+ }
+
+ /**
+ * Create a DNADraw panel from a file
+ *
+ * @param dna_current
+ * @return
+ */
+ private static EntryGroup getEntryGroupFromFile(File fileName)
+ {
+ Options.getOptions();
+ final EntryGroup entryGroup;
+
+ try
+ {
+ Entry entry = getEntry(fileName);
+
+ if (entry.getBases() != null)
+ entryGroup = new SimpleEntryGroup(entry.getBases());
+ else
+ entryGroup = new SimpleEntryGroup();
+ entryGroup.add(entry);
+ return entryGroup;
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ catch (NoSequenceException e)
+ {
+ JOptionPane.showMessageDialog(null, "No sequence found!",
+ "Sequence Missing", JOptionPane.WARNING_MESSAGE);
+ }
+ return null;
+ }
+
+ /**
+ * Return an Artemis entry from a file
+ *
+ * @param entryFileName
+ * @param entryGroup
+ * @return
+ * @throws NoSequenceException
+ * @throws OutOfRangeException
+ */
+ private static Entry getEntry(final File entryFileName)
+ throws NoSequenceException, OutOfRangeException
+ {
+
+ if (entryFileName == null)
+ {
+ // no file - prompt for a file
+ uk.ac.sanger.artemis.components.FileDialogEntrySource entrySource = new uk.ac.sanger.artemis.components.FileDialogEntrySource(
+ null, null);
+ Entry entry = entrySource.getEntry(true);
+ return entry;
+ }
+
+ final Document entry_document = DocumentFactory.makeDocument(entryFileName
+ .getAbsolutePath());
+ final EntryInformation artemis_entry_information = Options
+ .getArtemisEntryInformation();
+
+ final uk.ac.sanger.artemis.io.Entry new_embl_entry = EntryFileDialog
+ .getEntryFromFile(null, entry_document, artemis_entry_information,
+ false);
+
+ if (new_embl_entry == null) // the read failed
+ return null;
+
+ Entry entry = null;
+ try
+ {
+ entry = new Entry(new_embl_entry);
+ }
+ catch (OutOfRangeException e)
+ {
+ new MessageDialog(null, "read failed: one of the features in "
+ + entryFileName + " has an out of range " + "location: "
+ + e.getMessage());
+ }
+ return entry;
+ }
+
+ /**
+ * Prompt for the enzyme list.
+ * @return
+ */
+ private String promptForEnzymes()
+ {
+ Box yBox = Box.createVerticalBox();
+ JTextField enzymeList = new JTextField("HincII,hinfI,ppiI,hindiii");
+ yBox.add(enzymeList);
+ JCheckBox methylationCheckBox = new JCheckBox(
+ "RE sites will not match methylated bases", methylation);
+ yBox.add(methylationCheckBox);
+
+ JOptionPane.showMessageDialog(null, yBox, "Enzyme", JOptionPane.QUESTION_MESSAGE);
+ methylation = methylationCheckBox.isSelected();
+ return enzymeList.getText().trim();
+ }
+
+ public static void main(String args[])
+ {
+ if (System.getProperty("EMBOSS_ROOT") == null)
+ {
+ String embossRoot = JOptionPane.showInputDialog(null,
+ "Input the EMBOSS installation directory", "/usr/local/emboss");
+ System.setProperty("EMBOSS_ROOT", embossRoot.trim());
+ }
+
+ String enzymes = null;
+ final CircularGenomeController controller = new CircularGenomeController();
+ boolean methylation = false;
+
+ List<File> fileNames = null;
+ List<File> restrictOutputs = null;
+ if (args != null && args.length > 0)
+ {
+ if (args.length == 1)
+ {
+ if (args[0].startsWith("-h"))
+ {
+ System.out.println("-h\t\tshow help");
+ System.out
+ .println("-enz\t\tcomma separated list of digest enzymes (optional)");
+ System.out
+ .println("-seq\t\tspace separated list of sequences (optional)");
+ System.out
+ .println("-methylation\tif this is set then RE recognition sites "
+ + "will not match methylated bases.");
+ System.out
+ .println("-restrict\tspace separated lists of EMBOSS restrict output "
+ + "in the same order as the sequences (optional).");
+ System.exit(0);
+ }
+ fileNames = new Vector<File>();
+ fileNames.add(new File(args[0]));
+ }
+
+ for (int i = 0; i < args.length; i++)
+ {
+ if (args[i].startsWith("-enz"))
+ enzymes = args[i + 1];
+ else if (args[i].startsWith("-meth"))
+ methylation = true;
+ else if (args[i].startsWith("-seq"))
+ {
+ if (fileNames == null)
+ fileNames = new Vector<File>();
+
+ for (int j = i + 1; j < args.length; j++)
+ {
+ if (args[j].startsWith("-"))
+ break;
+ fileNames.add(new File(args[j]));
+ }
+ }
+ else if (args[i].startsWith("-restrict"))
+ {
+ if (restrictOutputs == null)
+ restrictOutputs = new Vector<File>();
+
+ for (int j = i + 1; j < args.length; j++)
+ {
+ if (args[j].startsWith("-"))
+ break;
+ restrictOutputs.add(new File(args[j]));
+ }
+ }
+ }
+ }
+
+ final FileSelectionPanel selectionPanel = new FileSelectionPanel(enzymes,
+ fileNames, restrictOutputs, methylation);
+ final JFrame f = new JFrame("Options and File Selection");
+ ActionListener displayButtonListener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ try
+ {
+ f.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ if(selectionPanel.getEmbossRootField() != null)
+ System.getProperties().put("EMBOSS_ROOT",
+ selectionPanel.getEmbossRootField().getText().trim());
+ controller.setup(selectionPanel.getEnzymes(),
+ selectionPanel.getSequenceFiles(),
+ selectionPanel.getRestrictOutputs(),
+ selectionPanel.isMethylation());
+ f.dispose();
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ finally
+ {
+ f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ };
+ selectionPanel.showJFrame(f, displayButtonListener);
+
+ }
+};
diff --git a/uk/ac/sanger/artemis/circular/digest/CutSite.java b/uk/ac/sanger/artemis/circular/digest/CutSite.java
new file mode 100644
index 0000000..350a2e7
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/digest/CutSite.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+public class CutSite
+{
+ private String enzymeName;
+ private int fivePrime;
+ private int threePrime;
+ private int fivePrimeRev;
+ private int threePrimeRev;
+ private boolean forward = false;
+ private boolean highlighted = false;
+
+ CutSite(final String enzymeName,
+ String fivePrimeStr, String threePrimeStr,
+ String fivePrimeRevStr, String threePrimeRevStr,
+ String strand)
+ {
+ this.enzymeName = enzymeName;
+ this.fivePrime = Integer.parseInt(fivePrimeStr);
+ this.threePrime = Integer.parseInt(threePrimeStr);
+ if(!fivePrimeRevStr.equals("."))
+ fivePrimeRev = Integer.parseInt(fivePrimeRevStr);
+ if(!threePrimeRevStr.equals("."))
+ threePrimeRev = Integer.parseInt(threePrimeRevStr);
+
+ if(strand.equals("+"))
+ forward = true;
+ }
+
+ public int getThreePrime()
+ {
+ return threePrime;
+ }
+
+ public int getFivePrime()
+ {
+ return fivePrime;
+ }
+
+ public int getFivePrimeRev()
+ {
+ return fivePrimeRev;
+ }
+
+ public int getThreePrimeRev()
+ {
+ return threePrimeRev;
+ }
+
+ public boolean isForward()
+ {
+ return forward;
+ }
+
+ public boolean isHighlighted()
+ {
+ return highlighted;
+ }
+
+ public void setHighlighted(boolean highlighted)
+ {
+ this.highlighted = highlighted;
+ }
+
+ public String getEnzymeName()
+ {
+ return enzymeName;
+ }
+}
diff --git a/uk/ac/sanger/artemis/circular/digest/EmbossDigestParser.java b/uk/ac/sanger/artemis/circular/digest/EmbossDigestParser.java
new file mode 100644
index 0000000..d39ad65
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/digest/EmbossDigestParser.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+import org.apache.log4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class EmbossDigestParser {
+
+ private static final Logger logger = Logger.getLogger(EmbossDigestParser.class);
+
+ String embossDir;
+
+ private List<String> digests = Collections.emptyList();
+
+ public List<String> getDigests() {
+ return digests;
+ }
+
+ public void afterPropertiesSet() {
+ try {
+ parseDigests();
+ } catch (IOException e) {
+ logger.warn(String.format("Emboss directory %s does't exist",embossDir));
+ }
+ }
+
+ private void parseDigests() throws IOException {
+ BufferedReader br = new BufferedReader(new FileReader(new File(embossDir+"REBASE/embossre.enz")));
+ String line;
+ digests = new ArrayList<String>();
+ while ((line = br.readLine()) != null) {
+ if (line.startsWith("#")) {
+ continue;
+ }
+ int space = line.indexOf("\t");
+ if (space>1) {
+ digests.add(line.substring(0, space));
+ } else {
+ System.err.println("Couldn't get enzyme name from '"+line+"'");
+ }
+ }
+ }
+
+ public void setEmbossDir(String embossDir) {
+ this.embossDir = embossDir;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/circular/digest/EmbossTableParser.java b/uk/ac/sanger/artemis/circular/digest/EmbossTableParser.java
new file mode 100644
index 0000000..5a33726
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/digest/EmbossTableParser.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class EmbossTableParser
+{
+ List<CutSite> list = new ArrayList<CutSite>();
+ private int length;
+
+ public List<CutSite> parse(BufferedReader br) throws IOException
+ {
+ String line;
+ while ((line = br.readLine()) != null)
+ {
+ String trim = line.trim();
+ if (trim.startsWith("#"))
+ {
+ if (trim.indexOf("Sequence:") != -1)
+ {
+ int pos = trim.indexOf("to: ");
+ String l = trim.substring(pos + 4);
+ length = Integer.parseInt(l);
+ }
+ continue;
+ }
+
+ if (trim.length() == 0)
+ continue;
+
+ String[] parts = trim.split("\\s+");
+ if (parts.length < 3)
+ continue;
+
+ if ("Start".equals(parts[0]))
+ continue;
+
+ CutSite cutSite;
+
+ if(parts.length > 8) // new EMBOSS format
+ cutSite = new CutSite(
+ parts[3], parts[5], parts[6], parts[7], parts[8], parts[2]);
+ else
+ cutSite = new CutSite(
+ parts[2], parts[4], parts[5], parts[6], parts[7], "+");
+ list.add(cutSite);
+ }
+ return list;
+ }
+
+ public int getLength()
+ {
+ return this.length;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/circular/digest/FileSelectionPanel.java b/uk/ac/sanger/artemis/circular/digest/FileSelectionPanel.java
new file mode 100644
index 0000000..1169793
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/digest/FileSelectionPanel.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.circular.digest;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.components.StickyFileChooser;
+import uk.ac.sanger.artemis.components.Utilities;
+
+/**
+ * File selection panel to allow input of DNA sequences
+ */
+class FileSelectionPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ private GridBagConstraints c = new GridBagConstraints();
+ private int row = 0;
+ private List<SelectionRow> selectionRows = new Vector<SelectionRow>();
+
+ private List<File> restrictOutputs;
+ private JTextField enzymeField;
+ private JTextField embossRootField;
+ private JCheckBox methylationCheckBox;
+ private JFrame f;
+
+ /**
+ * Constructor to display any given input files and options provided and
+ * provide a graphical input window for adding sequences.
+ * that have been given on the command line.
+ * @param enzymes enzyme string, takes the value from the -enz parameter
+ * @param sequenceFiles sequence file, takes the value(s) from the -seq parameter
+ * @param restrictOutputs pre-computed EMBOSS restrict output, takes the value(s)
+ * from the -restrict parameter
+ * @param methylation if true takes into account methylation blocked sites, takes
+ * the value of -methylation parameter
+ */
+ public FileSelectionPanel(
+ String enzymes,
+ List<File> sequenceFiles,
+ List<File> restrictOutputs,
+ boolean methylation)
+ {
+ super(new GridBagLayout());
+
+ this.restrictOutputs = restrictOutputs;
+ if(enzymes == null || enzymes.equals(""))
+ enzymes = "xbai";
+ enzymeField = new JTextField(enzymes, 30);
+ enzymeField.setPreferredSize(
+ new Dimension(200,enzymeField.getPreferredSize().height));
+ c.gridy = row;
+ c.gridx = 0;
+ c.gridwidth = 2;
+ c.anchor = GridBagConstraints.WEST;
+ add(new JLabel(" Comma separated list of digest enzymes: "), c);
+ row++;
+ c.gridx = 1;
+ c.gridy = row;
+ c.gridwidth = 1;
+ add(enzymeField, c);
+ row++;
+
+ methylationCheckBox = new JCheckBox(
+ "RE sites will not match methylated bases", methylation);
+ c.gridy = row;
+ add(methylationCheckBox, c);
+ row++;
+
+ c.gridy = row;
+ add(Box.createVerticalStrut(10), c);
+ row++;
+
+ if(restrictOutputs == null || restrictOutputs.size() == 0)
+ {
+ embossRootField = new JTextField(System.getProperty("EMBOSS_ROOT"), 30);
+ c.gridy = row;
+ c.gridx = 0;
+ add(new JLabel(" EMBOSS location: "), c);
+ c.gridx = 1;
+ add(embossRootField, c);
+ row++;
+
+ c.gridy = row;
+ add(Box.createVerticalStrut(10), c);
+ row++;
+ }
+
+
+ c.gridy = row;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.WEST;
+ add(new JLabel(" Sequence file(s): "), c);
+ row++;
+
+ if(sequenceFiles == null || sequenceFiles.size() < 1)
+ addSelectionRow(null);
+ else
+ {
+ for(int i=0; i<sequenceFiles.size(); i++)
+ addSelectionRow(sequenceFiles.get(i).getAbsolutePath());
+ }
+
+ JButton addMoreFiles = new JButton("Add More");
+ addMoreFiles.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ addSelectionRow(null);
+ }
+ });
+ c.gridx = 1;
+ c.gridy = row+100;
+ add(addMoreFiles, c);
+ }
+
+ /**
+ * Add a file selection row to the input window.
+ * @param pathToSequence
+ */
+ private void addSelectionRow(final String pathToSequence)
+ {
+ final SelectionRow sRow = new SelectionRow();
+ selectionRows.add(sRow);
+
+ JButton fileSelectionButton = new JButton("Select File...");
+ fileSelectionButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ StickyFileChooser fileChooser = new StickyFileChooser();
+ int status = fileChooser.showOpenDialog(null);
+ if(status == StickyFileChooser.CANCEL_OPTION)
+ return;
+ sRow.fileNameField.setText(fileChooser.getSelectedFile().getAbsolutePath());
+ FileSelectionPanel.this.repaint();
+ }
+ });
+
+ if(pathToSequence != null)
+ sRow.fileNameField.setText(pathToSequence);
+
+ c.gridy = row;
+ c.gridx = 0;
+ add(fileSelectionButton, c);
+ c.gridx = 1;
+ add(sRow.fileNameField, c);
+ row++;
+ if(f == null)
+ revalidate();
+ else
+ f.pack();
+ }
+
+ /**
+ * Open up the FileSelectionPanel in a JFrame
+ * @param f
+ * @param displayButtonListener
+ * @return
+ */
+ protected JButton showJFrame(final JFrame f,
+ ActionListener displayButtonListener)
+ {
+ this.f = f;
+ f.getContentPane().add(this, BorderLayout.CENTER);
+ JButton displayButton = new JButton("Display");
+ displayButton.addActionListener(displayButtonListener);
+ f.getContentPane().add(displayButton, BorderLayout.SOUTH);
+ f.pack();
+ Utilities.centreFrame(f);
+ f.setVisible(true);
+
+ return displayButton;
+ }
+
+ /**
+ * Get the comma separated enzyme list
+ * @return
+ */
+ protected String getEnzymes()
+ {
+ return enzymeField.getText();
+ }
+
+ protected List<File> getSequenceFiles()
+ {
+ List<File> sequenceFiles = new Vector<File>();
+ for(int i=0; i<selectionRows.size(); i++)
+ {
+ SelectionRow r = selectionRows.get(i);
+ if(!r.fileNameField.getText().equals(""))
+ sequenceFiles.add(new File(r.fileNameField.getText()));
+ }
+ return sequenceFiles;
+ }
+
+ protected List<File> getRestrictOutputs()
+ {
+ return restrictOutputs;
+ }
+
+ protected JTextField getEmbossRootField()
+ {
+ return embossRootField;
+ }
+
+ protected boolean isMethylation()
+ {
+ return methylationCheckBox.isSelected();
+ }
+
+ class SelectionRow
+ {
+ final JTextField fileNameField = new JTextField(30);
+
+ SelectionRow()
+ {
+ fileNameField.setPreferredSize(
+ new Dimension(200,fileNameField.getPreferredSize().height));
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/digest/FragmentBand.java b/uk/ac/sanger/artemis/circular/digest/FragmentBand.java
new file mode 100644
index 0000000..d61a8d0
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/digest/FragmentBand.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+
+class FragmentBand
+{
+ CutSite bandCutSite;
+ int genomeFragmentLength;
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/digest/InSilicoGelPanel.java b/uk/ac/sanger/artemis/circular/digest/InSilicoGelPanel.java
new file mode 100644
index 0000000..93fa744
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/digest/InSilicoGelPanel.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButtonMenuItem;
+
+import uk.ac.sanger.artemis.components.FileViewer;
+
+public class InSilicoGelPanel extends JPanel implements ActionListener
+{
+ private static final long serialVersionUID = 1L;
+ private int marginHeight = 50;
+ private int marginWidth = 55;
+ private int panelHeight;
+ private List<FragmentBand> genomeFragments = new Vector<FragmentBand>();
+ private static int MAX_FRAGMENT_LENGTH = 0;
+ private static int MIN_FRAGMENT_LENGTH = Integer.MAX_VALUE;
+ private boolean drawLog = false;
+ private JPopupMenu popup;
+ private File restrictOutput;
+
+ /**
+ * @param genomeLength
+ * @param cutSites
+ * @param panelHeight
+ * @param restrictOutput
+ */
+ public InSilicoGelPanel(final int genomeLength, final List<CutSite> cutSites,
+ final int panelHeight, final File restrictOutput,
+ final String name)
+ {
+ this.panelHeight = panelHeight;
+ this.restrictOutput = restrictOutput;
+ setToolTipText(name);
+
+ Integer len;
+ int firstSiteEnd = 0;
+ int lastSite = 0;
+ for (int i = 0; i < cutSites.size(); i++)
+ {
+ CutSite cutSite = cutSites.get(i);
+ if (i == 0)
+ firstSiteEnd = cutSite.getFivePrime() + 1;
+ else
+ {
+ len = cutSite.getFivePrime() - lastSite;
+
+ FragmentBand band = new FragmentBand();
+ band.genomeFragmentLength = len;
+ band.bandCutSite = cutSite;
+ genomeFragments.add(band);
+ if (len > MAX_FRAGMENT_LENGTH)
+ MAX_FRAGMENT_LENGTH = len;
+ if (len < MIN_FRAGMENT_LENGTH)
+ MIN_FRAGMENT_LENGTH = len;
+ }
+ lastSite = cutSite.getFivePrime();
+ }
+
+ len = genomeLength - lastSite + firstSiteEnd;
+ FragmentBand band = new FragmentBand();
+ band.genomeFragmentLength = len;
+
+ if(cutSites.size() > 0)
+ {
+ band.bandCutSite = cutSites.get(0);
+ genomeFragments.add(band);
+ }
+ if (len > MAX_FRAGMENT_LENGTH)
+ MAX_FRAGMENT_LENGTH = len;
+ // System.out.println(len.toString());
+
+ init();
+ }
+
+ public InSilicoGelPanel(final List<FragmentBand> genomeFragments,
+ final int panelHeight, final File restrictOutput,
+ final String name)
+ {
+ this.panelHeight = panelHeight;
+ this.restrictOutput = restrictOutput;
+ this.genomeFragments = genomeFragments;
+
+ setToolTipText(name);
+ for(int i=0; i<genomeFragments.size(); i++)
+ {
+ int len = genomeFragments.get(i).genomeFragmentLength;
+ if (len > MAX_FRAGMENT_LENGTH)
+ MAX_FRAGMENT_LENGTH = len;
+ if (len < MIN_FRAGMENT_LENGTH)
+ MIN_FRAGMENT_LENGTH = len;
+ }
+ init();
+ }
+
+ /**
+ * Initialise the panel
+ */
+ private void init()
+ {
+ setBackground(Color.white);
+ setPreferredSize(new Dimension(150, panelHeight));
+
+ MouseListener popupListener = new PopupListener();
+ addMouseListener(popupListener);
+
+ popup = new JPopupMenu();
+ JRadioButtonMenuItem linearScale = new JRadioButtonMenuItem("Linear scale");
+ popup.add(linearScale);
+ linearScale.addActionListener(this);
+ JRadioButtonMenuItem logScale = new JRadioButtonMenuItem("Log scale");
+ popup.add(logScale);
+ logScale.addActionListener(this);
+ ButtonGroup group = new ButtonGroup();
+ group.add(linearScale);
+ group.add(logScale);
+ linearScale.setSelected(true);
+
+ JMenuItem showCutSites = new JMenuItem("Show cut site details");
+ popup.add(showCutSites);
+ showCutSites.addActionListener(this);
+ }
+
+ /**
+ * Override
+ */
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+
+ Graphics2D g2D = (Graphics2D) g;
+ g2D.draw3DRect(marginWidth, (marginHeight / 2), marginWidth, panelHeight
+ - (marginHeight), true);
+
+ int gelHeight = (panelHeight - (2 * marginHeight));
+ g2D.setColor(Color.blue);
+
+ BasicStroke stroke = new BasicStroke(1.f);
+ BasicStroke stroke2 = new BasicStroke(2.f);
+ g2D.setStroke(stroke);
+
+ for (int i = 0; i < genomeFragments.size(); i++)
+ {
+ final int y = getYPosition(genomeFragments.get(i), gelHeight);
+
+ if ((genomeFragments.get(i).bandCutSite == null) ||
+ !genomeFragments.get(i).bandCutSite.isHighlighted())
+ {
+ g2D.setStroke(stroke);
+ g2D.setColor(Color.blue);
+ }
+ else
+ {
+ g2D.setColor(Color.black);
+ g2D.drawString(genomeFragments.get(i).bandCutSite.getEnzymeName(),
+ marginWidth + marginWidth + 2, y);
+ g2D.setStroke(stroke2);
+ g2D.setColor(Color.yellow);
+ }
+ g2D.drawLine(marginWidth, y, marginWidth + marginWidth, y);
+ }
+
+ drawScale(g2D, stroke, gelHeight);
+ }
+
+ private int getYPosition(final FragmentBand band, int gelHeight)
+ {
+ int fragmentLength = band.genomeFragmentLength;
+ final int y;
+ if (isDrawLog())
+ y = getLogValue(fragmentLength, marginHeight, gelHeight);
+ else
+ y = gelHeight
+ + marginHeight
+ - (int) (((float) (gelHeight) / (float) (MAX_FRAGMENT_LENGTH - MIN_FRAGMENT_LENGTH)) * fragmentLength);
+ return y;
+ }
+
+ /**
+ * Draw the fragment length scale
+ *
+ * @param g2D
+ * @param stroke
+ * @param gelHeight
+ */
+ private void drawScale(Graphics2D g2D, BasicStroke stroke, int gelHeight)
+ {
+ g2D.setColor(Color.black);
+ g2D.setStroke(stroke);
+ NumberFormat formatter = new DecimalFormat("#0.0");
+
+ int nscale = 8;
+ if (isDrawLog())
+ nscale = 5;
+
+ float range = (MAX_FRAGMENT_LENGTH - MIN_FRAGMENT_LENGTH) / (float) nscale;
+ for (int i = 0; i < nscale + 1; i++)
+ {
+ float length = MIN_FRAGMENT_LENGTH + (range * i);
+
+ int y;
+ if (isDrawLog())
+ y = getLogValue((int) length, marginHeight, gelHeight);
+ else
+ y = gelHeight
+ + marginHeight
+ - (int) (((float) (gelHeight) / (float) (MAX_FRAGMENT_LENGTH - MIN_FRAGMENT_LENGTH)) * length);
+
+ g2D.drawLine(marginWidth, y, marginWidth - 10, y);
+
+ g2D.drawString(formatter.format(length / 1000) + "kb", 0, y);
+ }
+ }
+
+ private static final double LOG10SCALE = 1.d / Math.log(10);
+
+ protected FragmentBand getBandAtLocation(Point loc)
+ {
+ int gelHeight = (panelHeight - (2 * marginHeight));
+ for (int i = 0; i < genomeFragments.size(); i++)
+ {
+ int y = getYPosition(genomeFragments.get(i), gelHeight);
+
+ if(loc.y == y)
+ return genomeFragments.get(i);
+ }
+ return null;
+ }
+
+ /**
+ * Get base 10 commons log
+ *
+ * @param val
+ * @return
+ */
+ private static double log10(double val)
+ {
+ return Math.log(val) * LOG10SCALE;
+ }
+
+ private int getLogValue(int val, int gelStart, int gelRange)
+ {
+ double log_low = log10(MIN_FRAGMENT_LENGTH);
+ double log_high = log10(MAX_FRAGMENT_LENGTH);
+ double log_val = log10(val);
+
+ double log_unit = ((double) gelRange) / (log_high - log_low);
+ return (int) ((double) (gelRange + gelStart) - ((log_val - log_low) * log_unit));
+ }
+
+ private boolean isDrawLog()
+ {
+ return drawLog;
+ }
+
+ private void setDrawLog(boolean drawLog)
+ {
+ this.drawLog = drawLog;
+ }
+
+ class PopupListener extends MouseAdapter
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if (e.isPopupTrigger())
+ popup.show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ public void actionPerformed(ActionEvent e)
+ {
+ if (e.getSource() instanceof JRadioButtonMenuItem)
+ {
+ JRadioButtonMenuItem radioButton = (JRadioButtonMenuItem) e.getSource();
+ if (radioButton.isSelected())
+ {
+ setDrawLog(radioButton.getText().startsWith("Log"));
+ repaint();
+ }
+ }
+ else
+ {
+ final FileViewer viewer = new FileViewer(restrictOutput.getName(), true,
+ false, false);
+ BufferedReader br;
+ try
+ {
+ br = new BufferedReader(new FileReader(restrictOutput));
+
+ StringBuffer buff = new StringBuffer();
+ String line;
+ while ((line = br.readLine()) != null)
+ buff.append(line + "\n");
+ viewer.getTextPane().setText(buff.toString());
+ }
+ catch (Exception e2)
+ {
+ e2.printStackTrace();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/circular/digest/Utils.java b/uk/ac/sanger/artemis/circular/digest/Utils.java
new file mode 100644
index 0000000..0618367
--- /dev/null
+++ b/uk/ac/sanger/artemis/circular/digest/Utils.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.circular.digest;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.circular.DNADraw;
+import uk.ac.sanger.artemis.circular.Track;
+import uk.ac.sanger.artemis.circular.TrackManager;
+import uk.ac.sanger.artemis.circular.Wizard;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JOptionPane;
+
+public class Utils
+{
+ //private static final Logger logger = Logger.getLogger(Utils.class);
+
+ protected static ReportDetails findCutSitesFromEmbossReport(Reader reader)
+ {
+ EmbossTableParser etp = new EmbossTableParser();
+ ReportDetails ret = new ReportDetails();
+ try
+ {
+ BufferedReader br = new BufferedReader(reader);
+ ret.cutSites = etp.parse(br);
+ ret.length = etp.getLength();
+ }
+ catch (IOException exp)
+ {
+ throw new RuntimeException("Couldn't read, or parse results");
+ }
+ return ret;
+ }
+
+ protected static List<FragmentBand> findCutSitesFromExperiment(Reader reader)
+ throws IOException
+ {
+ BufferedReader br = new BufferedReader(reader);
+ String line;
+
+ List<FragmentBand> list = new ArrayList<FragmentBand>();
+ while ((line = br.readLine()) != null)
+ {
+ FragmentBand band = new FragmentBand();
+ band.genomeFragmentLength = Integer.parseInt(line.trim());
+
+ list.add(band);
+ }
+ return list;
+ }
+
+ /**
+ * Creates the DNAPlotter components and adds a forward and reverse track for
+ * the restriction enzyme results.
+ *
+ * @param rd
+ * @param entryGroup
+ * @return
+ */
+ protected static DNADraw createDNADrawFromReportDetails(
+ final ReportDetails rd, final EntryGroup entryGroup)
+ {
+ DNADraw dna = new DNADraw();
+ dna.setArtemisEntryGroup(entryGroup);
+
+ int sequenceLength = rd.length;
+ Hashtable<String, Object> lineAttr = new Hashtable<String, Object>();
+ lineAttr.put("lsize", new Integer(1));
+ lineAttr.put("circular", new Boolean(true));
+ lineAttr.put("start", new Integer(0));
+ lineAttr.put("end", new Integer(sequenceLength));
+ dna.setLineAttributes(lineAttr);
+
+ // set ticks
+ int div;
+ if (sequenceLength < 1000)
+ div = 100;
+ else if (sequenceLength < 10000)
+ div = 1000;
+ else if (sequenceLength < 100000)
+ div = 10000;
+ else
+ div = 100000;
+ int tickNum = sequenceLength / div;
+ int tick = tickNum * (div / 10);
+
+ if(tick < 1)
+ tick = 2;
+ while ((sequenceLength % tick) < (div / 10))
+ {
+ tickNum++;
+ tick = tickNum * (div / 10);
+ }
+ dna.setMinorTickInterval(tick);
+ dna.setTickInterval(tick);
+ dna.setOpaque(true);
+
+ final Entry newEntry = dna.getArtemisEntryGroup().createEntry("RE");
+ newEntry.getEMBLEntry().setName("Restriction Sites");
+ Track forward = new Track(0.9, "misc_feature", true, false, newEntry);
+ forward.setQualifier("note");
+ forward.setQualifierValue("plus");
+ Track reverse = new Track(0.85, "misc_feature", true, false, newEntry);
+ reverse.setQualifier("note");
+ reverse.setQualifierValue("minus");
+ Track fileTrack = new Track(0.8, "CDS", true, true, entryGroup
+ .getSequenceEntry());
+
+ Wizard.tracks = new Track[3];
+ Wizard.tracks[0] = forward;
+ Wizard.tracks[1] = reverse;
+ Wizard.tracks[2] = fileTrack;
+
+ int counter = 0;
+ /*if (rd.cutSites.size() == 1)
+ {
+ CutSite cutSite = rd.cutSites.get(0);
+ // dna.addFeatureToTrack(createFeature(cutSite.getEnd(), rd.length
+ // + cutSite.getStart(), 1, sequenceLength), forward, false);
+ addFeature(cutSite.getFivePrime(), rd.length, counter, newEntry, dna,
+ cutSite.isForward());
+
+ dna.setGeneticMarker(new Vector());
+ TrackManager trackManager = dna.getTrackManager();
+ if (trackManager == null)
+ {
+ trackManager = new TrackManager(dna);
+ dna.setTrackManager(trackManager);
+ }
+ trackManager.update(Wizard.tracks);
+
+ return dna;
+ }*/
+
+ Iterator<CutSite> it = rd.cutSites.iterator();
+ CutSite firstCutSite = null;
+ int lastCutPos = 1;
+
+ while (it.hasNext())
+ {
+ CutSite cutSite = it.next();
+
+ if (counter == 0)
+ {
+ firstCutSite = cutSite;
+ lastCutPos = cutSite.getThreePrime();
+ counter++;
+ continue;
+ }
+
+ addFeature(lastCutPos, cutSite.getFivePrime(), counter, newEntry, dna,
+ cutSite.isForward());
+ lastCutPos = cutSite.getFivePrime();
+ counter++;
+ }
+
+ if (firstCutSite != null)
+ {
+ addFeature(lastCutPos, rd.length, -1, newEntry, dna, firstCutSite
+ .isForward());
+ addFeature(1, firstCutSite.getFivePrime(), -1, newEntry, dna,
+ firstCutSite.isForward());
+ }
+
+ dna.setGeneticMarker(new Vector());
+ TrackManager trackManager = dna.getTrackManager();
+ if (trackManager == null)
+ {
+ trackManager = new TrackManager(dna);
+ dna.setTrackManager(trackManager);
+ }
+ trackManager.update(Wizard.tracks);
+
+ return dna;
+ }
+
+ /**
+ * Add a new feature to the entry with the given coordinates
+ *
+ * @param coord1
+ * @param coord2
+ * @param counter
+ * @param newEntry
+ * @param dna
+ * @param isForward
+ */
+ private static void addFeature(int coord1, int coord2, int counter,
+ Entry entry, DNADraw dna, boolean isForward)
+ {
+ String colour;
+ if (counter < 0)
+ colour = "7";
+ else if (counter % 2 == 0)
+ colour = "2";
+ else
+ colour = "5";
+
+ try
+ {
+ QualifierVector qualifiers = new QualifierVector();
+ qualifiers.add(new Qualifier("colour", colour));
+ final MarkerRange r;
+
+ if (isForward)
+ qualifiers.add(new Qualifier("note", "plus"));
+ else
+ qualifiers.add(new Qualifier("note", "minus"));
+ // if(isForward)
+ r = new MarkerRange(dna.getArtemisEntryGroup().getSequenceEntry()
+ .getBases().getForwardStrand(), coord1, coord2);
+ /*
+ * else r = new MarkerRange(
+ * dna.getArtemisEntryGroup().getSequenceEntry().
+ * getBases().getReverseStrand(), coord1,coord2);
+ */
+
+ // int len = r.getCount();
+ uk.ac.sanger.artemis.io.Feature f = new uk.ac.sanger.artemis.io.EmblStreamFeature(
+ new Key("misc_feature"), r.createLocation(), qualifiers);
+ entry.add(new uk.ac.sanger.artemis.Feature(f), false);
+
+ // if(!isForward)
+ // System.out.println(counter+" "+coord1+".."+coord2+" "+f.getLocation().toStringShort());
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+}
+
+class ReportDetails
+{
+ int length;
+ List<CutSite> cutSites;
+}
diff --git a/uk/ac/sanger/artemis/components/ActMain.java b/uk/ac/sanger/artemis/components/ActMain.java
new file mode 100644
index 0000000..fabcbb5
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ActMain.java
@@ -0,0 +1,379 @@
+/* ActMain.java
+ *
+ * created: Wed May 10 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ActMain.java,v 1.17 2008-11-17 13:52:34 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.components.filetree.FileManager;
+import uk.ac.sanger.artemis.components.filetree.LocalAndRemoteFileManager;
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.components.database.DatabaseEntrySource;
+import uk.ac.sanger.artemis.components.database.DatabaseTreeNode;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.SimpleEntryInformation;
+
+import java.awt.event.*;
+import java.io.IOException;
+import javax.swing.JFrame;
+
+/**
+ * The main window for the Artemis Comparison Tool.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ActMain.java,v 1.17 2008-11-17 13:52:34 tjc Exp $
+ **/
+
+public class ActMain extends Splash
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ /** Version String use for banner messages and title bars. */
+ public static final String version = "Release 6";
+ /** File manager */
+ protected static FileManager filemanager = null;
+ private static DatabaseEntrySource dbEntrySource;
+
+ /**
+ * The constructor creates all the components for the main ACT window
+ * and sets up all the menu callbacks.
+ **/
+ public ActMain()
+ {
+ super("Artemis Comparison Tool", "ACT", version);
+
+ ActionListener open_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ makeOpenDialog();
+ }
+ };
+
+ makeMenuItem(file_menu, "Open ...", open_listener);
+
+ ActionListener quit_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ exit();
+ }
+ };
+
+ ActionListener menu_listener_ssh = new ActionListener()
+ {
+ private LocalAndRemoteFileManager fm;
+ public void actionPerformed(ActionEvent event)
+ {
+ if(fm == null)
+ fm = new LocalAndRemoteFileManager(ActMain.this);
+ else
+ fm.setVisible(true);
+ dbEntrySource = fm.getDatabaseEntrySource();
+ new ComparatorDialog(ActMain.this).setVisible(true);
+ }
+ };
+
+ if(System.getProperty("chado") != null)
+ makeMenuItem(file_menu, "Open Database and SSH File Manager ...", menu_listener_ssh);
+ else
+ makeMenuItem(file_menu, "Open SSH File Manager ...", menu_listener_ssh);
+
+/* final boolean sanger_options =
+ Options.getOptions().getPropertyTruthValue("sanger_options");
+
+ if(sanger_options)
+ {
+ ActionListener menu_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ launchDatabaseJFrame(true);
+ }
+ };
+
+ makeMenuItem(file_menu, "Database Entry ...", menu_listener);
+ if(System.getProperty("chado") != null)
+ launchDatabaseJFrame(false);
+ }*/
+
+ makeMenuItem(file_menu, "Quit", quit_listener);
+ }
+
+ /**
+ * Make a new Comparator component from the given files.
+ * @param frame The JFrame used when making a new MessageDialog.
+ * @param progress_listener The object to which InputStreamProgressEvents
+ * will be send while reading. Can be null.
+ * @param file_names Alternating sequence and comparison data file names.
+ * Must be >= 3. I there are an even number of file names the first
+ * file/sequence object will be added to the send of the display and the
+ * last comparison file will be assumed to be a a comparison between the
+ * last and first sequence files.
+ **/
+ public static boolean makeMultiComparator(final JFrame frame,
+ final InputStreamProgressListener progress_listener,
+ final Object[] file_names)
+ {
+ processJnlpAttributes();
+ final ProgressThread progress_thread = new ProgressThread(null,
+ "Loading Entry...");
+
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ progress_thread.start();
+ final EntryGroup[] entry_group_array =
+ new EntryGroup[file_names.length / 2 + 1];
+
+ final ComparisonData[] comparison_data_array =
+ new ComparisonData[file_names.length / 2];
+
+ for(int i = 0; i<file_names.length; i += 2)
+ {
+ final EntryInformation entry_information =
+ new SimpleEntryInformation(Options.getArtemisEntryInformation());
+
+ final Object this_file_name = file_names[i];
+ //File this_file = new File(this_file_name);
+
+ try
+ {
+ if(!openEntry(this_file_name, entry_group_array,
+ entry_information, i))
+ return null;
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(frame, "read failed: one of the features has an " +
+ "out of range location: " + e.getMessage());
+ return null;
+ }
+ }
+
+ // add the first entry at the end to make the MultiComparator
+ // circular(-ish)
+ if(file_names.length % 2 == 0)
+ entry_group_array[entry_group_array.length - 1] = entry_group_array[0];
+
+ try
+ {
+ for(int i = 1 ; i < file_names.length ; i += 2)
+ {
+ final String comparison_data_file_name = (String)file_names[i];
+ final Document comparison_data_document =
+ DocumentFactory.makeDocument(comparison_data_file_name);
+
+ comparison_data_array[i / 2] =
+ ComparisonDataFactory.readComparisonData(comparison_data_document);
+
+ final Bases prev_bases = entry_group_array[i/2].getBases();
+ final Bases next_bases = entry_group_array[i/2 + 1].getBases();
+
+ final ComparisonData swapped_comparison_data =
+ comparison_data_array[i / 2].flipMatchesIfNeeded(prev_bases,
+ next_bases);
+
+ if(swapped_comparison_data != null)
+ comparison_data_array[i / 2] = swapped_comparison_data;
+
+ if(swapped_comparison_data != null)
+ {
+ final MessageFrame message_frame =
+ new MessageFrame("note: hits from " + comparison_data_file_name +
+ " have been flipped to match the " +
+ "sequences");
+
+ message_frame.setVisible(true);
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(frame, "error while reading: " + e.getMessage());
+ return null;
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(frame, "comparison file read failed: " +
+ "out of range error: " + e.getMessage());
+ return null;
+ }
+
+ final MultiComparator comparator =
+ new MultiComparator(entry_group_array,
+ comparison_data_array,
+ progress_listener);
+
+ comparator.setVisible(true);
+ return null;
+ }
+
+ public void finished()
+ {
+ if(progress_thread !=null)
+ progress_thread.finished();
+ }
+
+ private boolean openEntry(Object this_file_name, EntryGroup[] entry_group_array,
+ final EntryInformation entry_information, int i)
+ throws OutOfRangeException
+ {
+ uk.ac.sanger.artemis.io.Entry embl_entry = null;
+ Entry entry = null;
+ // test if this is a database entry rather than a file
+ if(this_file_name instanceof DatabaseTreeNode)
+ {
+ DatabaseTreeNode dbNode = (DatabaseTreeNode)this_file_name;
+ try
+ {
+ entry = dbEntrySource.getEntry(dbNode.getFeatureId(),
+ dbNode.getUserName(), progress_listener);
+
+ boolean isMitochondrial = false;
+ if(dbNode.getFeatureType() != null &&
+ dbNode.getFeatureType().startsWith("mitochondrial_"))
+ isMitochondrial = true;
+ boolean readOnly = DatabaseTreeNode.setOrganismProps(
+ dbNode.getOrganism().getOrganismProps(), isMitochondrial);
+ embl_entry = (DatabaseDocumentEntry)entry.getEMBLEntry();
+ ((DatabaseDocumentEntry)embl_entry).setReadOnly(readOnly);
+ }
+ catch(NoSequenceException e)
+ {
+ e.printStackTrace();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ DatabaseDocument doc =
+ (DatabaseDocument)((DatabaseDocumentEntry)embl_entry).getDocument();
+ doc.setName((String)dbNode.getUserObject());
+ }
+ else
+ {
+ final Document entry_document =
+ DocumentFactory.makeDocument((String)this_file_name);
+
+ if(progress_listener != null)
+ entry_document.addInputStreamProgressListener(progress_listener);
+
+ embl_entry =
+ EntryFileDialog.getEntryFromFile(frame, entry_document,
+ entry_information,
+ false);
+ }
+
+ // getEntryFromFile() has alerted the user so we just need to quit
+ if(embl_entry == null)
+ return false;
+
+ final uk.ac.sanger.artemis.io.Sequence sequence =
+ embl_entry.getSequence();
+
+ if(sequence == null)
+ {
+ new MessageDialog(frame, "This file contains no sequence: " +
+ (String)this_file_name);
+ return false;
+ }
+
+ if(entry == null)
+ {
+ final Bases embl_bases = new Bases(sequence);
+ entry = new Entry(embl_bases, embl_entry);
+ }
+
+ EntryGroup entry_group = new SimpleEntryGroup(entry.getBases());
+
+ entry_group.add(entry);
+ entry_group_array[i / 2] = entry_group;
+ return true;
+ }
+ };
+ entryWorker.start();
+ return true;
+ }
+
+
+ /**
+ * Create a dialog that allow the user to the choose two files to compare
+ * and a file containing comparison data.
+ **/
+ private void makeOpenDialog()
+ {
+ if(filemanager == null)
+ filemanager = new FileManager(this,null);
+ else
+ filemanager.setVisible(true);
+ new ComparatorDialog(this).setVisible(true);
+ }
+
+ /**
+ * Exit from ACT.
+ **/
+ protected void exit()
+ {
+ System.exit(0);
+ }
+
+ /**
+ * Main entry point for ACT
+ **/
+ public static void main(final String [] args)
+ {
+ final ActMain main_window = new ActMain();
+ main_window.setVisible(true);
+
+ final InputStreamProgressListener progress_listener =
+ main_window.getInputStreamProgressListener();
+
+ if(args.length >= 3)
+ ActMain.makeMultiComparator(main_window, progress_listener,
+ args);
+ else
+ {
+ if(args.length != 0)
+ {
+ System.err.println("Error - this program needs either no " +
+ " arguments or an odd number\n" +
+ "(3 or more):");
+ System.err.println(" act sequence_1 comparison_data sequence_2");
+ System.err.println("or");
+ System.err.println(" act seq_1 comparison_data_2_v_1 seq_2 comparison_data_3_v_2 seq_3");
+ System.err.println("or");
+ System.err.println(" act");
+ System.exit(1);
+ }
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/ActPanelResizer.java b/uk/ac/sanger/artemis/components/ActPanelResizer.java
new file mode 100644
index 0000000..cb25077
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ActPanelResizer.java
@@ -0,0 +1,181 @@
+/*
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+
+import uk.ac.sanger.artemis.circular.TextFieldFloat;
+
+/**
+ * Enable fine tuning of the heights of the individual panels
+ * (plots, BAM view, VCF view, comparisons).
+ */
+class ActPanelResizer extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private GridBagConstraints c = new GridBagConstraints();
+
+ ActPanelResizer(final MultiComparator comparator, final GridBagLayout layout)
+ {
+ setTitle("Panel Height Weights");
+ final JPanel panel = (JPanel) getContentPane();
+ panel.setLayout(new GridBagLayout());
+ c.anchor = GridBagConstraints.WEST;
+
+ for(int i = 0 ; i < comparator.getEntryGroupArray().length ; i++)
+ showWeighty(comparator, layout, i);
+
+ c.gridx = 0;
+ c.gridy+=1;
+ final JButton close = new JButton("CLOSE");
+ close.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ dispose();
+ }
+ });
+ getContentPane().add(close, c);
+
+ pack();
+ Utilities.centreFrame(this);
+ setVisible(true);
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ }
+
+ /**
+ * Display the panel height weighting for an entry
+ * @param comp
+ * @param layout
+ * @param idx - entry index
+ */
+ private void showWeighty(final MultiComparator comp, final GridBagLayout layout, int idx)
+ {
+ if(idx > 0)
+ addSeparator();
+
+ c.gridx = 0;
+ c.gridy+=1;
+ final JLabel l = new JLabel("SEQUENCE "+(idx+1));
+ l.setFont(l.getFont().deriveFont(Font.BOLD));
+ getContentPane().add(l, c);
+
+ // plots
+ if(comp.getBasePlotGroupArray()[idx].getVisibleCount() > 0)
+ showComponentWgtY(comp, layout, comp.getBasePlotGroupArray()[idx], "Plots");
+
+ // bams
+ if(comp.getBamPanelArray()[idx].isVisible())
+ showComponentWgtY(comp, layout, comp.getBamPanelArray()[idx], "BAM view");
+
+ // vcfs
+ if(comp.getVcfPanelArray()[idx].isVisible())
+ showComponentWgtY(comp, layout, comp.getVcfPanelArray()[idx], "VCF view");
+
+ if (idx < comp.getAlignmentViewerArray().length )
+ {
+ addSeparator();
+ // comparison panel
+ showComponentWgtY(comp, layout, comp.getAlignmentViewerArray()[idx], "Comparison");
+ }
+ }
+
+
+ /**
+ * Display the panel height weight for a component
+ * @param comparator
+ * @param layout
+ * @param component
+ * @param label
+ */
+ private void showComponentWgtY(final MultiComparator comparator,
+ final GridBagLayout layout,
+ final JComponent component,
+ final String label)
+ {
+ c.gridx = 0;
+ c.gridy+=1;
+
+ getContentPane().add(new JLabel(label), c);
+ final Dimension d = new Dimension(80,25);
+ final GridBagConstraints cc = layout.getConstraints(component);
+ final TextFieldFloat wt = new TextFieldFloat();
+ final JSlider slider = new JSlider(0, 10, (int) (cc.weighty * 10));
+ slider.setToolTipText("set the y-weight to between 0 - 1");
+ wt.setPreferredSize(d);
+ wt.setMaximumSize(d);
+ wt.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ slider.setValue((int) (wt.getValue()*10));
+ cc.weighty = wt.getValue();
+ layout.setConstraints(component, cc);
+ component.revalidate();
+ comparator.validate();
+ comparator.repaint();
+ }
+ });
+
+ slider.addChangeListener(new javax.swing.event.ChangeListener()
+ {
+ public void stateChanged(ChangeEvent e)
+ {
+ double y = slider.getValue() / 10.d;
+ wt.setValue(y);
+ cc.weighty = y;
+ layout.setConstraints(component, cc);
+ component.revalidate();
+ comparator.validate();
+ comparator.repaint();
+ }
+ });
+
+ c.gridx+=1;
+ getContentPane().add(slider, c);
+ wt.setValue(cc.weighty);
+ c.gridx+=1;
+ getContentPane().add(wt, c);
+ }
+
+ /**
+ * Add a separator between rows
+ */
+ private void addSeparator()
+ {
+ c.gridx = 1;
+ c.gridy+=1;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ getContentPane().add(new JSeparator(), c);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/AddMenu.java b/uk/ac/sanger/artemis/components/AddMenu.java
new file mode 100644
index 0000000..0c7fcca
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/AddMenu.java
@@ -0,0 +1,1793 @@
+/* AddMenu.java
+ *
+ * created: Tue Dec 29 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/AddMenu.java,v 1.42 2009-06-01 09:49:07 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+import uk.ac.sanger.artemis.plot.CodonUsageAlgorithm;
+
+import uk.ac.sanger.artemis.sequence.BasePattern;
+import uk.ac.sanger.artemis.sequence.BasePatternFormatException;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.sequence.MarkerRangeVector;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.LocationParseException;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+
+import java.awt.Cursor;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.Comparator;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.regex.Pattern;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+
+
+/**
+ * A Menu with commands that add new features/entries to an EntryGroup. This
+ * should have been called CreateMenu.
+ *
+ * @author Kim Rutherford
+ * @version $Id: AddMenu.java,v 1.42 2009-06-01 09:49:07 tjc Exp $
+ **/
+public class AddMenu extends SelectionMenu
+{
+
+ private static final long serialVersionUID = 1L;
+
+ /** The GotoEventSource object that was passed to the constructor. */
+ private GotoEventSource goto_event_source = null;
+
+ /** The EntryGroup object that was passed to the constructor. */
+ private EntryGroup entry_group;
+
+ private BasePlotGroup base_plot_group;
+
+ /**
+ * The shortcut for "Create From Base Range".
+ **/
+ final static KeyStroke CREATE_FROM_BASE_RANGE_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_C,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ final static public int CREATE_FROM_BASE_RANGE_KEY_CODE = KeyEvent.VK_C;
+
+ /** busy cursor */
+ private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ /** done cursor */
+ private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+
+ /*private AlignmentViewer alignQueryViewer;
+ private AlignmentViewer alignSubjectViewer;*/
+
+ /**
+ * Create a new AddMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group The EntryGroup object where new features/entries will
+ * be added.
+ * @param goto_event_source The object the we will call makeBaseVisible ()
+ * on.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ * @param base_plot_group The AlignmentViewer associated with this JMenu
+ *
+ * @param menu_name The name of the new menu.
+ **/
+ public AddMenu (final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group,
+ final String menu_name)
+ {
+ this(frame,selection,entry_group,
+ goto_event_source,base_plot_group,null,null,menu_name);
+ }
+
+ /**
+ * Create a new AddMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group The EntryGroup object where new features/entries will
+ * be added.
+ * @param goto_event_source The object the we will call makeBaseVisible ()
+ * on.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ * @param menu_name The name of the new menu.
+ **/
+ public AddMenu(final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group,
+ final AlignmentViewer alignQueryViewer,
+ final AlignmentViewer alignSubjectViewer,
+ final String menu_name)
+ {
+ super (frame, menu_name, selection);
+
+ /*this.alignQueryViewer = alignQueryViewer;
+ this.alignSubjectViewer = alignSubjectViewer;*/
+ this.entry_group = entry_group;
+ this.base_plot_group = base_plot_group;
+
+ final JMenuItem new_feature_item = new JMenuItem ("New Feature");
+ new_feature_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ makeNewFeature ();
+ }
+ });
+
+ add (new_feature_item);
+
+
+ final JMenuItem create_feature_from_range_item =
+ new JMenuItem ("Feature From Base Range");
+
+ if(!GeneUtils.isDatabaseEntry(entry_group))
+ create_feature_from_range_item.setAccelerator (CREATE_FROM_BASE_RANGE_KEY);
+ create_feature_from_range_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ createFeatureFromBaseRange (getParentFrame (), getSelection (),
+ entry_group, getGotoEventSource ());
+ }
+ });
+ add (create_feature_from_range_item);
+
+ final JMenuItem create_gene_model_from_range_item = new JMenuItem(
+ "Gene Model From Base Range");
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ create_gene_model_from_range_item.setAccelerator(CREATE_FROM_BASE_RANGE_KEY);
+ create_gene_model_from_range_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ final Entry default_entry = entry_group.getDefaultEntry();
+ if(default_entry == null)
+ {
+ new MessageDialog(frame, "There is no default entry.");
+ return;
+ }
+ final uk.ac.sanger.artemis.io.Entry entry = default_entry.getEMBLEntry();
+ if( !(entry instanceof GFFDocumentEntry || entry instanceof DatabaseDocumentEntry) )
+ {
+ new MessageDialog(frame,
+ "Expecting a GFF entry. The default entry "+
+ default_entry.getName()+" does not support this.");
+ return;
+ }
+ entry_group.getActionController ().startAction ();
+ GeneUtils.createGeneModel(getParentFrame(), getSelection(),
+ entry_group, getGotoEventSource());
+ entry_group.getActionController ().endAction ();
+ }
+ });
+ add (create_gene_model_from_range_item);
+
+ if(alignQueryViewer != null || alignSubjectViewer != null)
+ {
+ JMenuItem create_difference_feature =
+ new JMenuItem("Features From Non-matching Regions");
+ create_difference_feature.addActionListener(new ActionListener()
+ {
+ public void actionPerformed (ActionEvent event)
+ {
+ frame.setCursor(cbusy);
+ Vector diffs = null;
+ String comparisonNote = "";
+ if(alignQueryViewer == null || alignSubjectViewer == null)
+ {
+ final Entry sequence_entry;
+ if(alignQueryViewer != null)
+ {
+ diffs = alignQueryViewer.getDifferenceCoords(false);
+ sequence_entry = alignQueryViewer.getSubjectEntryGroup().getSequenceEntry();
+ }
+ else
+ {
+ diffs = alignSubjectViewer.getDifferenceCoords(true);
+ sequence_entry = alignSubjectViewer.getQueryEntryGroup().getSequenceEntry();
+ }
+
+ comparisonNote = comparisonNote + sequence_entry.getName();
+ }
+ else // multi-comparison
+ {
+ Vector diffs1 = alignQueryViewer.getDifferenceCoords(false);
+ Vector diffs2 = alignSubjectViewer.getDifferenceCoords(true);
+
+ Entry sequence_entry;
+ sequence_entry = alignQueryViewer.getSubjectEntryGroup().getSequenceEntry();
+ comparisonNote = comparisonNote + sequence_entry.getName();
+
+ sequence_entry = alignSubjectViewer.getQueryEntryGroup().getSequenceEntry();
+ comparisonNote = comparisonNote + " and " + sequence_entry.getName();
+
+ diffs = union(diffs1,diffs2);
+ }
+
+ createFeatures(diffs, frame, comparisonNote);
+ frame.setCursor(cdone);
+ }
+ });
+
+ add (create_difference_feature);
+ }
+
+ final JMenuItem create_intron_features_item =
+ new JMenuItem ("Intron Features");
+ create_intron_features_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ createIntronFeatures (getParentFrame (), getSelection (),
+ entry_group);
+ }
+ });
+
+ add (create_intron_features_item);
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ create_intron_features_item.setEnabled(false);
+
+ final JMenuItem create_intergenic_features_item =
+ new JMenuItem ("Intergenic Features");
+ create_intergenic_features_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ createIntergenicFeatures(getParentFrame (), entry_group);
+ }
+ });
+ add (create_intergenic_features_item);
+
+ final JMenuItem create_exon_features_item =
+ new JMenuItem ("Exon Features");
+ create_exon_features_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ createExonFeatures (getParentFrame (), getSelection (),
+ entry_group);
+ }
+ });
+
+ add (create_exon_features_item);
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ create_exon_features_item.setEnabled(false);
+
+ final JMenuItem create_gene_features_item =
+ new JMenuItem ("Gene Features");
+ create_gene_features_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ createGeneFeatures (getParentFrame (), getSelection (),
+ entry_group);
+ }
+ });
+
+ add (create_gene_features_item);
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ create_gene_features_item.setEnabled(false);
+
+ addSeparator ();
+
+ final JMenuItem new_entry_item = new JMenuItem ("New Entry");
+ new_entry_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ makeNewEntry ();
+ }
+ });
+
+ add (new_entry_item);
+
+ addSeparator ();
+
+ final JMenuItem mark_orfs_with_size_item = new JMenuItem ("Mark Open Reading Frames ...");
+
+ mark_orfs_with_size_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ markORFSWithSize (false, frame);
+ }
+ });
+
+ add (mark_orfs_with_size_item);
+
+
+ final JMenuItem mark_empty_orfs_with_size_item = new JMenuItem ("Mark Empty ORFs ...");
+
+ mark_empty_orfs_with_size_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ markORFSWithSize (true, frame);
+ }
+ });
+
+ add (mark_empty_orfs_with_size_item);
+
+
+ final JMenuItem mark_orfs_range_item = new JMenuItem ("Mark ORFs In Range ...");
+ mark_orfs_range_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ markOpenReadingFramesInRange ();
+ }
+ });
+
+ add (mark_orfs_range_item);
+
+
+ final JMenuItem mark_pattern_item = new JMenuItem ("Mark From Pattern ...");
+ mark_pattern_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ makeFeaturesFromPattern ();
+ }
+ });
+
+ add (mark_pattern_item);
+
+ final JMenuItem mark_ambiguities_item = new JMenuItem ("Mark Ambiguities");
+ mark_ambiguities_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ markAmbiguities ();
+ }
+ });
+
+ add (mark_ambiguities_item);
+ }
+
+ /**
+ *
+ * Find the union of coordinates in two Vecor objects.
+ * @param v1 Vector of Integer coordinates
+ * @param v2 Vector of Integer coordinates
+ *
+ */
+ protected static Vector union(final Vector v1, final Vector v2)
+ {
+ final Vector union = new Vector();
+
+ for(int i=0; i<v1.size(); i++)
+ {
+ Integer[] imatch = (Integer[])v1.get(i);
+ int istart = imatch[0].intValue();
+ int iend = imatch[1].intValue();
+
+ for(int j=0; j<v2.size(); j++)
+ {
+ Integer[] jmatch = (Integer[])v2.get(j);
+ int jstart = jmatch[0].intValue();
+ int jend = jmatch[1].intValue();
+
+ if( (istart >= jstart && istart <= jend) ||
+ (iend >= jstart && iend <= jend) ||
+ (jstart > istart && jend < iend) )
+ {
+ if(jstart > istart)
+ istart = jstart;
+
+ if(iend < jend)
+ jend = iend;
+
+ final Integer coords[] = new Integer[2];
+ coords[0] = new Integer(istart);
+ coords[1] = new Integer(jend);
+ union.add(coords);
+ }
+ }
+ }
+
+ return union;
+ }
+
+ /**
+ *
+ * Create features from Vector of coordinates.
+ * @param diffs Vector of coordinates to create feature from.
+ * @param frame The JFrame that owns this JMenu.
+ *
+ */
+ private void createFeatures(Vector diffs, JFrame frame, String name)
+ {
+ Enumeration eDiffs = diffs.elements();
+ while(eDiffs.hasMoreElements())
+ {
+ Integer coords[] = (Integer[])eDiffs.nextElement();
+ int start = coords[0].intValue();
+ int end = coords[1].intValue();
+// System.out.println(start+" "+end);
+
+ final Entry default_entry = entry_group.getDefaultEntry();
+ if(default_entry == null)
+ {
+ new MessageDialog(frame, "There is no default entry");
+ return;
+ }
+
+ Location loc = null;
+ Feature temp_feature;
+ try
+ {
+ loc = new Location(start+".."+end);
+ Key misc_feature = new Key("misc_feature");
+ temp_feature = default_entry.createFeature(misc_feature, loc);
+ Qualifier note = new Qualifier("note",
+ "Automatically generated region of difference with "+
+ name);
+ temp_feature.setQualifier(note);
+ }
+ catch(EntryInformationException e)
+ {
+ // use the default key instead
+ final Key default_key =
+ default_entry.getEntryInformation().getDefaultKey();
+
+ try
+ {
+ temp_feature =
+ default_entry.createFeature(default_key, loc);
+ }
+ catch(EntryInformationException einfo)
+ {
+ throw new Error("internal error - unexpected exception: " + einfo);
+ }
+ catch(ReadOnlyException eRead)
+ {
+ new MessageDialog(frame, "feature not created: " +
+ "the default entry is read only");
+ }
+ catch(OutOfRangeException eout)
+ {
+ throw new Error("internal error - unexpected exception: " + eout);
+ }
+
+ }
+ catch(ReadOnlyException e)
+ {
+ new MessageDialog(frame, "feature not created: " +
+ "the default entry is read only");
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(LocationParseException lpe)
+ {
+ throw new Error("internal error - unexpected exception: " + lpe);
+ }
+ }
+ }
+
+ /**
+ * Create a new AddMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group The EntryGroup object where new features/entries will
+ * be added.
+ * @param goto_event_source The object the we will call makeBaseVisible ()
+ * on.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ public AddMenu (final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group) {
+ this (frame, selection, entry_group,
+ goto_event_source, base_plot_group, "Create");
+ }
+
+ /**
+ * Create a new feature in the default Entry of the entry group. See
+ * EntryGroup.createFeature () for details.
+ **/
+ private void makeNewFeature () {
+ if (entry_group.size () > 0) {
+ if (entry_group.getDefaultEntry () == null) {
+ new MessageDialog (getParentFrame (), "There is no default entry");
+ } else {
+
+ try {
+ entry_group.getActionController ().startAction ();
+
+ final Feature new_feature = entry_group.createFeature ();
+
+
+ final JFrame edit_frame = new JFrame("Artemis Feature Edit: " +
+ new_feature.getIDString() +
+ (new_feature.isReadOnly() ?
+ " - (read only)" :
+ ""));
+
+ final FeatureEdit feature_edit = new FeatureEdit(new_feature, entry_group,
+ getSelection(), getGotoEventSource(), edit_frame);
+
+ edit_frame.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ feature_edit.stopListening();
+ edit_frame.dispose();
+ }
+ });
+
+ edit_frame.getContentPane().add(feature_edit);
+ edit_frame.pack();
+
+ //final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ //edit_frame.setLocation(new Point((screen.width - edit_frame.getSize().width)/2,
+ // (screen.height - edit_frame.getSize().height)/2));
+ Utilities.centreFrame(edit_frame);
+
+ final ActionListener cancel_listener =
+ new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ try {
+ new_feature.removeFromEntry ();
+ } catch (ReadOnlyException exception) {
+ throw new Error ("internal error - unexpected exception: " +
+ exception);
+ }
+ }
+ };
+
+ feature_edit.addCancelActionListener (cancel_listener);
+
+ feature_edit.addApplyActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ // after apply is pressed cancel should not remove the new
+ // feature
+ feature_edit.removeCancelActionListener (cancel_listener);
+ }
+ });
+
+ edit_frame.setVisible(true);
+ } catch (ReadOnlyException e) {
+ new MessageDialog (getParentFrame (), "feature not created: " +
+ "the default entry is read only");
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+ } else {
+ new MessageDialog (getParentFrame (),
+ "Cannot make a feature without an existing entry");
+ }
+ }
+
+ /**
+ * Create a new Entry in the first Entry of the entry group.
+ **/
+ private void makeNewEntry () {
+ entry_group.createEntry ();
+ }
+
+ /**
+ * Create a new Feature in entry_group from the selected range of bases and
+ * then display a FeatureEdit component for the new Feature.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection containing the sequence to create the
+ * feature from.
+ * @param entry_group The EntryGroup to create the feature in.
+ * @param goto_event_source Needed to create a FeatureEdit component.
+ **/
+ static void createFeatureFromBaseRange (final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource
+ goto_event_source) {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ if (!checkForSelectionRange (frame, selection)) {
+ return;
+ }
+
+ final MarkerRange range = selection.getMarkerRange ();
+ final Entry default_entry = entry_group.getDefaultEntry ();
+
+ if (default_entry == null) {
+ new MessageDialog (frame, "There is no default entry");
+ return;
+ } else if(default_entry.isReadOnly()) {
+ new MessageDialog (frame, "The default entry is read only");
+ return;
+ }
+
+
+ try {
+ final Location new_location = range.createLocation ();
+
+ /*final*/ Feature temp_feature;
+ QualifierVector qualifiers = null;
+ final boolean isDatabaseEntry = GeneUtils.isDatabaseEntry(entry_group);
+
+ if(isDatabaseEntry)
+ {
+ String uniquename = GeneUtils.promptForUniquename(entry_group,
+ range.isForwardMarker(), range.getRawRange());
+ Qualifier qualifier = new Qualifier("ID", uniquename);
+ qualifiers = new QualifierVector();
+ qualifiers.add(qualifier);
+ }
+
+ try
+ {
+ final Key key;
+ if(isDatabaseEntry)
+ key = new Key("region");
+ else
+ key = Key.CDS;
+
+ if(qualifiers == null)
+ temp_feature = default_entry.createFeature (key, new_location);
+ else
+ temp_feature = default_entry.createFeature (key, new_location, qualifiers);
+
+ /*if(isDatabaseEntry)
+ {
+ final ChadoCanonicalGene chado_gene = new ChadoCanonicalGene();
+ chado_gene.setGene(temp_feature.getEmblFeature());
+ ((uk.ac.sanger.artemis.io.GFFStreamFeature)
+ (temp_feature.getEmblFeature())).setChadoGene(chado_gene);
+ }*/
+ }
+ catch (EntryInformationException e)
+ {
+ // use the default key instead
+
+ final Key default_key =
+ default_entry.getEntryInformation ().getDefaultKey ();
+
+ try
+ {
+ if(qualifiers == null)
+ temp_feature =
+ default_entry.createFeature (default_key, new_location);
+ else
+ temp_feature =
+ default_entry.createFeature (default_key, new_location, qualifiers);
+ }
+ catch (EntryInformationException ex)
+ {
+ throw new Error ("internal error - unexpected exception: " + ex);
+ }
+ }
+
+ final Feature new_feature = temp_feature;
+
+ selection.setMarkerRange (null);
+ selection.set (new_feature);
+
+ final ActionListener cancel_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ try
+ {
+ new_feature.removeFromEntry ();
+ selection.setMarkerRange(range);
+ }
+ catch (ReadOnlyException exception)
+ {
+ throw new Error("internal error - unexpected exception: " +
+ exception);
+ }
+ }
+ };
+
+ EditMenu.editSelectedFeatures(entry_group, selection, goto_event_source,
+ new_feature, cancel_listener, null);
+
+ } catch (ReadOnlyException e) {
+ new MessageDialog (frame, "feature not created: " +
+ "the default entry is read only");
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Create a new intron between each pair of exons in the selected CDS
+ * features. The introns are created in the Entry that contains the CDSs.
+ * @param frame The Frame to use for MessageDialog components.
+ * @param selection The Selection containing the CDS features to create the
+ * introns for.
+ * @param entry_group The EntryGroup to create the features in.
+ **/
+ static void createIntronFeatures (final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group) {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ if (!checkForSelectionFeatures (frame, selection)) {
+ return;
+ }
+
+ final FeatureVector selected_features = selection.getAllFeatures ();
+
+ for (int feature_index = 0 ;
+ feature_index < selected_features.size () ;
+ ++feature_index) {
+
+ final Feature selection_feature =
+ selected_features.elementAt (feature_index);
+
+ if (!(selection_feature.isProteinFeature () ||
+ selection_feature.getKey().equals("5'UTR") ||
+ selection_feature.getKey().equals("3'UTR"))) {
+ continue;
+ }
+
+ final Location cds_location = selection_feature.getLocation ();
+
+ final RangeVector cds_ranges = cds_location.getRanges ();
+
+ if (cds_ranges.size () < 2) {
+ continue;
+ }
+
+ if (cds_location.isComplement ()) {
+ cds_ranges.reverse ();
+ }
+
+ int select = 0;
+ for (int range_index = 0 ;
+ range_index < cds_ranges.size () - 1 ;
+ ++range_index) {
+ final int end_of_range_1 =
+ ((Range)cds_ranges.elementAt(range_index)).getEnd ();
+ final int start_of_range_2 =
+ ((Range)cds_ranges.elementAt(range_index + 1)).getStart ();
+
+ if (end_of_range_1 > start_of_range_2) {
+ // ignore - the exons overlap so there is no room for an intron
+ continue;
+ }
+
+ Range new_range = null;
+
+ try {
+ new_range = new Range (end_of_range_1 + 1,
+ start_of_range_2 - 1);
+ } catch (OutOfRangeException e) {
+ Object[] options = { "CANCEL", "IGNORE", "IGNORE ALL"};
+
+ if(select != 2)
+ {
+ select = JOptionPane.showOptionDialog(null,
+ "Found overlapping CDS\n"+e,
+ "Out of Range",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.WARNING_MESSAGE,
+ null,
+ options,
+ options[0]);
+ if(select == 0)
+ throw new Error ("internal error - unexpected exception: " + e);
+
+ continue;
+ }
+ }
+
+ final RangeVector intron_ranges = new RangeVector ();
+
+ intron_ranges.add (new_range);
+
+ final Key intron_key = new Key ("intron");
+ final Location intron_location =
+ new Location (intron_ranges, cds_location.isComplement ());
+ final QualifierVector qualifiers = new QualifierVector ();
+
+ try {
+ StringVector sysNames = Options.getOptions().getSystematicQualifierNames();
+ for(int i=0; i<sysNames.size(); i++) {
+ Qualifier qual = selection_feature.getQualifierByName((String) sysNames.get(i));
+ if(qual != null && qual.getValues() != null && qual.getValues().size() > 0) {
+ qualifiers.addQualifierValues(qual);
+ break;
+ }
+ }
+ } catch (InvalidRelationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ try {
+ selection_feature.getEntry ().createFeature (intron_key,
+ intron_location,
+ qualifiers);
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ Object[] options = { "CANCEL", "IGNORE", "IGNORE ALL"};
+
+ if(select != 2)
+ {
+ select = JOptionPane.showOptionDialog(null,
+ "Found overlapping CDS\n"+e,
+ "Out of Range",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.WARNING_MESSAGE,
+ null,
+ options,
+ options[0]);
+ if(select == 0)
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+
+ /**
+ * Create intergenic regions between CDS.
+ * @param frame The Frame to use for MessageDialog components.
+ * @param entry_group The EntryGroup to create the features in.
+ **/
+ static void createIntergenicFeatures (final JFrame frame,
+ final EntryGroup entry_group)
+ {
+ try
+ {
+ entry_group.getActionController ().startAction ();
+
+ final FeaturePredicate predicate;
+
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ predicate = new FeatureKeyPredicate(new Key("gene"));
+ else
+ {
+ final FeaturePredicateVector temp_predicates =
+ new FeaturePredicateVector();
+
+ temp_predicates.add(new FeatureKeyPredicate(Key.CDS));
+ temp_predicates.add(new FeatureKeyPredicate(new Key("tRNA")));
+
+ predicate =
+ new FeaturePredicateConjunction(temp_predicates,
+ FeaturePredicateConjunction.OR);
+ }
+
+ FeatureVector cdsFeatures = new FeatureVector ();
+ final FeatureEnumeration feature_enum = entry_group.features ();
+ while (feature_enum.hasMoreFeatures ())
+ {
+ final Feature current_feature = feature_enum.nextFeature ();
+ if(predicate.testPredicate (current_feature))
+ cdsFeatures.add (current_feature);
+ }
+
+ cdsFeatures = cdsFeatures.sort(feature_comparator);
+
+ int prevEnd = 0;
+ Entry newEntry = null;
+ boolean prevForward = true;
+
+ // search for intergenic regions
+ for(int i=0; i < cdsFeatures.size (); i++)
+ {
+ final Feature this_feature = cdsFeatures.elementAt(i);
+ final Location cds_location = this_feature.getLocation();
+ final Range r = cds_location.getTotalRange();
+
+ int currentStart = r.getStart();
+
+ if(i==0 && r.getStart()==1)
+ {
+ prevEnd = r.getEnd();
+ prevForward = this_feature.isForwardFeature();
+ // check for overlapping CDS
+ if(i<cdsFeatures.size()-1)
+ {
+ int next = i+1;
+ while(getTotalRange((Feature)cdsFeatures.elementAt(next)).getStart() <= prevEnd+1)
+ {
+ Feature f = (Feature)cdsFeatures.elementAt(next);
+
+ if(prevEnd < getTotalRange(f).getEnd())
+ {
+ prevEnd = getTotalRange(f).getEnd();
+ prevForward = f.isForwardFeature();
+ }
+
+ i = next;
+ next++;
+ }
+ }
+ continue;
+ }
+
+ try
+ {
+ Range new_range = new Range(prevEnd + 1,
+ currentStart - 1);
+ Location location = new Location(new_range);
+ final Key key;
+
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ key = new Key ("region");
+ else
+ key = new Key ("misc_feature");
+
+
+ // intergenic regions (IGR) - flanking CDS 4 possible:
+ // IGR-F (forward): cds> IGR cds>
+ // IGR-R (reverse): <cds IGR <cds
+ // IGR-B (both): <cds IGR cds>
+ // IGR-X: cds> IGR <cds
+ String note;
+
+ if(this_feature.isForwardFeature())
+ {
+ if(prevForward)
+ note = "IGR-F";
+ else
+ note = "IGR-B";
+ }
+ else
+ {
+ if(prevForward)
+ note = "IGR-X";
+ else
+ note = "IGR-R";
+ }
+ final QualifierVector qualifiers = new QualifierVector ();
+ final Qualifier qualifier = new Qualifier("note", note);
+ qualifiers.add(qualifier);
+
+ if(newEntry == null)
+ newEntry = entry_group.createEntry("intergenic");
+
+ newEntry.createFeature(key, location, qualifiers);
+ prevEnd = r.getEnd();
+ prevForward = this_feature.isForwardFeature();
+
+ // check for overlapping CDS
+ if(i<cdsFeatures.size()-1)
+ {
+ int next = i+1;
+ while( next < cdsFeatures.size() &&
+ getTotalRange((Feature)cdsFeatures.elementAt(next)).getStart() <= prevEnd+1)
+ {
+ Feature f = (Feature)cdsFeatures.elementAt(next);
+
+ if(prevEnd < getTotalRange(f).getEnd())
+ {
+ prevEnd = getTotalRange(f).getEnd();
+ prevForward = f.isForwardFeature();
+ }
+ i = next;
+ next++;
+ }
+ }
+ if(i==cdsFeatures.size()-1)
+ {
+ if(entry_group.getSequenceLength() > r.getEnd())
+ {
+ new_range = new Range(prevEnd + 1,
+ entry_group.getSequenceLength());
+ location = new Location(new_range);
+ newEntry.createFeature(key,
+ location, qualifiers);
+ }
+ }
+ }
+ catch (OutOfRangeException e) {}
+ catch(ReadOnlyException e) {}
+ catch(EntryInformationException e) {}
+
+ }
+ }
+ finally
+ {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ private static Range getTotalRange(Feature f)
+ {
+ return ((Range) f.getLocation().getTotalRange() );
+ }
+
+ /**
+ * This is used by getSortedFeaturesInRange().
+ **/
+ final private static Comparator feature_comparator = new Comparator()
+ {
+ /**
+ * Compare two Objects with respect to ordering.
+ * @return a negative number if feature1_object is less than
+ * feature2_object ; a positive number if feature1_object is greater
+ * than feature2_object; else 0
+ **/
+ public int compare(final Object feature1_object,
+ final Object feature2_object)
+ {
+ final Feature feature1 =(Feature) feature1_object;
+ final Feature feature2 =(Feature) feature2_object;
+
+ final int feature1_start = feature1.getLocation().getTotalRange().getStart();
+ final int feature2_start = feature2.getLocation().getTotalRange().getStart();
+
+ if(feature1_start < feature2_start)
+ return -1;
+ else if(feature1_start > feature2_start)
+ return 1;
+
+ final int feature1_end = feature1.getLocation().getTotalRange().getEnd();
+ final int feature2_end = feature2.getLocation().getTotalRange().getEnd();
+
+ if(feature1_end < feature2_end)
+ return -1;
+ else
+ return 1;
+ }
+ };
+
+
+ /**
+ * Create a new exon for each FeatureSegment in the selected CDS features.
+ * The exons are created in the Entry that contains the CDSs.
+ * @param frame The Frame to use for MessageDialog components.
+ * @param selection The Selection containing the CDS features to create the
+ * exons for.
+ * @param entry_group The EntryGroup to create the features in.
+ **/
+ static void createExonFeatures (final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group) {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ if (!checkForSelectionFeatures (frame, selection)) {
+ return;
+ }
+
+ final FeatureVector selected_features = selection.getAllFeatures ();
+
+ for (int feature_index = 0 ;
+ feature_index < selected_features.size () ;
+ ++feature_index) {
+
+ final Feature selection_feature =
+ selected_features.elementAt (feature_index);
+
+ if (!selection_feature.isProteinFeature ()) {
+ continue;
+ }
+
+ final Location cds_location = selection_feature.getLocation ();
+
+ final RangeVector cds_ranges = cds_location.getRanges ();
+
+ for (int range_index = 0 ;
+ range_index < cds_ranges.size () ;
+ ++range_index) {
+ final Range this_range = (Range)cds_ranges.elementAt (range_index);
+
+ final RangeVector exon_ranges = new RangeVector ();
+
+ exon_ranges.add (this_range);
+
+ final Key exon_key = new Key ("exon");
+ final Location exon_location =
+ new Location (exon_ranges, cds_location.isComplement ());
+ final QualifierVector qualifiers = new QualifierVector ();
+
+ try {
+ selection_feature.getEntry ().createFeature (exon_key,
+ exon_location,
+ qualifiers);
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Create a new gene for each of the selected CDS features.
+ * @param frame The Frame to use for MessageDialog components.
+ * @param selection The Selection containing the CDS features.
+ * @param entry_group The EntryGroup to create the features in.
+ **/
+ static void createGeneFeatures (final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group) {
+ /*
+ * XXX - FIXME - include 5'UTR at the start and 3'UTR at the end and if
+ * two (or more) CDSs have the same primary name create one gene feature
+ * that covers them.
+ */
+ try {
+ entry_group.getActionController ().startAction ();
+
+ if (!checkForSelectionFeatures (frame, selection)) {
+ return;
+ }
+
+ final FeatureVector selected_features = selection.getAllFeatures ();
+
+ for (int feature_index = 0 ;
+ feature_index < selected_features.size () ;
+ ++feature_index) {
+
+ final Feature selection_feature =
+ selected_features.elementAt (feature_index);
+
+ if (!selection_feature.isProteinFeature ()) {
+ continue;
+ }
+
+ final Range max_range = selection_feature.getMaxRawRange ();
+ final boolean complement_flag =
+ selection_feature.getLocation ().isComplement ();
+
+ final RangeVector ranges = new RangeVector ();
+ ranges.add (max_range);
+
+ final Key gene_key = new Key ("gene");
+ final Location gene_location =
+ new Location (ranges, complement_flag);
+ final QualifierVector qualifiers = new QualifierVector ();
+
+ try
+ {
+
+ if(selection_feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ String uniquename = GeneUtils.promptForUniquename(entry_group,
+ selection_feature.isForwardFeature());
+
+ Qualifier qualifier = new Qualifier("ID", uniquename);
+ qualifiers.setQualifier(qualifier);
+ }
+
+ Feature feature =
+ selection_feature.getEntry ().createFeature (gene_key,
+ gene_location,
+ qualifiers);
+
+
+ if(feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ ChadoCanonicalGene chado_gene = new ChadoCanonicalGene();
+ chado_gene.setGene(feature.getEmblFeature());
+ ((uk.ac.sanger.artemis.io.GFFStreamFeature)
+ (feature.getEmblFeature())).setChadoGene(chado_gene);
+ }
+
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Open a TextRequester to ask the user for the minimum ORF size then call
+ * markOpenReadingFrames ().
+ * @param empty_only If true only those ORFS that don't already contain a
+ * segment will be marked.
+ **/
+ private void markORFSWithSize (final boolean empty_only, final JFrame frame) {
+ final int default_minimum_orf_size =
+ Options.getOptions ().getMinimumORFSize ();
+
+ Box inputBox = Box.createVerticalBox();
+ inputBox.add(new JLabel("Minimum open reading frame size:"));
+ JTextField minSize = new JTextField(String.valueOf (default_minimum_orf_size));
+ inputBox.add(minSize);
+ JCheckBox useFastaBoundary = new JCheckBox("break at contig boundaries (multiple fasta only)", false);
+ inputBox.add(useFastaBoundary);
+
+ int select = JOptionPane.showConfirmDialog(getParentFrame(),
+ inputBox, "ORF options",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+
+ final String requester_text = minSize.getText().trim();
+
+ if (requester_text.length () == 0)
+ return;
+
+ try
+ {
+ final int minimum_orf_size =
+ Integer.valueOf (requester_text).intValue ();
+
+ markOpenReadingFrames(minimum_orf_size, empty_only,
+ useFastaBoundary.isSelected(), frame);
+ }
+ catch (NumberFormatException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "this is not a number: " + requester_text);
+ }
+ }
+
+ /**
+ * Create a new Feature for each open reading frame.
+ * @param minimum_orf_size All the returned ORFs will be at least this many
+ * amino acids long.
+ * @param empty_only If true only those ORFS that don't already contain a
+ * segment will be marked.
+ **/
+ private void markOpenReadingFrames (final int minimum_orf_size,
+ final boolean empty_only,
+ final boolean isMultiFasta,
+ final JFrame frame) {
+ frame.setCursor(cbusy);
+ try {
+ final Entry new_entry =
+ entry_group.createEntry ("ORFS_" + minimum_orf_size + '+');
+
+ if(isMultiFasta) // ensure ORF's do not cross fasta boundaries
+ {
+ final FeatureKeyPredicate predicate =
+ new FeatureKeyPredicate(new Key("fasta_record"));
+ final FeatureVector fasta_features = new FeatureVector ();
+
+ final FeatureEnumeration feature_enum = entry_group.features ();
+
+ while (feature_enum.hasMoreFeatures ())
+ {
+ final Feature current_feature = feature_enum.nextFeature ();
+ if (predicate.testPredicate (current_feature))
+ fasta_features.add (current_feature);
+ }
+
+ for(int i=0;i<fasta_features.size();i++)
+ {
+ final int start = fasta_features.elementAt(i).getFirstBase();
+ final int last = fasta_features.elementAt(i).getLastBase();
+
+ final MarkerRange forward_range =
+ entry_group.getBases().getForwardStrand().
+ makeMarkerRangeFromPositions(start, last);
+
+ markOpenReadingFrames(new_entry, forward_range, minimum_orf_size,
+ empty_only, last, start);
+
+ int length = entry_group.getBases().getLength();
+ final MarkerRange backward_range =
+ entry_group.getBases().getReverseStrand().
+ makeMarkerRangeFromPositions((length-last+1), (length-start+1));
+
+ markOpenReadingFrames(new_entry, backward_range, minimum_orf_size,
+ empty_only, length-start+1, length-last+1);
+ }
+ }
+ else
+ {
+ final int sequence_length = entry_group.getSequenceLength();
+
+ final Strand forward_strand = entry_group.getBases().getForwardStrand();
+
+ final MarkerRange forward_range = forward_strand
+ .makeMarkerRangeFromPositions(1, sequence_length);
+
+ markOpenReadingFrames(new_entry, forward_range, minimum_orf_size,
+ empty_only, sequence_length, 1);
+
+ final Strand backward_strand = entry_group.getBases()
+ .getReverseStrand();
+
+ final MarkerRange backward_range = backward_strand
+ .makeMarkerRangeFromPositions(1, sequence_length);
+
+ markOpenReadingFrames(new_entry, backward_range, minimum_orf_size,
+ empty_only, sequence_length, 1);
+ }
+ } catch (OutOfRangeException e) {
+ frame.setCursor(cdone);
+ throw new Error ("internal error - unexpected OutOfRangeException");
+ }
+ frame.setCursor(cdone);
+ }
+
+ /**
+ * Create a new Feature for each open reading frame. The minimum size of
+ * the ORFS is specified in the options file.
+ **/
+ private void markOpenReadingFramesInRange () {
+ if (!checkForSelectionRange (getParentFrame (), getSelection ())) {
+ return;
+ }
+
+ final int default_minimum_orf_size =
+ Options.getOptions ().getMinimumORFSize ();
+
+ final TextRequester text_requester =
+ new TextRequester ("minimum open reading frame size?",
+ 18, String.valueOf (default_minimum_orf_size));
+
+ text_requester.addTextRequesterListener (new TextRequesterListener () {
+ public void actionPerformed (final TextRequesterEvent event) {
+ if (event.getType () == TextRequesterEvent.CANCEL) {
+ return;
+ }
+
+ final String requester_text = event.getRequesterText ().trim ();
+
+ if (requester_text.length () == 0) {
+ return;
+ }
+
+ try {
+ final int minimum_orf_size =
+ Integer.valueOf (requester_text).intValue ();
+
+ final Entry new_entry =
+ entry_group.createEntry ("ORFS_" + minimum_orf_size + '+');
+
+ final MarkerRange selection_range =
+ getSelection ().getMarkerRange ();
+
+ markOpenReadingFrames (new_entry, selection_range, minimum_orf_size,
+ false, entry_group.getSequenceLength(), 1);
+
+
+ } catch (NumberFormatException e) {
+ new MessageDialog (getParentFrame (),
+ "this is not a number: " + requester_text);
+ }
+ }
+ });
+
+ text_requester.setVisible (true);
+
+ }
+
+ /**
+ * Create a new Feature in the given Entry for each open reading frame that
+ * overlaps the given range. The minimum size of the ORFS is specified in
+ * the options file.
+ * @param entry The new features are created in this entry.
+ * @param search_range The range of bases to search for ORFs.
+ * @param minimum_orf_size All the returned ORFs will be at least this many
+ * amino acids long.
+ * @param empty_only If true only those ORFS that don't already contain a
+ * segment will be marked.
+ **/
+ private void markOpenReadingFrames (final Entry entry,
+ final MarkerRange search_range,
+ final int minimum_orf_size,
+ final boolean empty_only,
+ final int sequence_end,
+ final int sequence_start) {
+ final MarkerRange [] forward_orf_ranges =
+ Strand.getOpenReadingFrameRanges (search_range, minimum_orf_size, sequence_end,
+ sequence_start);
+
+ String uniquename = GeneUtils.promptForUniquename(entry_group, search_range.isForwardMarker());
+
+ final Key key;
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ key = new Key("region");
+ else
+ key = Key.CDS;
+
+ for(int i = 0 ; i < forward_orf_ranges.length ; ++i)
+ {
+ final MarkerRange this_range = forward_orf_ranges[i];
+ final Feature new_feature;
+
+ try
+ {
+ QualifierVector qualifiers = null;
+ if(entry.getEMBLEntry() instanceof
+ uk.ac.sanger.artemis.io.DatabaseDocumentEntry)
+ {
+ Qualifier qualifier = new Qualifier("ID", uniquename+Integer.toString(i+1));
+ qualifiers = new QualifierVector();
+ qualifiers.setQualifier(qualifier);
+ }
+ new_feature = makeFeatureFromMarkerRange (entry, this_range, key, qualifiers);
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ new MessageDialog (getParentFrame (), "cannot continue: " +
+ "the default entry does not support CDS features");
+ return;
+ }
+ catch (ReadOnlyException e)
+ {
+ new MessageDialog (getParentFrame (), "cannot continue: " +
+ "the default entry is read only");
+ return;
+ }
+
+ if(empty_only && overlapsAnActiveSegment (new_feature))
+ {
+ try
+ {
+ new_feature.removeFromEntry ();
+ }
+ catch (ReadOnlyException exception)
+ {
+ throw new Error ("internal error - unexpected exception: " +
+ exception);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return true if and only if the given feature overlaps (and is in the
+ * same frame as) a segment in an active entry.
+ **/
+ private boolean overlapsAnActiveSegment (final Feature test_feature) {
+ final Range test_feature_range = test_feature.getMaxRawRange ();
+
+ FeatureVector overlapping_features;
+
+ try {
+ overlapping_features =
+ entry_group.getFeaturesInRange (test_feature_range);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ for (int feature_index = 0 ;
+ feature_index < overlapping_features.size () ;
+ ++feature_index ) {
+
+ final Feature current_feature =
+ overlapping_features.elementAt (feature_index);
+
+ if (current_feature != test_feature && current_feature.isCDS ()) {
+ final FeatureSegmentVector segments = current_feature.getSegments ();
+
+ for (int segment_index = 0;
+ segment_index < segments.size () ;
+ ++segment_index) {
+ final FeatureSegment this_segment =
+ segments.elementAt (segment_index);
+
+ if (test_feature_range.overlaps (this_segment.getRawRange ())) {
+ final int test_feature_frame =
+ test_feature.getSegments ().elementAt (0).getFrameID ();
+ final int this_segment_frame = this_segment.getFrameID ();
+
+ if (test_feature_frame == this_segment_frame) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Make a new Feature from the given MarkerRange in the given Entry. The
+ * new feature will be given the key 'CDS' and it's location will match the
+ * MarkerRange.
+ * @param entry The new feature is created in this entry.
+ * @param range The location of the new feature.
+ * @param key The key give the new feature
+ * @exception EntryInformationException Thrown if this Entry does not
+ * support features with the given key. Also thrown if any of these
+ * qualifiers aren't supported: note, label or gene.
+ **/
+ private Feature makeFeatureFromMarkerRange (final Entry entry,
+ final MarkerRange range,
+ final Key key,
+ QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException
+ {
+ try
+ {
+ final Location new_location = range.createLocation ();
+
+ if(qualifiers == null)
+ {
+ qualifiers = new QualifierVector ();
+ qualifiers.setQualifier (new Qualifier ("note", "none"));
+ }
+
+ final Feature new_feature =
+ entry.createFeature (key, new_location, qualifiers);
+
+ final CodonUsageAlgorithm codon_usage_algorithm =
+ base_plot_group.getCodonUsageAlgorithm ();
+
+ if(codon_usage_algorithm != null)
+ {
+ int score =
+ (int) (codon_usage_algorithm.getFeatureScore (new_feature) * 50);
+
+ if(score < 0)
+ score = 0;
+
+ if(score > 100)
+ score = 100;
+
+ final String score_string = String.valueOf (score);
+ new_feature.addQualifierValues (new Qualifier ("score",
+ score_string));
+
+ final int var_colour = 255 - score * 5 / 2;
+ final String colour_string = var_colour + " " + var_colour + " 255";
+ new_feature.addQualifierValues (new Qualifier ("colour",
+ colour_string));
+ }
+
+ return new_feature;
+ }
+ catch (OutOfRangeException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * This method will ask the user for a BasePattern (using a TextRequester
+ * component) then search the sequence for the given pattern and make a new
+ * feature from each match. The new features will created in an Entry
+ * called "matches: <pattern>".
+ **/
+ private void makeFeaturesFromPattern () {
+ final TextRequester text_requester =
+ new TextRequester ("create features from this pattern:", 18, "");
+
+ text_requester.addTextRequesterListener (new TextRequesterListener () {
+ public void actionPerformed (final TextRequesterEvent event) {
+ final String pattern_string = event.getRequesterText ().trim ();
+
+ try {
+ if (pattern_string.length () == 0) {
+ new MessageDialog (getParentFrame (), "the pattern is too short");
+ return;
+ }
+
+ final BasePattern pattern = new BasePattern (pattern_string);
+
+ makeFeaturesFromPattern (pattern);
+ } catch (BasePatternFormatException e) {
+ new MessageDialog (getParentFrame (),
+ "Illegal base pattern: " +
+ pattern_string);
+ }
+ }
+ });
+
+ text_requester.setVisible(true);
+ }
+
+ /**
+ * Search the sequence for the given pattern and make a new feature from
+ * each match. The new features will created in an Entry called "matches:
+ * <pattern>".
+ **/
+ private void makeFeaturesFromPattern (final BasePattern pattern)
+ {
+ final MarkerRangeVector matches =
+ pattern.findMatches (entry_group.getBases (),
+ null, // search from start
+ entry_group.getSequenceLength ());
+
+ if (matches.size () == 0)
+ {
+ new MessageDialog (getParentFrame (),
+ "no matches found for: " + pattern);
+ return;
+ }
+
+ final int TOO_MANY_MATCHES = 100;
+
+ if (matches.size () > TOO_MANY_MATCHES)
+ {
+ final YesNoDialog dialog =
+ new YesNoDialog (getParentFrame (),
+ matches.size () + " matches, continue?");
+
+ if (dialog.getResult ()) {
+ // yes - continue
+ } else {
+ // no
+ return;
+ }
+ }
+
+ final Entry new_entry = entry_group.createEntry ("matches: " + pattern);
+ final Key key = new_entry.getEntryInformation ().getDefaultKey ();
+
+ String uniquename = null;
+
+ if(entry_group.getDefaultEntry().getEMBLEntry() instanceof
+ uk.ac.sanger.artemis.io.DatabaseDocumentEntry)
+ uniquename = GeneUtils.promptForUniquename(entry_group, true);
+
+ for (int i = 0 ; i < matches.size () ; ++i)
+ {
+ try
+ {
+ QualifierVector qualifiers = null;
+
+ if(uniquename != null)
+ {
+ Qualifier qualifier = new Qualifier("ID", uniquename+Integer.toString(i+1));
+ qualifiers = new QualifierVector();
+ qualifiers.setQualifier(qualifier);
+ }
+
+ final Feature new_feature =
+ makeFeatureFromMarkerRange (new_entry, matches.elementAt (i), key, qualifiers);
+ new_feature.setQualifier (new Qualifier ("note", pattern.toString ()));
+ }
+ catch (EntryInformationException e)
+ {
+ new MessageDialog (getParentFrame (), "cannot continue: " +
+ e.getMessage ());
+ return;
+ }
+ catch (ReadOnlyException e)
+ {
+ new MessageDialog (getParentFrame (), "cannot continue: " +
+ "the default entry is read only");
+ return;
+ }
+ }
+ }
+
+ /**
+ * Create a misc_feature for each block of ambiguous bases. The new
+ * features will created in an Entry called "ambiguous bases".
+ **/
+ private void markAmbiguities () {
+ Entry new_entry = null;
+
+ final Bases bases = entry_group.getBases ();
+ final Key unsure;
+ if(entry_group.getSequenceEntry() != null &&
+ entry_group.getSequenceEntry().getEMBLEntry() instanceof DatabaseDocumentEntry)
+ unsure = new Key ("region");
+ else
+ unsure = new Key ("unsure");
+
+ Pattern p = Pattern.compile("^[nN]+$"); // pattern match for all n's
+
+ for (int i = 1 ; i <= bases.getLength () ; ++i) {
+ try {
+ if (! Bases.isLegalBase (bases.getBaseAt (i))) {
+ final int start_index = i;
+
+ while (i < bases.getLength () &&
+ ! Bases.isLegalBase (bases.getBaseAt (i + 1))) {
+ ++i;
+ }
+
+ final int end_index = i;
+
+ if (new_entry == null) {
+ new_entry = entry_group.createEntry ("ambiguous bases");
+ }
+
+ final Range range = new Range (start_index, end_index);
+
+ final String unsure_bases =
+ bases.getSubSequence (range, Bases.FORWARD);
+
+ final Location location = new Location (range);
+
+ final QualifierVector qualifiers = new QualifierVector ();
+
+ if(p.matcher(unsure_bases).matches())
+ {
+ final Feature feature =
+ new_entry.createFeature (new Key("gap"), location, qualifiers);
+ }
+ else
+ {
+ qualifiers.setQualifier (new Qualifier ("note", unsure_bases));
+ final Feature feature =
+ new_entry.createFeature (unsure, location, qualifiers);
+ }
+ }
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ if (new_entry == null) {
+ new MessageDialog (getParentFrame (), "No ambiguities found");
+ } else {
+ if (new_entry.getFeatureCount () == 1) {
+ new MessageDialog (getParentFrame (), "Created one feature");
+
+ } else {
+ new MessageDialog (getParentFrame (), "Created " +
+ new_entry.getFeatureCount () + " features");
+
+ }
+ }
+ }
+
+ /**
+ * Return the GotoEventSource object that was passed to the constructor.
+ **/
+ private GotoEventSource getGotoEventSource () {
+ return goto_event_source;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/AlignMatchViewer.java b/uk/ac/sanger/artemis/components/AlignMatchViewer.java
new file mode 100644
index 0000000..68722dc
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/AlignMatchViewer.java
@@ -0,0 +1,473 @@
+/* AlignMatchViewer.java
+ *
+ * created: Tue Feb 13 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/AlignMatchViewer.java,v 1.3 2008-05-29 14:18:48 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.ListSelectionEvent;
+import java.util.Comparator;
+import java.util.Collections;
+
+/**
+ * A component for viewing AlignMatchVectors selected in an AlignmentViewer.
+ *
+ * @author Kim Rutherford
+ * @version $Id: AlignMatchViewer.java,v 1.3 2008-05-29 14:18:48 tjc Exp $
+ **/
+
+public class AlignMatchViewer extends JFrame
+{
+
+ /**
+ * The AlignmentViewer to call alignAt () and setSelection () on when the
+ * user clicks on a match.
+ **/
+ private AlignmentViewer alignment_viewer = null;
+
+ /**
+ * The Vector of AlignMatch objects that was passed to the constructor.
+ **/
+ private AlignMatchVector matches = null;
+
+ /**
+ * The list that contains the matches.
+ **/
+ private JList list;
+
+ /**
+ * If selected the list items will be sorted by score.
+ **/
+ private JCheckBoxMenuItem sort_by_score_menu_item =
+ new JCheckBoxMenuItem ("Sort by Score");
+
+ /**
+ * If selected the list items will be sorted by percent identity.
+ **/
+ private JCheckBoxMenuItem sort_by_percent_id_menu_item =
+ new JCheckBoxMenuItem ("Sort by Percent Identity");
+
+ /**
+ * If selected the list items will be sorted by the start position of the
+ * query.
+ **/
+ private JCheckBoxMenuItem sort_by_query_start =
+ new JCheckBoxMenuItem ("Sort by Hit Query Start");
+
+ /**
+ * If selected the list items will be sorted by the start position of the
+ * subject.
+ **/
+ private JCheckBoxMenuItem sort_by_subject_start =
+ new JCheckBoxMenuItem ("Sort by Hit Subject start");
+
+ /**
+ * Create a new AlignMatchViewer which shows the given matches.
+ * @param alignment_viewer The AlignmentViewer to call alignAt () and
+ * setSelection () on when the user clicks on a match.
+ * @param matches The Vector of AlignMatch objects to show.
+ **/
+ public AlignMatchViewer(final AlignmentViewer alignment_viewer,
+ final AlignMatchVector matches)
+ {
+ this.alignment_viewer = alignment_viewer;
+ this.matches = matches;
+
+ final JMenuBar menu_bar = new JMenuBar();
+ final JMenu file_menu = new JMenu("File");
+
+ final JMenuItem save = new JMenuItem("Save List to File...");
+ save.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ saveMatchList();
+ }
+ });
+
+ final JMenuItem close = new JMenuItem("Close");
+ close.addActionListener(new ActionListener ()
+ {
+ public void actionPerformed (ActionEvent event)
+ {
+ setVisible (false);
+ AlignMatchViewer.this.dispose ();
+ }
+ });
+
+ file_menu.add (save);
+ file_menu.add (close);
+ menu_bar.add (file_menu);
+
+ final JMenu sort_menu = new JMenu("Sort");
+ sort_menu.add(sort_by_score_menu_item);
+
+ sort_by_score_menu_item.addItemListener(new ItemListener ()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if (sort_by_score_menu_item.getState())
+ {
+ sort_by_percent_id_menu_item.setState (false);
+ sort_by_query_start.setState (false);
+ sort_by_subject_start.setState (false);
+ }
+ setList ();
+ }
+ });
+
+ sort_menu.add (sort_by_percent_id_menu_item);
+
+ sort_by_percent_id_menu_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(sort_by_percent_id_menu_item.getState())
+ {
+ sort_by_score_menu_item.setState(false);
+ sort_by_query_start.setState(false);
+ sort_by_subject_start.setState (false);
+ }
+ setList ();
+ }
+ });
+
+ sort_menu.add (sort_by_query_start);
+
+ sort_by_query_start.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(sort_by_query_start.getState())
+ {
+ sort_by_percent_id_menu_item.setState(false);
+ sort_by_score_menu_item.setState(false);
+ sort_by_subject_start.setState(false);
+ }
+
+ setList();
+ }
+ });
+
+ sort_menu.add(sort_by_subject_start);
+
+ sort_by_subject_start.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(sort_by_subject_start.getState())
+ {
+ sort_by_percent_id_menu_item.setState(false);
+ sort_by_score_menu_item.setState(false);
+ sort_by_query_start.setState(false);
+ }
+ setList ();
+ }
+ });
+
+ menu_bar.add(sort_menu);
+
+ setJMenuBar(menu_bar);
+
+ list = new JList();
+
+ list.setBackground(Color.white);
+
+ getContentPane().add(new JScrollPane(list), "Center");
+
+ final JPanel panel = new JPanel();
+
+ final JButton close_button = new JButton("Close");
+
+ panel.add(close_button);
+ close_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ AlignMatchViewer.this.dispose ();
+ }
+ });
+
+ close_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ AlignMatchViewer.this.dispose();
+ }
+ });
+
+ getContentPane().add(panel, "South");
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ AlignMatchViewer.this.dispose();
+ }
+ });
+
+ pack();
+
+ setList();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ int screen_height = screen.height;
+ int screen_width = screen.width;
+
+ if(screen_height <= 600)
+ setSize (350, screen_height * 9 / 10);
+ else
+ setSize (350, screen_height - 200);
+
+ setLocation(new Point (screen.width - getSize ().width - 5,
+ (screen.height - getSize ().height) / 2));
+ }
+
+ /**
+ * Sort the matches depending on the setting of sort_by_score_menu_item and
+ * sort_by_percent_id_menu_item.
+ **/
+ private AlignMatchVector getSortedMatches()
+ {
+ final AlignMatchVector matches_copy = (AlignMatchVector)matches.clone();
+
+ final Comparator comparator;
+
+ if(sort_by_score_menu_item.getState())
+ {
+ comparator =
+ new Comparator ()
+ {
+ public int compare (Object fst, Object snd)
+ {
+ final int fst_score = ((AlignMatch)fst).getScore();
+ final int snd_score = ((AlignMatch)snd).getScore();
+
+ if (fst_score < snd_score)
+ return 1;
+ else
+ {
+ if (fst_score > snd_score)
+ return -1;
+ else
+ return 0;
+ }
+ }
+ };
+ }
+ else
+ {
+ if(sort_by_percent_id_menu_item.getState())
+ {
+ comparator =
+ new Comparator()
+ {
+ public int compare (Object fst, Object snd)
+ {
+ final int fst_value = ((AlignMatch)fst).getPercentID();
+ final int snd_value = ((AlignMatch)snd).getPercentID();
+
+ if (fst_value < snd_value)
+ return 1;
+ else
+ {
+ if(fst_value > snd_value)
+ return -1;
+ else
+ return 0;
+ }
+ }
+ };
+ }
+ else
+ {
+ if(sort_by_query_start.getState())
+ {
+ comparator =
+ new Comparator()
+ {
+ public int compare(Object fst, Object snd)
+ {
+ final int fst_value =
+ ((AlignMatch)fst).getQuerySequenceStart();
+ final int snd_value =
+ ((AlignMatch)snd).getQuerySequenceStart();
+
+ if(fst_value > snd_value)
+ return 1;
+ else
+ {
+ if (fst_value < snd_value)
+ return -1;
+ else
+ return 0;
+ }
+ }
+ };
+ }
+ else
+ {
+ if(sort_by_subject_start.getState())
+ {
+ comparator =
+ new Comparator()
+ {
+ public int compare (Object fst, Object snd)
+ {
+ final int fst_value =
+ ((AlignMatch)fst).getSubjectSequenceStart();
+ final int snd_value =
+ ((AlignMatch)snd).getSubjectSequenceStart();
+
+ if (fst_value > snd_value)
+ return 1;
+ else
+ {
+ if (fst_value < snd_value)
+ return -1;
+ else
+ return 0;
+ }
+ }
+ };
+ }
+ else
+ return matches;
+ }
+ }
+ }
+
+ matches_copy.sort(comparator);
+ return matches_copy;
+ }
+
+ /**
+ * Clear the List and then fill it with the matches in the order
+ **/
+ private void setList ()
+ {
+ final AlignMatchVector sorted_matches = getSortedMatches();
+
+ list.setEnabled(false);
+ list.setVisible(false);
+ list.removeAll();
+
+ String listItems[] = new String[sorted_matches.size()];
+
+ for(int i = 0 ; i<sorted_matches.size() ; ++i)
+ {
+ final AlignMatch this_align_match = sorted_matches.elementAt(i);
+
+ listItems[i] = new String(
+ this_align_match.getQuerySequenceStart() + ".." +
+ this_align_match.getQuerySequenceEnd() + " -> " +
+ this_align_match.getSubjectSequenceStart() + ".." +
+ this_align_match.getSubjectSequenceEnd() + " " +
+ (this_align_match.isRevMatch() ? "rev " : "") +
+ this_align_match.getPercentID() + "% id score " +
+ this_align_match.getScore() );
+
+ }
+
+ list.setListData(listItems);
+
+ list.addListSelectionListener(new ListSelectionListener()
+ {
+ public void valueChanged(ListSelectionEvent e)
+ {
+ final int item_number = list.getSelectedIndex();
+
+ final AlignMatch selected_match = matches.elementAt(item_number);
+
+ alignment_viewer.setSelection(selected_match);
+ alignment_viewer.alignAt(selected_match);
+ }
+ });
+
+ list.setEnabled(true);
+ list.setVisible(true);
+ }
+
+
+ /**
+ * Save the text of the match list to a file.
+ **/
+ private void saveMatchList()
+ {
+ final StickyFileChooser file_dialog = new StickyFileChooser();
+
+ file_dialog.setDialogTitle("Choose save file ...");
+ file_dialog.setDialogType(JFileChooser.SAVE_DIALOG);
+ final int status = file_dialog.showSaveDialog(this);
+
+ if(status != JFileChooser.APPROVE_OPTION ||
+ file_dialog.getSelectedFile() == null)
+ return;
+
+ final File write_file =
+ new File(file_dialog.getCurrentDirectory(),
+ file_dialog.getSelectedFile().getName());
+
+ if(write_file.exists())
+ {
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog(this,
+ "this file exists: " + write_file +
+ " overwrite it?");
+ if(yes_no_dialog.getResult())
+ {
+ // yes - continue
+ }
+ else
+ return;
+ }
+
+ try
+ {
+ final PrintWriter writer =
+ new PrintWriter(new FileWriter(write_file));
+
+ for(int i = 0 ; i < list.getModel().getSize() ; ++i)
+ writer.println(list.getModel().getElementAt(i));
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(this, "error while writing: " + e.getMessage());
+ }
+ }
+
+
+}
diff --git a/uk/ac/sanger/artemis/components/AlignmentEvent.java b/uk/ac/sanger/artemis/components/AlignmentEvent.java
new file mode 100644
index 0000000..0cbf0e2
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/AlignmentEvent.java
@@ -0,0 +1,57 @@
+/* AlignmentEvent.java
+ *
+ * created: Mon Sep 10 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/AlignmentEvent.java,v 1.1 2004-06-09 09:45:59 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+/**
+ * This event is generated when an AlignmentViewer centres on a particular
+ * AlignMatch.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: AlignmentEvent.java,v 1.1 2004-06-09 09:45:59 tjc Exp $
+ **/
+
+public class AlignmentEvent {
+ /**
+ * Create a new AlignmentEvent and store the given AlignMatch.
+ **/
+ public AlignmentEvent (AlignMatch match) {
+ this.match = match;
+ }
+
+ /**
+ * Return the AlignMatch that was passed to the constructor.
+ **/
+ public AlignMatch getMatch () {
+ return match;
+ }
+
+ /**
+ * The AlignMatch that was passed to the constructor.
+ **/
+ final private AlignMatch match;
+}
diff --git a/uk/ac/sanger/artemis/components/AlignmentListener.java b/uk/ac/sanger/artemis/components/AlignmentListener.java
new file mode 100644
index 0000000..3061817
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/AlignmentListener.java
@@ -0,0 +1,43 @@
+/* AlignmentListener.java
+ *
+ * created: Mon Sep 10 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/AlignmentListener.java,v 1.1 2004-06-09 09:46:00 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * This interface is implemented by those objects that wish to know when an
+ * AlignmentViewer centres on a particular AlignMatch
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: AlignmentListener.java,v 1.1 2004-06-09 09:46:00 tjc Exp $
+ **/
+
+public interface AlignmentListener {
+ /**
+ * Called when an AlignmentViewer centres on a particular AlignMatch. The
+ * Comparator component implements this so that it can scroll the
+ * FeatureDisplay components appropriately.
+ **/
+ public void alignMatchChosen (AlignmentEvent e);
+}
diff --git a/uk/ac/sanger/artemis/components/AlignmentViewer.java b/uk/ac/sanger/artemis/components/AlignmentViewer.java
new file mode 100644
index 0000000..ddd1145
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/AlignmentViewer.java
@@ -0,0 +1,2472 @@
+/* AlignmentViewer.java
+ *
+ * created: Mon Jul 12 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999,2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/AlignmentViewer.java,v 1.43 2008-11-28 17:51:09 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.SequenceChangeListener;
+import uk.ac.sanger.artemis.sequence.SequenceChangeEvent;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+import java.util.Comparator;
+import java.util.Arrays;
+import javax.swing.*;
+
+import org.apache.batik.svggen.SVGGraphics2D;
+
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * This component shows an alignment of two sequences using the data from a
+ * ComparisonData object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: AlignmentViewer.java,v 1.43 2008-11-28 17:51:09 tjc Exp $
+ **/
+
+public class AlignmentViewer extends CanvasPanel
+ implements SequenceChangeListener
+{
+ private static final long serialVersionUID = 1L;
+
+ private Image offscreen;
+
+ /** Comparison data that will be displayed in this component. */
+ final private ComparisonData comparison_data;
+
+ /**
+ * All the AlignMatch objects from comparison_data (possibly in a
+ * different order.
+ **/
+ private AlignMatch[] all_matches = null;
+
+ /**
+ * This is the last DisplayAdjustmentEvent reference that was passed to
+ * setSubjectSeqeuencePosition().
+ **/
+ private DisplayAdjustmentEvent last_subject_event;
+
+ /**
+ * This is the last DisplayAdjustmentEvent reference that was passed to
+ * setQuerySeqeuencePosition().
+ **/
+ private DisplayAdjustmentEvent last_query_event;
+
+ /** FeatureDisplay that is above this component. (From the constructor). */
+ private FeatureDisplay subject_feature_display;
+
+ /** FeatureDisplay that is below this component. (From the constructor). */
+ private FeatureDisplay query_feature_display;
+
+ /**
+ * Set by the constructor to be the original forward strand for the subject
+ * sequence. This is use to determine whether to subject sequence has been
+ * reverse-complemented or not.
+ **/
+ private Strand orig_subject_forward_strand;
+
+ /**
+ * Set by the constructor to be the original forward strand for the query
+ * sequence. This is use to determine whether to query sequence has been
+ * reverse-complemented or not.
+ **/
+ private Strand orig_query_forward_strand;
+
+ /** One of the two Entry objects that we are comparing. */
+ final private EntryGroup subject_entry_group;
+
+ /** One of the two Entry objects that we are comparing. */
+ final private EntryGroup query_entry_group;
+
+ /** Selected matches. null means no matches are selected. */
+ private AlignMatchVector selected_matches = null;
+
+ /**
+ * The objects that are listening for AlignmentSelectionChangeEvents.
+ **/
+ private Vector<AlignmentSelectionChangeListener> selection_change_listeners =
+ new Vector<AlignmentSelectionChangeListener>();
+
+ /**
+ * The number of shades of red and blue to use for percentage ID colouring.
+ **/
+ private static int NUMBER_OF_SHADES = 13;
+
+ /** Reds used to display the percent identity of matches. */
+ private Color[] red_percent_id_colours;
+
+ /** Blues used to display the percent identity of matches. */
+ private Color[] blue_percent_id_colours;
+
+ /** Scroll bar used to set the minimum length of the visible matches. */
+ private JScrollBar scroll_bar = null;
+
+ /** Matches with scores below this value will not be shown. */
+ private int minimum_score = 0;
+
+ /** Matches with scores above this value will not be shown. */
+ private int maximum_score = 99999999;
+
+ /**
+ * Matches with percent id values below this number will not be shown.
+ **/
+ private int minimum_percent_id = 0;
+
+ /**
+ * Matches with percent id values above this number will not be shown.
+ **/
+ private int maximum_percent_id = 100;
+
+ /**
+ * True if we should offer to flip the query sequence when the user
+ * double clicks on a flipped match.
+ **/
+ private boolean offer_to_flip_flag = false;
+
+ /**
+ * If true ignore self matches (ie query start == subject start && query
+ * end == subject end)
+ **/
+ private boolean ignore_self_match_flag = false;
+
+ /** Vector of those objects that are listening for AlignmentEvents */
+ private Vector<AlignmentListener> alignment_event_listeners = new Vector<AlignmentListener> ();
+
+ /**
+ * If true then the FeatureDisplays above and below this AlignmentViewer
+ * should scroll together.
+ **/
+ private boolean displays_are_locked = true;
+
+ /**
+ * Setting this to true will temporarily disable selectFromQueryRange() and
+ * selectFromSubjectRange() until enableSelection(). This is need to allow
+ * the selections of the top and bottom FeatureDisplays to be set without
+ * changing which AlignMatches are selected.
+ **/
+ private boolean disable_selection_from_ranges = false;
+
+ /** user defined colours */
+ private boolean reverseMatchColour = false;
+ /** colour for reverse matches */
+ private Color revMatchColour = Color.blue;
+ /** colour for matches */
+ private Color matchColour = Color.red;
+
+ /**
+ * Create a new AlignmentViewer for the given entries.
+ * @param subject_feature_display The FeatureDisplay that is above this
+ * component.
+ * @param query_feature_display The FeatureDisplay that is below this
+ * component.
+ * @param comparison_data Provides the AlignMatch objects that will be
+ * displayed.
+ **/
+ public AlignmentViewer(final FeatureDisplay subject_feature_display,
+ final FeatureDisplay query_feature_display,
+ final ComparisonData comparison_data)
+ {
+ this.subject_feature_display = subject_feature_display;
+ this.query_feature_display = query_feature_display;
+ this.comparison_data = comparison_data;
+ this.all_matches = getComparisonData().getMatches();
+
+ subject_entry_group = getSubjectDisplay().getEntryGroup();
+ query_entry_group = getQueryDisplay().getEntryGroup();
+
+ final Bases subject_bases = getSubjectForwardStrand().getBases();
+ final Bases query_bases = getQueryForwardStrand().getBases();
+
+ final Selection subject_selection = getSubjectDisplay().getSelection();
+ final Selection query_selection = getQueryDisplay().getSelection();
+
+ final SelectionChangeListener subject_listener =
+ new SelectionChangeListener()
+ {
+ JFrame frame = null;
+ public void selectionChanged(SelectionChangeEvent event)
+ {
+ if(frame == null)
+ frame = subject_feature_display.getParentFrame();
+ if(!frame.isVisible())
+ return;
+
+ final RangeVector ranges = subject_selection.getSelectionRanges();
+ selectFromSubjectRanges(ranges);
+ }
+ };
+
+ final SelectionChangeListener query_listener =
+ new SelectionChangeListener()
+ {
+ JFrame frame = null;
+ public void selectionChanged (SelectionChangeEvent event)
+ {
+ if(frame == null)
+ frame = query_feature_display.getParentFrame();
+ if(!frame.isVisible())
+ return;
+
+ final RangeVector ranges = query_selection.getSelectionRanges ();
+ selectFromQueryRanges(ranges);
+ }
+ };
+
+ makeColours();
+
+ subject_selection.addSelectionChangeListener(subject_listener);
+ query_selection.addSelectionChangeListener(query_listener);
+
+ subject_bases.addSequenceChangeListener(this, 0);
+ query_bases.addSequenceChangeListener(this, 0);
+
+ orig_subject_forward_strand = getSubjectForwardStrand();
+ orig_query_forward_strand = getQueryForwardStrand();
+
+ addMouseListener(new MouseAdapter()
+ {
+ public void mousePressed(final MouseEvent event)
+ {
+ // on windows we have to check isPopupTrigger in mouseReleased(),
+ // but do it in mousePressed() on UNIX
+ if(isMenuTrigger(event))
+ popupMenu(event);
+ else
+ handleCanvasMousePress(event);
+ }
+ });
+
+ addMouseMotionListener(new MouseMotionAdapter()
+ {
+ public void mouseDragged(final MouseEvent event)
+ {
+ if(isMenuTrigger(event))
+ return;
+
+ if(!modifiersForLockToggle(event))
+ {
+ if(!event.isShiftDown())
+ {
+ selected_matches = null;
+ toggleSelection (event.getPoint());
+ }
+ repaint();
+ }
+ }
+ });
+
+ scroll_bar = new JScrollBar(Scrollbar.VERTICAL);
+ scroll_bar.setValues(1, 1, 1, 1000);
+ scroll_bar.setBlockIncrement(10);
+
+ scroll_bar.addAdjustmentListener(new AdjustmentListener()
+ {
+ public void adjustmentValueChanged(AdjustmentEvent e)
+ {
+ repaint();
+ }
+ });
+
+ maximum_score = getComparisonData().getMaximumScore();
+
+ add(scroll_bar, "East");
+ setBackground(Color.white);
+ }
+
+ /**
+ * Returns true if and only if the given MouseEvent should toggle the lock
+ * displays toggle.
+ **/
+ private boolean modifiersForLockToggle(final MouseEvent event)
+ {
+ return(event.getModifiers() & InputEvent.BUTTON2_MASK) != 0 ||
+ event.isAltDown();
+ }
+
+ /**
+ * Select those matches that overlap the given range on the subject
+ * sequence.
+ **/
+ public void selectFromSubjectRanges(final RangeVector select_ranges)
+ {
+ if(disable_selection_from_ranges)
+ return;
+
+ selected_matches = null;
+ final int all_matches_length = all_matches.length;
+ final int select_ranges_size = select_ranges.size();
+
+ final Strand current_subject_fwd_strand =
+ getSubjectForwardStrand();
+
+ final int subject_length = current_subject_fwd_strand.getSequenceLength();
+
+ for(int match_index = 0; match_index < all_matches_length; ++match_index)
+ {
+ final AlignMatch this_match = all_matches[match_index];
+
+ if(!isVisible(this_match))
+ continue;
+
+ int subject_sequence_start = getRealSubjectSequenceStart(this_match,
+ subject_length,
+ (getOrigSubjectForwardStrand() != current_subject_fwd_strand));
+ int subject_sequence_end = getRealSubjectSequenceEnd(this_match,
+ subject_length,
+ (getOrigSubjectForwardStrand() != current_subject_fwd_strand));
+
+ if(subject_sequence_end < subject_sequence_start)
+ {
+ final int tmp = subject_sequence_start;
+ subject_sequence_start = subject_sequence_end;
+ subject_sequence_end = tmp;
+ }
+
+ for(int range_index = 0; range_index < select_ranges_size; ++range_index)
+ {
+ final Range select_range = (Range) select_ranges.elementAt(range_index);
+ final int select_range_start = select_range.getStart();
+ final int select_range_end = select_range.getEnd();
+
+ if(select_range_start < subject_sequence_start
+ && select_range_end < subject_sequence_start)
+ continue;
+
+ if(select_range_start > subject_sequence_end &&
+ select_range_end > subject_sequence_end)
+ continue;
+
+ if(selected_matches == null)
+ selected_matches = new AlignMatchVector();
+
+ //if(!selected_matches.contains(this_match))
+ selected_matches.add(this_match);
+ break;
+ }
+ }
+
+ if(selected_matches != null)
+ selectionChanged();
+ else
+ repaint();
+ }
+
+ /**
+ * Select those matches that overlap the given range on the query sequence.
+ **/
+ public void selectFromQueryRanges(final RangeVector select_ranges)
+ {
+ if(disable_selection_from_ranges)
+ return;
+
+ selected_matches = null;
+ final int select_ranges_size = select_ranges.size();
+ final int all_matches_length = all_matches.length;
+ final Strand current_query_forward_strand = getQueryForwardStrand();
+ final int query_length =
+ current_query_forward_strand.getSequenceLength();
+
+ for(int match_index = 0; match_index < all_matches_length; ++match_index)
+ {
+ final AlignMatch this_match = all_matches[match_index];
+
+ if(!isVisible(this_match))
+ continue;
+
+ int query_sequence_start = getRealQuerySequenceStart(this_match,
+ query_length,
+ (getOrigQueryForwardStrand() != current_query_forward_strand));
+ int query_sequence_end = getRealQuerySequenceEnd(this_match,
+ query_length,
+ (getOrigQueryForwardStrand() != current_query_forward_strand));
+
+ if(query_sequence_end < query_sequence_start)
+ {
+ final int tmp = query_sequence_start;
+ query_sequence_start = query_sequence_end;
+ query_sequence_end = tmp;
+ }
+
+ for(int range_index = 0; range_index < select_ranges_size; ++range_index)
+ {
+ final Range select_range = (Range) select_ranges.elementAt(range_index);
+ final int select_range_start = select_range.getStart();
+ final int select_range_end = select_range.getEnd();
+ if(select_range_start < query_sequence_start
+ && select_range_end < query_sequence_start)
+ continue;
+
+ if(select_range_start > query_sequence_end &&
+ select_range_end > query_sequence_end)
+ continue;
+
+ if(selected_matches == null)
+ selected_matches = new AlignMatchVector();
+
+ //if(!selected_matches.contains(this_match))
+ selected_matches.add(this_match);
+ break;
+ }
+ }
+
+ if(selected_matches != null)
+ selectionChanged();
+ else
+ repaint();
+ }
+
+ /**
+ * Select the given match and move it to the top of the display.
+ **/
+ public void setSelection(final AlignMatch match)
+ {
+ selected_matches = new AlignMatchVector();
+ selected_matches.add(match);
+ selectionChanged();
+ }
+
+ /**
+ * This method tells this AlignmentViewer component where the subject
+ * sequence is now.
+ **/
+ public void setSubjectSequencePosition(final DisplayAdjustmentEvent event)
+ {
+ last_subject_event = event;
+ repaint();
+ }
+
+ /**
+ * This method tells this AlignmentViewer component where the query
+ * sequence is now.
+ **/
+ public void setQuerySequencePosition(final DisplayAdjustmentEvent event)
+ {
+ last_query_event = event;
+ repaint();
+ }
+
+ /**
+ * Implementation of the SequenceChangeListener interface. The display is
+ * redrawn if there is an event.
+ **/
+ public void sequenceChanged(final SequenceChangeEvent event)
+ {
+ repaint();
+ }
+
+ /**
+ * Return true if and only if the given MouseEvent (a mouse press) should
+ * pop up a JPopupMenu.
+ **/
+ private boolean isMenuTrigger(final MouseEvent event)
+ {
+ if( event.isPopupTrigger() ||
+ (event.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Popup a menu.
+ **/
+ private void popupMenu(final MouseEvent event)
+ {
+ final JPopupMenu popup = new JPopupMenu();
+
+
+ final JMenuItem save_matches = new JMenuItem("Save Comparison File...");
+ save_matches.addActionListener(new ActionListener()
+ {
+ public void actionPerformed (ActionEvent _)
+ {
+ StickyFileChooser fc = new StickyFileChooser();
+
+ int returnVal = fc.showSaveDialog(null);
+ if(returnVal != JFileChooser.APPROVE_OPTION)
+ return;
+ else if(fc.getSelectedFile().exists())
+ {
+ Object[] possibleValues = { "YES", "NO" };
+ int select = JOptionPane.showOptionDialog(null,
+ fc.getSelectedFile().getName()+"\n"+
+ "exists. Overwrite?",
+ "File Exists",
+ JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE,null,
+ possibleValues, possibleValues[0]);
+ if(select == 1)
+ return;
+ }
+
+ try
+ {
+/*
+ if(!fc.getSelectedFile().canWrite())
+ {
+ JOptionPane.showMessageDialog(null,
+ "Cannot write to "+
+ fc.getSelectedFile().getCanonicalPath(),
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+*/
+ final FileWriter out_writer = new FileWriter(fc.getSelectedFile());
+ final String query = getQueryEntryGroup().getDefaultEntry().getName();
+ final String subject = getSubjectEntryGroup().getDefaultEntry().getName();
+
+ for(int i = 0; i < all_matches.length; ++i)
+ MSPcrunchComparisonData.writeMatchFromAlignMatch(all_matches[i],
+ query, subject,
+ out_writer);
+ out_writer.close();
+ }
+ catch(IOException ioe)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Error writing out comparison file.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ ioe.printStackTrace();
+ }
+ }
+ });
+ popup.add(save_matches);
+ popup.add(new JSeparator());
+
+ final JMenuItem alignmatch_list_item =
+ new JMenuItem("View Selected Matches");
+
+ popup.add(alignmatch_list_item);
+
+ alignmatch_list_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed (ActionEvent _)
+ {
+ if(selected_matches == null)
+ new MessageFrame("No matches selected").setVisible (true);
+ else
+ {
+ final AlignMatchVector matches =
+ (AlignMatchVector)selected_matches.clone();
+
+ final AlignMatchViewer viewer =
+ new AlignMatchViewer(AlignmentViewer.this, matches);
+
+ viewer.setVisible(true);
+ }
+ }
+ });
+
+ final JMenuItem flip_subject_item =
+ new JMenuItem("Flip Subject Sequence");
+
+ popup.add (flip_subject_item);
+
+ flip_subject_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ if(getSubjectDisplay().isRevCompDisplay())
+ getSubjectDisplay().setRevCompDisplay(false);
+ else
+ getSubjectDisplay().setRevCompDisplay(true);
+ }
+ });
+
+ final JMenuItem flip_query_item =
+ new JMenuItem ("Flip Query Sequence");
+
+ popup.add (flip_query_item);
+
+ flip_query_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ if(getQueryDisplay().isRevCompDisplay())
+ getQueryDisplay().setRevCompDisplay(false);
+ else
+ getQueryDisplay().setRevCompDisplay(true);
+ }
+ });
+
+ final JMenuItem cutoffs_item = new JMenuItem("Set Score Cutoffs ...");
+ popup.add(cutoffs_item);
+
+ cutoffs_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ final ScoreChangeListener minimum_listener =
+ new ScoreChangeListener()
+ {
+ public void scoreChanged(final ScoreChangeEvent event)
+ {
+ minimum_score = event.getValue();
+ repaint();
+ }
+ };
+
+ final ScoreChangeListener maximum_listener =
+ new ScoreChangeListener()
+ {
+ public void scoreChanged(final ScoreChangeEvent event)
+ {
+ maximum_score = event.getValue();
+ repaint();
+ }
+ };
+
+ final ScoreChanger score_changer =
+ new ScoreChanger("Score Cutoffs",
+ minimum_listener, maximum_listener,
+ getComparisonData().getMinimumScore(),
+ getComparisonData().getMaximumScore());
+
+ score_changer.setVisible (true);
+ }
+ });
+
+ final JMenuItem percent_id_cutoffs_item =
+ new JMenuItem("Set Percent ID Cutoffs ...");
+ popup.add(percent_id_cutoffs_item);
+
+ percent_id_cutoffs_item.addActionListener(new ActionListener ()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ final ScoreChangeListener minimum_listener =
+ new ScoreChangeListener()
+ {
+ public void scoreChanged(final ScoreChangeEvent event)
+ {
+ minimum_percent_id = event.getValue();
+ repaint();
+ }
+ };
+
+ final ScoreChangeListener maximum_listener =
+ new ScoreChangeListener()
+ {
+ public void scoreChanged(final ScoreChangeEvent event)
+ {
+ maximum_percent_id = event.getValue();
+ repaint();
+ }
+ };
+
+ final ScoreChanger score_changer =
+ new ScoreChanger("Percent Identity Cutoffs",
+ minimum_listener, maximum_listener,
+ 0, 100);
+
+ score_changer.setVisible(true);
+ }
+ });
+
+
+ final JCheckBoxMenuItem lock_item = new JCheckBoxMenuItem("Lock Sequences");
+ lock_item.setSelected(displaysAreLocked());
+ popup.add(lock_item);
+
+ lock_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(lock_item.isSelected())
+ lockDisplays();
+ else
+ unlockDisplays();
+ }
+ });
+
+ popup.addSeparator();
+
+ final JCheckBoxMenuItem sameColour =
+ new JCheckBoxMenuItem("Colour reverse & forward matches the same",reverseMatchColour);
+ sameColour.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ reverseMatchColour = sameColour.getState();
+ repaint();
+ }
+ });
+ popup.add(sameColour);
+
+ JMenuItem colourMatches = new JMenuItem("Colour matches...");
+ colourMatches.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ ColorChooserShades shades = createColours("Colour Matches",
+ red_percent_id_colours[NUMBER_OF_SHADES-1]);
+ if(shades != null)
+ {
+ red_percent_id_colours = shades.getDefinedColour();
+ repaint();
+ }
+ }
+ });
+ popup.add(colourMatches);
+
+ if(!reverseMatchColour)
+ {
+ JMenuItem colourRevMatches = new JMenuItem("Colour reverse matches...");
+ colourRevMatches.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ ColorChooserShades shades = createColours("Colour Reverse Matches",
+ blue_percent_id_colours[NUMBER_OF_SHADES-1]);
+ if(shades != null)
+ {
+ blue_percent_id_colours = shades.getDefinedColour();
+ repaint();
+ }
+ }
+ });
+ popup.add(colourRevMatches);
+ }
+
+ popup.addSeparator();
+
+ final JCheckBoxMenuItem offer_to_flip_item =
+ new JCheckBoxMenuItem("Offer To RevComp", offer_to_flip_flag);
+
+ offer_to_flip_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ offer_to_flip_flag = !offer_to_flip_flag;
+ }
+ });
+
+ popup.add(offer_to_flip_item);
+
+ final JCheckBoxMenuItem ignore_self_match_item =
+ new JCheckBoxMenuItem("Ignore Self Matches");
+
+ ignore_self_match_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ ignore_self_match_flag = ignore_self_match_item.getState();
+ repaint();
+ }
+ });
+
+ ignore_self_match_item.setState(ignore_self_match_flag);
+
+ popup.add(ignore_self_match_item);
+
+ add(popup);
+ popup.show(this, event.getX(), event.getY());
+ }
+
+
+ private ColorChooserShades createColours(String title, Color initialColour)
+ {
+ //Make sure we have nice window decorations.
+ JFrame.setDefaultLookAndFeelDecorated(true);
+
+ //Create and set up the window.
+ JFrame frame = new JFrame("ColorChooserDemo");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ //Create and set up the content pane.
+ ColorChooserShades newContentPane = new ColorChooserShades(title,initialColour);
+
+ Object[] possibleValues = { "OK", "CANCEL" };
+ int select = JOptionPane.showOptionDialog(null, newContentPane,
+ "Colour Selection",
+ JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE,null,
+ possibleValues, possibleValues[0]);
+ if(select == 0)
+ return newContentPane;
+
+ return null;
+ }
+
+
+ /**
+ * Handle a mouse press event on the drawing canvas - select on click,
+ * select and broadcast it on double click.
+ **/
+ private void handleCanvasMousePress(final MouseEvent event)
+ {
+ if(event.getID() != MouseEvent.MOUSE_PRESSED)
+ return;
+
+ if(event.getClickCount() == 2)
+ handleCanvasDoubleClick(event);
+ else
+ handleCanvasSingleClick(event);
+
+ repaint();
+ }
+
+ /**
+ * Handle a double click on the canvas.
+ **/
+ private void handleCanvasDoubleClick(final MouseEvent event)
+ {
+ // there should be only one match in the array
+ if(selected_matches != null)
+ alignAt(selected_matches.elementAt(0));
+ }
+
+ /**
+ * Send an AlignmentEvent to all the AlignmentListeners.
+ * @param align_match The AlignMatch that we have just centred on.
+ **/
+ public void alignAt(final AlignMatch align_match)
+ {
+ final Vector<AlignmentListener> targets;
+ // copied from a book - synchronizing the whole method might cause a
+ // deadlock
+ synchronized(this)
+ {
+ targets = new Vector<AlignmentListener>(alignment_event_listeners);
+ }
+
+ for(int i = 0; i < targets.size(); ++i)
+ {
+ final AlignmentListener listener = targets.elementAt(i);
+ listener.alignMatchChosen(new AlignmentEvent(align_match));
+ }
+ }
+
+ /**
+ * Handle a single click on the canvas.
+ **/
+ private void handleCanvasSingleClick(final MouseEvent event)
+ {
+ if(modifiersForLockToggle(event))
+ toggleDisplayLock();
+ else
+ {
+ if(!event.isShiftDown())
+ selected_matches = null;
+
+ toggleSelection(event.getPoint());
+ }
+ }
+
+ /**
+ * Add or remove the match at the given mouse position to the selection.
+ **/
+ private void toggleSelection(final Point point)
+ {
+ final AlignMatch clicked_align_match =
+ getAlignMatchFromPosition(point);
+
+ if(clicked_align_match != null)
+ {
+ if(selected_matches == null)
+ {
+ selected_matches = new AlignMatchVector ();
+ selected_matches.add (clicked_align_match);
+ }
+ else
+ {
+ if(selected_matches.contains(clicked_align_match))
+ {
+ selected_matches.remove(clicked_align_match);
+ if(selected_matches.size() == 0)
+ selected_matches = null;
+ }
+ else
+ selected_matches.add(clicked_align_match);
+ }
+ }
+
+ selectionChanged();
+ }
+
+ /**
+ * Return the AlignMatch at the given Point on screen or null if there is
+ * no match at that point. The alignment_data_array is searched in reverse
+ * order.
+ **/
+ private AlignMatch getAlignMatchFromPosition(final Point click_point)
+ {
+ final int canvas_height = getSize().height;
+ final int canvas_width = getSize().width;
+
+ final int subject_length = getSubjectForwardStrand().getSequenceLength();
+ final int query_length = getQueryForwardStrand().getSequenceLength();
+
+ final boolean subject_flipped = subjectIsRevComp();
+ final boolean query_flipped = queryIsRevComp();
+
+ final int all_matches_length = all_matches.length;
+ final float base_width = last_subject_event.getBaseWidth();
+ final float query_base_width = last_query_event.getBaseWidth();
+
+ final int subject_start = last_subject_event.getStart();
+ final int query_start = last_query_event.getStart();
+ final boolean subject_is_rev_comp = subjectIsRevComp();
+ final boolean query_is_rev_comp = queryIsRevComp();
+ boolean is_rev_match;
+ int[] match_x_positions;
+
+ for(int i = all_matches_length - 1; i >= 0 ; --i)
+ {
+ final AlignMatch this_match = all_matches [i];
+
+ is_rev_match = this_match.isRevMatch();
+ match_x_positions =
+ getMatchCoords(canvas_width, this_match, subject_length, query_length,
+ subject_flipped, query_flipped, base_width, query_base_width, subject_start,
+ query_start, subject_is_rev_comp, query_is_rev_comp, is_rev_match);
+
+ if(match_x_positions == null)
+ continue;
+
+ if(!isVisible(this_match))
+ continue;
+
+ final int subject_start_x = match_x_positions[0];
+ final int subject_end_x = match_x_positions[1];
+ final int query_start_x = match_x_positions[2];
+ final int query_end_x = match_x_positions[3];
+
+ // this is the x coordinate of the point where the line y = click_point
+ // hits the left edge of the match box
+ final double match_left_x =
+ subject_start_x +
+ (1.0 * (query_start_x - subject_start_x)) *
+ (1.0 * click_point.y / canvas_height);
+
+ // this is the x coordinate of the point where the line y = click_point
+ // hits the right edge of the match box
+ final double match_right_x =
+ subject_end_x +
+ (1.0 * (query_end_x - subject_end_x)) *
+ (1.0 * click_point.y / canvas_height);
+
+ if(click_point.x >= match_left_x - 1 &&
+ click_point.x <= match_right_x + 1 ||
+ click_point.x <= match_left_x + 1 &&
+ click_point.x >= match_right_x - 1)
+ return this_match;
+ }
+
+ return null;
+ }
+
+ /**
+ * This method is called by setSelection() and others whenever the list of
+ * selected/highlighted hits changes. Calls alignmentSelectionChanged()
+ * on all interested AlignmentSelectionChangeListener objects, moves the
+ * selected matches to the top of the display and then calls
+ * repaint
+ **/
+ private void selectionChanged()
+ {
+ for(int i = 0 ; i < selection_change_listeners.size() ; ++i)
+ {
+ final AlignMatchVector matches =
+ (AlignMatchVector) selected_matches.clone();
+
+ final AlignmentSelectionChangeEvent ev =
+ new AlignmentSelectionChangeEvent(this, matches);
+
+ final AlignmentSelectionChangeListener listener = selection_change_listeners.elementAt(i);
+ listener.alignmentSelectionChanged(ev);
+ }
+
+ if(selected_matches == null)
+ return;
+
+ final int selected_matches_size = selected_matches.size();
+ if(selected_matches != null && selected_matches_size > 0)
+ {
+ // a count of the number of selected matches seen so far
+ int seen_and_selected_count = 0;
+ final int all_matches_length = all_matches.length;
+
+ for(int i = 0 ; i < all_matches_length ; ++i)
+ {
+ final AlignMatch this_match = all_matches[i];
+
+ if(selected_matches.contains(this_match))
+ ++seen_and_selected_count;
+ else
+ {
+ if(seen_and_selected_count > 0)
+ {
+ // move the matches down to fill the gap
+ all_matches[i-seen_and_selected_count] = all_matches[i];
+ }
+ }
+ }
+
+ // put the selected_matches at the end of all_matches
+ for(int i = 0; i < selected_matches_size; ++i)
+ {
+ all_matches[all_matches_length - selected_matches_size + i] =
+ selected_matches.elementAt(i);
+ }
+ }
+
+ repaint();
+ }
+
+ /**
+ * Add the AlignmentSelectionChangeListener to the list of objects
+ * listening for AlignmentSelectionChangeEvents.
+ **/
+ public void
+ addAlignmentSelectionChangeListener(final AlignmentSelectionChangeListener l)
+ {
+ selection_change_listeners.addElement(l);
+ }
+
+ /**
+ * Remove the AlignmentSelectionChangeListener from the list of objects
+ * listening for AlignmentSelectionChangeEvents.
+ **/
+ public void
+ removeAlignmentSelectionChangeListener(final AlignmentSelectionChangeListener l)
+ {
+ selection_change_listeners.removeElement(l);
+ }
+
+ /**
+ * Adds the specified AlignmentEvent listener to receive events from this
+ * object.
+ * @param l the listener.
+ **/
+ public void addAlignmentListener(AlignmentListener l)
+ {
+ alignment_event_listeners.addElement(l);
+ }
+
+ /**
+ * Removes the specified AlignmentEvent listener so that it no longer
+ * receives events from this object.
+ * @param l the listener.
+ **/
+ public void removeAlignmentListener(AlignmentListener l)
+ {
+ alignment_event_listeners.removeElement(l);
+ }
+
+ /**
+ * Returns true if and only if we should offer to flip the query
+ * sequence when the user double clicks on a flipped match.
+ **/
+ public boolean offerToFlip()
+ {
+ return offer_to_flip_flag;
+ }
+
+
+ /**
+ * Set the offscreen buffer to null as part of invalidation.
+ **/
+ public void invalidate()
+ {
+ super.invalidate();
+ offscreen = null;
+ }
+
+
+ /**
+ * The main paint function for the canvas. An off screen image used for
+ * double buffering when drawing the canvas.
+ * @param g The Graphics object of the canvas.
+ **/
+ protected void paintComponent(final Graphics g)
+ {
+ super.paintComponent(g);
+
+ if(last_subject_event != null && last_query_event != null)
+ {
+ final int canvas_height = getSize().height;
+ int canvas_width = getSize().width;
+ if(scroll_bar != null)
+ canvas_width -= scroll_bar.getPreferredSize().width;
+
+ // need to do this off-screen otherwise it
+ // does not draw properly on windows
+ if(offscreen == null)
+ offscreen = createImage(canvas_width, canvas_height);
+
+ Graphics og = offscreen.getGraphics();
+ og.setClip(0,0,canvas_width,canvas_height);
+ og.setColor(Color.white);
+ og.fillRect(0, 0, canvas_width, canvas_height);
+ drawAlignments(og);
+ drawLabels(og);
+ g.drawImage(offscreen, 0, 0, null);
+ og.dispose();
+ }
+ }
+
+
+ /**
+ * The paint function for printing.
+ *
+ * @param g The Graphics object of the canvas.
+ * @param drawLabel draw the labels
+ **/
+ protected void paintComponentForPrint(final Graphics g, final boolean drawLabel)
+ {
+ super.paintComponent(g);
+
+ if(last_subject_event != null && last_query_event != null)
+ {
+ final int canvas_height = getSize().height;
+ int canvas_width = getSize().width;
+ if(scroll_bar != null)
+ canvas_width -= scroll_bar.getPreferredSize().width;
+
+ if(offscreen == null)
+ offscreen = createImage(canvas_width, canvas_height);
+
+ final Graphics og;
+ if(!(g instanceof SVGGraphics2D))
+ {
+ og = offscreen.getGraphics();
+ og.setClip(0,0,canvas_width,canvas_height);
+ }
+ else
+ og = g;
+
+ og.setColor(Color.white);
+ og.fillRect(0, 0, canvas_width, canvas_height);
+ drawAlignments(og);
+ drawLabels(og);
+
+ if(!(g instanceof SVGGraphics2D))
+ {
+ g.drawImage(offscreen, 0, 0, null);
+ og.dispose();
+ }
+ }
+ }
+
+
+ /**
+ * Draw the labels into the given Graphics object. There is a label at the
+ * top left showing info about the current AlignMatch object,
+ *
+ * XXX
+ * a label at
+ * the bottom left showing whether or not the query sequence is reverse
+ * complemented
+ * XXX
+ *
+ * and a label beside the Scrollbar showing the current score
+ * cutoff.
+ **/
+ private void drawLabels(final Graphics g)
+ {
+ final FontMetrics fm = g.getFontMetrics();
+ int canvas_width = getSize().width;
+
+ if(scroll_bar != null)
+ canvas_width -= scroll_bar.getPreferredSize().width;
+
+ final int canvas_height = getSize().height;
+
+ final String cutoff_label =
+ Integer.toString(scroll_bar.getValue());
+
+ final int cutoff_label_width = fm.stringWidth(cutoff_label);
+
+ int cutoff_label_position =
+ (int)((scroll_bar.getValue() -
+ scroll_bar.getMinimum()) / (1.0 *
+ (scroll_bar.getMaximum() -
+ scroll_bar.getMinimum())) * canvas_height);
+
+ if(cutoff_label_position < getFontAscent())
+ cutoff_label_position = getFontAscent();
+
+
+ final int[] cutoff_x_points =
+ {
+ canvas_width - cutoff_label_width,
+ canvas_width - 2,
+ canvas_width - 2,
+ canvas_width - cutoff_label_width,
+ };
+
+ final int[] cutoff_y_points =
+ {
+ cutoff_label_position + 1,
+ cutoff_label_position + 1,
+ cutoff_label_position - getFontAscent(),
+ cutoff_label_position - getFontAscent(),
+ };
+
+ g.setColor(Color.white);
+ g.fillPolygon(cutoff_x_points, cutoff_y_points, 4);
+
+ g.setColor(Color.black);
+ g.drawString(cutoff_label, canvas_width - cutoff_label_width,
+ cutoff_label_position);
+
+ final int font_height = getFontAscent() + getFontDescent();
+
+ if(selected_matches != null)
+ {
+ final String match_string_1;
+
+ if(selected_matches.size() > 1)
+ match_string_1 = selected_matches.size() + " matches selected";
+ else
+ {
+ final AlignMatch selected_align_match = selected_matches.elementAt(0);
+
+ match_string_1 =
+ selected_align_match.getQuerySequenceStart() + ".." +
+ selected_align_match.getQuerySequenceEnd() + " -> " +
+ selected_align_match.getSubjectSequenceStart() + ".." +
+ selected_align_match.getSubjectSequenceEnd();
+ }
+
+ final int match_string_1_width = fm.stringWidth(match_string_1);
+
+ final int[] match_1_x_points =
+ {
+ 0, 0, match_string_1_width, match_string_1_width
+ };
+
+ final int[] match_1_y_points =
+ {
+ 0, font_height, font_height, 0
+ };
+
+ g.setColor(Color.white);
+ g.fillPolygon(match_1_x_points, match_1_y_points, 4);
+
+ g.setColor(Color.black);
+ g.drawString(match_string_1, 0, getFontAscent ());
+
+ if(selected_matches.size() == 1)
+ {
+ final AlignMatch selected_align_match = selected_matches.elementAt(0);
+
+ final String match_string_2 = "score: " +
+ selected_align_match.getScore() + " percent id: " +
+ selected_align_match.getPercentID() + "%";
+
+ final int match_string_2_width = fm.stringWidth(match_string_2);
+
+ final int[] match_2_x_points =
+ {
+ 0, 0, match_string_2_width, match_string_2_width
+ };
+
+ final int[] match_2_y_points =
+ {
+ font_height, font_height * 2, font_height * 2, font_height
+ };
+
+ g.setColor(Color.white);
+ g.fillPolygon(match_2_x_points, match_2_y_points, 4);
+
+ g.setColor(Color.black);
+ g.drawString(match_string_2, 0, getFontAscent() + font_height);
+ }
+ }
+
+ final StringVector status_strings = new StringVector();
+
+ if(displaysAreLocked())
+ status_strings.add("LOCKED");
+
+ if(getSubjectDisplay().isRevCompDisplay())
+ status_strings.add("Subject: Flipped");
+
+ if(getQueryDisplay().isRevCompDisplay())
+ status_strings.add("Query: Flipped");
+
+ if(getOrigSubjectForwardStrand() != getSubjectForwardStrand())
+ status_strings.add("Subject: Reverse Complemented");
+
+ if(getOrigQueryForwardStrand() != getQueryForwardStrand())
+ status_strings.add("Query: Reverse Complemented");
+
+ g.setColor(Color.white);
+
+ for(int i = 0 ; i < status_strings.size() ; ++i)
+ {
+ final String status_string = (String)status_strings.elementAt(i);
+
+ final int status_string_width = fm.stringWidth(status_string);
+
+ final int[] x_points =
+ {
+ 0, 0, status_string_width, status_string_width
+ };
+
+ final int string_offset = font_height * (status_strings.size () - i - 1);
+
+ final int[] y_points =
+ {
+ canvas_height - string_offset,
+ canvas_height - font_height - string_offset,
+ canvas_height - font_height - string_offset,
+ canvas_height - string_offset
+ };
+
+ g.fillPolygon(x_points, y_points, 4);
+ }
+
+ g.setColor(Color.black);
+ for(int i = 0; i < status_strings.size(); ++i)
+ {
+ final String status_string = (String)status_strings.elementAt(i);
+ final int string_offset = font_height * (status_strings.size() - i - 1);
+
+ g.drawString(status_string, 0,
+ canvas_height - string_offset - getFontDescent());
+ }
+ }
+
+ /**
+ * Draw the alignments into the given Graphics object.
+ **/
+ private void drawAlignments(final Graphics g)
+ {
+ final int canvas_height = getSize().height;
+ final int canvas_width = getSize().width;
+ final int OFFSCREEN = 3000;
+
+ final int subject_length = getSubjectForwardStrand().getSequenceLength();
+ final int query_length = getQueryForwardStrand().getSequenceLength();
+
+ final boolean subject_flipped = subjectIsRevComp();
+ final boolean query_flipped = queryIsRevComp();
+
+ final float base_width = last_subject_event.getBaseWidth();
+ final float query_base_width = last_query_event.getBaseWidth();
+
+ final int subject_start = last_subject_event.getStart();
+ final int query_start = last_query_event.getStart();
+ final boolean subject_is_rev_comp = subjectIsRevComp();
+ final boolean query_is_rev_comp = queryIsRevComp();
+ boolean is_rev_match;
+ int[] match_x_positions;
+ AlignMatch this_match;
+ final int all_matches_length = all_matches.length;
+
+ for(int i = 0 ; i < all_matches_length ; ++i)
+ {
+ this_match = all_matches[i];
+
+ is_rev_match = this_match.isRevMatch();
+ match_x_positions =
+ getMatchCoords(canvas_width, this_match, subject_length, query_length,
+ subject_flipped, query_flipped, base_width, query_base_width, subject_start,
+ query_start, subject_is_rev_comp, query_is_rev_comp, is_rev_match);
+
+ if(match_x_positions == null)
+ continue;
+
+ if(!isVisible(this_match))
+ continue;
+
+ final int subject_start_x = match_x_positions[0];
+ final int subject_end_x = match_x_positions[1];
+ final int query_start_x = match_x_positions[2];
+ final int query_end_x = match_x_positions[3];
+
+ final int[] x_coords = new int[4];
+ final int[] y_coords = new int[4];
+
+ x_coords[0] = subject_start_x;
+ y_coords[0] = 0;
+ x_coords[1] = query_start_x;
+ y_coords[1] = canvas_height;
+ x_coords[2] = query_end_x;
+ y_coords[2] = canvas_height;
+ x_coords[3] = subject_end_x;
+ y_coords[3] = 0;
+
+ final boolean highlight_this_match;
+
+ if(selected_matches != null &&
+ selected_matches.contains(this_match))
+ highlight_this_match = true;
+ else
+ highlight_this_match = false;
+
+ final int percent_id = this_match.getPercentID();
+
+ if(highlight_this_match)
+ g.setColor (Color.yellow);
+ else
+ {
+ if(percent_id == -1)
+ {
+ if(is_rev_match)
+ g.setColor(revMatchColour);
+ else
+ g.setColor(matchColour);
+ }
+ else
+ {
+ int colour_index = red_percent_id_colours.length - 1;
+
+ if(maximum_percent_id > minimum_percent_id)
+ {
+ colour_index =
+ (int)(red_percent_id_colours.length * 0.999 *
+ (percent_id - minimum_percent_id) /
+ (maximum_percent_id - minimum_percent_id));
+ }
+
+ if(is_rev_match && !reverseMatchColour)
+ g.setColor(blue_percent_id_colours[colour_index]);
+ else
+ g.setColor(red_percent_id_colours[colour_index]);
+ }
+ }
+
+ g.fillPolygon(x_coords, y_coords, x_coords.length);
+
+ if(subject_end_x - subject_start_x < 5 &&
+ subject_end_x - subject_start_x > -5 ||
+ subject_start_x < -OFFSCREEN ||
+ subject_end_x > OFFSCREEN ||
+ query_start_x < -OFFSCREEN ||
+ query_end_x > OFFSCREEN)
+ {
+ // match is (probably) narrow so draw the border to the same colour as
+ // the polygon
+ }
+ else
+ {
+ // draw a black outline the make the match stand out
+ g.setColor (Color.black);
+ }
+
+ g.drawLine(subject_start_x, 0, query_start_x, canvas_height);
+ g.drawLine(subject_end_x, 0, query_end_x, canvas_height);
+ }
+ }
+
+ /**
+ * Return true if and only if the given match is currently visible.
+ **/
+ private boolean isVisible(final AlignMatch match)
+ {
+ if(ignore_self_match_flag && match.isSelfMatch())
+ return false;
+
+ final int score = match.getScore();
+ if(score > -1)
+ {
+ if(score < minimum_score || score > maximum_score)
+ return false;
+ }
+
+ final int percent_id = match.getPercentID();
+ if(percent_id > -1)
+ {
+ if(percent_id < minimum_percent_id || percent_id > maximum_percent_id)
+ return false;
+ }
+
+ final int match_length = match.getLength();
+// Math.abs(match.getSubjectSequenceStart() -
+// match.getSubjectSequenceEnd());
+
+ if(match_length < scroll_bar.getValue())
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Return the start position in the subject sequence of the given
+ * AlignMatch, taking into account the current orientation of the
+ * sequences. If the reverse_position argument is true reverse the
+ * complemented match coordinates will be returned.
+ **/
+ protected static int getRealSubjectSequenceStart(final AlignMatch match,
+ final int sequence_length,
+ final boolean reverse_position)
+ {
+ if(reverse_position)
+ return sequence_length - match.getSubjectSequenceStart() + 1;
+ else
+ return match.getSubjectSequenceStart();
+ }
+
+ /**
+ * Return the end position in the subject sequence of the given AlignMatch,
+ * taking into account the current orientation of the sequences. If the
+ * reverse_position argument is true reverse the complemented match
+ * coordinates will be returned.
+ **/
+ protected static int getRealSubjectSequenceEnd(final AlignMatch match,
+ final int sequence_length,
+ final boolean reverse_position)
+ {
+ if(reverse_position)
+ return sequence_length - match.getSubjectSequenceEnd() + 1;
+ else
+ return match.getSubjectSequenceEnd();
+ }
+
+ /**
+ * Return the start position in the query sequence of the given AlignMatch,
+ * taking into account the current orientation of the sequences. If the
+ * reverse_position argument is true reverse the complemented match
+ * coordinates will be returned.
+ **/
+ protected static int getRealQuerySequenceStart(final AlignMatch match,
+ final int sequence_length,
+ final boolean reverse_position)
+ {
+ if(reverse_position)
+ return sequence_length - match.getQuerySequenceStart () + 1;
+ else
+ return match.getQuerySequenceStart ();
+ }
+
+ /**
+ * Return the end position in the query sequence of the given AlignMatch,
+ * taking into account the current orientation of the sequences. If the
+ * reverse_position argument is true reverse the complemented match
+ * coordinates will be returned.
+ **/
+ protected static int getRealQuerySequenceEnd(final AlignMatch match,
+ final int sequence_length,
+ final boolean reverse_position)
+ {
+ if(reverse_position)
+ return sequence_length - match.getQuerySequenceEnd() + 1;
+ else
+ return match.getQuerySequenceEnd();
+ }
+
+ /**
+ * Remove AlignMatch from the all_matches array
+ * @param collection of indexes to be removed from the array
+ */
+ private void removeMatches(Vector<Integer> index)
+ {
+ AlignMatch tmp_matches[] = new AlignMatch[all_matches.length - index.size()];
+ int start_old = 0;
+ int curr_index = 0;
+
+ for(int i=0; i<index.size(); i++)
+ {
+ curr_index = ( index.get(i) ).intValue();
+
+ if(curr_index !=0)
+ System.arraycopy(all_matches, start_old, tmp_matches,
+ start_old-i, curr_index-start_old);
+ start_old = curr_index+1;
+ }
+
+ if(start_old < all_matches.length)
+ System.arraycopy(all_matches, start_old, tmp_matches,
+ start_old-index.size(),
+ all_matches.length-start_old);
+
+ this.all_matches = new AlignMatch[tmp_matches.length];
+ this.all_matches = tmp_matches;
+ }
+
+ /**
+ * Used in contig reordering.
+ * Split alignment matches that go over boundaries of contig
+ * that is being moved. If the match contains gaps (i.e. subject
+ * and query length of match not same length) then we don't know
+ * where to split so these are deleted.
+ *
+ * @param subject true if contig is on the subject sequence
+ * @param start of the contig
+ * @param end of the contig
+ * @param drop_position position where the contig is moving to
+ */
+ private void splitMatches(final boolean subject,
+ final int start, final int end,
+ final int drop_position)
+ {
+ int curr_index;
+ int match_start;
+ int match_end;
+ int split_at;
+ int delete_overlaps = -1;
+
+ Vector<Integer> matches_to_split = new Vector<Integer>();
+ Vector<Integer> removals = new Vector<Integer>();
+
+ for(int i=0; i<all_matches.length; i++)
+ {
+ if(subject)
+ {
+ match_start = all_matches[i].getSubjectSequenceStart();
+ match_end = all_matches[i].getSubjectSequenceEnd();
+ }
+ else
+ {
+ match_start = all_matches[i].getQuerySequenceStart();
+ match_end = all_matches[i].getQuerySequenceEnd();
+ }
+
+ // catch matches that span 2 contigs that need moving
+ if( (match_start < start && match_end >= start) ||
+ (match_start <= end && match_end > end) ||
+ (match_start < drop_position && match_end >= drop_position) )
+ {
+ // check query and subject ranges same length
+ //
+ if( (all_matches[i].getQuerySequenceEnd()-
+ all_matches[i].getQuerySequenceStart()) !=
+ (all_matches[i].getSubjectSequenceStart()-
+ all_matches[i].getSubjectSequenceEnd()) )
+ {
+ // this match extends past end of contig
+ if(delete_overlaps == -1)
+ {
+ Range q_range = all_matches[i].getQuerySequenceRange();
+ Range s_range = all_matches[i].getSubjectSequenceRange();
+ delete_overlaps = JOptionPane.showConfirmDialog(null,
+ "Found a match that extends past the boundary of a contig\n"+
+ "with query and subject ranges of different lengths:\n"+
+ q_range.toString()+
+ " ("+q_range.getCount()+")\n"+
+ s_range.toString()+
+ " ("+s_range.getCount()+")\n"+
+ "Delete all such matches?",
+ "Delete Overlapping Matches",
+ JOptionPane.YES_NO_OPTION);
+ }
+
+ if(delete_overlaps == JOptionPane.YES_OPTION)
+ removals.add(new Integer(i));
+ }
+ else
+ matches_to_split.add(new Integer(i));
+ }
+ }
+
+ // now split the matches
+ AlignMatch tmp_matches[] = new AlignMatch[all_matches.length+
+ matches_to_split.size()];
+ System.arraycopy(all_matches, 0, tmp_matches,
+ 0, all_matches.length);
+ int tmp_match_start;
+
+ for(int i=0; i<matches_to_split.size(); i++)
+ {
+ curr_index = matches_to_split.get(i).intValue();
+
+ //
+ if(subject)
+ {
+ match_start = tmp_matches[curr_index].getSubjectSequenceStart();
+ match_end = tmp_matches[curr_index].getSubjectSequenceEnd();
+ }
+ else
+ {
+ match_start = tmp_matches[curr_index].getQuerySequenceStart();
+ match_end = tmp_matches[curr_index].getQuerySequenceEnd();
+ }
+
+ if(match_start <= start && match_end >= start)
+ split_at = start-1;
+ else if(match_start <= end && match_end >= end)
+ split_at = end;
+ else
+ split_at = drop_position-1;
+
+ tmp_matches[curr_index].setRange(match_start, split_at, subject, false);
+ tmp_matches[all_matches.length+i] = AlignMatch.copy(tmp_matches[curr_index]);
+ tmp_matches[all_matches.length+i].setRange(split_at+1, match_end, subject, false);
+
+ tmp_match_start = match_start;
+ //
+ if(!subject)
+ {
+ match_start = tmp_matches[curr_index].getSubjectSequenceStart();
+ match_end = tmp_matches[curr_index].getSubjectSequenceEnd();
+ }
+ else
+ {
+ match_start = tmp_matches[curr_index].getQuerySequenceStart();
+ match_end = tmp_matches[curr_index].getQuerySequenceEnd();
+ }
+
+ split_at = match_start+(tmp_match_start-split_at);
+ tmp_matches[curr_index].setRange(match_start, split_at, !subject, false);
+
+ if(tmp_matches[curr_index].isRevMatch())
+ split_at--;
+ else
+ split_at++;
+
+ tmp_matches[all_matches.length+i].setRange(split_at, match_end, !subject, false);
+ }
+
+ this.all_matches = new AlignMatch[tmp_matches.length];
+ this.all_matches = tmp_matches;
+
+ if(removals.size() > 0)
+ removeMatches(removals);
+ }
+
+ /**
+ * Reorder matches on reordering contigs
+ *
+ * @param subject true if reordering the subject
+ * @param start of the contig
+ * @param end of the contig
+ */
+ protected void reorder(boolean subject, final int start, final int end,
+ final int drop_position)
+ {
+ // find matches that cross contig boundary
+ splitMatches(subject,start,end,drop_position);
+
+ int match_start;
+ int match_end;
+ //int delete_overlaps = -1;
+
+ //Vector removals = new Vector();
+
+ for(int i = 0; i < all_matches.length; ++i)
+ {
+ if(subject)
+ {
+ match_start = all_matches[i].getSubjectSequenceStart();
+ match_end = all_matches[i].getSubjectSequenceEnd();
+ }
+ else
+ {
+ match_start = all_matches[i].getQuerySequenceStart();
+ match_end = all_matches[i].getQuerySequenceEnd();
+ }
+
+ if(match_start >= start || match_start>=drop_position)
+ {
+ if(drop_position < start)
+ {
+ if(match_start <= start &&
+ match_end < start)
+ {
+ match_start = match_start + (end-start+1);
+ match_end = match_end + (end-start+1);
+ all_matches[i].setRange(match_start, match_end, subject, false);
+ }
+ else if(match_start >= start && match_start <= end && // within contig
+ match_end >= start && match_end <= end)
+ {
+ match_start = match_start - (start - drop_position);
+ match_end = match_end - (start - drop_position);
+ all_matches[i].setRange(match_start, match_end, subject, false);
+ }
+ }
+ else
+ {
+ if(match_start < end && // within contig
+ match_end <= end)
+ {
+ match_start = match_start + (drop_position-end-1);
+ match_end = match_end + (drop_position-end-1);
+ all_matches[i].setRange(match_start, match_end, subject, false);
+ }
+ else if(match_start > end && match_start < drop_position &&
+ match_end > end && match_end < drop_position)
+ {
+ match_start = match_start - (end-start+1);
+ match_end = match_end - (end-start+1);
+ all_matches[i].setRange(match_start, match_end, subject, false);
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Flip the matches within a contig and deleted those that overlap
+ * with other contigs
+ * @param subject true if flipping the subject
+ * @param start of the contig
+ * @param end of the contig
+ */
+ protected void flippingContig(boolean subject, int start, int end)
+ {
+ int match_start;
+ int match_end;
+ int delete_overlaps = -1;
+ Vector<Integer> removals = new Vector<Integer>();
+
+ for(int i = 0; i < all_matches.length; ++i)
+ {
+ if(subject)
+ {
+ match_start = all_matches[i].getSubjectSequenceStart();
+ match_end = all_matches[i].getSubjectSequenceEnd();
+
+ if(match_start >= start &&
+ match_end <= end)
+ {
+ match_start = end - (match_start - start);
+ match_end = end - (match_end -start);
+ all_matches[i].setRange(match_start, match_end, subject, true);
+ }
+ else if( (match_start >= start && match_start <= end) ||
+ (match_end <= end && match_end >= start) )
+ {
+ // this match extends past end of contig
+ if(delete_overlaps == -1)
+ delete_overlaps = JOptionPane.showConfirmDialog(null,
+ "Found a match extending past the boundary of the contig:\n"+
+ match_start+".."+match_end+
+ "\nDelete all such matches?",
+ "Delete Overlapping Matches",
+ JOptionPane.YES_NO_OPTION);
+
+
+ if(delete_overlaps == JOptionPane.YES_OPTION)
+ removals.add(new Integer(i));
+ }
+ }
+ else
+ {
+ match_start = all_matches[i].getQuerySequenceStart();
+ match_end = all_matches[i].getQuerySequenceEnd();
+
+ if(match_start >= start &&
+ match_end <= end)
+ {
+ match_start = end - (match_start - start);
+ match_end = end - (match_end -start);
+ all_matches[i].setRange(match_start, match_end, subject, true);
+ }
+ else if( (match_start >= start && match_start <= end) ||
+ (match_end <= end && match_end >= start) )
+ {
+ // this match extends past end of contig
+ if(delete_overlaps == -1)
+ delete_overlaps = JOptionPane.showConfirmDialog(null,
+ "Found a match extending past the boundary of the contig:\n"+
+ match_start+".."+match_end+
+ "\nDelete all such matches?",
+ "Delete Overlapping Matches",
+ JOptionPane.YES_NO_OPTION);
+
+
+ if(delete_overlaps == JOptionPane.YES_OPTION)
+ removals.add(new Integer(i));
+ }
+ }
+ }
+
+ if(removals.size() > 0)
+ removeMatches(removals);
+ }
+
+ /**
+ *
+ * Find regions where there are no matches.
+ * @return vector of coordinates of regions where there are
+ * no matches.
+ *
+ */
+ protected Vector<Integer[]> getDifferenceCoords(boolean subject)
+ {
+ final int length;
+ final boolean flipped;
+
+ if(subject)
+ {
+ flipped = subjectIsRevComp();
+ length = getSubjectForwardStrand().getSequenceLength();
+ }
+ else
+ {
+ flipped = queryIsRevComp();
+ length = getQueryForwardStrand().getSequenceLength();
+ }
+
+ AlignMatchComparator comparator = new AlignMatchComparator(subject, length,
+ flipped);
+ int imatch = 0;
+ final AlignMatch[] sorted_all_matches = new AlignMatch[all_matches.length];
+ for(int i = 0; i < sorted_all_matches.length; ++i)
+ {
+ if(isVisible(all_matches[i]))
+ sorted_all_matches[imatch++] = AlignMatch.copy(all_matches[i]);
+ }
+
+ // sort alignment matches based on where they start
+ Arrays.sort(sorted_all_matches, 0, imatch, comparator);
+
+ int start = 1;
+ Vector<Integer[]> differences = new Vector<Integer[]>();
+
+ // find & record regions of no match
+ for(int i = 0; i < imatch; ++i)
+ {
+ Integer coords[];
+ final AlignMatch this_match = sorted_all_matches[i];
+
+ if(this_match == null)
+ continue;
+
+ int this_start;
+ int this_end;
+
+ if(subject)
+ {
+ this_start = getRealSubjectSequenceStart(this_match,
+ length, flipped);
+ this_end = getRealSubjectSequenceEnd(this_match,
+ length, flipped);
+ }
+ else
+ {
+ this_start = getRealQuerySequenceStart(this_match,
+ length, flipped);
+ this_end = getRealQuerySequenceEnd(this_match,
+ length, flipped);
+ }
+
+ if(this_start > this_end)
+ {
+ int tmp_this_start = this_start;
+ this_start = this_end;
+ this_end = tmp_this_start;
+ }
+
+ if(i == 0 && this_start > 1)
+ {
+ coords = new Integer[2];
+ coords[0] = new Integer(start);
+ coords[1] = new Integer(this_start-1);
+ differences.add(coords);
+ }
+
+ if(this_end > start)
+ start = this_end;
+
+ if(i < imatch-1)
+ {
+ int next_start = 0;
+
+ // quicksort doesn't get it quite right so check
+ // start position of several ahead
+ for(int j=1; j<11; j++)
+ {
+ if(i+j > imatch-1)
+ continue;
+
+ final AlignMatch next_match = sorted_all_matches[i+j];
+ int jstart;
+ int jend;
+
+ if(subject)
+ {
+ jstart = getRealSubjectSequenceStart(next_match, length, flipped);
+ jend = getRealSubjectSequenceEnd(next_match, length, flipped);
+ }
+ else
+ {
+ jstart = getRealQuerySequenceStart(next_match, length, flipped);
+ jend = getRealQuerySequenceEnd(next_match, length, flipped);
+ }
+
+ if(jend < jstart)
+ jstart = jend;
+
+ if(j == 1 || jstart < next_start)
+ next_start = jstart;
+ }
+
+ if(next_start > start+1)
+ {
+ coords = new Integer[2];
+ coords[0] = new Integer(start+1);
+ coords[1] = new Integer(next_start-1);
+ differences.add(coords);
+ }
+ }
+ else if(i == imatch-1 &&
+ this_end < length &&
+ start+1 < length)
+ {
+ coords = new Integer[2];
+ coords[0] = new Integer(start+1);
+ coords[1] = new Integer(length);
+ differences.add(coords);
+ }
+ }
+
+ return differences;
+ }
+
+ /**
+ * Return the screen x positions of the corners of the given match. The
+ * order is Top Left, Top Right, Bottom Left, Bottom Right, unless the
+ * match is an inversion, in which case it will be TL,TR,BR,BL. Returns
+ * null if and only if the match is not currently visible.
+ **/
+ private int[] getMatchCoords(final int canvas_width, final AlignMatch this_match,
+ final int subject_length, final int query_length,
+ final boolean subject_flipped, final boolean query_flipped,
+ final float base_width, final float query_base_width,
+ final int subject_start, final int query_start,
+ final boolean subject_is_rev_comp, final boolean query_is_rev_comp,
+ final boolean is_rev_match)
+ {
+ int subject_sequence_start =
+ getRealSubjectSequenceStart(this_match,
+ subject_length, subject_flipped);
+ int subject_sequence_end =
+ getRealSubjectSequenceEnd(this_match,
+ subject_length, subject_flipped);
+ int query_sequence_start =
+ getRealQuerySequenceStart(this_match,
+ query_length, query_flipped);
+ int query_sequence_end =
+ getRealQuerySequenceEnd(this_match,
+ query_length, query_flipped);
+
+ // add one base because we want to draw to the end of the base
+ if(subject_is_rev_comp)
+ subject_sequence_start += 1;
+ else
+ subject_sequence_end += 1;
+
+ if(is_rev_match && !query_is_rev_comp ||
+ !is_rev_match && query_is_rev_comp)
+ query_sequence_start += 1;
+ else
+ query_sequence_end += 1;
+
+ // this is the base that is at the left of the screen
+ final int subject_start_x = getScreenPosition(base_width, subject_sequence_start,
+ subject_start);
+ final int subject_end_x = getScreenPosition(base_width, subject_sequence_end,
+ subject_start);
+
+ final int query_start_x = getScreenPosition(query_base_width, query_sequence_start,
+ query_start);
+ final int query_end_x = getScreenPosition(query_base_width, query_sequence_end,
+ query_start);
+
+ boolean subject_off_left = false;
+ boolean subject_off_right = false;
+ boolean query_off_left = false;
+ boolean query_off_right = false;
+
+ if(subject_start_x < 0 && subject_end_x < 0)
+ subject_off_left = true;
+
+ if(subject_start_x >= canvas_width && subject_end_x >= canvas_width)
+ subject_off_right = true;
+
+ if(query_start_x < 0 && query_end_x < 0)
+ query_off_left = true;
+
+ if(query_start_x >= canvas_width && query_end_x >= canvas_width)
+ query_off_right = true;
+
+ if((subject_off_left ? 1 : 0) +
+ (query_off_left ? 1 : 0) +
+ (subject_off_right ? 1 : 0) +
+ (query_off_right ? 1 : 0) == 2)
+ return null;
+ else
+ {
+ final int[] return_values = new int[4];
+
+ return_values[0] = subject_start_x;
+ return_values[1] = subject_end_x;
+ return_values[2] = query_start_x;
+ return_values[3] = query_end_x;
+
+ return return_values;
+ }
+ }
+
+ /**
+ * Return the current forward Strand of the subject EntryGroup.
+ **/
+ private Strand getSubjectForwardStrand()
+ {
+ return getSubjectEntryGroup().getBases().getForwardStrand();
+ }
+
+ /**
+ * Return the current forward Strand of the query EntryGroup.
+ **/
+ private Strand getQueryForwardStrand()
+ {
+ return getQueryEntryGroup().getBases().getForwardStrand();
+ }
+
+ /**
+ * Return the subject EntryGroup that was passed to the constructor.
+ **/
+ protected EntryGroup getSubjectEntryGroup()
+ {
+ return subject_entry_group;
+ }
+
+ /**
+ * Return the subject EntryGroup that was passed to the constructor.
+ **/
+ protected EntryGroup getQueryEntryGroup()
+ {
+ return query_entry_group;
+ }
+
+ /**
+ * Return the reference of the subject FeatureDisplay.
+ **/
+ private FeatureDisplay getSubjectDisplay()
+ {
+ return subject_feature_display;
+ }
+
+ /**
+ * Return the reference of the query FeatureDisplay.
+ **/
+ private FeatureDisplay getQueryDisplay()
+ {
+ return query_feature_display;
+ }
+
+ /**
+ * Returns true if and only if the subject sequence has been flipped since
+ * this object was created.
+ **/
+ protected boolean subjectIsRevComp()
+ {
+ final Strand current_subject_forward_strand = getSubjectForwardStrand();
+
+ if(getOrigSubjectForwardStrand() == current_subject_forward_strand ^
+ getSubjectDisplay().isRevCompDisplay())
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Returns true if and only if the query sequence has been flipped since
+ * this object was created.
+ **/
+ protected boolean queryIsRevComp()
+ {
+ final Strand current_query_forward_strand = getQueryForwardStrand ();
+
+ if(getOrigQueryForwardStrand() == current_query_forward_strand ^
+ getQueryDisplay().isRevCompDisplay())
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Return the forward Strand of the subject EntryGroup from when the
+ * Comparator was created.
+ **/
+ private Strand getOrigSubjectForwardStrand()
+ {
+ return orig_subject_forward_strand;
+ }
+
+ /**
+ * Return the forward Strand of the query EntryGroup from when the
+ * Comparator was created.
+ **/
+ private Strand getOrigQueryForwardStrand()
+ {
+ return orig_query_forward_strand;
+ }
+
+ /**
+ * Arrange for the two FeatureDisplay objects to scroll in parallel.
+ **/
+ protected void lockDisplays()
+ {
+ displays_are_locked = true;
+ repaint();
+ }
+
+ /**
+ * Arrange for the two FeatureDisplay objects to scroll independently.
+ **/
+ protected void unlockDisplays()
+ {
+ displays_are_locked = false;
+ repaint();
+ }
+
+ /**
+ * Return true if and only if the displays are currently locked.
+ **/
+ protected boolean displaysAreLocked()
+ {
+ return displays_are_locked;
+ }
+
+ /**
+ * Toggle whether the two displays are locked.
+ **/
+ protected void toggleDisplayLock()
+ {
+ displays_are_locked = !displays_are_locked;
+ repaint();
+ }
+
+ /**
+ * Calling this method will temporarily disable selectFromQueryRange() and
+ * selectFromSubjectRange() until enableSelection(). This is need to allow
+ * the selections of the top and bottom FeatureDisplays to be set without
+ * changing which AlignMatches are selected.
+ **/
+ protected void disableSelection()
+ {
+ disable_selection_from_ranges = true;
+ }
+
+ /**
+ * Enable selectFromQueryRange() and selectFromSubjectRange().
+ **/
+ protected void enableSelection()
+ {
+ disable_selection_from_ranges = false;
+ }
+
+ /**
+ * Convert a base position into a screen x coordinate.
+ **/
+ private int getScreenPosition(final float base_width,
+ final int base_position,
+ final int screen_start_base)
+ {
+ final float base_pos = base_width *
+ (base_position - screen_start_base);
+ if(base_pos > 30000)
+ return 30000;
+ else if(base_pos < -30000)
+ return -30000;
+ else
+ return (int)base_pos;
+ }
+
+ /**
+ * Return an array of colours that will be used for colouring the matches
+ * (depending on score).
+ **/
+ private void makeColours()
+ {
+ red_percent_id_colours = new Color[NUMBER_OF_SHADES];
+ blue_percent_id_colours = new Color[NUMBER_OF_SHADES];
+
+ for(int i = 0; i < blue_percent_id_colours.length; ++i)
+ {
+ final int shade_value = 255 - (int) (256 * i / NUMBER_OF_SHADES);
+ red_percent_id_colours[i] = new Color (255, shade_value, shade_value);
+ blue_percent_id_colours[i] = new Color (shade_value, shade_value, 255);
+ }
+ }
+
+ /**
+ * Return the ComparisonData object that was passed to the constructor.
+ **/
+ private ComparisonData getComparisonData ()
+ {
+ return comparison_data;
+ }
+
+ public class AlignMatchComparator implements Comparator<AlignMatch>
+ {
+ final private boolean subject;
+ final private int length;
+ final private boolean flipped;
+
+ public AlignMatchComparator(boolean subject, int length,
+ boolean flipped)
+ {
+ this.subject = subject;
+ this.length = length;
+ this.flipped = flipped;
+ }
+
+ public int compare(AlignMatch c1,AlignMatch c2) throws ClassCastException
+ {
+ int start1;
+ int start2;
+
+ if(subject)
+ {
+ start1 = getRealSubjectSequenceStart(c1, length, flipped);
+ int end1 = getRealSubjectSequenceEnd(c1, length, flipped);
+ if(end1 < start1)
+ start1 = end1;
+
+ start2 = getRealSubjectSequenceStart(c2, length, flipped);
+ int end2 = getRealSubjectSequenceEnd(c2, length, flipped);
+ if(end2 < start2)
+ start2 = end2;
+ }
+ else
+ {
+ start1 = getRealQuerySequenceStart(c1, length, flipped);
+ int end1 = getRealQuerySequenceEnd(c1, length, flipped);
+ if(end1 < start1)
+ start1 = end1;
+
+ start2 = getRealQuerySequenceStart(c2, length, flipped);
+ int end2 = getRealQuerySequenceEnd(c2, length, flipped);
+ if(end2 < start2)
+ start2 = end2;
+ }
+
+ if(start1 < start2)
+ return -1;
+ else if(start1 > start2)
+ return 1;
+ else
+ return 0;
+ }
+ }
+
+ public class ColorChooserShades extends JPanel
+ implements javax.swing.event.ChangeListener
+ {
+ private static final long serialVersionUID = 1L;
+ private JPanel bannerPanel = new JPanel(new BorderLayout());
+ private JColorChooser tcc;
+ private Color definedColour[] = new Color[NUMBER_OF_SHADES];
+ private JLabel banner[] = new JLabel[NUMBER_OF_SHADES];
+ private JSlider scaleColour;
+ private Box bacross = Box.createHorizontalBox();
+
+ public ColorChooserShades(String title, Color initialColour)
+ {
+ super(new BorderLayout());
+
+ Dimension d = new Dimension(35, 35);
+ double fract = (maximum_percent_id - minimum_percent_id)/
+ (NUMBER_OF_SHADES* 0.999);
+
+ for(int i = 0; i < NUMBER_OF_SHADES; ++i)
+ {
+ int percent_id = (int)(i*fract)+minimum_percent_id;
+ banner[i] = new JLabel(" "+percent_id+" ",JLabel.CENTER);
+ banner[i].setOpaque(true);
+ banner[i].setPreferredSize(d);
+ bacross.add(banner[i]);
+ }
+
+ //Set up color chooser for setting text color
+ tcc = new JColorChooser(initialColour);
+ tcc.getSelectionModel().addChangeListener(ColorChooserShades.this);
+ tcc.setBorder(BorderFactory.createTitledBorder(title));
+
+ //set scale
+ scaleColour = new JSlider(0,20,3);
+ scaleColour.addChangeListener(ColorChooserShades.this);
+
+ makeColours();
+
+ //Set up the banner at the top of the window
+ colourBox();
+ bannerPanel.add(bacross, BorderLayout.CENTER);
+ bannerPanel.add(scaleColour, BorderLayout.SOUTH);
+ bannerPanel.setBorder(BorderFactory.createTitledBorder("% ID Scale"));
+
+ add(bannerPanel, BorderLayout.CENTER);
+ add(tcc, BorderLayout.SOUTH);
+ }
+
+ public void stateChanged(javax.swing.event.ChangeEvent e)
+ {
+ makeColours();
+ colourBox();
+ bannerPanel.repaint();
+ repaint();
+ }
+
+ private void colourBox()
+ {
+ for(int i = 0; i < NUMBER_OF_SHADES; ++i)
+ {
+ banner[i].setBackground(definedColour[i]);
+ banner[i].repaint();
+ }
+ }
+
+ /**
+ * Return an array of colours that will be used for colouring the matches
+ * (depending on score).
+ **/
+ private void makeColours()
+ {
+ Color newColour = tcc.getColor();
+
+ for(int i = 0; i < NUMBER_OF_SHADES; ++i)
+ {
+ int R = newColour.getRed();
+ int G = newColour.getGreen();
+ int B = newColour.getBlue();
+
+ int scale = (NUMBER_OF_SHADES-i)*scaleColour.getValue()*5;
+ if((R+scale) < 255)
+ R += scale;
+ if((G+scale) < 255)
+ G += scale;
+ if((B+scale) < 255)
+ B += scale;
+
+ definedColour[i] = new Color(R,G,B);
+ }
+ }
+
+ public Color[] getDefinedColour()
+ {
+ return definedColour;
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/ArtemisMain.java b/uk/ac/sanger/artemis/components/ArtemisMain.java
new file mode 100644
index 0000000..44f18d5
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ArtemisMain.java
@@ -0,0 +1,795 @@
+/* ArtemisMain.java
+ *
+ * created: Wed Feb 23 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ArtemisMain.java,v 1.33 2008-12-10 16:43:38 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.components.database.DatabaseEntrySource;
+import uk.ac.sanger.artemis.components.database.DatabaseJPanel;
+import uk.ac.sanger.artemis.components.filetree.FileManager;
+import uk.ac.sanger.artemis.components.filetree.LocalAndRemoteFileManager;
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.TextDocument;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.io.EntryInformation;
+
+import org.biojava.bio.seq.io.SequenceFormat;
+
+import java.awt.event.*;
+import java.awt.Toolkit;
+import java.io.*;
+import java.util.Vector;
+import java.awt.datatransfer.*;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+/**
+ * The main window for the Artemis sequence editor.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ArtemisMain.java,v 1.33 2008-12-10 16:43:38 tjc Exp $
+ **/
+
+public class ArtemisMain extends Splash
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /** A vector containing all EntryEdit object we have created. */
+ private Vector<EntryEdit> entry_edit_objects = new Vector<EntryEdit>();
+
+ protected static FileManager filemanager = null;
+
+ private LocalAndRemoteFileManager fm;
+
+ /**
+ * The constructor creates all the components for the main Artemis
+ * window and sets up all the menu callbacks.
+ **/
+ public ArtemisMain(final String args[])
+ {
+ super("Artemis", "Artemis", "15");
+
+ makeMenuItem(file_menu, "Open Project Manager ...", new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ new ProjectProperty(ArtemisMain.this);
+ }
+ });
+
+ ActionListener menu_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(filemanager == null)
+ filemanager = new FileManager(ArtemisMain.this);
+ else
+ filemanager.setVisible(true);
+ }
+ };
+ makeMenuItem(file_menu, "Open File Manager ...", menu_listener);
+
+ ActionListener menu_listener_ssh = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(fm == null)
+ fm = new LocalAndRemoteFileManager(ArtemisMain.this);
+ else
+ fm.setVisible(true);
+ }
+ };
+
+ if(System.getProperty("chado") != null)
+ makeMenuItem(file_menu, "Open Database and SSH File Manager ...", menu_listener_ssh);
+ else
+ makeMenuItem(file_menu, "Open SSH File Manager ...", menu_listener_ssh);
+
+
+ final EntrySourceVector entry_sources = getEntrySources(this);
+
+ for(int source_index=0; source_index<entry_sources.size();
+ ++source_index)
+ {
+ final EntrySource this_entry_source =
+ entry_sources.elementAt(source_index);
+
+ String entry_source_name = this_entry_source.getSourceName();
+ String menu_name = null;
+
+ if(entry_source_name.equals("Filesystem"))
+ menu_name = "Open ...";
+ else
+ menu_name = "Open from " + entry_source_name + " ...";
+
+ menu_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ getEntryEditFromEntrySource(this_entry_source);
+ }
+ };
+ makeMenuItem(file_menu, menu_name, menu_listener);
+ }
+
+ menu_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ launchDatabaseJFrame();
+ }
+ };
+
+ //final boolean sanger_options =
+ // Options.getOptions().getPropertyTruthValue("sanger_options");
+
+ //makeMenuItem(file_menu, "Database Entry ...", menu_listener);
+
+ menu_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ exit();
+ }
+ };
+ makeMenuItem(file_menu, "Quit", menu_listener);
+
+ getCanvas().addMouseListener(new MouseAdapter()
+ {
+ /**
+ * Listen for mouse press events so that we can do popup menus and
+ * selection.
+ **/
+ public void mousePressed(MouseEvent event)
+ {
+ handleCanvasMousePress(event);
+ }
+ });
+ }
+
+
+ /**
+ * Handle a mouse press event on the drawing canvas - select on click,
+ * select and broadcast it on double click.
+ **/
+ private void handleCanvasMousePress(MouseEvent event)
+ {
+ if(event.getID() != MouseEvent.MOUSE_PRESSED)
+ return;
+
+ if((event.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
+ {
+ openClipboardContents();
+ }
+ }
+
+ /**
+ * Get the String residing on the clipboard.
+ *
+ * @return any text found on the Clipboard; if none found, return an
+ * empty String.
+ */
+ public void openClipboardContents()
+ {
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ //odd: the Object param of getContents is not currently used
+ Transferable contents = clipboard.getContents(null);
+ boolean hasTransferableText = (contents != null) &&
+ contents.isDataFlavorSupported(DataFlavor.stringFlavor);
+ if(hasTransferableText)
+ {
+ TextDocument entry_document = new TextDocument();
+ final InputStreamProgressListener progress_listener =
+ getInputStreamProgressListener();
+
+ entry_document.addInputStreamProgressListener(progress_listener);
+
+ final EntryInformation artemis_entry_information =
+ Options.getArtemisEntryInformation();
+
+ final uk.ac.sanger.artemis.io.Entry new_embl_entry =
+ EntryFileDialog.getEntryFromFile(this, entry_document,
+ artemis_entry_information,
+ false);
+
+ if(new_embl_entry == null) // the read failed
+ return;
+
+ try
+ {
+ final Entry entry = new Entry(new_embl_entry);
+ EntryEdit last_entry_edit = makeEntryEdit(entry);
+ addEntryEdit(last_entry_edit);
+ getStatusLabel().setText("");
+ last_entry_edit.setVisible(true);
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(this, "read failed: one of the features in " +
+ " cut and paste has an out of range " +
+ "location: " + e.getMessage());
+ }
+ catch(NoSequenceException e)
+ {
+ new MessageDialog(this, "read failed: " +
+ " cut and paste contains no sequence");
+ }
+ }
+ }
+
+
+ /**
+ *
+ * Launch database manager window
+ *
+ */
+ private void launchDatabaseJFrame()
+ {
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ getStatusLabel().setText("Connecting ...");
+ DatabaseEntrySource entry_source = new DatabaseEntrySource();
+ boolean promptUser = true;
+ if(System.getProperty("read_only") != null)
+ promptUser = false;
+
+ if(!entry_source.setLocation(promptUser))
+ return null;
+
+ JFrame frame = new JFrame("Organism List");
+ final DatabaseJPanel pane = new DatabaseJPanel(entry_source,
+ ArtemisMain.this);
+ frame.getContentPane().add(pane);
+ frame.pack();
+ Utilities.rightJustifyFrame(frame);
+ frame.setVisible(true);
+ //frame.setJMenuBar(pane.makeMenuBar(entry_source, ArtemisMain.this));
+ getStatusLabel().setText("");
+ return null;
+ }
+ };
+ entryWorker.start();
+ }
+
+ /**
+ * Read the entries named in args and in the diana.ini file.
+ **/
+ protected void readArgsAndOptions(final String [] args, final JFrame f)
+ {
+ processJnlpAttributes();
+ if(args.length == 0)
+ {
+ if(System.getProperty("chado") != null &&
+ (args.length < 1 || args[0].indexOf(':') == -1))
+ fm = new LocalAndRemoteFileManager(ArtemisMain.this);
+
+ // open the entries given in the options file(diana.ini)
+ readDefaultEntries();
+ return;
+ }
+
+ if(args[0].equals("-biojava"))
+ {
+ handleBioJava(args);
+ return;
+ }
+
+ final EntryInformation artemis_entry_information =
+ Options.getArtemisEntryInformation();
+
+ EntryEdit last_entry_edit = null;
+ boolean seen_plus = false;
+
+ for(int i = 0 ; i<args.length ; ++i)
+ {
+ String new_entry_name = args[i];
+
+ if(new_entry_name.length() == 0)
+ continue;
+
+ if(new_entry_name.equals("+"))
+ {
+ seen_plus = true;
+ continue;
+ }
+
+ if(new_entry_name.startsWith("+") && last_entry_edit != null ||
+ seen_plus)
+ {
+ // new feature file
+
+ final Document entry_document;
+
+ if(seen_plus)
+ entry_document = DocumentFactory.makeDocument(new_entry_name);
+ else
+ entry_document =
+ DocumentFactory.makeDocument(new_entry_name.substring(1));
+
+ final InputStreamProgressListener progress_listener =
+ getInputStreamProgressListener();
+
+ entry_document.addInputStreamProgressListener(progress_listener);
+
+ final uk.ac.sanger.artemis.io.Entry new_embl_entry =
+ EntryFileDialog.getEntryFromFile(f, entry_document,
+ artemis_entry_information,
+ false);
+
+ if(new_embl_entry == null) // the read failed
+ break;
+
+ try
+ {
+ final Entry new_entry =
+ new Entry(last_entry_edit.getEntryGroup().getBases(),
+ new_embl_entry);
+
+ last_entry_edit.getEntryGroup().add(new_entry);
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(this, "read failed: one of the features in " +
+ new_entry_name + " has an out of range " +
+ "location: " + e.getMessage());
+ }
+ seen_plus = false; // reset
+ }
+ else if(System.getProperty("chado") != null && new_entry_name.indexOf(':')>-1)
+ {
+ // open from database e.g. Pfalciparum:Pf3D7_09:95000..150000
+ Splash.logger4j.info("OPEN ENTRY "+new_entry_name);
+ getStatusLabel().setText("Connecting ...");
+ DatabaseEntrySource entry_source = new DatabaseEntrySource();
+
+ boolean promptUser = true;
+ if(System.getProperty("read_only") != null)
+ {
+ promptUser = false;
+ entry_source.setReadOnly(true);
+ }
+
+ last_entry_edit = dbLogin(entry_source, promptUser, new_entry_name);
+ if(last_entry_edit == null)
+ return;
+ }
+ else
+ {
+ // new sequence file
+
+ if(last_entry_edit != null)
+ {
+ last_entry_edit.setVisible(true);
+ last_entry_edit = null;
+ }
+
+
+ if (new_entry_name.indexOf ("://") == -1)
+ {
+ File file = new File (new_entry_name);
+ if(!file.exists())
+ {
+ JOptionPane.showMessageDialog(null, "File "+
+ new_entry_name +" not found.\n"+
+ "Check the file name.", "File Not Found",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ }
+
+ final Document entry_document =
+ DocumentFactory.makeDocument(new_entry_name);
+
+ entry_document.addInputStreamProgressListener(getInputStreamProgressListener());
+
+ final uk.ac.sanger.artemis.io.Entry new_embl_entry =
+ EntryFileDialog.getEntryFromFile(f, entry_document,
+ artemis_entry_information,
+ false);
+
+ if(new_embl_entry == null) // the read failed
+ break;
+
+ try
+ {
+ final Entry entry = new Entry(new_embl_entry);
+ last_entry_edit = makeEntryEdit(entry);
+ addEntryEdit(last_entry_edit);
+ getStatusLabel().setText("");
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(this, "read failed: one of the features in " +
+ new_entry_name + " has an out of range " +
+ "location: " + e.getMessage());
+ break;
+ }
+ catch(NoSequenceException e)
+ {
+ new MessageDialog(this, "read failed: " +
+ new_entry_name + " contains no sequence");
+ break;
+ }
+ }
+ }
+
+ if(System.getProperty("offset") != null)
+ last_entry_edit.getGotoEventSource().gotoBase(
+ Integer.parseInt(System.getProperty("offset")));
+
+ last_entry_edit.setVisible(true);
+ for(int entry_index=0; entry_index<entry_edit_objects.size();
+ ++entry_index)
+ {
+ if(System.getProperty("offset") != null)
+ entry_edit_objects.elementAt(entry_index).getGotoEventSource().gotoBase(
+ Integer.parseInt(System.getProperty("offset")));
+ }
+ }
+
+ /**
+ * Handle database connection and construction of EntryEdit
+ * @param entry_source
+ * @param promptUser
+ * @param new_entry_name
+ * @return
+ */
+ private EntryEdit dbLogin(final DatabaseEntrySource entry_source,
+ final boolean promptUser,
+ final String new_entry_name)
+ {
+ EntryEdit last_entry_edit = null;
+
+ // allow 3 attempts to login
+ for(int i = 0; i < 3; i++)
+ {
+ if (!entry_source.setLocation(promptUser))
+ return null;
+
+ try
+ {
+ last_entry_edit = DatabaseJPanel.show(entry_source, this,
+ getInputStreamProgressListener(), new_entry_name);
+ break;
+ }
+ catch (Exception e)
+ {
+ new MessageDialog(this, e.getMessage());
+ entry_source.getDatabaseDocument().reset();
+ }
+ }
+
+ return last_entry_edit;
+ }
+
+ /**
+ *
+ * Handle the -biojava option
+ *
+ * Command line syntax:
+ * art -biojava org.biojava.bio.seq.io.EmblLikeFormat foo.embl
+ *
+ * BioJava formats:
+ * EmblLikeFormat, FastaFormat, GAMEFormat, GenbankFormat, PhredFormat
+ *
+ **/
+ private void handleBioJava(final String [] args)
+ {
+ if(args.length == 3)
+ {
+ final String class_name = args[1];
+ final String location = args[2];
+
+ final Document location_document =
+ DocumentFactory.makeDocument(location);
+
+ try
+ {
+ final Object biojava_object =
+ Class.forName(class_name).newInstance();
+
+ final EntryInformation entry_information =
+ Options.getArtemisEntryInformation();
+
+ final uk.ac.sanger.artemis.io.BioJavaEntry emblEntry;
+
+ if(biojava_object instanceof SequenceFormat)
+ {
+ final SequenceFormat sequence_format = (SequenceFormat)biojava_object;
+
+ emblEntry =
+ new uk.ac.sanger.artemis.io.BioJavaEntry(entry_information,
+ location_document,
+ sequence_format);
+
+ final Entry new_entry = new Entry(emblEntry);
+ final EntryEdit new_entry_edit = makeEntryEdit(new_entry);
+ new_entry_edit.setVisible(true);
+ }
+ else
+ new MessageDialog(this, "not a SequenceFormat: " + class_name);
+ }
+ catch(IllegalAccessException e)
+ {
+ new MessageDialog(this, "cannot create class: " + class_name +
+ " - IllegalAccessException");
+ }
+ catch(ClassNotFoundException e)
+ {
+ new MessageDialog(this, "cannot find class: " + class_name);
+ }
+ catch(ClassCastException e)
+ {
+ new MessageDialog(this, class_name + " is not a sub-class of " +
+ "SequenceFormat");
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(this, "I/O error while reading from " +
+ location + ": " + e.getMessage());
+ }
+ catch(NoSequenceException e)
+ {
+ new MessageDialog(this, location + " contained no sequence");
+ }
+ catch(InstantiationException e)
+ {
+ new MessageDialog(this, "cannot instantiate " + class_name);
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(this, "read failed: one of the features in " +
+ location +
+ " has an out of range location: " +
+ e.getMessage());
+ }
+ }
+ else
+ new MessageDialog(this, "the -biojava option needs two arguments");
+ }
+
+ /**
+ * Read the entries given in the uk.ac.sanger.artemis.ini file.
+ **/
+ private void readDefaultEntries()
+ {
+ final EntryInformation artemis_entry_information =
+ Options.getArtemisEntryInformation();
+
+ final String default_sequence_file_name =
+ Options.getOptions().getDefaultSequenceFileName();
+
+ final String default_feature_file_name =
+ Options.getOptions().getDefaultFeatureFileName();
+
+ if(default_sequence_file_name != null)
+ {
+ final String default_sequence_file_name_embl =
+ default_sequence_file_name + "_embl";
+
+ uk.ac.sanger.artemis.io.Entry new_embl_entry = null;
+
+ // try opening the default sequence file with "_embl" added to the name
+ // if that fails try the plain sequence file name
+
+ final Document entry_document =
+ DocumentFactory.makeDocument(default_sequence_file_name_embl);
+
+ final InputStreamProgressListener progress_listener =
+ getInputStreamProgressListener();
+
+ entry_document.addInputStreamProgressListener(progress_listener);
+
+ if(entry_document.readable())
+ {
+ new_embl_entry =
+ EntryFileDialog.getEntryFromFile(this,
+ entry_document,
+ artemis_entry_information,
+ false);
+ }
+
+ if(new_embl_entry == null || new_embl_entry.getSequence() == null ||
+ new_embl_entry.getSequence().length() == 0)
+ {
+ final File entry_file = new File(default_sequence_file_name);
+
+ if(entry_file.exists())
+ {
+ new_embl_entry =
+ EntryFileDialog.getEntryFromFile(this,
+ entry_document,
+ artemis_entry_information,
+ false);
+ }
+ else
+ {
+ // read failed
+ System.err.println("file does not exist: " +
+ default_sequence_file_name +
+ "(given in options files)");
+ return;
+ }
+ }
+
+ if(new_embl_entry == null || new_embl_entry.getSequence() == null ||
+ new_embl_entry.getSequence().length() == 0)
+ {
+ // read failed
+ System.err.println("failed to read " + default_sequence_file_name +
+ "(given in options files)");
+ return;
+ }
+
+ getStatusLabel().setText("");
+
+ try
+ {
+ final Entry entry = new Entry(new_embl_entry);
+
+ final EntryEdit new_entry_edit = makeEntryEdit(entry);
+
+ new_entry_edit.setVisible(true);
+
+ if(default_feature_file_name != null)
+ {
+ final Document feature_document =
+ DocumentFactory.makeDocument(default_feature_file_name);
+
+ final uk.ac.sanger.artemis.io.Entry new_embl_table_entry =
+ EntryFileDialog.getEntryFromFile(this,
+ feature_document,
+ artemis_entry_information,
+ false);
+
+ if(new_embl_table_entry == null) // open failed
+ return;
+
+ final EntryGroup entry_group = new_entry_edit.getEntryGroup();
+
+ final Entry new_table_entry =
+ new Entry(entry.getBases(), new_embl_table_entry);
+
+ entry_group.add(new_table_entry);
+ }
+ } catch(OutOfRangeException e) {
+ new MessageDialog(this, "read failed: one of the features in " +
+ default_feature_file_name +
+ " has an out of range location: " +
+ e.getMessage());
+ } catch(NoSequenceException e) {
+ new MessageDialog(this, "read failed: " +
+ new_embl_entry.getName() +
+ " contains no sequence");
+ }
+ }
+ }
+
+ /**
+ * Make an EntryEdit component from the given Entry.
+ **/
+ public static EntryEdit makeEntryEdit(final Entry entry)
+ {
+ final Bases bases = entry.getBases();
+ final EntryGroup entry_group = new SimpleEntryGroup(bases);
+ entry_group.add(entry);
+ final EntryEdit entry_edit = new EntryEdit(entry_group);
+
+ return entry_edit;
+ }
+
+ /**
+ * Add an EntryEdit object to our list of objects.
+ * @param entry_edit The object to add.
+ **/
+ private synchronized void addEntryEdit(EntryEdit entry_edit)
+ {
+ entry_edit_objects.addElement(entry_edit);
+ }
+
+
+ /**
+ * Read an Entry from the given EntrySource and make a new EntryEdit
+ * component for the Entry.
+ **/
+ private void getEntryEditFromEntrySource(final EntrySource entry_source)
+ {
+ try
+ {
+ final Entry entry = entry_source.getEntry(true);
+ if(entry == null)
+ return ;
+
+ final EntryGroup entry_group =
+ new SimpleEntryGroup(entry.getBases());
+ entry_group.add(entry);
+
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ EntryEdit entry_edit = new EntryEdit(entry_group);
+ entry_edit.setVisible(true);
+ }
+ });
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(ArtemisMain.this, "read failed: one of the features in " +
+ " the entry has an out of range " +
+ "location: " + e.getMessage());
+ }
+ catch(NoSequenceException e)
+ {
+ new MessageDialog(ArtemisMain.this, "read failed: entry contains no sequence");
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(ArtemisMain.this, "read failed due to IO error: " + e);
+ }
+ }
+
+
+ /**
+ * Close the main frame and all EntryEdit frames and then this frame,
+ * then exit.
+ **/
+ protected void exit()
+ {
+ Splash.exitApp();
+// for(int i=0 ; i<entry_edit_objects.size() ;++i)
+// entryEditFinished(entry_edit_objects.elementAt(i));
+
+// if(filemanager != null)
+// filemanager.setVisible(false);
+
+// setVisible(false);
+// dispose();
+// System.gc();
+ }
+
+ /**
+ * Main entry point for the stand-alone version of Artemis.
+ **/
+ public static void main(final String [] args)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ final ArtemisMain main_window = new ArtemisMain(args);
+ main_window.setVisible(true);
+ main_window.readArgsAndOptions(args, main_window);
+ }
+ });
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/BasePlot.java b/uk/ac/sanger/artemis/components/BasePlot.java
new file mode 100644
index 0000000..a2c9b94
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/BasePlot.java
@@ -0,0 +1,1059 @@
+/* BasePlot.java
+ *
+ * created: Tue Dec 15 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/BasePlot.java,v 1.20 2009-07-20 15:11:17 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.plot.*;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.text.NumberFormat;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.apache.log4j.Level;
+
+
+/**
+ * A component for plotting functions over the base sequence. Scrolling and
+ * scale is tied to a FeatureDisplay component.
+ *
+ * @author Kim Rutherford
+ * @version $Id: BasePlot.java,v 1.20 2009-07-20 15:11:17 tjc Exp $
+ **/
+
+public class BasePlot extends Plot
+ implements DisplayAdjustmentListener, SelectionChangeListener
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The start base to plot, as obtained from the DisplayAdjustmentEvent.
+ **/
+ private int start_base;
+
+ /**
+ * The end base to plot, as obtained from the DisplayAdjustmentEvent.
+ **/
+ private int end_base;
+
+ /**
+ * The width in bases of the display, as obtained from the
+ * DisplayAdjustmentEvent.
+ **/
+ private int width_in_bases;
+
+ /**
+ * True if and only if the FeatureDisplay is drawing in reverse complement
+ * mode.
+ **/
+ private boolean rev_comp_display;
+
+ /**
+ * The Bases that this BasePlot is graphing.
+ **/
+ private Bases bases;
+
+ /**
+ * The Marker of the start of the selection. This is a cache used by
+ * getSelectionStartMarker().
+ **/
+ private Marker selection_start_marker = null;
+
+ /**
+ * The Marker of the end of the selection. This is a cache used by
+ * getSelectionEndMarker()
+ **/
+ private Marker selection_end_marker = null;
+
+ /**
+ * The Selection that was passed to the constructor.
+ **/
+ private Selection selection;
+
+ /**
+ * The GotoEventSource that was passed to the constructor.
+ **/
+ private GotoEventSource goto_event_source;
+
+ /**
+ * This array is used by drawMultiValueGraph(). It is reallocated when
+ * the scale changes.
+ **/
+ private float[][] value_array_array = null;
+
+ /**
+ * The number of bases to step before each evaluation of the algorithm.
+ * (Set by recalculateValues()).
+ **/
+ private int step_size = 0;
+
+ /**
+ * The maximum of the values in value_array_array.
+ **/
+ private float min_value = Float.MAX_VALUE;
+
+ /**
+ * The minimum of the values in value_array_array.
+ **/
+ private float max_value = Float.MIN_VALUE;
+
+ private EntryGroup entryGroup;
+
+ /**
+ * Used by getPreferredSize() and getMinimumSize();
+ **/
+ protected static int HEIGHT;
+
+ static
+ {
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ final Integer base_plot_height =
+ Options.getOptions().getIntegerProperty("base_plot_height");
+
+ if(base_plot_height == null)
+ {
+ if(screen.height <= 600)
+ HEIGHT = 100;
+ else
+ HEIGHT = 120;
+ }
+ else
+ HEIGHT = base_plot_height.intValue();
+ }
+
+ /**
+ * Create a new FeatureDisplay object.
+ * @param algorithm The object that will generate the value we plot in
+ * this component.
+ * @param selection Used to set and display the current selection in the
+ * BasePlot.
+ * @param goto_event_source The object the we will call gotoBase() on.
+ * This allows the user to double click on a base in a BasePlot and have
+ * the FeatureDisplay follow.
+ **/
+ public BasePlot(final BaseAlgorithm algorithm,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entryGroup)
+ {
+ super(algorithm, false); // false means don't draw the scale line
+
+ this.selection = selection;
+ this.goto_event_source = goto_event_source;
+ this.bases = getBaseAlgorithm().getBases();
+ this.entryGroup = entryGroup;
+
+ setBackground(Color.WHITE);
+ getSelection().addSelectionChangeListener(this);
+
+ MultiLineToolTipUI.initialize();
+ setToolTipText("tool_tip");
+
+ addPlotMouseListener(new PlotMouseListener()
+ {
+ /**
+ * Set the selection to be a range from start_base to end_base.
+ **/
+ private void setSelectionRange(final int start_base,
+ final int end_base)
+ {
+ final Strand strand = bases.getForwardStrand();
+
+ try
+ {
+ final MarkerRange marker_range =
+ strand.makeMarkerRangeFromPositions(start_base, end_base);
+ getSelection().setMarkerRange(marker_range);
+ }
+ catch(uk.ac.sanger.artemis.util.OutOfRangeException e)
+ {
+ getSelection().clear();
+ }
+ }
+
+ /**
+ * Called when the user clicks somewhere on the plot canvas.
+ * @param position the base/amino acid position of the click. This is
+ * -1 if and only if the click was outside the graph (eg. in the
+ * label at the top)
+ **/
+ public void mouseClick(final int position)
+ {
+ }
+
+ /**
+ * Called when the user drags the mouse over the plot.
+ * @param drag_start_position The base/amnino acid position where the
+ * drag started or -1 if the drag was started outside the graph.
+ * @param current_position the base/amino acid position of the click.
+ * This is -1 if and only if the user has dragged the mouse out of
+ * the graph (eg. in the label at the top)
+ **/
+ public void mouseDrag(int drag_start_position,
+ int current_position)
+ {
+ if(rev_comp_display)
+ {
+ drag_start_position =
+ bases.getComplementPosition(drag_start_position);
+ current_position =
+ bases.getComplementPosition(current_position);
+ }
+ setSelectionRange(drag_start_position,
+ current_position);
+ }
+
+ /**
+ * Called when the user double-clicks somewhere on the plot.
+ * @param position the base/amino acid position of the click. This is
+ * -1 if and only if the click was outside the graph (eg. in the
+ * label at the top)
+ **/
+ public void mouseDoubleClick(int position)
+ {
+ if(rev_comp_display)
+ position = bases.getComplementPosition(position);
+
+ setSelectionRange(position, position);
+ getGotoEventSource().gotoBase(position);
+ }
+ });
+ }
+
+
+ /**
+ * Overridden to set the component height to 150.
+ **/
+ public Dimension getPreferredSize()
+ {
+ return(new Dimension(getSize().width, HEIGHT));
+ }
+
+ /**
+ * Overridden to set the component height to 150.
+ **/
+ /*public Dimension getMinimumSize()
+ {
+ return (new Dimension(getSize().width, HEIGHT));
+ }*/
+
+ /**
+ * Implementation of the DisplayAdjustmentListener interface. Invoked when
+ * a component or changes the scale.
+ **/
+ public void displayAdjustmentValueChanged(DisplayAdjustmentEvent event)
+ {
+ start_base = event.getStart();
+ end_base = event.getEnd();
+ width_in_bases = event.getWidthInBases();
+ rev_comp_display = event.isRevCompDisplay();
+ recalculate_flag = true;
+
+ if(event.getType() == DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT)
+ {
+ selection_start_marker = null;
+ selection_end_marker = null;
+ resetOffscreenImage();
+ }
+
+ repaint();
+ }
+
+ /**
+ * We listen for SelectionChange events so that we can update the
+ * crosshairs.
+ **/
+ public void selectionChanged(SelectionChangeEvent event)
+ {
+ selection_start_marker = null;
+ selection_end_marker = null;
+ repaint();
+ }
+
+ /**
+ * Return the algorithm that was passed to the constructor.
+ **/
+ public BaseAlgorithm getBaseAlgorithm()
+ {
+ return (BaseAlgorithm)super.getAlgorithm();
+ }
+
+ /**
+ * Return the new start base to display, from the last event.
+ **/
+ private int getStart()
+ {
+ return start_base;
+ }
+
+ /**
+ * Return the new end base to display, from the last event.
+ **/
+ private int getEnd()
+ {
+ return end_base;
+ }
+
+ /**
+ * Return the width in bases of the display, from the last event.
+ **/
+ private int getWidthInBases()
+ {
+ return width_in_bases;
+ }
+
+ /**
+ * Recalculate the values in value_array_array, step_size, min_value and
+ * max_value.
+ **/
+ protected void calculateFeatures(boolean fromPeak)
+ {
+ GridBagLayout gridbag = new GridBagLayout();
+ JPanel pane = new JPanel(gridbag);
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.anchor = GridBagConstraints.EAST;
+ c.gridx = 0;
+ c.gridy = 0;
+ pane.add(new JLabel("Minimum feature size:"), c);
+
+ c.gridx = 0;
+ c.gridy = 1;
+ pane.add(new JLabel("Cut-off value:"), c);
+
+ c.gridx = 0;
+ c.gridy = 2;
+ pane.add(new JLabel("Key:"), c);
+
+ JTextField minSize = new JTextField(String.valueOf (100), 15);
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 1;
+ c.gridy = 0;
+ pane.add(minSize, c);
+
+ JTextField cutoffField = new JTextField(getAlgorithm().getAverage().toString(),15);
+ c.gridx = 1;
+ c.gridy = 1;
+ pane.add(cutoffField,c);
+
+ final Key defaultKey;
+ if(GeneUtils.isDatabaseEntry(entryGroup))
+ defaultKey = new Key("region");
+ else
+ defaultKey = Key.CDS;
+
+ Entry entry = entryGroup.getDefaultEntry();
+ if(entry == null)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Please select a default entry\nand try again!", "No default entry",
+ JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ KeyChoice keyChoice = new KeyChoice(
+ entry.getEntryInformation(), defaultKey);
+
+ c.gridx = 1;
+ c.gridy = 2;
+ pane.add(keyChoice, c);
+
+ int select = JOptionPane.showConfirmDialog(null,
+ pane, "Options",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+
+ int minFeatureSize = Integer.parseInt(minSize.getText());
+ float cutoff = Float.parseFloat(cutoffField.getText());
+ Key key = keyChoice.getSelectedItem();
+
+ final int end = getBaseAlgorithm().getBases().getLength();
+ final int window_size = getWindowSize();
+ final Integer default_step_size =
+ getAlgorithm().getDefaultStepSize(window_size);
+ if(default_step_size == null)
+ step_size = 1;
+ else
+ {
+ if(default_step_size.intValue() < window_size)
+ step_size = default_step_size.intValue();
+ else
+ step_size = window_size;
+ }
+
+ // the number of plot points in the graph
+ final int number_of_values =
+ (end - (getWindowSize() - step_size)) / step_size;
+
+ getBaseAlgorithm().setRevCompDisplay(rev_comp_display);
+
+ // just one graph calculated
+ float [] temp_values = new float [1];
+ int featureStart = -1;
+
+ final Entry new_entry =
+ entryGroup.createEntry ("CDS_" + minFeatureSize + "_" +
+ getBaseAlgorithm().getAlgorithmShortName());
+
+ float average = 0.f;
+ int averageCount = 0;
+ final String noteField = "Auto-generated from "+getAlgorithm().getAlgorithmName()+
+ " plot;"+" window size="+getWindowSize()+
+ "; score cut-off="+cutoffField.getText()+
+ (fromPeak ? "; from the peaks" : "; from the trough");
+
+ int lastPos = 0;
+ for(int i = 0 ; i < number_of_values ; ++i)
+ {
+ getBaseAlgorithm().getValues((i * step_size) + 1,
+ (i * step_size) + 1 +
+ getWindowSize() - 1,
+ temp_values);
+
+ final float current_value = temp_values[0];
+ int pos = getWindowSize()/2 + (i * step_size) + 1;
+ if(i == 0)
+ lastPos = pos;
+
+ if(current_value > cutoff)
+ {
+ average+=current_value;
+ averageCount++;
+ }
+
+ try
+ {
+ if (fromPeak && current_value > cutoff && featureStart == -1)
+ featureStart = pos;
+ else if (!fromPeak && current_value < cutoff && featureStart == -1)
+ featureStart = pos;
+ else if (fromPeak && current_value < cutoff && featureStart > -1)
+ {
+ if (lastPos - featureStart < minFeatureSize)
+ {
+ average = 0.f;
+ averageCount = 0;
+ featureStart = -1;
+ continue;
+ }
+
+ // create feature
+ MarkerRange range = new MarkerRange(
+ getBaseAlgorithm().getStrand(), featureStart,lastPos);
+ final Location new_location = range.createLocation();
+
+ average = average / averageCount;
+ QualifierVector qualifiers = new QualifierVector();
+ qualifiers.add(new Qualifier("score", Float.toString(average)));
+ qualifiers.add(new Qualifier("note", noteField));
+
+ new_entry.createFeature(key, new_location, qualifiers);
+ featureStart = -1;
+ average = 0.f;
+ averageCount = 0;
+ }
+ else if (!fromPeak && current_value > cutoff && featureStart > -1)
+ {
+ if (lastPos - featureStart < minFeatureSize)
+ {
+ average = 0.f;
+ averageCount = 0;
+ featureStart = -1;
+ continue;
+ }
+
+ // create feature
+ MarkerRange range = new MarkerRange(
+ getBaseAlgorithm().getStrand(), featureStart, lastPos);
+ final Location new_location = range.createLocation();
+
+ average = average / averageCount;
+ QualifierVector qualifiers = new QualifierVector();
+ qualifiers.add(new Qualifier("score", Float.toString(average)));
+ qualifiers.add(new Qualifier("note", noteField));
+
+ new_entry.createFeature(key, new_location, qualifiers);
+ featureStart = -1;
+ average = 0.f;
+ averageCount = 0;
+ }
+ }
+ catch (OutOfRangeException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ catch (ReadOnlyException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ catch (EntryInformationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ lastPos = pos;
+ }
+ return;
+ }
+
+ protected void showAveragesForRange()
+ {
+ final int end = getBaseAlgorithm().getBases().getLength();
+
+ // the number of plot points in the graph
+ final int number_of_values =
+ (end - (getWindowSize() - step_size)) / step_size;
+
+ if(getSelectionStartMarker() == null)
+ {
+ JOptionPane.showMessageDialog(null, "No range selected.",
+ "Message", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+
+ int rangeStart = getSelectionStartMarker().getRawPosition();
+ int rangeEnd = getSelectionEndMarker().getRawPosition();
+
+ final int numPlots = getBaseAlgorithm().getValueCount();
+ float[] temp_values = new float [numPlots];
+ float[] av_values = new float [numPlots];
+
+ int count = 0;
+
+ FileViewer fileViewer = new FileViewer(
+ getBaseAlgorithm().getAlgorithmShortName()+" :: "+
+ rangeStart+".."+rangeEnd);
+ fileViewer.appendString("Base\tValue(s)\n\n",Level.INFO);
+
+ for(int i = 0 ; i < number_of_values ; ++i)
+ {
+ int pos = getWindowSize()/2 + (i * step_size) + 1;
+
+ if(pos < rangeStart || pos > rangeEnd)
+ continue;
+
+ getBaseAlgorithm().getValues((i * step_size) + 1,
+ (i * step_size) + 1 +
+ getWindowSize() - 1,
+ temp_values);
+
+ fileViewer.appendString(pos+"\t");
+ for(int j=0; j<numPlots; j++)
+ {
+ av_values[j] += temp_values[j];
+ fileViewer.appendString(temp_values[j]+"\t");
+ }
+ fileViewer.appendString("\n");
+
+ count++;
+ }
+
+ fileViewer.appendString("\n\nAverage Value(s)\n",Level.INFO);
+ fileViewer.appendString( "================\n",Level.INFO);
+
+ for(int j=0; j<numPlots; j++)
+ fileViewer.appendString(Integer.toString(j+1)+"\t"+
+ Float.toString(av_values[j]/count)+"\n");
+ }
+
+ /**
+ * Recalculate the values in value_array_array, step_size, min_value and
+ * max_value.
+ **/
+ protected void recalculateValues()
+ {
+ final Float algorithm_minimum = getAlgorithm().getMinimum();
+ final Float algorithm_maximum = getAlgorithm().getMaximum();
+
+ // use the Algorithm specified maximum if there is one - otherwise
+ // calculate it
+ if(algorithm_maximum == null)
+ max_value = Float.MIN_VALUE;
+ else
+ max_value = algorithm_maximum.floatValue();
+
+ // use the Algorithm specified minimum if there is one - otherwise
+ // calculate it
+ if(algorithm_minimum == null)
+ min_value = Float.MAX_VALUE;
+ else
+ min_value = algorithm_minimum.floatValue();
+
+ final int window_size = getWindowSize();
+
+ final Integer default_step_size =
+ getAlgorithm().getDefaultStepSize(window_size);
+
+ if(default_step_size == null)
+ step_size = 1;
+ else
+ {
+ if(default_step_size.intValue() < window_size)
+ step_size = default_step_size.intValue();
+ else
+ step_size = window_size;
+ }
+
+ int real_start = getStart();
+
+ if(real_start < 1)
+ real_start = 1;
+
+ final int unit_count = getEnd() - real_start;
+
+ // the number of plot points in the graph
+ final int number_of_values =
+ (unit_count - (window_size - step_size)) / step_size;
+
+ if(number_of_values < 2)
+ {
+ // there is nothing to plot
+ value_array_array = null;
+ return;
+ }
+
+ getBaseAlgorithm().setRevCompDisplay(rev_comp_display);
+
+ // the number of values that getValues() will return
+ final int get_values_return_count =
+ getBaseAlgorithm().getValueCount();
+
+ if(value_array_array == null)
+ value_array_array = new float [get_values_return_count][];
+
+ if(value_array_array[0] == null ||
+ value_array_array[0].length != number_of_values)
+ {
+ for(int i = 0 ; i < value_array_array.length ; ++i)
+ value_array_array[i] = new float [number_of_values];
+ }
+ else
+ {
+ // reuse the previous arrays
+ }
+
+ float [] temp_values = new float [get_values_return_count];
+
+ for(int i = 0 ; i < number_of_values ; ++i)
+ {
+ getBaseAlgorithm().getValues(real_start + i * step_size,
+ real_start + i * step_size +
+ window_size - 1,
+ temp_values);
+
+ for(int value_index = 0 ;
+ value_index < get_values_return_count ;
+ ++value_index)
+ {
+ final float current_value = temp_values[value_index];
+
+ value_array_array[value_index][i] = current_value;
+
+ // use the Algorithm specified maximum if there is one - otherwise
+ // calculate it
+ if(algorithm_maximum == null)
+ {
+ if (current_value > max_value)
+ max_value = current_value;
+ }
+
+ // use the Algorithm specified minimum if there is one - otherwise
+ // calculate it
+ if(algorithm_minimum == null)
+ {
+ if(current_value < min_value)
+ min_value = current_value;
+ }
+ }
+ }
+
+ recalculate_flag = false;
+ }
+
+ /**
+ * Redraw the graph on the canvas using the algorithm, start_base and
+ * end_base. This method plots BaseWindowAlgorithm objects only.
+ * @param g The object to draw into.
+ **/
+ public int drawMultiValueGraph(Graphics g, LineAttributes[] lines)
+ {
+ if( getAlgorithm() instanceof UserDataAlgorithm &&
+ ((UserDataAlgorithm)getAlgorithm()).FORMAT == UserDataAlgorithm.TABIX_INDEXED_FORMAT)
+ {
+ ((UserDataAlgorithm) getAlgorithm()).readIndexValues(recalculate_flag,
+ entryGroup.getSequenceEntry(), getStart(), getEnd());
+ }
+
+ if(recalculate_flag)
+ recalculateValues();
+
+ if(value_array_array == null)
+ {
+ // there is nothing to draw - probably because the sequence is too short
+ drawMinMax(g, 0, 1);
+ return 0;
+ }
+
+ final int window_size = getWindowSize();
+
+ // the number of values to plot at each x position
+ final int nvalues =
+ getBaseAlgorithm().getValueCount();
+
+ boolean isWiggle = false;
+ boolean isBlast = false;
+ if(getAlgorithm() instanceof UserDataAlgorithm)
+ {
+ int format = ((UserDataAlgorithm)getAlgorithm()).FORMAT;
+ if(format == UserDataAlgorithm.WIGGLE_FIXED_STEP_FORMAT ||
+ format == UserDataAlgorithm.WIGGLE_VARIABLE_STEP_FORMAT)
+ isWiggle = true;
+
+ if(format == UserDataAlgorithm.BLAST_FORMAT)
+ isBlast = true;
+ }
+
+ if(value_array_array[0].length > 1 && !isWiggle)
+ drawGlobalAverage(g, min_value, max_value);
+
+ Stroke stroke = ((Graphics2D)g).getStroke();
+ for(int i = 0; i < nvalues; ++i)
+ {
+ if(i < lines.length)
+ {
+ g.setColor(lines[i].getLineColour());
+ if(lines[i].getStroke() == null)
+ continue;
+ ((Graphics2D)g).setStroke(lines[i].getStroke());
+ }
+ else
+ g.setColor(Color.black);
+
+ final int offset;
+
+ if(getStart() < 1)
+ offset = 1 - getStart();
+ else
+ offset = 0;
+
+ drawPoints(g, min_value, max_value, step_size, window_size,
+ getWidthInBases(),
+ offset,
+ value_array_array[i], i,
+ nvalues, isWiggle, isBlast);
+ }
+ ((Graphics2D)g).setStroke(stroke);
+
+ drawMinMax(g, min_value, max_value);
+
+ if(getCrossHairPosition() >= 0)
+ {
+ final int cross_hair_position = getCrossHairPosition();
+ final int selection_base = getPointPosition(cross_hair_position);
+
+ if(selection_base >= 1)
+ {
+ if(selection_base > end_base)
+ cancelCrossHairs();
+ else
+ {
+ final String label_string;
+
+ if(rev_comp_display)
+ {
+ label_string =
+ String.valueOf(bases.getLength() - selection_base + 1);
+ }
+ else
+ label_string = String.valueOf(selection_base);
+
+ drawCrossHair(g, cross_hair_position, label_string, 0);
+ }
+ }
+ }
+
+ if(getCrossHairPosition() >= 0 && getSelectionStartMarker() != null)
+ {
+ final int selection_first_base =
+ getSelectionStartMarker().getRawPosition();
+
+ final String label_string;
+
+ if(rev_comp_display)
+ {
+ label_string =
+ String.valueOf(bases.getLength() - selection_first_base + 1);
+ }
+ else
+ label_string = String.valueOf(selection_first_base);
+
+ if(Math.abs(selection_first_base -
+ getPointPosition(getCrossHairPosition())) > 3)
+ {
+ drawCrossHair(g, getCanvasPosition(selection_first_base),
+ label_string, 1);
+ }
+ else
+ {
+ // don't draw - too close to main cross hair
+ }
+ }
+
+ if(getCrossHairPosition() >= 0 && getSelectionEndMarker() != null)
+ {
+ if(getSelectionStartMarker() != null &&
+ Math.abs((getSelectionEndMarker().getRawPosition() -
+ getSelectionStartMarker().getRawPosition())) >= 3)
+ {
+ final int selection_last_base =
+ getSelectionEndMarker().getRawPosition();
+
+ final String label_string;
+
+ if(rev_comp_display)
+ {
+ label_string =
+ String.valueOf(bases.getLength() - selection_last_base + 1);
+ }
+ else
+ label_string = String.valueOf(selection_last_base);
+
+ if(Math.abs(selection_last_base -
+ getPointPosition(getCrossHairPosition())) > 3)
+ {
+ drawCrossHair(g, getCanvasPosition (selection_last_base),
+ label_string, 2);
+ }
+ else
+ {
+ // don't draw - too close to main cross hair
+ }
+ }
+ }
+
+ return nvalues;
+ }
+
+
+ /**
+ * Get the position in the Feature of the given canvas x position. This
+ * base position is the label used when the user clicks the mouse in on the
+ * canvas (see drawCrossHair()).
+ **/
+ protected int getPointPosition(final int canvas_x_position)
+ {
+ return (int)((1.0 * canvas_x_position / getSize().width) *
+ getWidthInBases()) + getStart();
+ }
+
+ /**
+ * Return the canvas position of the given base,
+ **/
+ private int getCanvasPosition(final int base)
+ {
+ return (int)((1.0 * base - getStart()) / getWidthInBases() *
+ getSize().width);
+ }
+
+ /**
+ * Return the Marker of the start base of the Selection or null if there is
+ * nothing selected.
+ **/
+ private Marker getSelectionStartMarker()
+ {
+ if(selection_start_marker == null)
+ {
+ selection_start_marker = getSelection().getLowestBaseOfSelection();
+
+ if(selection_start_marker != null &&
+ rev_comp_display)
+ {
+ final Strand strand = bases.getReverseStrand();
+ final int orig_position = selection_start_marker.getRawPosition();
+ final int rev_comp_position =
+ bases.getComplementPosition(orig_position);
+ try
+ {
+ selection_start_marker = strand.makeMarker(orig_position);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+
+ return selection_start_marker;
+ }
+
+ /**
+ * Return the Marker of the end base of the Selection or null if there is
+ * nothing selected.
+ **/
+ private Marker getSelectionEndMarker()
+ {
+ if(selection_end_marker == null)
+ {
+ selection_end_marker = getSelection().getHighestBaseOfSelection();
+
+ if(selection_end_marker != null &&
+ rev_comp_display)
+ {
+ final Strand strand = bases.getReverseStrand();
+ final int orig_position = selection_end_marker.getRawPosition();
+ final int rev_comp_position =
+ bases.getComplementPosition(orig_position);
+ try
+ {
+ selection_end_marker = strand.makeMarker(orig_position);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+
+ return selection_end_marker;
+ }
+
+ /**
+ * Return the Selection object that was passed to the constructor.
+ **/
+ private Selection getSelection()
+ {
+ return selection;
+ }
+
+ /**
+ * Return the GotoEventSource object that was passed to the constructor.
+ **/
+ private GotoEventSource getGotoEventSource()
+ {
+ return goto_event_source;
+ }
+
+ /**
+ * (non-Javadoc)
+ * @see javax.swing.JComponent#getToolTipText(java.awt.event.MouseEvent)
+ */
+ public String getToolTipText(MouseEvent event)
+ {
+ if(value_array_array == null) // nothing to plot
+ return null;
+
+ int offset = getStart();
+ if(offset < 1)
+ offset = 0;
+
+ final int nvalues =
+ getBaseAlgorithm().getValueCount();
+
+ int xpos = getPointPosition(event.getPoint().x);
+ String tt = Integer.toString(xpos);
+
+ NumberFormat df = NumberFormat.getNumberInstance();
+ df.setMaximumFractionDigits(2);
+
+ for(int i = 0; i < nvalues; ++i)
+ tt += ", " + df.format(
+ getYCoordinate(step_size, getWindowSize(),
+ offset, value_array_array[i], xpos));
+
+ if(lines != null)
+ {
+ // if heat map display column number
+ String plotType = lines[0].getPlotType();
+ if(plotType.equals(LineAttributes.PLOT_TYPES[2]))
+ {
+ final int graph_height = getSize().height -
+ getLabelHeight() - getScaleHeight() - 2;
+
+ int hgt = graph_height/value_array_array.length;
+ float mousePos = (float) (event.getPoint().getY()-
+ getLabelHeight() - getScaleHeight() - 2.f );
+
+ int plotNumber = Math.round(( mousePos / hgt )+.5f);
+ if(plotNumber < 1)
+ plotNumber = 1;
+ else if(plotNumber > value_array_array.length)
+ plotNumber = value_array_array.length;
+
+ if(nvalues > 5)
+ tt = Integer.toString(xpos)+", "+df.format(
+ getYCoordinate(step_size, getWindowSize(),
+ offset, value_array_array[plotNumber-1], xpos));
+
+ tt = "Plot number : "+plotNumber+"\n"+tt;
+ }
+ }
+
+ return tt;
+ }
+
+
+ public void setMin_value(float min_value)
+ {
+ this.min_value = min_value;
+ }
+
+ public void setMax_value(float max_value)
+ {
+ this.max_value = max_value;
+ }
+
+ public float getMin_value()
+ {
+ return min_value;
+ }
+
+ public float getMax_value()
+ {
+ return max_value;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/BasePlotGroup.java b/uk/ac/sanger/artemis/components/BasePlotGroup.java
new file mode 100644
index 0000000..0b2e6a5
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/BasePlotGroup.java
@@ -0,0 +1,415 @@
+/* BasePlotGroup.java
+ *
+ * created: Tue Dec 15 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998-2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/BasePlotGroup.java,v 1.9 2008-06-16 12:11:01 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.plot.*;
+
+import java.awt.*;
+import java.util.Vector;
+
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+
+/**
+ * This is a super-component containing several BasePlot components, each of
+ * which can toggled off and on.
+ *
+ * @author Kim Rutherford
+ * @version $Id: BasePlotGroup.java,v 1.9 2008-06-16 12:11:01 tjc Exp $
+ **/
+
+public class BasePlotGroup extends JPanel
+ implements DisplayAdjustmentListener
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The EntryGroup that contains the sequence that this JComponent is
+ * displaying.
+ **/
+ private final EntryGroup entry_group;
+
+ /**
+ * This array contains the Algorithm objects of the BasePlot components in
+ * this BasePlotGroup, as set by the constructor.
+ **/
+ private final Vector<BaseAlgorithm> plot_value_producers = new Vector<BaseAlgorithm> ();
+
+ /**
+ * The layout object used by this component.
+ **/
+ private final GridBagLayout gridbag = new GridBagLayout();
+
+ /**
+ * The constraints object used by this component.
+ **/
+ private final GridBagConstraints c = new GridBagConstraints();
+
+ /**
+ * The Selection that was passed to the constructor.
+ **/
+ private Selection selection;
+
+ /**
+ * The GotoEventSource that was passed to the constructor.
+ **/
+ private GotoEventSource goto_event_source;
+
+ /**
+ * Create a new BasePlotGroup component for the given EntryGroup.
+ * @param selection Used to set and display the current selection in the
+ * BasePlot.
+ * @param goto_event_source The object the we will call gotoBase () on.
+ * This allows the user to double click on a base in a BasePlot and have
+ * the FeatureDisplay follow.
+ **/
+ public BasePlotGroup(final EntryGroup entry_group,
+ final Component owning_component,
+ final Selection selection,
+ final GotoEventSource goto_event_source)
+ {
+ this.entry_group = entry_group;
+ this.selection = selection;
+ this.goto_event_source = goto_event_source;
+
+ final Strand forward_strand =
+ entry_group.getBases().getForwardStrand();
+
+ // obtain the reverse complement
+ final Strand reverse_strand =
+ entry_group.getBases().getReverseStrand();
+
+ setLayout(gridbag);
+ setBackground(Color.WHITE);
+
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.NORTH;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.weightx = 1;
+ c.weighty = 1;
+ c.insets = new Insets(0,0,2,0);
+
+ addAlgorithm(new GCWindowAlgorithm(forward_strand));
+ addAlgorithm(new GCSDWindowAlgorithm(forward_strand));
+ addAlgorithm(new AGWindowAlgorithm(forward_strand));
+ addAlgorithm(new GCFrameAlgorithm(forward_strand));
+ addAlgorithm(new GCFrameAlgorithm(reverse_strand));
+ addAlgorithm(new Codon12CorrelationAlgorithm(forward_strand));
+ addAlgorithm(new Codon12CorrelationAlgorithm(reverse_strand));
+ addAlgorithm(new GCDeviationAlgorithm(forward_strand));
+ addAlgorithm(new ATDeviationAlgorithm(forward_strand));
+ addAlgorithm(new KarlinSigAlgorithm(forward_strand));
+
+
+ //CumulativeATSkewAlgorithm
+ addAlgorithm(new CumulativeATSkewAlgorithm(forward_strand));
+ addAlgorithm(new CumulativeGCSkewAlgorithm(forward_strand));
+
+ //Positional Asymmetry
+ addAlgorithm(new PositionalAsymmetryAlgorithm(forward_strand));
+
+ //Informational Entropy
+ addAlgorithm(new EntropyAlgorithm(forward_strand));
+
+ //Scaled Chi
+ addAlgorithm(new ScaledChiAlgorithm(forward_strand));
+ addAlgorithm(new ScaledChiAlgorithm(reverse_strand));
+
+ //Corrected Scaled Chi Square
+ addAlgorithm(new CSCSAlgorithm(forward_strand));
+ addAlgorithm(new CSCSAlgorithm(reverse_strand));
+
+ //Mutational Response Index
+ addAlgorithm(new MRIAlgorithm(forward_strand));
+ addAlgorithm(new MRIAlgorithm(reverse_strand));
+
+ //Effective Codon Number
+ addAlgorithm(new NcAlgorithm(forward_strand));
+ addAlgorithm(new NcAlgorithm(reverse_strand));
+
+ //Intrinsic Codon Deviation Index
+ addAlgorithm(new ICDIAlgorithm(forward_strand));
+ addAlgorithm(new ICDIAlgorithm(reverse_strand));
+ }
+
+
+ /**
+ *
+ * Routine for printing graphs in artemis
+ *
+ */
+ protected void printComponent(Graphics g)
+ {
+ final Component[] children = getComponents();
+ for(int i = 0 ; i<children.length ; ++i)
+ if(children[i] instanceof BasePlot)
+ {
+ BasePlot bp = (BasePlot)children[i];
+ if(!bp.isVisible())
+ continue;
+ bp.paintComponent(g);
+ g.translate(0,bp.getHeight());
+ }
+ }
+
+
+ /**
+ *
+ * Return the number of visible BasePlot objects
+ *
+ */
+ protected int getNumberBasePlots()
+ {
+ final Component[] children = getComponents();
+ int num = 0;
+
+ for(int i = 0 ; i<children.length ; ++i)
+ if(children[i] instanceof BasePlot)
+ {
+ BasePlot bp = (BasePlot)children[i];
+ if(bp.isVisible())
+ num++;
+ }
+ return num;
+ }
+
+ /**
+ * Implementation of the DisplayAdjustmentListener interface. Invoked when
+ * a component (FeatureDisplay) scrolls or changes the scale. Sends the
+ * event to all the BasePlot components in this BasePlotGroup.
+ **/
+ public void displayAdjustmentValueChanged(DisplayAdjustmentEvent event)
+ {
+ final StringBuffer closingPlots = new StringBuffer();
+ final Component[] children = getComponents();
+ int nvis = 0;
+ for(int i = 0 ; i<children.length ; ++i)
+ {
+ if(children[i] instanceof BasePlot)
+ {
+ // if this is an indexed sequence change hide any
+ // userplots that are not indexed
+ if(event.getType() == DisplayAdjustmentEvent.IDX_SEQUENCE_CHANGE)
+ {
+ Algorithm alg = ((BasePlot)children[i]).getAlgorithm();
+ if(alg instanceof UserDataAlgorithm &&
+ ((UserDataAlgorithm)alg).FORMAT != UserDataAlgorithm.TABIX_INDEXED_FORMAT &&
+ findPlotByAlgorithm(alg).isVisible())
+ {
+ closingPlots.append(alg.getAlgorithmName()+"\n");
+ children[i].setVisible(false);
+ continue;
+ }
+ }
+
+ if(children[i].isVisible())
+ nvis++;
+ ((BasePlot)children[i]).displayAdjustmentValueChanged(event);
+ }
+ }
+
+ if(event.getType() == DisplayAdjustmentEvent.IDX_SEQUENCE_CHANGE &&
+ closingPlots.length() > 0)
+ {
+ if(nvis == 0 && getParent() instanceof JSplitPane)
+ {
+ JSplitPane splitPane = (JSplitPane) getParent();
+ splitPane.setDividerSize(0);
+ splitPane.setDividerLocation(0);
+ }
+
+ JOptionPane.showMessageDialog(this,
+ closingPlots.toString()+
+ "\nAs the sequence is changing the above user plot(s) are closing as they are\n"+
+ "not indexed with multiple sequences. You can load in the corresponding plot\n"+
+ "for the new sequence.",
+ "Closing Userplot", JOptionPane.INFORMATION_MESSAGE);
+ }
+ }
+
+ /**
+ * Add a new BasePlot component for the given algorithm.
+ * @return The new BasePlot
+ **/
+ public BasePlot addAlgorithm(final BaseAlgorithm algorithm)
+ {
+ plot_value_producers.addElement(algorithm);
+
+ return makePlot(algorithm, gridbag, c);
+ }
+
+ /**
+ * Return the first CodonUsageAlgorithm or null if there isn't one in the
+ * group.
+ **/
+ public CodonUsageAlgorithm getCodonUsageAlgorithm()
+ {
+ for(int i = 0 ; i < plot_value_producers.size() ; ++i)
+ {
+ final BaseAlgorithm this_algorithm = plot_value_producers.elementAt(i);
+ if(this_algorithm instanceof CodonUsageAlgorithm)
+ return (CodonUsageAlgorithm) this_algorithm;
+ }
+
+ return null;
+ }
+
+ /**
+ * Return an array containing the Algorithm objects of the BasePlot
+ * components in this BasePlotGroup.
+ **/
+ public BaseAlgorithm[] getPlotAlgorithms()
+ {
+ final BaseAlgorithm[] return_array =
+ new BaseAlgorithm[plot_value_producers.size ()];
+
+ for(int i = 0 ; i < plot_value_producers.size () ; ++i)
+ {
+ final BaseAlgorithm this_algorithm = plot_value_producers.elementAt (i);
+ return_array[i] = this_algorithm;
+ }
+
+ return return_array;
+ }
+
+ /**
+ * Return true if and only if the BasePlot for the given Algorithm is
+ * visible.
+ **/
+ public boolean basePlotIsVisible(final Algorithm algorithm)
+ {
+ final Component base_plot = findPlotByAlgorithm(algorithm);
+ return base_plot.isVisible ();
+ }
+
+
+ /**
+ * Return the number of visible plots
+ * @return
+ */
+ public int getVisibleCount()
+ {
+ int cnt = 0;
+ Component comp[] = getComponents();
+ for(int i = 0 ; i<comp.length ; ++i)
+ if(comp[i] instanceof BasePlot)
+ {
+ if(comp[i].isVisible())
+ cnt++;
+ }
+ return cnt;
+ }
+
+ /**
+ * Given an Algorithm, find and set the visibility of the corresponding
+ * BasePlot component.
+ **/
+ public void setVisibleByAlgorithm(final Algorithm algorithm,
+ final boolean visible)
+ {
+ final JComponent base_plot = findPlotByAlgorithm(algorithm);
+
+ base_plot.setVisible(visible);
+ if (getParent () != null) {
+ // XXX change to revalidate().
+ getParent ().validate ();
+ }
+ }
+
+ /**
+ * Find the BasePlot component in this BasePlotGroup object that is
+ * plotting the given Algorithm or null if no such BasePlot object exists.
+ **/
+ private JComponent findPlotByAlgorithm(final Algorithm algorithm)
+ {
+ final Component[] children = getComponents();
+
+ for(int i = 0 ; i < children.length ; ++i)
+ {
+ if(children[i] instanceof BasePlot)
+ {
+ final Algorithm component_algorithm =
+ ((BasePlot)children[i]).getAlgorithm ();
+ if(component_algorithm == algorithm)
+ return (JComponent)children[i];
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Create a Plot component for the given Algorithm and then make a button
+ * for it so that it can be shown and hidden. Note that button making is
+ * currently disabled
+ * @param algorithm The Algorithm to create a Plot of.
+ * @param gridbag The GridBagLayout to use to lay out the Plot components.
+ * @param constraints The GridBagConstraints object to use to lay out the
+ * Plot components.
+ * @return The new BasePlot
+ **/
+ private BasePlot makePlot(BaseAlgorithm algorithm,
+ GridBagLayout gridbag,
+ GridBagConstraints constraints)
+ {
+ final BasePlot new_base_plot =
+ new BasePlot(algorithm, getSelection(), getGotoEventSource(), entry_group);
+
+ gridbag.setConstraints(new_base_plot, constraints);
+ add(new_base_plot);
+ new_base_plot.setVisible(false);
+
+ getSelection().addSelectionChangeListener(new_base_plot);
+
+ if (getParent () != null) {
+ // XXX change to revalidate().
+ getParent ().validate ();
+ }
+
+ return new_base_plot;
+ }
+
+ /**
+ * Return the Selection object that was passed to the constructor.
+ **/
+ private Selection getSelection()
+ {
+ return selection;
+ }
+
+ /**
+ * Return the GotoEventSource object that was passed to the constructor.
+ **/
+ private GotoEventSource getGotoEventSource()
+ {
+ return goto_event_source;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/BioJavaEntrySource.java b/uk/ac/sanger/artemis/components/BioJavaEntrySource.java
new file mode 100644
index 0000000..ca1c454
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/BioJavaEntrySource.java
@@ -0,0 +1,154 @@
+/* BioJavaEntrySource.java
+ *
+ * created: Tue Apr 10 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/BioJavaEntrySource.java,v 1.1 2004-06-09 09:46:06 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.io.*;
+
+import org.biojava.bio.seq.io.EmblLikeFormat;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.BioJavaEntry;
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ * This is an EntrySource that reads Entry objects from a BioJava Sequence
+ * object.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: BioJavaEntrySource.java,v 1.1 2004-06-09 09:46:06 tjc Exp $
+ **/
+
+public class BioJavaEntrySource implements EntrySource
+{
+ /**
+ * Create a new BioJavaEntrySource.
+ **/
+ public BioJavaEntrySource () {
+
+ }
+
+ /**
+ * Get an Entry object from this source (by reading from a file, reading
+ * from a CORBA server, or whatever).
+ * @param bases The Bases object to pass to the Entry constructor.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @return null if and only if the read is cancelled by the user or if the
+ * read fails.
+ **/
+ public Entry getEntry (final Bases bases, final ProgressThread progress_thread,
+ final boolean show_progress)
+ throws OutOfRangeException, IOException
+ {
+ final String fileName = "/nfs/team81/kmr/pow/java2/AB000095.embl";
+ final FileDocument document = new FileDocument (new File (fileName));
+
+ final BioJavaEntry emblEntry =
+ new BioJavaEntry (document, new EmblLikeFormat ());
+
+ return new Entry (bases, emblEntry);
+ }
+
+ /**
+ * Get an Entry object from this source (by reading from a file, reading
+ * from a CORBA server, or whatever).
+ * @param bases The Bases object to pass to the Entry constructor.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @return null if and only if the read is cancelled by the user or if the
+ * read fails.
+ **/
+ public Entry getEntry (final Bases bases, final boolean show_progress)
+ throws OutOfRangeException, IOException
+ {
+ return getEntry(bases, null, show_progress);
+ }
+
+ /**
+ * Get an Entry object from this source (by reading from a file, reading
+ * from a CORBA server, or whatever). A Bases object will be created for
+ * the sequence of the new Entry.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if the entry that we read has no
+ * sequence.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry(final boolean show_progress,
+ final ProgressThread progress_thread)
+ throws OutOfRangeException, NoSequenceException, IOException
+ {
+ return getEntry(show_progress);
+ }
+
+ /**
+ * Get an Entry object from this source (by reading from a file, reading
+ * from a CORBA server, or whatever). A Bases object will be created for
+ * the sequence of the new Entry.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if the entry that we read has no
+ * sequence.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry (final boolean show_progress)
+ throws OutOfRangeException, NoSequenceException, IOException
+ {
+ final String fileName = "/nfs/team81/kmr/pow/java2/AE002734.game";
+ final FileDocument document = new FileDocument (new File (fileName));
+
+ final BioJavaEntry emblEntry =
+ new BioJavaEntry (document, new EmblLikeFormat ());
+
+ return new Entry (emblEntry);
+ }
+
+ /**
+ * Returns true if and only if this EntrySource always returns "full"
+ * entries. ie. entries that contain features and sequence.
+ **/
+ public boolean isFullEntrySource () {
+ return true;
+ }
+
+ /**
+ * Return the name of this source (for display to the user in menus).
+ **/
+ public String getSourceName () {
+ return "BioJava";
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/CanvasPanel.java b/uk/ac/sanger/artemis/components/CanvasPanel.java
new file mode 100644
index 0000000..ff0e523
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/CanvasPanel.java
@@ -0,0 +1,115 @@
+/* CanvasPanel.java
+ *
+ * created: Sat Jun 17 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/CanvasPanel.java,v 1.5 2009-02-06 14:58:40 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.BorderLayout;
+import java.awt.FontMetrics;
+import javax.swing.JPanel;
+
+/**
+ * This is a JPanel that contains a JPanel containing a JComponent. Both Panels
+ * have BorderLayout. The JComponent is added at "Center".
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: CanvasPanel.java,v 1.5 2009-02-06 14:58:40 tjc Exp $
+ **/
+
+abstract public class CanvasPanel extends JPanel
+{
+ /** The height of the font used in this component. */
+ private int font_ascent;
+
+ /** descent of the font used in this component. */
+ private int font_descent;
+
+ /** The (maximum) width of the font used in this component. */
+ private int font_width;
+
+ /**
+ * Create a new JPanel(mid_panel) and a JComponent.
+ **/
+ public CanvasPanel()
+ {
+ setLayout(new BorderLayout());
+ setFontInfo();
+ }
+
+ /**
+ * Set font_width and font_ascent from the default font.
+ **/
+ private void setFontInfo()
+ {
+ FontMetrics fm = getFontMetrics(getFont());
+
+ // find the width of a wide character
+ font_width = fm.charWidth('M');
+ font_ascent = fm.getAscent();
+ font_descent = fm.getDescent();
+ }
+
+ /**
+ * Return the width of our font, as calculated by setFontInfo().
+ **/
+ protected int getFontWidth()
+ {
+ return font_width;
+ }
+
+ /**
+ * Return the ascent(height above the baseline) of our font, as calculated
+ * by setFontInfo().
+ **/
+ protected int getFontAscent()
+ {
+ return font_ascent;
+ }
+
+ /**
+ * Return the max ascent(height above the baseline) of our font, as
+ * calculated by setFontInfo().
+ **/
+ protected int getFontMaxAscent()
+ {
+ return font_ascent;
+ }
+
+ /**
+ * Return the descent of our font, as calculated by setFontInfo().
+ **/
+ protected int getFontDescent()
+ {
+ return font_descent;
+ }
+
+ /**
+ * The max ascent + descent of the default font.
+ **/
+ protected int getFontHeight()
+ {
+ return getFontMaxAscent() + getFontDescent();
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/ChoiceFrame.java b/uk/ac/sanger/artemis/components/ChoiceFrame.java
new file mode 100644
index 0000000..e4c0fa3
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ChoiceFrame.java
@@ -0,0 +1,109 @@
+/* ChoiceFrame.java
+ *
+ * created: Tue Aug 6 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ChoiceFrame.java,v 1.1 2004-06-09 09:46:08 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.util.StringVector;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+
+/**
+ * A Choice in a JFrame.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ **/
+
+public class ChoiceFrame extends JFrame {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new ChoiceFrame component with the given list of Strings.
+ **/
+ public ChoiceFrame (final String choice_title, final StringVector strings) {
+ super (choice_title);
+
+ choice = new JComboBox ();
+
+ for (String s: strings)
+ choice.addItem (s);
+
+ final JPanel choice_panel = new JPanel ();
+ choice_panel.add (choice);
+
+ getContentPane ().add (choice_panel, "Center");
+
+ final JPanel panel = new JPanel ();
+ panel.add (ok_button);
+ ok_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ ChoiceFrame.this.dispose ();
+ }
+ });
+
+ final JButton close_button = new JButton ("Cancel");
+ panel.add (close_button);
+ close_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ ChoiceFrame.this.dispose ();
+ }
+ });
+
+ getContentPane ().add (panel, "South");
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+ pack ();
+ }
+
+ /**
+ * Return the Choice component that is displayed in this JFrame.
+ **/
+ protected JComboBox getChoice () {
+ return choice;
+ }
+
+ /**
+ * Return the reference of the OK button of this Chooser.
+ **/
+ protected JButton getOKButton () {
+ return ok_button;
+ }
+
+ private JComboBox choice;
+ final private JButton ok_button = new JButton ("OK");
+}
diff --git a/uk/ac/sanger/artemis/components/ComparatorDialog.java b/uk/ac/sanger/artemis/components/ComparatorDialog.java
new file mode 100644
index 0000000..f922309
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ComparatorDialog.java
@@ -0,0 +1,333 @@
+/* ComparatorDialog.java
+ *
+ * created: Wed May 10 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ComparatorDialog.java,v 1.3 2006-10-18 14:25:23 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.util.Vector;
+
+import javax.swing.*;
+
+/**
+ * ComparatorDialog a dialog that allows the user to the choose two files to
+ * compare with a Comparator component and a file containing comparison data.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ComparatorDialog.java,v 1.3 2006-10-18 14:25:23 tjc Exp $
+ **/
+
+public class ComparatorDialog extends JFrame
+{
+ /**
+ * Create a dialog that allow the user to the choose two files to compare
+ * and a file containing comparison data.
+ * @param ActMain the object to call makeMultiComparator() on
+ **/
+ public ComparatorDialog(final ActMain act_main)
+ {
+ final JPanel top_panel = new JPanel();
+
+ getContentPane().add(top_panel, "Center");
+
+ final GridBagLayout gridbag = new GridBagLayout();
+ top_panel.setLayout(gridbag);
+
+ final Vector text_field_vector = new Vector();
+
+ for(int i = 0; i < 3; ++i)
+ {
+ final String label;
+ switch(i)
+ {
+ case 0:
+ label = "Sequence file 1";
+ break;
+ case 1:
+ label = "Comparison file 1";
+ break;
+ case 2:
+ label = "Sequence file 2";
+ break;
+ default:
+ throw new Error("internal error");
+ }
+
+ final JTextField text_field =
+ makeFileNamePanel(label, top_panel, gridbag);
+
+ text_field_vector.addElement(text_field);
+ }
+
+
+ final GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.NORTH;
+ c.weighty = 0;
+ c.gridwidth = 1;
+
+ final JButton more_button = new JButton("more files ...");
+
+ final JPanel more_button_panel = new JPanel();
+ more_button_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ more_button_panel.add(more_button);
+
+ c.gridwidth = 1;
+ gridbag.setConstraints(more_button_panel, c);
+ top_panel.add(more_button_panel);
+
+ more_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ top_panel.remove(more_button_panel);
+
+ final boolean add_sequence_flag;
+
+ if(text_field_vector.size() % 2 == 0 ||
+ Options.getOptions().isNoddyMode())
+ add_sequence_flag = true;
+ else
+ add_sequence_flag = false;
+
+ if(text_field_vector.size() % 2 == 1 ||
+ Options.getOptions().isNoddyMode())
+ {
+ final String seq_label =
+ "Comparison file " +(text_field_vector.size() / 2 + 1);
+ final JTextField seq_text_field =
+ makeFileNamePanel(seq_label, top_panel, gridbag);
+
+ text_field_vector.addElement(seq_text_field);
+ }
+
+ if(add_sequence_flag)
+ {
+ final String comp_label =
+ "Sequence file " +(text_field_vector.size() / 2 + 1);
+ final JTextField comp_text_field =
+ makeFileNamePanel(comp_label, top_panel, gridbag);
+
+ text_field_vector.addElement(comp_text_field);
+ }
+
+ top_panel.add(more_button_panel);
+
+ packAndCentre();
+ }
+ });
+
+
+ final JButton apply_button = new JButton("Apply");
+
+ apply_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ doApply(act_main, text_field_vector);
+ }
+ });
+
+
+ final JButton close_button = new JButton("Close");
+
+ close_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ ComparatorDialog.this.dispose();
+ }
+ });
+
+
+ final FlowLayout flow_layout =
+ new FlowLayout(FlowLayout.CENTER, 15, 5);
+
+ final JPanel close_and_apply_panel = new JPanel(flow_layout);
+
+ close_and_apply_panel.add(apply_button);
+ close_and_apply_panel.add(close_button);
+
+ getContentPane().add(close_and_apply_panel, "South");
+
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ ComparatorDialog.this.dispose();
+ }
+ });
+
+ packAndCentre();
+ }
+
+ /**
+ * Call pack() and then centre the JFrame in the middle of the screen.
+ **/
+ private void packAndCentre()
+ {
+ pack();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation(new Point((screen.width - getSize().width) / 2,
+ (screen.height - getSize().height) / 2));
+ }
+
+ /**
+ * Make a panel for choosing one file name panel.
+ **/
+ private JTextField makeFileNamePanel(final String label_string,
+ final JPanel parent_panel,
+ final GridBagLayout gridbag)
+ {
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.NORTH;
+ c.weighty = 0;
+
+ final JLabel label = new JLabel(label_string);
+
+ final JPanel panel = new JPanel();
+ panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ panel.add(label);
+
+ c.gridwidth = 1;
+ gridbag.setConstraints(panel, c);
+ parent_panel.add(panel);
+
+ final TextFieldSink text_field = new TextFieldSink("", 28);
+ gridbag.setConstraints(text_field, c);
+ parent_panel.add(text_field);
+
+ final JButton choose_button = new JButton("Choose ...");
+
+ choose_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ final StickyFileChooser file_dialog =
+ new StickyFileChooser();
+
+ file_dialog.setDialogTitle("Choose first sequence ...");
+ file_dialog.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ file_dialog.setDialogType(JFileChooser.OPEN_DIALOG);
+
+ file_dialog.showOpenDialog(ComparatorDialog.this);
+
+ if(file_dialog.getSelectedFile() != null)
+ {
+ final File selected_file =
+ new File(file_dialog.getCurrentDirectory(),
+ file_dialog.getSelectedFile().getName());
+
+ text_field.setText(selected_file.toString());
+ }
+ }
+ });
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(choose_button, c);
+ parent_panel.add(choose_button);
+
+ return text_field;
+ }
+
+ /**
+ * Attempt to call ActMain.makeComparator() for the given file names.
+ * @param ActMain the object to call makeMultiComparator() on
+ **/
+ private void doApply(final ActMain act_main,
+ final Vector text_field_vector)
+ {
+ if(text_field_vector.size() < 3)
+ throw new Error("internal error - not enough file names given to " +
+ "ComparatorDialog.doApply()");
+
+ Object[] file_names = new Object[text_field_vector.size()];
+
+ for(int i = 0; i < text_field_vector.size(); ++i)
+ {
+ TextFieldSink tfield = (TextFieldSink)text_field_vector.elementAt(i);
+
+ if(tfield.getDbNode() != null)
+ file_names[i] = tfield.getDbNode();
+ else
+ file_names[i] = tfield.getText().trim();
+
+ if(file_names[i] instanceof String && ((String)file_names[i]).length() == 0)
+ {
+ // ignore the problem if there are at least 3 files listed(ie. seq1
+ // comp1v2 seq2) and the remaining files lengths are zero
+ if(i > 3)
+ {
+ // set to true if a text field is found that isn't zero length
+ boolean found_file = false;
+
+ for(int sub_index = i; sub_index < text_field_vector.size();
+ ++sub_index)
+ {
+ final JTextField text_field =
+ (JTextField) text_field_vector.elementAt(sub_index);
+
+ final String this_text = text_field.getText().trim();
+
+ if(this_text.length() != 0)
+ found_file = true;
+ }
+
+ if(!found_file)
+ {
+ // truncate file_names
+ final String [] new_file_names = new String [i];
+ System.arraycopy(file_names, 0, new_file_names, 0, i);
+ file_names = new_file_names;
+ break;
+ }
+ }
+
+ new MessageDialog(this, "one of the file names is missing");
+ return;
+ }
+ }
+
+ final MessageFrame reading_message = new MessageFrame("reading ...");
+
+ final InputStreamProgressListener progress_listener =
+ act_main.getInputStreamProgressListener();
+
+ if(ActMain.makeMultiComparator(act_main, progress_listener,
+ file_names))
+ ComparatorDialog.this.dispose();
+
+ reading_message.dispose();
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/ComparatorGlue.java b/uk/ac/sanger/artemis/components/ComparatorGlue.java
new file mode 100644
index 0000000..485ff98
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ComparatorGlue.java
@@ -0,0 +1,510 @@
+/* ComparatorGlue.java
+ *
+ * created: Tue Sep 11 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ComparatorGlue.java,v 1.4 2005-12-02 14:58:57 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+import javax.swing.*;
+
+/**
+ * This class contains the Event glue needed to combine two FeatureDisplays
+ * and an AlignmentViewer.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ComparatorGlue.java,v 1.4 2005-12-02 14:58:57 tjc Exp $
+ **/
+
+public class ComparatorGlue {
+ /**
+ * Create a new ComparatorGlue object that glues the given components
+ * togeather.
+ **/
+ public ComparatorGlue (final JFrame parent_frame,
+ final FeatureDisplay subject_feature_display,
+ final FeatureDisplay query_feature_display,
+ final AlignmentViewer alignment_viewer) {
+ this.parent_frame = parent_frame;
+ this.subject_feature_display = subject_feature_display;
+ this.query_feature_display = query_feature_display;
+ this.alignment_viewer = alignment_viewer;
+
+ getSubjectEntryGroup ().addFeatureChangeListener (getSubjectSelection ());
+ getQueryEntryGroup ().addEntryChangeListener (getQuerySelection ());
+
+ addDisplayListeners (subject_feature_display, query_feature_display);
+
+ orig_subject_forward_strand =
+ getSubjectEntryGroup ().getBases ().getForwardStrand ();
+ orig_query_forward_strand =
+ getQueryEntryGroup ().getBases ().getForwardStrand ();
+
+ makeAlignmentEventListener ();
+ }
+
+ /**
+ * Wire-up the two FeatureDisplay objects with DisplayAdjustmentListeners.
+ **/
+ private void addDisplayListeners (final FeatureDisplay subject_display,
+ final FeatureDisplay query_display)
+ {
+
+ subject_listener = new DisplayAdjustmentListener()
+ {
+ public void displayAdjustmentValueChanged(DisplayAdjustmentEvent e)
+ {
+ if(e.getType() == DisplayAdjustmentEvent.REV_COMP_EVENT)
+ {
+ getAlignmentViewer().unlockDisplays();
+ return;
+ }
+ else if(e.getType() == DisplayAdjustmentEvent.CONTIG_REV_COMP_EVENT)
+ {
+ getAlignmentViewer().flippingContig(true, e.getStart(), e.getEnd());
+ getAlignmentViewer().unlockDisplays();
+ getSubjectDisplay().setFirstBase(e.getStart());
+ return;
+ }
+ else if(e.getType() == DisplayAdjustmentEvent.CONTIG_REORDER)
+ {
+ getAlignmentViewer().reorder(true, e.getStart(), e.getEnd(),
+ e.getDropPosition());
+ getAlignmentViewer().unlockDisplays();
+
+ if(e.getStart() < e.getDropPosition())
+ getSubjectDisplay().setFirstBase(e.getStart());
+ else
+ getSubjectDisplay().setFirstBase(e.getDropPosition());
+
+ return;
+ }
+
+ subject_display.removeDisplayAdjustmentListener(subject_listener);
+ query_display.removeDisplayAdjustmentListener(query_listener);
+
+ query_display.setScaleFactor (e.getScaleFactor ());
+
+ if (getAlignmentViewer ().displaysAreLocked () &&
+ (e.getType () == DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT ||
+ e.getType () == DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT)) {
+ final int difference = e.getStart () - subject_first_base_position;
+
+ query_first_base_position =
+ query_display.getForwardBaseAtLeftEdge () + difference;
+
+ query_display.setFirstBase (query_first_base_position);
+ }
+
+ subject_first_base_position = e.getStart ();
+
+ subject_display.addDisplayAdjustmentListener (subject_listener);
+ query_display.addDisplayAdjustmentListener (query_listener);
+ }
+ };
+
+ query_listener = new DisplayAdjustmentListener ()
+ {
+ public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e)
+ {
+ if (e.getType () == DisplayAdjustmentEvent.REV_COMP_EVENT)
+ {
+ getAlignmentViewer().unlockDisplays();
+ return;
+ }
+ else if (e.getType () == DisplayAdjustmentEvent.CONTIG_REV_COMP_EVENT)
+ {
+ getAlignmentViewer().flippingContig(false, e.getStart(), e.getEnd());
+ getAlignmentViewer().unlockDisplays();
+ getQueryDisplay().setFirstBase(e.getStart());
+ return;
+ }
+ else if(e.getType() == DisplayAdjustmentEvent.CONTIG_REORDER)
+ {
+ getAlignmentViewer().reorder(false, e.getStart(), e.getEnd(),
+ e.getDropPosition());
+ getAlignmentViewer().unlockDisplays();
+
+ if(e.getStart() < e.getDropPosition())
+ getSubjectDisplay().setFirstBase(e.getStart());
+ else
+ getSubjectDisplay().setFirstBase(e.getDropPosition());
+
+ return;
+ }
+
+ subject_display.removeDisplayAdjustmentListener (subject_listener);
+ query_display.removeDisplayAdjustmentListener (query_listener);
+
+ subject_display.setScaleFactor (e.getScaleFactor ());
+
+ if (getAlignmentViewer ().displaysAreLocked () &&
+ (e.getType () == DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT ||
+ e.getType () == DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT))
+ {
+ final int difference = e.getStart () - query_first_base_position;
+
+ subject_first_base_position =
+ subject_display.getForwardBaseAtLeftEdge () + difference;
+
+ subject_display.setFirstBase (subject_first_base_position);
+ }
+
+ query_first_base_position = e.getStart ();
+
+ subject_display.addDisplayAdjustmentListener (subject_listener);
+ query_display.addDisplayAdjustmentListener (query_listener);
+ }
+ };
+
+ subject_display.addDisplayAdjustmentListener (subject_listener);
+ query_display.addDisplayAdjustmentListener (query_listener);
+
+ final DisplayAdjustmentListener subject_align_listener =
+ new DisplayAdjustmentListener ()
+ {
+ public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e)
+ {
+ getAlignmentViewer ().setSubjectSequencePosition (e);
+ }
+ };
+
+ subject_display.addDisplayAdjustmentListener (subject_align_listener);
+
+ final DisplayAdjustmentListener query_align_listener =
+ new DisplayAdjustmentListener () {
+ public void displayAdjustmentValueChanged (DisplayAdjustmentEvent e) {
+ getAlignmentViewer ().setQuerySequencePosition (e);
+ }
+ };
+
+ query_display.addDisplayAdjustmentListener (query_align_listener);
+ }
+
+ /**
+ * Make an AlignmentListener which calls alignAt ().
+ **/
+ private void makeAlignmentEventListener () {
+ final AlignmentListener listener =
+ new AlignmentListener () {
+ public void alignMatchChosen (AlignmentEvent e) {
+ alignAt (e.getMatch ());
+ }
+ };
+
+ getAlignmentViewer ().addAlignmentListener (listener);
+ }
+
+ /**
+ * Scroll both the subject and query so that they centre on the given
+ * AlignMatch.
+ **/
+ private void alignAt (final AlignMatch align_match) {
+ getAlignmentViewer ().unlockDisplays ();
+ getAlignmentViewer ().disableSelection ();
+
+ maybeRevCompQuery (align_match);
+
+ final int subject_length = getSubjectForwardStrand ().getSequenceLength ();
+ final int query_length = getQueryForwardStrand ().getSequenceLength ();
+
+ int subject_sequence_start =
+ AlignmentViewer.getRealSubjectSequenceStart (align_match,
+ subject_length,
+ getAlignmentViewer ().subjectIsRevComp ());
+ int subject_sequence_end =
+ AlignmentViewer.getRealSubjectSequenceEnd (align_match,
+ subject_length,
+ getAlignmentViewer ().subjectIsRevComp ());
+ int query_sequence_start =
+ AlignmentViewer.getRealQuerySequenceStart (align_match,
+ query_length,
+ getAlignmentViewer ().queryIsRevComp ());
+ int query_sequence_end =
+ AlignmentViewer.getRealQuerySequenceEnd (align_match,
+ query_length,
+ getAlignmentViewer ().queryIsRevComp ());
+
+ if (getSubjectDisplay ().isRevCompDisplay ()) {
+ subject_sequence_start =
+ getSubjectDisplay ().getSequenceLength () - subject_sequence_start + 1;
+ subject_sequence_end =
+ getSubjectDisplay ().getSequenceLength () - subject_sequence_end + 1;
+ }
+
+ if (getQueryDisplay ().isRevCompDisplay ()) {
+ query_sequence_start =
+ getQueryDisplay ().getSequenceLength () - query_sequence_start + 1;
+ query_sequence_end =
+ getQueryDisplay ().getSequenceLength () - query_sequence_end + 1;
+ }
+
+ final int new_subject_base =
+ subject_sequence_start +
+ (subject_sequence_end - subject_sequence_start) / 2;
+ getSubjectDisplay ().makeBaseVisible (new_subject_base);
+
+ try {
+ final Strand subject_strand;
+
+ if (align_match.isRevMatch () ^
+ !getOrigSubjectForwardStrand ().isForwardStrand ()) {
+ subject_strand = getSubjectReverseStrand ();
+ } else {
+ subject_strand = getSubjectForwardStrand ();
+ }
+
+ final MarkerRange new_subject_marker =
+ subject_strand.makeMarkerRangeFromRawPositions (subject_sequence_start,
+ subject_sequence_end);
+ getSubjectSelection ().setMarkerRange (new_subject_marker);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ final int new_query_base =
+ query_sequence_start +
+ (query_sequence_end - query_sequence_start) / 2;
+ getQueryDisplay ().makeBaseVisible (new_query_base);
+
+ try {
+ final Strand query_strand;
+
+ if (getOrigQueryForwardStrand ().isForwardStrand ()) {
+ query_strand = getQueryForwardStrand ();
+ } else {
+ query_strand = getQueryReverseStrand ();
+ }
+
+ final MarkerRange new_query_marker_range =
+ query_strand.makeMarkerRangeFromRawPositions (query_sequence_start,
+ query_sequence_end);
+
+ getQuerySelection ().setMarkerRange (new_query_marker_range);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ getAlignmentViewer ().lockDisplays ();
+ getAlignmentViewer ().enableSelection ();
+ }
+
+ /**
+ * Called by alignAt () to reverse and complement the query sequence if the
+ * given AlignMatch is currently a match from the subject sequence to the
+ * reverse complement of the query sequence.
+ **/
+ private void maybeRevCompQuery (final AlignMatch align_match) {
+ if (!getAlignmentViewer ().offerToFlip ()) {
+ return;
+ }
+
+ // true if and only if exactly one of the query and subject is rev-comped
+ final boolean display_is_rev_comped;
+
+ if (getOrigSubjectForwardStrand ().isForwardStrand () ^
+ getOrigQueryForwardStrand ().isForwardStrand () ^
+ getSubjectDisplay ().isRevCompDisplay () ^
+ getQueryDisplay ().isRevCompDisplay ()) {
+ display_is_rev_comped = true;
+ } else {
+ display_is_rev_comped = false;
+ }
+
+ if (align_match.isRevMatch () ^ display_is_rev_comped) {
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog (parent_frame,
+ "reverse and complement query sequence display?");
+ if (yes_no_dialog.getResult ()) {
+ if (getQueryDisplay ().isRevCompDisplay ()) {
+ getQueryDisplay ().setRevCompDisplay (false);
+ } else {
+ getQueryDisplay ().setRevCompDisplay (true);
+ }
+ }
+ }
+ }
+
+/* *//**
+ * Send an event to those object listening for it.
+ * @param listeners A Vector of the objects that the event should be sent
+ * to.
+ * @param event The event to send
+ **//*
+ private void fireAction (final Vector listeners, final EventObject event) {
+ final Vector targets;
+ // copied from a book - synchronising the whole method might cause a
+ // deadlock
+ synchronized (this) {
+ targets = (Vector) listeners.clone ();
+ }
+
+ for (int i = 0 ; i < targets.size () ; ++i) {
+ GotoListener target = (GotoListener) targets.elementAt (i);
+
+ if (event instanceof GotoEvent) {
+ final GotoListener goto_listener = (GotoListener) target;
+ goto_listener.performGoto ((GotoEvent) event);
+ } else {
+ throw new Error ("EntryEdit.fireAction () - unknown event");
+ }
+ }
+ }*/
+
+ /**
+ * Return the forward Strand of the subject EntryGroup from when the
+ * Comparator was created.
+ **/
+ public Strand getOrigSubjectForwardStrand () {
+ return orig_subject_forward_strand;
+ }
+
+ /**
+ * Return the forward Strand of the query EntryGroup from when the
+ * Comparator was created.
+ **/
+ public Strand getOrigQueryForwardStrand () {
+ return orig_query_forward_strand;
+ }
+
+ /**
+ * Return the GotoEventSource object used by the subject FeatureDisplay.
+ **/
+ public GotoEventSource getSubjectGotoEventSource () {
+ return getSubjectDisplay ().getGotoEventSource ();
+ }
+
+ /**
+ * Return the GotoEventSource object used by the query FeatureDisplay.
+ **/
+ public GotoEventSource getQueryGotoEventSource () {
+ return getQueryDisplay ().getGotoEventSource ();
+ }
+
+ /**
+ * Return the reference of the EntryGroup in view in the subject
+ * FeatureDisplay.
+ **/
+ public EntryGroup getSubjectEntryGroup () {
+ return getSubjectDisplay ().getEntryGroup ();
+ }
+
+ /**
+ * Return the reference of the EntryGroup in view in the query
+ * FeatureDisplay.
+ **/
+ public EntryGroup getQueryEntryGroup () {
+ return getQueryDisplay ().getEntryGroup ();
+ }
+
+ /**
+ * Return the Selection object used by the subject FeatureDisplay.
+ **/
+ public Selection getSubjectSelection () {
+ return getSubjectDisplay ().getSelection ();
+ }
+
+ /**
+ * Return the Selection object used by the query FeatureDisplay.
+ **/
+ public Selection getQuerySelection () {
+ return getQueryDisplay ().getSelection ();
+ }
+
+ /**
+ * Return the AlignmentViewer that was created in the constructor.
+ **/
+ private AlignmentViewer getAlignmentViewer () {
+ return alignment_viewer;
+ }
+
+ /**
+ * Return the reference of the subject FeatureDisplay.
+ **/
+ public FeatureDisplay getSubjectDisplay () {
+ return subject_feature_display;
+ }
+
+ /**
+ * Return the reference of the query FeatureDisplay.
+ **/
+ public FeatureDisplay getQueryDisplay () {
+ return query_feature_display;
+ }
+
+ /**
+ * Return the current forward Strand of the subject EntryGroup.
+ **/
+ private Strand getSubjectForwardStrand () {
+ return getSubjectEntryGroup ().getBases ().getForwardStrand ();
+ }
+
+ /**
+ * Return the current forward Strand of the query EntryGroup.
+ **/
+ private Strand getQueryForwardStrand () {
+ return getQueryEntryGroup ().getBases ().getForwardStrand ();
+ }
+
+ /**
+ * Return the current reverse Strand of the subject EntryGroup.
+ **/
+ private Strand getSubjectReverseStrand () {
+ return getSubjectEntryGroup ().getBases ().getReverseStrand ();
+ }
+
+ /**
+ * Return the current reverse Strand of the query EntryGroup.
+ **/
+ private Strand getQueryReverseStrand () {
+ return getQueryEntryGroup ().getBases ().getReverseStrand ();
+ }
+
+ final private JFrame parent_frame;
+ final private FeatureDisplay subject_feature_display;
+ final private FeatureDisplay query_feature_display;
+ final private AlignmentViewer alignment_viewer;
+
+ private int subject_first_base_position = 1;
+ private int query_first_base_position = 1;
+
+ private DisplayAdjustmentListener subject_listener = null;
+ private DisplayAdjustmentListener query_listener = null;
+
+ /**
+ * The forward Strand of the subject EntryGroup when the Comparator was
+ * created.
+ **/
+ final private Strand orig_subject_forward_strand;
+
+ /**
+ * The forward Strand of the query EntryGroup when the Comparator was
+ * created.
+ **/
+ final private Strand orig_query_forward_strand;
+
+}
diff --git a/uk/ac/sanger/artemis/components/ContigTool.java b/uk/ac/sanger/artemis/components/ContigTool.java
new file mode 100644
index 0000000..4813e9f
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ContigTool.java
@@ -0,0 +1,693 @@
+/* ContigTool.java
+ *
+ * created: 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
+import uk.ac.sanger.artemis.SelectionChangeListener;
+import uk.ac.sanger.artemis.SelectionChangeEvent;
+import uk.ac.sanger.artemis.Selection;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import java.util.Vector;
+
+import java.awt.geom.RoundRectangle2D;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.border.Border;
+import java.awt.datatransfer.*;
+import java.awt.dnd.*;
+
+public class ContigTool extends JPanel
+ implements DragGestureListener, DropTargetListener,
+ DragSourceListener, Autoscroll,
+ SelectionChangeListener
+{
+ private static final long serialVersionUID = 1L;
+ private FeatureVector contig_features;
+ private FeatureDisplay feature_display;
+ private Selection selection;
+
+ private int scale = 1000;
+ private int xbound = 50;
+ private int length = xbound*2;
+
+ /** pop up menu */
+ private JPopupMenu popup = new JPopupMenu();
+
+ private int highlight_drop_base = -1;
+ /** AutoScroll margin */
+ private static final int AUTOSCROLL_MARGIN = 45;
+ /** used by AutoScroll method */
+ private Insets autoscrollInsets = new Insets( 0, 0, 0, 0 );
+
+ /** status label */
+ final private JLabel status_line = new JLabel("");
+
+ public ContigTool(final FeatureVector contig_features,
+ final FeatureDisplay feature_display,
+ final JScrollPane jsp,
+ final Selection selection)
+ {
+ super();
+ this.contig_features = contig_features;
+ this.feature_display = feature_display;
+ this.selection = selection;
+
+ setFocusable(true); // required for KeyEvent to work
+ MultiLineToolTipUI.initialize();
+ setToolTipText(""); //enable tooltip display
+
+ /*for(int i=0; i<contig_features.size(); i++)
+ {
+ final Range this_feature_range = contig_features.elementAt(i).getMaxRawRange();
+ length += ((this_feature_range.getEnd() - this_feature_range.getStart())/scale);
+ }*/
+
+ length += contig_features.elementAt(0).getStrand().getSequenceLength() / scale;
+
+
+ Dimension dim = new Dimension(length, 20);
+ setPreferredSize(dim);
+
+ DragSource dragSource = DragSource.getDefaultDragSource();
+
+ dragSource.createDefaultDragGestureRecognizer(
+ this, // component where drag originates
+ DnDConstants.ACTION_COPY_OR_MOVE, // actions
+ this); // drag gesture recognizer
+
+ setDropTarget(new DropTarget(this,this));
+
+ getSelection().addSelectionChangeListener(this);
+
+ addKeyListener(new KeyAdapter()
+ {
+ public void keyPressed(final KeyEvent event)
+ {
+ // this is done so that menu shortcuts don't cause each action to be
+ // performed twice
+ if(event.getModifiers() != 0)
+ return;
+
+ switch(event.getKeyCode())
+ {
+ case KeyEvent.VK_UP:
+ goToNext(true);
+ repaint();
+ break;
+ case KeyEvent.VK_DOWN:
+ goToNext(false);
+ repaint();
+ break;
+ default:
+ break;
+ }
+ }
+ });
+
+
+ addMouseListener(new MouseAdapter()
+ {
+ public void mouseReleased(MouseEvent event)
+ {
+ if(event.isPopupTrigger())
+ {
+ popup.show(event.getComponent(),
+ event.getX(), event.getY());
+ return;
+ }
+
+ FeatureVector contig_features = ContigTool.this.contig_features;
+ if(event.getClickCount() == 1 &&
+ event.getID() == MouseEvent.MOUSE_RELEASED)
+ {
+ Point p = event.getPoint();
+ for(int i=0; i<contig_features.size(); i++)
+ {
+ final Feature feature = contig_features.elementAt(i);
+ final Range this_feature_range = feature.getMaxRawRange();
+
+ int xstart = xbound + this_feature_range.getStart()/scale;
+ int xend = xbound + this_feature_range.getEnd()/scale;
+
+ if(p.x >= xstart && p.x <= xend)
+ {
+ if(getSelection().contains(feature))
+ getSelection().remove(feature);
+ else
+ {
+ clearSelection();
+
+ String tt = this_feature_range.getStart()+".."+
+ this_feature_range.getEnd();
+
+ if(feature.getIDString() != null)
+ tt = tt + ", " + feature.getIDString();
+
+ status_line.setText(tt);
+
+ getSelection().add(feature);
+ }
+ }
+ }
+ repaint();
+ }
+ }
+
+ public void mousePressed(MouseEvent event)
+ {
+ if(event.isPopupTrigger())
+ {
+ popup.show(event.getComponent(),
+ event.getX(), event.getY());
+ return;
+ }
+ }
+ });
+
+ JMenu zoomIn = new JMenu("Zoom In");
+ popup.add(zoomIn);
+ // set popup menu items
+ JMenuItem zoomIn5 = new JMenuItem("x 1/5");
+ zoomIn.add(zoomIn5);
+ zoomIn5.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ zoomIn(5,jsp);
+ }
+ });
+
+ JMenuItem zoomIn10 = new JMenuItem("x 1/10");
+ zoomIn.add(zoomIn10);
+ zoomIn10.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ zoomIn(10,jsp);
+ }
+ });
+
+
+ JMenu zoomOut = new JMenu("Zoom Out");
+ popup.add(zoomOut);
+ JMenuItem zoomOut5 = new JMenuItem("x5");
+ zoomOut.add(zoomOut5);
+ zoomOut5.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ scale = scale * 5;
+ adjustSize(jsp);
+ repaint();
+ }
+ });
+
+ JMenuItem zoomOut10 = new JMenuItem("x10");
+ zoomOut.add(zoomOut10);
+ zoomOut10.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ scale = scale * 10;
+ adjustSize(jsp);
+ repaint();
+ }
+ });
+
+ // set up status bar
+ status_line.setFont(Options.getOptions().getFont());
+ final FontMetrics fm =
+ this.getFontMetrics(status_line.getFont());
+
+ final int font_height = fm.getHeight()+10;
+
+ status_line.setMinimumSize(new Dimension(100, font_height));
+ status_line.setPreferredSize(new Dimension(100, font_height));
+
+ Border loweredbevel = BorderFactory.createLoweredBevelBorder();
+ Border raisedbevel = BorderFactory.createRaisedBevelBorder();
+ Border compound = BorderFactory.createCompoundBorder(raisedbevel,loweredbevel);
+ status_line.setBorder(compound);
+ }
+
+ private void zoomIn(final int factor, final JScrollPane jsp)
+ {
+ if(scale < factor)
+ {
+ scale = 1;
+ return;
+ }
+
+ scale = scale / factor;
+ adjustSize(jsp);
+ repaint();
+ }
+
+ /**
+ *
+ * Clear all selected features
+ *
+ **/
+ private void clearSelection()
+ {
+ FeatureVector selected_features = getSelection().getAllFeatures();
+
+ for(int i=0; i<selected_features.size(); i++)
+ {
+ Feature this_feature = selected_features.elementAt(i);
+ getSelection().remove(this_feature);
+ }
+ }
+
+ private Selection getSelection()
+ {
+ return selection;
+ }
+
+ /**
+ * Implementation of the SelectionChangeListener interface. We listen to
+ * SelectionChange events so that we can update the list to reflect the
+ * current selection.
+ **/
+ public void selectionChanged(SelectionChangeEvent event)
+ {
+ if(!isVisible())
+ return;
+
+ // don't bother with events we sent ourself
+ if(event.getSource() == this)
+ return;
+
+ // if the selected range changes we don't care
+ if(getSelection().getMarkerRange() != null &&
+ event.getType() == SelectionChangeEvent.OBJECT_CHANGED)
+ return;
+
+ repaint();
+ }
+
+ /**
+ *
+ * Select the next feature in the sequence.
+ *
+ */
+ private void goToNext(boolean up)
+ {
+ if(getSelection().getSelectedFeatures().size() != 1)
+ return;
+
+ final Feature curr_feature = getSelection().getSelectedFeatures().elementAt(0);
+ final Range curr_feature_range = curr_feature.getMaxRawRange();
+ int start = curr_feature_range.getStart();
+ int end = curr_feature_range.getEnd();
+
+ if(up && start == 1)
+ return;
+
+ for(int i=0; i<contig_features.size(); i++)
+ {
+ final Feature feature = contig_features.elementAt(i);
+ final Range this_feature_range = feature.getMaxRawRange();
+ if(up && this_feature_range.getEnd() == start-1)
+ {
+ clearSelection();
+ getSelection().add(feature);
+ return;
+ }
+ else if(!up && this_feature_range.getStart() == end+1)
+ {
+ clearSelection();
+ getSelection().add(feature);
+ return;
+ }
+ }
+ }
+
+ /**
+ *
+ * Used when changing the scale / magnification.
+ * @param jsp scrollpane to reset viewport size
+ *
+ */
+ private void adjustSize(final JScrollPane jsp)
+ {
+ length = xbound*2;
+ length += contig_features.elementAt(0).getStrand().getSequenceLength() / scale;
+ setPreferredSize(new Dimension(length, 60));
+ jsp.revalidate();
+ }
+
+ /**
+ *
+ * Get the status bar.
+ * @return status label
+ *
+ */
+ protected JLabel getStatusBar()
+ {
+ return status_line;
+ }
+
+ /**
+ *
+ * Override paintComponent()
+ *
+ */
+ protected void paintComponent(Graphics g)
+ {
+ Graphics2D g2 = (Graphics2D)g;
+ super.paintComponent(g2);
+
+ setFont(Options.getOptions().getFont());
+ final FontMetrics fm =
+ this.getFontMetrics(getFont());
+
+ for(int i=0; i<contig_features.size(); i++)
+ {
+ final Feature feature = contig_features.elementAt(i);
+ final Range this_feature_range = feature.getMaxRawRange();
+ Color colour = feature.getColour();
+ if(colour == null)
+ colour = Color.white;
+
+ int xstart = xbound + this_feature_range.getStart()/scale;
+ int xend = xbound + this_feature_range.getEnd()/scale;
+
+ RoundRectangle2D e = new RoundRectangle2D.Float(xstart, 10, xend-xstart,
+ 20, 0, 10);
+ GradientPaint gp = new GradientPaint(xstart, 10, colour,
+ xstart, 10+10, Color.white, true);
+ g2.setPaint(gp);
+ g2.fill(e);
+ }
+
+ BasicStroke stroke = (BasicStroke)g2.getStroke();
+ BasicStroke stroke1 = new BasicStroke(1.f);
+ BasicStroke stroke2 = new BasicStroke(2.f);
+ g2.setColor(Color.black);
+
+ FeatureVector selected_features = getSelection().getSelectedFeatures();
+ // draw feature outline
+ for(int i=0; i<contig_features.size(); i++)
+ {
+ final Feature feature = contig_features.elementAt(i);
+ final Range this_feature_range = feature.getMaxRawRange();
+ int xstart = xbound + this_feature_range.getStart()/scale;
+ int xend = xbound + this_feature_range.getEnd()/scale;
+
+ if(selected_features.contains(feature))
+ g2.setStroke(stroke2);
+ else
+ g2.setStroke(stroke1);
+
+ g2.drawRect(xstart, 10, xend-xstart, 20);
+
+ final String label_or_gene = feature.getIDString();
+ final Shape saved_clip = g.getClip();
+ g2.setColor(Color.black);
+ g2.setClip(xstart, 10, xend-xstart, 20);
+ g2.drawString(label_or_gene, xstart,
+ 10 + fm.getMaxAscent() + 1);
+ g2.setClip(saved_clip);
+ }
+
+ g2.setStroke(stroke);
+ if(highlight_drop_base > 0)
+ {
+ g2.setColor(Color.red);
+ final int draw_x_position = xbound + highlight_drop_base/scale;
+ g.drawLine(draw_x_position, 0,
+ draw_x_position, 100);
+ }
+ }
+
+ /**
+ *
+ * Determine the tool tip to display
+ * @param e mouse event
+ * @return tool tip
+ *
+ */
+ public String getToolTipText(MouseEvent e)
+ {
+ Point loc = e.getPoint();
+ int pos = loc.x*scale - xbound;
+ int first;
+ int last;
+
+ for(int i = 0; i < contig_features.size(); i++)
+ {
+ final Feature this_feature = contig_features.elementAt(i);
+ first = this_feature.getRawFirstBase();
+ last = this_feature.getRawLastBase();
+ if(pos >= first && pos <=last)
+ {
+ return first+".."+last;
+ }
+ }
+ return null;
+ }
+
+
+
+////////////////////
+// DRAG AND DROP
+////////////////////
+
+ /**
+ *
+ * Given a point find the nearest start/stop of a feature and
+ * set highlight_drop_base.
+ *
+ */
+ private void getNearestFeatureEnd(Point loc)
+ {
+ final int base_pos = (loc.x-50)*scale;
+ int first;
+ int last;
+ final Vector contig_keys = FeatureDisplay.getContigKeys();
+
+ for(int i = 0; i < contig_features.size(); i++)
+ {
+ final Feature this_feature = contig_features.elementAt(i);
+
+ if(contig_keys.contains(this_feature.getKey()))
+ {
+ first = this_feature.getRawFirstBase();
+ last = this_feature.getRawLastBase();
+
+ if( Math.abs(first - base_pos) < Math.abs(base_pos - highlight_drop_base) )
+ highlight_drop_base = first;
+ if( Math.abs(last - base_pos) < Math.abs(base_pos - highlight_drop_base) )
+ highlight_drop_base = last+1;
+ }
+ }
+ }
+
+// drop
+ public void drop(DropTargetDropEvent e)
+ {
+ //Transferable t = e.getTransferable();
+ if(e.isDataFlavorSupported(DataFlavor.stringFlavor))
+ {
+ FeatureVector selected_features = getSelection().getSelectedFeatures();
+ feature_display.reorder(highlight_drop_base,
+ selected_features.elementAt(0)); // rearrange contigs
+
+ // reset status bar
+ final Range this_feature_range = selected_features.elementAt(0).getMaxRawRange();
+ String tt = this_feature_range.getStart()+".."+
+ this_feature_range.getEnd();
+
+ if(selected_features.elementAt(0).getIDString() != null)
+ tt = tt + ", " + selected_features.elementAt(0).getIDString();
+
+ status_line.setText(tt);
+ repaint();
+ }
+ highlight_drop_base = -1;
+ }
+
+ public void dragExit(DropTargetEvent e)
+ {
+ highlight_drop_base = -1;
+ }
+
+ public void dropActionChanged(DropTargetDragEvent e) {}
+
+ public void dragOver(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(DataFlavor.stringFlavor))
+ {
+ Point ploc = e.getLocation();
+ getNearestFeatureEnd(ploc);
+ repaint();
+ }
+ else
+ e.rejectDrag();
+ }
+
+ public void dragEnter(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(DataFlavor.stringFlavor))
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+
+// drag source
+ public void dragGestureRecognized(DragGestureEvent e)
+ {
+ // ignore if mouse popup trigger
+ InputEvent ie = e.getTriggerEvent();
+ if(ie instanceof MouseEvent)
+ if(((MouseEvent)ie).isPopupTrigger())
+ return;
+
+ final Vector contig_keys = FeatureDisplay.getContigKeys();
+
+ FeatureVector selected_features = getSelection().getSelectedFeatures();
+ if(selected_features.size() == 1 &&
+ contig_keys.contains(selected_features.elementAt(0).getKey()))
+ {
+ ClassLoader cl = this.getClass().getClassLoader();
+ ImageIcon icon = new ImageIcon(cl.getResource("images/icon.gif"));
+ final Image icon_image = icon.getImage();
+
+ //TransferableContig tcontig = new TransferableContig(selected_features.elementAt(0));
+ StringSelection name = new StringSelection(selected_features.elementAt(0).getGeneName());
+
+ e.startDrag(DragSource.DefaultCopyDrop, // cursor
+ icon_image, new Point(-1, -1),
+ (Transferable)name, // transferable data
+ this); // drag source listener
+ }
+ }
+
+ public void dragDropEnd(DragSourceDropEvent e) {}
+ public void dragEnter(DragSourceDragEvent e) {}
+
+ public void dragExit(DragSourceEvent e)
+ {
+// highlight_drop_base = -1;
+ }
+ public void dragOver(DragSourceDragEvent e) {}
+ public void dropActionChanged(DragSourceDragEvent e) {}
+
+
+////////////////////
+// AUTO SCROLLING //
+////////////////////
+ /**
+ *
+ * Handles the auto scrolling of the JTree.
+ * @param location The location of the mouse.
+ *
+ */
+ public void autoscroll( Point location )
+ {
+ int top = 0, left = 0, bottom = 0, right = 0;
+ Dimension size = getSize();
+ Rectangle rect = getVisibleRect();
+ int bottomEdge = rect.y + rect.height;
+ int rightEdge = rect.x + rect.width;
+ if( location.y - rect.y < AUTOSCROLL_MARGIN && rect.y > 0 )
+ top = AUTOSCROLL_MARGIN;
+ if( location.x - rect.x < AUTOSCROLL_MARGIN && rect.x > 0 )
+ left = AUTOSCROLL_MARGIN;
+ if( bottomEdge - location.y < AUTOSCROLL_MARGIN && bottomEdge < size.height )
+ bottom = AUTOSCROLL_MARGIN;
+ if( rightEdge - location.x < AUTOSCROLL_MARGIN && rightEdge < size.width )
+ right = AUTOSCROLL_MARGIN;
+ rect.x += right - left;
+ rect.y += bottom - top;
+ scrollRectToVisible( rect );
+ }
+
+ /**
+ *
+ * Gets the insets used for the autoscroll.
+ * @return The insets.
+ *
+ */
+ public Insets getAutoscrollInsets()
+ {
+ Dimension size = getSize();
+ Rectangle rect = getVisibleRect();
+ autoscrollInsets.top = rect.y + AUTOSCROLL_MARGIN;
+ autoscrollInsets.left = rect.x + AUTOSCROLL_MARGIN;
+ autoscrollInsets.bottom = size.height - (rect.y+rect.height) + AUTOSCROLL_MARGIN;
+ autoscrollInsets.right = size.width - (rect.x+rect.width) + AUTOSCROLL_MARGIN;
+ return autoscrollInsets;
+ }
+
+ protected void setScale(int scale)
+ {
+ this.scale = scale;
+ }
+
+ protected int getScale()
+ {
+ return scale;
+ }
+
+ /**
+ *
+ * Popup listener
+ *
+ */
+ class PopupListener extends MouseAdapter
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/CorbaEntrySource.java b/uk/ac/sanger/artemis/components/CorbaEntrySource.java
new file mode 100644
index 0000000..2c1950d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/CorbaEntrySource.java
@@ -0,0 +1,90 @@
+/* CorbaEntrySource.java
+ *
+ * created: Wed Jun 7 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/CorbaEntrySource.java,v 1.1 2004-06-09 09:46:11 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.io.*;
+import java.net.*;
+import java.awt.*;
+
+import javax.swing.*;
+
+/**
+ * This class contains the methods common to all EntrySource implementations
+ * that read from a CORBA server.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: CorbaEntrySource.java,v 1.1 2004-06-09 09:46:11 tjc Exp $
+ **/
+
+abstract public class CorbaEntrySource {
+ /**
+ * Create a new CorbaEntrySource using the given URL as the location of the
+ * server IOR.
+ * @param frame The component that created this EntrySource. (Used for
+ * requesters.)
+ * @param ior_url_string A String containing the URL of the IOR for the
+ * server.
+ **/
+ public CorbaEntrySource (final JFrame frame,
+ final String ior_url_string)
+ throws MalformedURLException //, IOException
+ {
+ this.frame = frame;
+ this.ior_url = new URL (ior_url_string);
+ }
+
+ /**
+ * Finds and returns a stringified object reference (IOR) for a CORBA
+ * server. The IOR is read from the URL that was passed to the
+ * constructor.
+ * @return The stringified IOR.
+ * @exception IOException Thrown if an error occurs while reading.
+ **/
+ public String getIOR ()
+ throws IOException {
+ // get URL of IOR
+ final BufferedReader in =
+ new BufferedReader (new InputStreamReader (ior_url.openStream()));
+ return in.readLine();
+ }
+
+ /**
+ * Return the JFrame that was passed to the constructor.
+ **/
+ public JFrame getFrame () {
+ return frame;
+ }
+
+ /**
+ * The URL containing the IOR of the CORBA server.
+ **/
+ private URL ior_url = null;
+
+ /**
+ * The JFrame that was passed to the constructor.
+ **/
+ private JFrame frame = null;
+}
diff --git a/uk/ac/sanger/artemis/components/DbfetchEntrySource.java b/uk/ac/sanger/artemis/components/DbfetchEntrySource.java
new file mode 100644
index 0000000..286771d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/DbfetchEntrySource.java
@@ -0,0 +1,203 @@
+/* DbfetchEntrySource.java
+ *
+ * created: Fri Nov 28 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/DbfetchEntrySource.java,v 1.3 2004-12-16 10:44:33 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.io.DocumentEntryFactory;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.SimpleEntryInformation;
+
+import java.io.*;
+import java.util.regex.Pattern;
+
+import javax.swing.*;
+
+/**
+ * This is an EntrySource that reads Entry objects from the EMBL Dbfetch
+ * server.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: DbfetchEntrySource.java,v 1.3 2004-12-16 10:44:33 tjc Exp $
+ **/
+
+public class DbfetchEntrySource
+ implements EntrySource
+{
+ private static Pattern REFSEQ_PATTERN = Pattern.compile("[a-zA-Z]{2}?_\\w*");
+ /**
+ * Create a new DbfetchEntrySource.
+ * @param frame The component that created this EntrySource. (Used for
+ * requesters.)
+ **/
+ public DbfetchEntrySource (final JFrame frame)
+ {
+ }
+
+ /**
+ * Get an Entry object from the Ensembl Dbfetch server.
+ * @param bases The Bases object to pass to the Entry constructor.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry (final Bases bases, final boolean show_progress)
+ throws OutOfRangeException, IOException
+ {
+ final TextDialog text_dialog =
+ new TextDialog (getFrame (), "Enter an accession number:", 10, "");
+
+ final String text = text_dialog.getText ();
+
+ if (text == null) {
+ // user cancel
+ return null;
+ }
+
+ final String embl_id = text.trim ();
+
+ if(embl_id.length () > 0)
+ {
+ final LogReadListener read_event_logger = new LogReadListener (embl_id);
+
+ final EntryInformation entry_information =
+ new SimpleEntryInformation(Options.getArtemisEntryInformation ());
+
+// final MessageDialog message_frame =
+// new MessageDialog (getFrame (),
+// "reading entry - please wait", false);
+
+ String db = "EMBL";
+ if(REFSEQ_PATTERN.matcher(embl_id).matches())
+ db = "refseq";
+
+ final String url_string =
+ "http://www.ebi.ac.uk/cgi-bin/dbfetch?db="+db+"&id=" + embl_id +"&style=raw";
+
+ final Document url_document =
+ DocumentFactory.makeDocument(url_string);
+
+ try
+ {
+ final uk.ac.sanger.artemis.io.Entry new_embl_entry =
+ DocumentEntryFactory.makeDocumentEntry(entry_information,
+ url_document,
+ read_event_logger);
+
+ if (read_event_logger.seenMessage()) {
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog (frame,
+ "there were warnings while reading - view now?");
+
+ if (yes_no_dialog.getResult ()) {
+ Splash.showLog ();
+ }
+ }
+
+ final Bases real_bases;
+
+ if (bases == null) {
+ if (new_embl_entry.getSequence () == null) {
+ final String message =
+ "the entry contains no sequence: " + embl_id;
+ new MessageDialog (getFrame (), message);
+ return null;
+ }
+
+ real_bases = new Bases (new_embl_entry.getSequence ());
+ } else {
+ real_bases = bases;
+ }
+
+ return new Entry (real_bases, new_embl_entry);
+ }
+ catch (EntryInformationException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+// finally
+// {
+// message_frame.dispose ();
+// }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an Entry object from the Ensembl Dbfetch server.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if the entry that we read has no
+ * sequence.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry (final boolean show_progress)
+ throws OutOfRangeException, NoSequenceException, IOException
+ {
+ return getEntry (null, show_progress);
+ }
+
+ /**
+ * Return the name of this source (for display to the user in menus).
+ **/
+ public String getSourceName () {
+ return "EBI - Dbfetch";
+ }
+
+ /**
+ * Returns true if and only if this EntrySource always returns "full"
+ * entries. ie. entries that contain features and sequence. Entries that
+ * are read from EMBL always contain sequence so in this class this method
+ * returns false.
+ **/
+ public boolean isFullEntrySource () {
+ return true;
+ }
+
+ /**
+ * Return the JFrame that was passed to the constructor.
+ **/
+ public JFrame getFrame () {
+ return frame;
+ }
+
+ /**
+ * The JFrame that was passed to the constructor.
+ **/
+ private JFrame frame = null;
+}
diff --git a/uk/ac/sanger/artemis/components/DisplayAdjustmentEvent.java b/uk/ac/sanger/artemis/components/DisplayAdjustmentEvent.java
new file mode 100644
index 0000000..911f0b7
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/DisplayAdjustmentEvent.java
@@ -0,0 +1,215 @@
+/* DisplayAdjustmentEvent.java
+ *
+ * created: Tue Dec 15 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/DisplayAdjustmentEvent.java,v 1.3 2005-12-02 14:58:57 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.ChangeEvent;
+
+/**
+ * This event is sent when a FeatureDisplay is scrolled.
+ *
+ * @author Kim Rutherford
+ * @version $Id: DisplayAdjustmentEvent.java,v 1.3 2005-12-02 14:58:57 tjc Exp $
+ **/
+
+public class DisplayAdjustmentEvent extends ChangeEvent
+{
+
+ /** The new start base, as passed to the constructor. */
+ private int start_base;
+
+ /** The new end base, as passed to the constructor. */
+ private int end_base;
+
+ /**
+ * The width in bases of the display, as passed to the constructor.
+ **/
+ private int width_in_bases;
+
+ /**
+ * The width of each base on the display, as passed to the constructor.
+ **/
+ private float base_width;
+
+ /** The scale factor, as passed to the constructor. */
+ private int scale_factor;
+
+ private int drop_position;
+
+ /**
+ * True if and only if the FeatureDisplay is drawing in
+ * reverse complement mode.
+ **/
+ private boolean rev_comp_display;
+
+ /**
+ * The type of event. One of: SCALE_ADJUST_EVENT, SCROLL_ADJUST_EVENT or
+ * ALL_CHANGE_ADJUST_EVENT.
+ **/
+ private int type;
+
+ /**
+ * The type of DisplayAdjustmentEvent where the scale (only) has changed
+ **/
+ final static public int SCALE_ADJUST_EVENT = 0;
+
+ /**
+ * The type of DisplayAdjustmentEvent where the display has scrolled but
+ * the scale has stayed the same.
+ **/
+ final static public int SCROLL_ADJUST_EVENT = 1;
+
+ /**
+ * The type of DisplayAdjustmentEvent where the display has been reverse
+ * complemented.
+ **/
+ final static public int REV_COMP_EVENT = 2;
+
+ /**
+ * The type of DisplayAdjustmentEvent where the display has scrolled and
+ * the the scale has changed.
+ **/
+ final static public int ALL_CHANGE_ADJUST_EVENT = 3;
+
+ /**
+ * The type of DisplayAdjustmentEvent where the display has been reverse
+ * complemented.
+ **/
+ final static public int CONTIG_REV_COMP_EVENT = 4;
+
+ final static public int CONTIG_REORDER = 5;
+
+ /** change tabix indexed sequence */
+ final static public int IDX_SEQUENCE_CHANGE = 6;
+
+ public DisplayAdjustmentEvent(Object source,
+ int start_base, int end_base,
+ int drop_position, int type)
+ {
+ super(source);
+ this.start_base = start_base;
+ this.end_base = end_base;
+ this.drop_position = drop_position;
+ this.type = type;
+ }
+
+ /**
+ * Create a new DisplayAdjustmentEvent.
+ * @param source The Object that generated the event - probably a component.
+ * @param start_base The new start base on the display.
+ * @param end_base The new end base on the display.
+ * @param width_in_bases The width of the drawing area in bases. We need
+ * this because if the user scrolls to the end of the sequence, the last
+ * visible base (end_base) may not be at the right of the screen. This
+ * might be greater than end_base - start_base.
+ * @param base_width The width in pixels of a base on screen.
+ * @param scale_factor This is the scale factor use by the FeatureDisplay
+ * component. A factor of zero means the full translation will be
+ * visible. At higher scale factors only stop codons are visible, and
+ * a bigger number will mean more bases are visible.
+ * @param type the type of event: SCALE_ADJUST_EVENT, SCROLL_ADJUST_EVENT
+ * or ALL_CHANGE_ADJUST_EVENT.
+ **/
+ public DisplayAdjustmentEvent(Object source,
+ int start_base, int end_base,
+ int width_in_bases, float base_width,
+ int scale_factor,
+ boolean rev_comp_display, int type)
+ {
+ super(source);
+ this.start_base = start_base;
+ this.end_base = end_base;
+ this.width_in_bases = width_in_bases;
+ this.base_width = base_width;
+ this.scale_factor = scale_factor;
+ this.rev_comp_display = rev_comp_display;
+ this.type = type;
+ }
+
+ /**
+ * Return the new start base to display, as passed to the constructor.
+ **/
+ public int getStart()
+ {
+ return start_base;
+ }
+
+ /**
+ * Return the new end base to display, as passed to the constructor.
+ **/
+ public int getEnd()
+ {
+ return end_base;
+ }
+
+ public int getDropPosition()
+ {
+ return drop_position;
+ }
+
+ /**
+ * Return the width in bases of the display, as passed to the constructor.
+ **/
+ public int getWidthInBases()
+ {
+ return width_in_bases;
+ }
+
+ /**
+ * Return the width of a base on the display, as passed to the constructor.
+ **/
+ public float getBaseWidth()
+ {
+ return base_width;
+ }
+
+ /**
+ * Return the scale factor that was passed to the constructor.
+ **/
+ public int getScaleFactor()
+ {
+ return scale_factor;
+ }
+
+ /**
+ * Return true if and only if the FeatureDisplay is drawing in reverse
+ * complement mode.
+ **/
+ public boolean isRevCompDisplay()
+ {
+ return rev_comp_display;
+ }
+
+ /**
+ * Return the type that was passed to the constructor.
+ **/
+ public int getType()
+ {
+ return type;
+ }
+
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/DisplayAdjustmentListener.java b/uk/ac/sanger/artemis/components/DisplayAdjustmentListener.java
new file mode 100644
index 0000000..31bcc82
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/DisplayAdjustmentListener.java
@@ -0,0 +1,45 @@
+/* DisplayAdjustmentListener.java
+ *
+ * created: Tue Dec 15 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/DisplayAdjustmentListener.java,v 1.1 2004-06-09 09:46:14 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * The DisplayAdjustmentListener interface is implemented by those
+ * components that need to track the scrolling of a component that can
+ * display bases. For example, BasePlot objects listen to FeatureDisplay
+ * objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: DisplayAdjustmentListener.java,v 1.1 2004-06-09 09:46:14 tjc Exp $
+ **/
+
+public interface DisplayAdjustmentListener extends uk.ac.sanger.artemis.ChangeListener {
+ /**
+ * Invoked when a component scrolls or changes the scale.
+ **/
+ void displayAdjustmentValueChanged (DisplayAdjustmentEvent event);
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/DisplayComponent.java b/uk/ac/sanger/artemis/components/DisplayComponent.java
new file mode 100644
index 0000000..3c15fab
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/DisplayComponent.java
@@ -0,0 +1,51 @@
+/* DisplayComponent.java (formally SelectionDisplayer.java)
+ *
+ * created: Fri Nov 13 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/DisplayComponent.java,v 1.2 2008-10-30 15:25:24 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.GotoEventSource;
+
+import javax.swing.JFrame;
+
+/**
+ * Interface discribing those methods common to all the classes in
+ * uk.ac.sanger.artemis.components that can display EntryGroup objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: DisplayComponent.java,v 1.2 2008-10-30 15:25:24 tjc Exp $
+ **/
+
+public interface DisplayComponent
+{
+ /**
+ * Return an object that implements the GotoEventSource interface.
+ **/
+ GotoEventSource getGotoEventSource ();
+
+ /**
+ * Return the reference of the JFrame that owns this component.
+ **/
+ JFrame getParentFrame ();
+}
diff --git a/uk/ac/sanger/artemis/components/EMBLCorbaEntrySource.java b/uk/ac/sanger/artemis/components/EMBLCorbaEntrySource.java
new file mode 100644
index 0000000..0840124
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EMBLCorbaEntrySource.java
@@ -0,0 +1,318 @@
+/* EMBLCorbaEntrySource.java
+ *
+ * created: Wed Jun 7 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EMBLCorbaEntrySource.java,v 1.1 2004-06-09 09:46:17 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.LocationParseException;
+import uk.ac.sanger.artemis.io.InvalidKeyException;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.SimpleEntryInformation;
+
+import nsdb.EmblSeq;
+import nsdb.EmblPackage.Superceded;
+import type.NoResult;
+
+import java.io.*;
+import java.awt.*;
+import java.net.*;
+
+import javax.swing.*;
+
+/**
+ * This is an EntrySource that reads Entry objects from the EMBL CORBA
+ * server.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: EMBLCorbaEntrySource.java,v 1.1 2004-06-09 09:46:17 tjc Exp $
+ **/
+
+public class EMBLCorbaEntrySource
+ extends CorbaEntrySource
+ implements EntrySource
+{
+ /**
+ * Create a new EMBLCorbaEntrySource from the given String.
+ * @param frame The component that created this EntrySource. (Used for
+ * requesters.)
+ * @param ior_url_string A String containing the URL of the IOR for the
+ * server.
+ **/
+ public EMBLCorbaEntrySource (final JFrame frame,
+ final String ior_url_string)
+ throws MalformedURLException//, IOException
+ {
+ super (frame, ior_url_string);
+ }
+
+ /**
+ * Get an Entry object from the Ensembl CORBA server.
+ * @param bases The Bases object to pass to the Entry constructor.
+ * @param progress_thread Progress thread to monitor entry reading.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry (final Bases bases, final ProgressThread progress_thread,
+ final boolean show_progress)
+ throws OutOfRangeException, IOException
+ {
+ return makeCorbaDialog (bases, false);
+ }
+
+ /**
+ * Get an Entry object from the Ensembl CORBA server.
+ * @param bases The Bases object to pass to the Entry constructor.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry (final Bases bases, final boolean show_progress)
+ throws OutOfRangeException, IOException
+ {
+ return makeCorbaDialog (bases, false);
+ }
+
+ /**
+ * Get an Entry object from the Ensembl CORBA server.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @param progress_thread Progress thread to monitor entry reading.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry (final boolean show_progress,
+ final ProgressThread progress_thread)
+ throws OutOfRangeException, IOException
+ {
+ return makeCorbaDialog (null, false);
+ }
+
+
+ /**
+ * Get an Entry object from the Ensembl CORBA server.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading. (Not implemented)
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if the entry that we read has no
+ * sequence.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry (final boolean show_progress)
+ throws OutOfRangeException, NoSequenceException, IOException {
+ return makeCorbaDialog (null, false);
+ }
+
+ /**
+ * Return the name of this source (for display to the user in menus).
+ **/
+ public String getSourceName () {
+ return "EBI - CORBA";
+ }
+
+ /**
+ * Returns true if and only if this EntrySource always returns "full"
+ * entries. ie. entries that contain features and sequence. Entries that
+ * are read from EMBL always contain sequence so in this class this method
+ * returns false.
+ **/
+ public boolean isFullEntrySource () {
+ return true;
+ }
+
+ /**
+ * Create a TextRequester, wait for the user to type an accession number
+ * and then read that entry from the EMBL CORBA server.
+ * @param bases If this is null a new Bases object will be created for the
+ * Entry once it has been read from the server. If not null then it will
+ * be passed to the Entry constructor.
+ * @param read_only true if and only if a read-only Entry should be created
+ * (some are always read only).
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ **/
+ protected Entry makeCorbaDialog (final Bases bases,
+ final boolean read_only)
+ throws OutOfRangeException, IOException {
+ final org.omg.CORBA.ORB orb =
+ org.omg.CORBA.ORB.init (new String [0], new java.util.Properties());
+
+ final TextDialog text_dialog =
+ new TextDialog (getFrame (), "Enter an accession number:", 10, "");
+
+ final String text = text_dialog.getText ();
+
+ if (text == null) {
+ // user cancel
+ return null;
+ } else {
+ final String corba_id = text.trim ();
+
+ if (corba_id.length () > 0) {
+ final MessageDialog message_frame =
+ new MessageDialog (getFrame (),
+ "reading entry - please wait", false);
+
+ try {
+ return makeFromCorbaID (bases, corba_id, read_only);
+ } finally {
+ message_frame.dispose ();
+ }
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Given an accession number and the handle of an EMBL corba server, this
+ * method will ask the user (using a TextRequester) for the id of a entry
+ * in the server and will then attempt to get it.
+ * @param bases If this is null a new Bases object will be created for the
+ * Entry once it has been read from the server. If not null then it will
+ * be passed to the Entry constructor.
+ * @param corba_handle The handle of the nsdb.Embl object from which we
+ * will read the entry.
+ * @param corba_id The id of the entry in the database
+ * @param read_only true if and only if a read-only Entry should be created
+ * (some are always read only).
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ **/
+ protected Entry makeFromCorbaID (final Bases bases,
+ final String corba_id,
+ final boolean read_only)
+ throws OutOfRangeException, IOException {
+ try {
+ final nsdb.Embl corba_handle = getServerHandle ();
+
+ final uk.ac.sanger.artemis.io.Entry new_embl_entry;
+
+ final nsdb.EmblWriter embl_writer =
+ nsdb.EmblWriterHelper.narrow (corba_handle);
+
+ final EntryInformation entry_information =
+ new SimpleEntryInformation (Options.getArtemisEntryInformation ());
+
+ if (read_only || embl_writer == null) {
+ // first try to make a plain EmblSeq object
+ final EmblSeq embl_seq = corba_handle.getEmblSeq (corba_id);
+
+ new_embl_entry =
+ new uk.ac.sanger.artemis.io.CorbaEntry (entry_information,
+ embl_seq);
+ } else {
+ // make a read-write object
+ final nsdb.EmblSeqWriter embl_write_seq =
+ embl_writer.getEmblSeqWriter (corba_id);
+
+ new_embl_entry =
+ new uk.ac.sanger.artemis.io.RWCorbaEntry (entry_information,
+ embl_write_seq);
+ }
+
+ final Bases real_bases;
+
+ if (bases == null) {
+ if (new_embl_entry.getSequence () == null) {
+ final String message =
+ "the entry contains no sequence: " + corba_id;
+ new MessageDialog (getFrame (), message);
+ return null;
+ }
+
+ real_bases = new Bases (new_embl_entry.getSequence ());
+ } else {
+ real_bases = bases;
+ }
+
+ return new Entry (real_bases, new_embl_entry);
+ } catch (NoResult e) {
+ final String message =
+ "Database query failed (no result) while getting id: " + corba_id;
+ new MessageDialog (getFrame (), message);
+ } catch (Superceded e) {
+ // Superceded is thrown by getEmblSeq method if accession number
+ // doesn't exist anymore because it was merged or split
+ final String message =
+ "This accession number has been superceded: " + corba_id;
+ new MessageDialog (getFrame (), message);
+ } catch (LocationParseException e) {
+ final String message =
+ "Unexpected error while accessing " + corba_id + ": " + e;
+ new MessageDialog (getFrame (), message);
+ } catch (InvalidKeyException e) {
+ final String message =
+ "Unexpected error while accessing " + corba_id + ": " + e;
+ new MessageDialog (getFrame (), message);
+ } catch (org.omg.CORBA.OBJECT_NOT_EXIST e) {
+ final String message =
+ "the object you requested (" + corba_id + ") does not exist";
+ new MessageDialog (getFrame (), message);
+ } catch (org.omg.CORBA.COMM_FAILURE e) {
+ final String message =
+ "Failed to get an object from Corba: " + e;
+ new MessageDialog (getFrame (), message);
+ } catch (EntryInformationException e) {
+ final String message =
+ "Failed to get an object from Corba: " + e;
+ new MessageDialog (getFrame (), message);
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the handle of the EMBL database. The database IOR is found by
+ * calling getIOR ().
+ **/
+ protected nsdb.Embl getServerHandle ()
+ throws org.omg.CORBA.COMM_FAILURE, IOException {
+ final org.omg.CORBA.ORB orb =
+ org.omg.CORBA.ORB.init (new String [0], new java.util.Properties());
+
+ final org.omg.CORBA.Object obj;
+
+ obj = orb.string_to_object (getIOR ());
+
+ return nsdb.EmblHelper.narrow (obj);
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/EditMenu.java b/uk/ac/sanger/artemis/components/EditMenu.java
new file mode 100644
index 0000000..50d124a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EditMenu.java
@@ -0,0 +1,3756 @@
+/* EditMenu.java
+ *
+ * created: Thu Dec 3 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EditMenu.java,v 1.62 2009-08-17 12:29:04 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.components.genebuilder.BasicGeneBuilderFrame;
+import uk.ac.sanger.artemis.components.genebuilder.GeneBuilderFrame;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.genebuilder.GeneViewerPanel;
+import uk.ac.sanger.artemis.components.genebuilder.gff.PropertiesPanel;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.OutOfDateException;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.RawStreamSequence;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.IOException;
+
+import javax.swing.*;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ * A menu with editing commands.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EditMenu.java,v 1.62 2009-08-17 12:29:04 tjc Exp $
+ **/
+
+public class EditMenu extends SelectionMenu
+ implements EntryGroupChangeListener, EntryChangeListener
+{
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The GotoEventSource object that was passed to the constructor.
+ **/
+ private GotoEventSource goto_event_source = null;
+
+ /**
+ * The EntryGroup object that was passed to the constructor.
+ **/
+ private EntryGroup entry_group = null;
+
+ /**
+ * The BasePlotGroup object that was passed to the constructor.
+ **/
+ private BasePlotGroup base_plot_group = null;
+
+ /** FeatureDisplay */
+ private DisplayComponent owner;
+
+
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(EditMenu.class);
+
+ /** records the gene builders that are open */
+ private static Hashtable<String, GeneBuilderFrame> geneBuilderHash;
+
+ /**
+ * Create a new EditMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param goto_event_source The object the we will call makeBaseVisible()
+ * on.
+ * @param entry_group The EntryGroup object where new features/entries will
+ * be added.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ * @param menu_name The name of the new menu.
+ **/
+ public EditMenu(final JFrame frame,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group,
+ final String menu_name,
+ final DisplayComponent owner)
+ {
+ super(frame, menu_name, selection);
+
+ this.entry_group = entry_group;
+ this.goto_event_source = goto_event_source;
+ this.base_plot_group = base_plot_group;
+ this.owner = owner;
+
+ getEntryGroup().addEntryGroupChangeListener(this);
+ getEntryGroup().addEntryChangeListener(this);
+ refreshMenu();
+ }
+
+ /**
+ * Create a new EditMenu object and use "Edit" as the menu name.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param goto_event_source The object the we will call makeBaseVisible()
+ * on.
+ * @param entry_group The EntryGroup object where new features/entries will
+ * be added.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ public EditMenu(final JFrame frame,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group,
+ final DisplayComponent owner)
+ {
+ this(frame, selection, goto_event_source, entry_group,
+ base_plot_group, "Edit", owner);
+ }
+
+ /**
+ * The shortcut for Edit Selected Features.
+ **/
+ final static KeyStroke EDIT_FEATURES_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_E,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+ final static public int EDIT_FEATURES_KEY_CODE = KeyEvent.VK_E;
+
+ /**
+ * The shortcut for Merge Selected Features.
+ **/
+ final static KeyStroke MERGE_FEATURES_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_M,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); // InputEvent.CTRL_MASK);
+ final static public int MERGE_FEATURES_KEY_CODE = KeyEvent.VK_M;
+
+ /**
+ * The shortcut for Duplicate Selected Features.
+ **/
+ final static KeyStroke DUPLICATE_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_D,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); // InputEvent.CTRL_MASK);
+ final static public int DUPLICATE_KEY_CODE = KeyEvent.VK_D;
+
+ /**
+ * The shortcut for Delete Selected Features.
+ **/
+ final static KeyStroke DELETE_FEATURES_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); // InputEvent.CTRL_MASK);
+ final static public int DELETE_FEATURES_KEY_CODE = KeyEvent.VK_DELETE;
+
+ /**
+ * The shortcut for Trim Selected Features.
+ **/
+ final static KeyStroke TRIM_FEATURES_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_T,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); // InputEvent.CTRL_MASK);
+ final static public int TRIM_FEATURES_KEY_CODE = KeyEvent.VK_T;
+
+ /**
+ * The shortcut for Trim Selected Features To Next Any.
+ **/
+ final static KeyStroke TRIM_FEATURES_TO_NEXT_ANY_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_Y,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); // InputEvent.CTRL_MASK);
+ final static public int TRIM_FEATURES_TO_NEXT_ANY_KEY_CODE = KeyEvent.VK_Y;
+
+ /**
+ * The shortcut for Extend to Previous Stop Codon.
+ **/
+ final static public int EXTEND_TO_PREVIOUS_STOP_CODON_KEY_CODE =
+ KeyEvent.VK_Q;
+ final static KeyStroke EXTEND_TO_PREVIOUS_STOP_CODON_KEY =
+ makeMenuKeyStroke(EXTEND_TO_PREVIOUS_STOP_CODON_KEY_CODE);
+
+ /**
+ * The shortcut for Undo.
+ **/
+ final static public int UNDO_KEY_CODE = KeyEvent.VK_U;
+ final static KeyStroke UNDO_KEY =
+ KeyStroke.getKeyStroke(UNDO_KEY_CODE,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); // InputEvent.CTRL_MASK);
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can update the display if entries
+ * are added or deleted.
+ **/
+ public void entryGroupChanged(final EntryGroupChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryGroupChangeEvent.ENTRY_ADDED:
+ case EntryGroupChangeEvent.ENTRY_DELETED:
+ case EntryGroupChangeEvent.ENTRY_INACTIVE:
+ case EntryGroupChangeEvent.ENTRY_ACTIVE:
+ case EntryGroupChangeEvent.NEW_DEFAULT_ENTRY:
+ refreshMenu();
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface.
+ **/
+ public void entryChanged(final EntryChangeEvent event)
+ {
+ if(event.getType() == EntryChangeEvent.NAME_CHANGED)
+ refreshMenu();
+ }
+
+ /**
+ * Update the menus to the reflect the current contents of the EntryGroup.
+ **/
+ private void refreshMenu()
+ {
+ removeAll();
+
+ final JMenuItem undo_item = new JMenuItem("Undo");
+ getEntryGroup().getActionController().addUndoMenu(undo_item);
+ undo_item.setAccelerator(UNDO_KEY);
+ undo_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ undo(getParentFrame(), getSelection(), getEntryGroup());
+ }
+ });
+
+
+ final JMenuItem redo_item = new JMenuItem("Redo");
+ //redo_item.setAccelerator(REDO_KEY);
+ getEntryGroup().getActionController().addRedoMenu(redo_item);
+ redo_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ redo(getParentFrame(), getSelection(), getEntryGroup());
+ }
+ });
+
+ final JMenuItem contig_reordering = new JMenuItem("Contig Reordering");
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ contig_reordering.setEnabled(false);
+
+ contig_reordering.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ FeatureDisplay display = (FeatureDisplay)owner;
+ FeatureVector contig_features = display.getContigs();
+
+ if(contig_features == null || contig_features.size() < 1)
+ {
+ final Vector<String> contigKeys = FeatureDisplay.getContigKeys();
+ String msg = "No contig feature keys found:\n";
+ for(int i=0; i<contigKeys.size(); i++)
+ msg = msg+contigKeys.get(i)+"\n";
+ JOptionPane.showMessageDialog(display,
+ msg, "No Contigs Found", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ final JFrame frame = new JFrame("Contig Tool");
+
+ JScrollPane jsp = new JScrollPane();
+ final ContigTool ct = new ContigTool(contig_features,
+ (FeatureDisplay)owner, jsp,
+ getSelection());
+ jsp.setViewportView(ct);
+
+ jsp.getViewport().setBackground(Color.white);
+ jsp.setPreferredSize(new Dimension(display.getWidth(),
+ ct.getPreferredSize().height+
+ jsp.getVerticalScrollBar().getPreferredSize().height));
+ frame.getContentPane().add(jsp, BorderLayout.CENTER);
+ frame.getContentPane().add(ct.getStatusBar(),
+ BorderLayout.SOUTH);
+
+ frame.pack();
+ frame.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ getSelection().removeSelectionChangeListener(ct);
+ frame.dispose();
+ }
+ });
+
+ Utilities.centreJustifyFrame(frame,0);
+ frame.setVisible(true);
+ }
+ });
+
+ final JMenuItem edit_feature_item = new JMenuItem("Selected Features in Editor");
+ edit_feature_item.setAccelerator(EDIT_FEATURES_KEY);
+ edit_feature_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ editSelectedFeatures(getParentFrame(), getEntryGroup(),
+ getSelection(), goto_event_source);
+ }
+ });
+
+ final JMenuItem edit_subsequence_item = new JMenuItem("Subsequence (and Features)");
+ edit_subsequence_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ editSubSequence();
+ }
+ });
+
+ final SelectionSubMenu qualifier_menu =
+ new SelectionSubMenu(this,"Qualifier of Selected Feature(s)");
+ final JMenuItem add_qualifiers_item = new JMenuItem("Change ...");
+ add_qualifiers_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ addQualifiers(getParentFrame(), getSelection());
+ }
+ });
+
+ final JMenuItem remove_qualifier_item = new JMenuItem("Remove ...");
+ remove_qualifier_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ removeQualifier(getParentFrame(), getSelection());
+ }
+ });
+
+ final JMenuItem convert_qualifier_item = new JMenuItem("Convert ...");
+ convert_qualifier_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ convertQualifier(getParentFrame(), getSelection());
+ }
+ });
+
+
+ final JMenuItem find_and_replace_qualifier_item = new JMenuItem("Find/Replace Qualifier Text ...");
+ find_and_replace_qualifier_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ new FindAndReplace(getSelection(), goto_event_source,
+ entry_group, base_plot_group);
+ }
+ });
+
+
+ final SelectionSubMenu feature_menu = new SelectionSubMenu(
+ this, "Selected Feature(s)");
+ final JMenuItem merge_features_item = new JMenuItem("Merge");
+ merge_features_item.setAccelerator(MERGE_FEATURES_KEY);
+ merge_features_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ mergeFeatures(getParentFrame(), getSelection(), getEntryGroup());
+ }
+ });
+
+ final JMenuItem unmerge_feature_item = new JMenuItem("Unmerge");
+
+ unmerge_feature_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ unmergeFeature(getParentFrame(), getSelection(), getEntryGroup());
+ }
+ });
+
+ final JMenuItem unmerge_all_feature_item = new JMenuItem("Unmerge All Segments");
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ unmerge_all_feature_item.setEnabled(false);
+
+ unmerge_all_feature_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ unmergeAllFeature(getParentFrame(), getSelection(), getEntryGroup());
+ }
+ });
+
+ final JMenuItem duplicate_item = new JMenuItem("Duplicate");
+ duplicate_item.setAccelerator(DUPLICATE_KEY);
+ duplicate_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ duplicateFeatures(getParentFrame(), getSelection(),
+ getEntryGroup());
+ }
+ });
+
+
+ final JMenuItem delete_features_item = new JMenuItem("Delete");
+ delete_features_item.setAccelerator(DELETE_FEATURES_KEY);
+ delete_features_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ deleteSelectedFeatures(getParentFrame(), getSelection(),
+ getEntryGroup());
+ }
+ });
+
+ final JMenuItem delete_segments_item = new JMenuItem("Delete Exons");
+ delete_segments_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ deleteSelectedSegments();
+ }
+ });
+
+ final JMenuItem delete_introns_item =
+ new JMenuItem("Remove Introns");
+ delete_introns_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ removeIntrons();
+ }
+ });
+
+ final JMenuItem convert_keys_item = new JMenuItem("Convert Keys ...");
+ convert_keys_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ convertKeys(getParentFrame(), getSelection());
+ }
+ });
+
+ final JMenuItem edit_header_item = new JMenuItem("Header Of Default Entry");
+ edit_header_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ editHeader();
+ }
+ });
+
+ final JMenu move_features_menu = new JMenu("Move Selected Features To");
+ final JMenu copy_features_menu = new JMenu("Copy Selected Features To");
+
+ if(entry_group == null || getEntryGroup().size() == 0)
+ {
+ move_features_menu.add(new JMenuItem("(No Entries Currently)"));
+ copy_features_menu.add(new JMenuItem("(No Entries Currently)"));
+ }
+ else
+ {
+ for(int i = 0 ; i < getEntryGroup().size() ; ++i)
+ {
+ final Entry this_entry = getEntryGroup().elementAt(i);
+
+ String entry_name = this_entry.getName();
+ if(entry_name == null)
+ entry_name = "no name";
+
+ final JMenuItem move_to_item = new JMenuItem(entry_name);
+ move_to_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ addGeneModelFeaturesToSelection();
+ // unselect, move, then reselect (for speed)
+ final FeatureVector selected_features =
+ getSelection().getAllFeatures();
+ getSelection().clear();
+ moveFeatures(selected_features, this_entry);
+ getSelection().set(selected_features);
+ }
+ });
+ move_features_menu.add(move_to_item);
+
+ final JMenuItem copy_to_item = new JMenuItem(entry_name);
+
+ copy_to_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ addGeneModelFeaturesToSelection();
+ copyFeatures(getSelection().getAllFeatures(), this_entry);
+ }
+ });
+ copy_features_menu.add(copy_to_item);
+ }
+ }
+
+ final SelectionSubMenu trim_menu =
+ new SelectionSubMenu(this, "Trim Selected Features");
+ final JMenuItem trim_to_any_item = new JMenuItem("To Any");
+ trim_to_any_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event) {
+ EditMenu.trimSelected(getParentFrame(), getSelection(),
+ getEntryGroup(), true, false);
+ }
+ });
+
+ final JMenuItem trim_item = new JMenuItem("To Met");
+ trim_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ EditMenu.trimSelected(getParentFrame(), getSelection(),
+ getEntryGroup(), false, false);
+ }
+ });
+
+ final JMenuItem trim_to_next_any_item =
+ new JMenuItem("To Next Any");
+ trim_to_next_any_item.setAccelerator(TRIM_FEATURES_TO_NEXT_ANY_KEY);
+ trim_to_next_any_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ EditMenu.trimSelected(getParentFrame(), getSelection(),
+ getEntryGroup(), true, true);
+ }
+ });
+
+ final JMenuItem trim_to_next_item = new JMenuItem("To Next Met");
+ trim_to_next_item.setAccelerator(TRIM_FEATURES_KEY);
+ trim_to_next_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ EditMenu.trimSelected(getParentFrame(), getSelection(),
+ getEntryGroup(), false, true);
+ }
+ });
+
+ final SelectionSubMenu extend_menu =
+ new SelectionSubMenu(this, "Extend Selected Features");
+ final JMenuItem extend_to_prev_stop_item =
+ new JMenuItem("To Previous Stop Codon");
+ extend_to_prev_stop_item.setAccelerator(EXTEND_TO_PREVIOUS_STOP_CODON_KEY);
+ extend_to_prev_stop_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ extendToORF(getParentFrame(), getSelection(),
+ getEntryGroup(), false);
+ }
+ });
+
+ final JMenuItem extend_to_next_stop_item = new JMenuItem("To Next Stop Codon");
+ extend_to_next_stop_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ extendToORF(getParentFrame(), getSelection(),
+ getEntryGroup(), true);
+ }
+ });
+
+ final JMenuItem fix_stop_codons_item = new JMenuItem("Fix Stop Codons");
+ fix_stop_codons_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ fixStopCodons();
+ }
+ });
+
+ final JMenuItem extend_to_next_stop_and_fix_item = new JMenuItem("To Next Stop Codon and Fix");
+ extend_to_next_stop_and_fix_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ extendToORF(getParentFrame(), getSelection(),
+ getEntryGroup(), true);
+ fixStopCodons();
+ }
+ });
+
+ final JMenuItem auto_gene_name_item = new JMenuItem("Automatically Create Gene Names");
+ auto_gene_name_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ autoGeneName();
+ }
+ });
+
+ final JMenuItem fix_gene_names_item = new JMenuItem("Fix Gene Names");
+ fix_gene_names_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ fixGeneNames(getParentFrame(), getEntryGroup(),
+ getSelection());
+ }
+ });
+
+ final JMenuItem reverse_complement_item = new JMenuItem("Reverse And Complement");
+ reverse_complement_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ reverseAndComplement();
+ }
+ });
+
+ final JMenuItem reverse_complement_range_item = new JMenuItem("Reverse And Complement Selected Contig");
+ reverse_complement_range_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(getEntryGroup().isReadOnly())
+ {
+ final String message =
+ "one or more of the entries or features are read only - " +
+ "cannot continue";
+ new MessageDialog(getParentFrame(), message);
+ return;
+ }
+
+ final FeatureVector selected_features = getSelection().getAllFeatures();
+
+ if(selected_features.size() == 1)
+ {
+ final Feature selection_feature = selected_features.elementAt(0);
+ final Range range = selection_feature.getMaxRawRange();
+ final YesNoDialog dialog =
+ new YesNoDialog (getParentFrame (),
+ "Are you sure you want to reverse complement this " +
+ "region "+ range.getStart()+".."+
+ range.getEnd()+"?");
+ if(!dialog.getResult())
+ return;
+
+ try
+ {
+ getEntryGroup().getBases().reverseComplement(selection_feature);
+ }
+ catch(ReadOnlyException roe)
+ {
+ final String message =
+ "one or more of the features is read-only or is in a " +
+ "read-only entry - cannot continue";
+ new MessageDialog(null, message);
+ return;
+ }
+ }
+ else
+ {
+ final String message =
+ "Select a single contig to reverse and complement";
+ new MessageDialog(null, message);
+ return;
+ }
+ }
+ });
+
+ final SelectionSubMenu bases_item =
+ new SelectionSubMenu(this, "Bases");
+
+ final JMenuItem delete_bases_item = new JMenuItem("Delete Selected Bases");
+ delete_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ deleteSelectedBases("Are you sure you want to delete the " +
+ "selected bases?");
+ }
+ });
+
+ final JMenuItem add_bases_item = new JMenuItem("Add Bases At Selection");
+ add_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ addBases();
+ }
+ });
+
+
+ final JMenuItem replace_bases_item = new JMenuItem("Replace Bases At Selection");
+ replace_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ MarkerRange marker_range = getSelection ().getMarkerRange ();
+ int start = getSelection().getHighestBaseOfSelection().getPosition();
+ boolean hasDeleted = deleteSelectedBases(
+ "Are you sure you want to replace the " +
+ "selected bases?");
+
+ if(!hasDeleted)
+ return;
+
+ if(!marker_range.isForwardMarker())
+ {
+ try
+ {
+ marker_range = new MarkerRange(
+ getEntryGroup().getBases().getReverseStrand(),
+ start,start+1);
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ return;
+ }
+ }
+ getSelection ().setMarkerRange (marker_range);
+ addBases();
+ getSelection ().setMarkerRange (null);
+ }
+ });
+
+
+ if(Options.getOptions().getPropertyTruthValue("val_mode"))
+ {
+ add(edit_feature_item);
+ add(edit_subsequence_item);
+ addSeparator();
+ }
+
+ if(Options.getOptions().getUndoLevels() > 0)
+ {
+ add(undo_item);
+ add(redo_item);
+ addSeparator();
+ }
+
+ if(!Options.getOptions().getPropertyTruthValue("val_mode"))
+ {
+ add(edit_feature_item);
+ add(edit_subsequence_item);
+ addSeparator();
+ }
+
+ add(find_and_replace_qualifier_item);
+ add(qualifier_menu);
+ qualifier_menu.add(add_qualifiers_item);
+ qualifier_menu.add(remove_qualifier_item);
+ qualifier_menu.add(convert_qualifier_item);
+ add(feature_menu);
+ feature_menu.add(duplicate_item);
+ feature_menu.add(merge_features_item);
+ feature_menu.add(unmerge_feature_item);
+ feature_menu.add(unmerge_all_feature_item);
+ feature_menu.add(delete_features_item);
+ feature_menu.add(delete_segments_item);
+ feature_menu.add(delete_introns_item);
+ feature_menu.add(convert_keys_item);
+ addSeparator();
+ add(move_features_menu);
+ add(copy_features_menu);
+ addSeparator();
+ add(trim_menu);
+ trim_menu.add(trim_item);
+ trim_menu.add(trim_to_any_item);
+ trim_menu.add(trim_to_next_item);
+ trim_menu.add(trim_to_next_any_item);
+ add(extend_menu);
+ extend_menu.add(extend_to_prev_stop_item);
+ extend_menu.add(extend_to_next_stop_item);
+ add(fix_stop_codons_item);
+ extend_menu.add(extend_to_next_stop_and_fix_item);
+ addSeparator();
+ add(auto_gene_name_item);
+ add(fix_gene_names_item);
+ add(bases_item);
+ bases_item.add(reverse_complement_item);
+ bases_item.add(reverse_complement_range_item);
+ bases_item.add(delete_bases_item);
+ bases_item.add(add_bases_item);
+
+ if(Options.readWritePossible())
+ {
+ // only the standalone version can save or read
+ final JMenuItem add_bases_from_file_item = new JMenuItem("Add Bases From File ...");
+ add_bases_from_file_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ addBasesFromFile();
+ }
+ });
+
+ bases_item.add(add_bases_from_file_item);
+ }
+
+ bases_item.add(replace_bases_item);
+
+ if(owner instanceof FeatureDisplay)
+ {
+ addSeparator();
+ add(contig_reordering);
+ }
+
+ addSeparator();
+ add(edit_header_item);
+
+ }
+
+ /**
+ * Add all GFF gene model features to the selection
+ * @param f
+ */
+ private void addGeneModelFeaturesToSelection()
+ {
+ if(!GeneUtils.isGFFEntry(getEntryGroup()))
+ return;
+ final FeatureVector selected_features = getSelection().getAllFeatures();
+ for(int i=0; i<selected_features.size(); i++)
+ {
+ if(selected_features.elementAt(i).getEmblFeature() instanceof GFFStreamFeature)
+ {
+ GFFStreamFeature f = (GFFStreamFeature) selected_features.elementAt(i).getEmblFeature();
+ if(f.getChadoGene() != null)
+ {
+ try
+ {
+ final ChadoCanonicalGene g = f.getChadoGene();
+ Set<uk.ac.sanger.artemis.io.Feature> children = g.getChildren(g.getGene());
+ getSelection().add( (Feature) g.getGene().getUserData() );
+ Iterator<uk.ac.sanger.artemis.io.Feature> it = children.iterator();
+ while(it.hasNext())
+ getSelection().add( (Feature) it.next().getUserData() );
+ }
+ catch(Exception e){}
+ }
+ }
+ }
+ }
+
+ /**
+ * Undo the last change by calling ActionController.undo().
+ * @param frame The Frame to use for MessageDialog components.
+ * @param selection The current Selection - needs to be cleared before undo
+ * @param entry_group Used to get the ActionController for calling
+ * ActionController.undo().
+ **/
+ protected static void undo(final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group)
+ {
+ // undo disabled
+ if(Options.getOptions().getUndoLevels() == 0)
+ return;
+
+ // clear the selection because something in the selection might
+ // disappear after the undo() eg. create a feature, select it then undo
+ if(entry_group.getActionController().canUndo())
+ selection.clear();
+
+ if(!entry_group.getActionController().undo())
+ new MessageDialog(frame, "sorry - no further undo information");
+ }
+
+ private static void redo(final JFrame frame, final Selection selection,
+ final EntryGroup entry_group)
+ {
+ // undo disabled
+ if(Options.getOptions().getUndoLevels() == 0)
+ return;
+
+ // clear the selection because something in the selection might
+ // disappear after the undo() eg. create a feature, select it then undo
+ //if(entry_group.getActionController().canUndo())
+ selection.clear();
+
+ if(!entry_group.getActionController().redo())
+ new MessageDialog(frame, "sorry - no further redo information");
+ }
+
+
+ /**
+ * Open an edit window (FeatureEdit) for each of the selected features.
+ * The edit component will listen for feature change events and update
+ * itself.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The selected features to edit.
+ * @param entry_group Used to get the ActionController for calling
+ * startAction() and endAction().
+ **/
+ protected static void editSelectedFeatures(final JFrame frame,
+ final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source)
+ {
+ frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ int MAX_SELECTED_FEATURES = 25;
+ final FeatureVector features_to_edit = selection.getAllFeatures();
+ boolean featureEdit = true;
+
+ if(features_to_edit.size() > MAX_SELECTED_FEATURES)
+ {
+ final JPanel msgPanel = new JPanel(new BorderLayout());
+ msgPanel.add(new JLabel("warning: only editing the first " +
+ MAX_SELECTED_FEATURES + " selected features"), BorderLayout.CENTER);
+ final JCheckBox allFeatures = new JCheckBox("ignore this and show all",false);
+ msgPanel.add(allFeatures, BorderLayout.SOUTH);
+
+ int val = JOptionPane.showConfirmDialog(frame,
+ msgPanel,
+ features_to_edit.size()+" features selected",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.WARNING_MESSAGE);
+
+ if(val == JOptionPane.CANCEL_OPTION)
+ {
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ if(allFeatures.isSelected())
+ {
+ if(features_to_edit.size() > 50)
+ {
+ val = JOptionPane.showConfirmDialog(frame,
+ "warning: about to open "+features_to_edit.size()+" edit windows",
+ features_to_edit.size()+" features selected",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.WARNING_MESSAGE);
+ if(val == JOptionPane.CANCEL_OPTION)
+ {
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ MAX_SELECTED_FEATURES = features_to_edit.size();
+ }
+ }
+
+ for(int i = 0; i < features_to_edit.size() && i < MAX_SELECTED_FEATURES;
+ ++i)
+ {
+ final Feature selection_feature = features_to_edit.elementAt(i);
+
+ featureEdit = editSelectedFeatures(entry_group, selection, goto_event_source,
+ selection_feature, null, null);
+ }
+
+ if(featureEdit)
+ selection.set(features_to_edit);
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+
+ public static boolean editSelectedFeatures(
+ final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final Feature selection_feature,
+ final ActionListener cancel_listener,
+ final ActionListener apply_listener)
+ {
+ if(selection_feature.getEmblFeature() instanceof GFFStreamFeature &&
+ ((GFFStreamFeature)selection_feature.getEmblFeature()).getChadoGene() != null)
+ {
+ if(geneBuilderHash == null)
+ geneBuilderHash = new Hashtable<String, GeneBuilderFrame>();
+
+ final String gene =
+ ((GFFStreamFeature)selection_feature.getEmblFeature()).getChadoGene().getGeneUniqueName();
+
+
+ if(geneBuilderHash.containsKey(gene) &&
+ JOptionPane.showConfirmDialog(null,
+ "Show gene builder already open\nfor this gene model?", gene,
+ JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION)
+ {
+ geneBuilderHash.get(gene).toFront();
+ }
+ else
+ {
+ if(System.getProperty("basic") == null ||
+ System.getProperty("basic").equals("false"))
+ {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run()
+ {
+ final GeneBuilderFrame gbFrame =
+ new GeneBuilderFrame(selection_feature, entry_group,
+ selection, goto_event_source);
+ gbFrame.addGeneBuilderHash(geneBuilderHash);
+ geneBuilderHash.put(gene, gbFrame);
+ }
+ });
+ }
+ else
+ new BasicGeneBuilderFrame(selection_feature, entry_group,
+ selection, null);
+ }
+
+ return false;
+ }
+ else
+ {
+ final JFrame edit_frame = new JFrame("Artemis Feature Edit: " +
+ selection_feature.getIDString() +
+ (selection_feature.isReadOnly() ?
+ " - (read only)" :
+ ""));
+
+ final FeatureEdit fe = new FeatureEdit(selection_feature, entry_group,
+ selection, goto_event_source, edit_frame);
+
+ edit_frame.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ fe.stopListening();
+ edit_frame.dispose();
+ }
+ });
+
+ if(cancel_listener != null)
+ fe.addCancelActionListener(cancel_listener);
+ if(apply_listener != null)
+ fe.addApplyActionListener(apply_listener);
+ edit_frame.getContentPane().add(fe);
+ edit_frame.pack();
+
+ Utilities.centreFrame(edit_frame);
+ edit_frame.setVisible(true);
+ return true;
+ }
+ }
+
+ /**
+ * Create a new EntryEdit component that contains only the selected
+ * sequence and the features in the selected range.
+ **/
+ private void editSubSequence()
+ {
+ if(getSelection().isEmpty())
+ new MessageDialog(getParentFrame(), "nothing selected");
+
+ final Range range = getSelection().getSelectionRange();
+ final EntryGroup new_entry_group = getEntryGroup().truncate(range);
+ new EntryEdit(new_entry_group).setVisible(true);
+ }
+
+ /**
+ * Open a EntryHeaderEdit window for the default entry.
+ **/
+ private void editHeader()
+ {
+ final Entry default_entry = getEntryGroup().getDefaultEntry();
+
+ if(default_entry == null)
+ {
+ final String message = "there is no default entry";
+ new MessageDialog(getParentFrame(), message);
+ }
+ else
+ {
+ if(default_entry.isReadOnly())
+ {
+ new MessageDialog(getParentFrame(),
+ "the default entry is read-only " +
+ "- cannot continue");
+ return;
+ }
+
+ new EntryHeaderEdit(entry_group, default_entry);
+ }
+ }
+
+ /**
+ * Merge the selected features into one Feature. If there are selected
+ * segments then the owning Feature of each segment will be the Feature
+ * that is merged. This method will create a new Feature.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection containing the features to merge.
+ * @param entry_group Used to get the ActionController for calling
+ * startAction() and endAction().
+ **/
+ protected static void mergeFeatures(final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group)
+ {
+ try
+ {
+ entry_group.getActionController().startAction();
+
+ if(!checkForSelectionFeatures(frame, selection, 10,
+ "really merge all (>10) " + "selected features?"))
+ return;
+
+ final FeatureVector features_to_merge = selection.getAllFeatures();
+
+ if(features_to_merge.size() < 2)
+ {
+ new MessageDialog(frame,
+ "nothing to merge - select more than one feature");
+ return;
+ }
+
+ final Feature merge_feature = features_to_merge.elementAt(0);
+
+ // make sure all the features are on the same strand
+ for(int i = 1; i < features_to_merge.size(); ++i)
+ {
+ final Feature this_feature = features_to_merge.elementAt(i);
+
+ if(this_feature.isForwardFeature() !=
+ merge_feature.isForwardFeature())
+ {
+ new MessageDialog(frame,
+ "all the features in a merge must be on the " +
+ "same strand");
+ return;
+ }
+
+ if(!this_feature.getKey().equals(merge_feature.getKey()))
+ {
+ new MessageDialog(frame,
+ "all the features in a merge must have the " +
+ "same key");
+ return;
+ }
+ }
+
+ if(Options.getOptions().isNoddyMode())
+ {
+ final YesNoDialog dialog =
+ new YesNoDialog(frame, "Are you sure you want to merge the selected " +
+ "features?");
+ if(!dialog.getResult())
+ return;
+ }
+
+ final Feature new_feature;
+ //
+ // GFF merge
+ if(merge_feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ if(!merge_feature.getKey().equals(DatabaseDocument.EXONMODEL) &&
+ !merge_feature.getKey().equals("pseudogenic_exon"))
+ {
+ new MessageDialog(frame,"The features in a merge should be "+
+ DatabaseDocument.EXONMODEL+
+ " or pseudogenic_exon features");
+ return;
+ }
+
+ gffMergeFeatures(features_to_merge, merge_feature,
+ selection, entry_group);
+
+ entry_group.getActionController().endAction();
+ return;
+ }
+
+ try
+ {
+ new_feature = merge_feature.duplicate();
+ }
+ catch(ReadOnlyException e)
+ {
+ final String message =
+ "one or more of the features is read-only or is in a " +
+ "read-only entry - cannot continue";
+ new MessageDialog(frame, message);
+ return;
+ }
+
+ for(int i = 1; i < features_to_merge.size(); ++i)
+ {
+ final Feature this_feature = features_to_merge.elementAt(i);
+ final QualifierVector qualifiers = this_feature.getQualifiers();
+
+ for(int j = 0; j < qualifiers.size(); ++j)
+ {
+ final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(j);
+
+ try
+ {
+ new_feature.addQualifierValues(this_qualifier);
+ }
+ catch(EntryInformationException e)
+ {
+ try
+ {
+ new_feature.removeFromEntry();
+ }
+ catch(ReadOnlyException _)
+ {
+ // give up ...
+ }
+ final String message =
+ "destination entry does not support all the qualifiers " +
+ "needed by " + this_feature.getIDString();
+ new MessageDialog(frame, message);
+ }
+ catch(ReadOnlyException e)
+ {
+ final String message = "the new feature is read-only so " +
+ "some qualifiers have been lost";
+ new MessageDialog(frame, message);
+ }
+ }
+
+ final FeatureSegmentVector segments = this_feature.getSegments();
+
+ for(int j = 0; j < segments.size(); ++j)
+ {
+ final FeatureSegment this_segment =
+ segments.elementAt(j);
+
+ final Range this_range = this_segment.getRawRange();
+
+ try
+ {
+ new_feature.addSegment(this_range);
+ }
+ catch(ReadOnlyException e)
+ {
+ final String message =
+ "merging failed because the entry is read-only";
+ new MessageDialog(frame, message);
+ try
+ {
+ new_feature.removeFromEntry();
+ }
+ catch(ReadOnlyException _) {}
+ }
+ }
+ }
+
+ // this is set to true when a merge is done in the next (inner) loop
+ boolean keep_looping = true;
+
+ // this is a bit inefficient, but there aren't normally many segments
+ LOOP:
+ while(keep_looping)
+ {
+ final FeatureSegmentVector feature_segments =
+ new_feature.getSegments();
+
+ keep_looping = false;
+
+ // now merge overlapping ranges
+ for(int i = 0; i < feature_segments.size() - 1; ++i)
+ {
+ final FeatureSegment this_segment =
+ feature_segments.elementAt(i);
+ final MarkerRange this_range = this_segment.getMarkerRange();
+
+ final FeatureSegment next_segment =
+ feature_segments.elementAt(i + 1);
+ final MarkerRange next_range = next_segment.getMarkerRange();
+
+ // if it overlaps the next Range then merge it
+ if(this_range.overlaps(next_range) &&
+ this_segment.getFrameID() == next_segment.getFrameID())
+ {
+ try
+ {
+ final Range new_range =
+ this_range.combineRanges(next_range, false).getRawRange();
+ new_feature.addSegment(new_range);
+ new_feature.removeSegment(this_segment);
+ new_feature.removeSegment(next_segment);
+
+ // start again
+ keep_looping = true;
+ continue LOOP;
+ }
+ catch(ReadOnlyException e)
+ {
+ final String message =
+ "merging failed because the entry is read-only";
+ new MessageDialog(frame, message);
+ }
+ catch(LastSegmentException e)
+ {
+ throw new Error("internal error - tried to remove " +
+ "last segment: " + e);
+ }
+ }
+ }
+ }
+
+ boolean delete_old_features;
+
+ if(Options.getOptions().isNoddyMode())
+ {
+ final YesNoDialog delete_old_dialog =
+ new YesNoDialog(frame, "delete old features?");
+
+ delete_old_features = delete_old_dialog.getResult();
+ }
+ else
+ delete_old_features = true;
+
+ if(delete_old_features)
+ {
+ if(getReadOnlyFeatures(features_to_merge).size() > 0)
+ new MessageDialog(frame, "deletion failed because the features " +
+ "are read-only");
+ else
+ {
+ for(int i = 0; i < features_to_merge.size(); ++i)
+ {
+ try
+ {
+ features_to_merge.elementAt(i).removeFromEntry();
+ }
+ catch(ReadOnlyException e)
+ {
+ new MessageDialog(frame, "deletion failed one or more of the " +
+ "features are read-only");
+ }
+ }
+ }
+ }
+ selection.set(new_feature);
+ }
+ finally
+ {
+ entry_group.getActionController().endAction();
+ }
+ }
+
+ /**
+ * Merge features / gene model - creating gene model if not already present
+ * @param features_to_merge
+ * @param merge_feature
+ * @param selection
+ * @param entry_group
+ */
+ public static void gffMergeFeatures(final FeatureVector features_to_merge,
+ final Feature merge_feature,
+ final Selection selection,
+ final EntryGroup entry_group)
+ {
+ try
+ {
+ Qualifier parentQualifier;
+ ChadoCanonicalGene chadoGene = null;
+ ChadoCanonicalGene chadoGene2 = null;
+ String transcriptId = null;
+ java.util.List geneModels = getGeneModels(features_to_merge);
+
+ if(geneModels.size() == 0)
+ {
+ // create gene model
+ final Location geneLocation = new Location(selection.getSelectionRange());
+
+ final String parentId = GeneUtils.getUniqueName(
+ merge_feature.getEmblFeature())+":gene";
+ final QualifierVector qualifiers = new QualifierVector();
+ qualifiers.setQualifier(new Qualifier("ID", parentId));
+
+ // create gene
+ final Key key;
+ if(merge_feature.getKey().getKeyString().equals("pseudogenic_exon"))
+ key = new Key("pseudogene");
+ else
+ key = new Key("gene");
+
+ Feature parentGene = merge_feature.getEntry().createFeature(key,
+ geneLocation, qualifiers);
+
+ chadoGene = new ChadoCanonicalGene();
+ chadoGene.setGene(parentGene.getEmblFeature());
+ ((uk.ac.sanger.artemis.io.GFFStreamFeature)
+ (parentGene.getEmblFeature())).setChadoGene(chadoGene);
+
+ // create transcript
+ Feature transcript = GeneViewerPanel.createTranscript(chadoGene, entry_group);
+ ((uk.ac.sanger.artemis.io.GFFStreamFeature)
+ (transcript.getEmblFeature())).setChadoGene(chadoGene);
+ transcriptId = GeneUtils.getUniqueName(transcript.getEmblFeature());
+
+ parentQualifier = new Qualifier("Parent", transcriptId);
+ merge_feature.setQualifier(parentQualifier);
+ }
+ else
+ {
+ boolean isMultipleTranscript = false;
+ for(int i=0; i<geneModels.size(); i++)
+ {
+ if(((ChadoCanonicalGene)geneModels.get(i)).getTranscripts().size() != 1)
+ isMultipleTranscript = true;
+ }
+
+ if(features_to_merge.size() > 2 ||
+ geneModels.size() > 2 ||
+ isMultipleTranscript)
+ {
+ JOptionPane.showMessageDialog(null,
+ "This option cannot be used to merge more than 2 gene models.\n"+
+ "Select two exons in two gene models to be merged.\n"+
+ "The gene models must have just one transcript.");
+ return;
+ }
+
+ logger4j.debug("Found "+geneModels.size()+" gene models for merging");
+
+ if(geneModels.size() == 2)
+ {
+ parentQualifier = merge_feature.getQualifierByName("Parent");
+ transcriptId = (String)parentQualifier.getValues().get(0);
+ chadoGene = ((GFFStreamFeature)merge_feature.getEmblFeature()).getChadoGene();
+
+ final String chadoGeneName = chadoGene.getGeneUniqueName();
+ for(int i=0; i<geneModels.size(); i++)
+ {
+ final ChadoCanonicalGene thisChadoGene = (ChadoCanonicalGene)geneModels.get(i);
+ if(!thisChadoGene.equals(chadoGeneName))
+ chadoGene2 = thisChadoGene;
+ }
+
+ //
+ // merge qualifiers
+ try
+ {
+ final uk.ac.sanger.artemis.io.Feature transcript1 = chadoGene.getTranscripts().get(0);
+ final uk.ac.sanger.artemis.io.Feature transcript2 = chadoGene2.getTranscripts().get(0);
+ mergeQualifiers(transcript1, transcript2);
+
+ final uk.ac.sanger.artemis.io.Feature protein1 =
+ chadoGene.getProteinOfTranscript(GeneUtils.getUniqueName(transcript1));
+ final uk.ac.sanger.artemis.io.Feature protein2 =
+ chadoGene2.getProteinOfTranscript(GeneUtils.getUniqueName(transcript2));
+ mergeQualifiers(protein1, protein2);
+ }
+ catch(Exception e){ logger4j.warn(e.getMessage()); }
+ }
+ else
+ {
+ chadoGene = (ChadoCanonicalGene)geneModels.get(0);
+ transcriptId = GeneUtils.getUniqueName(
+ (uk.ac.sanger.artemis.io.Feature)chadoGene.getTranscripts().get(0));
+ parentQualifier = new Qualifier("Parent", transcriptId);
+ merge_feature.setQualifier(parentQualifier);
+ }
+
+ // TODO - merge transcript / peptide qualifiers into chadoGene ??
+ }
+
+ final RangeVector ranges = new RangeVector();
+ java.util.Hashtable id_range_store =
+ ((GFFStreamFeature)merge_feature.getEmblFeature()).getSegmentRangeStore();
+
+ for(int i=1; i< features_to_merge.size(); i++)
+ {
+ final Feature this_feature = features_to_merge.elementAt(i);
+ final FeatureSegmentVector segments = this_feature.getSegments();
+
+ this_feature.setQualifier(parentQualifier);
+
+ for(int j = 0; j < segments.size(); ++j)
+ {
+ final FeatureSegment this_segment = segments.elementAt(j);
+ ranges.add(this_segment.getRawRange());
+ }
+ }
+
+ for(int i=0; i<features_to_merge.size(); i++)
+ {
+ final Feature this_feature = features_to_merge.elementAt(i);
+
+ // remove the duplicate feature
+ if(i > 0)
+ this_feature.getEntry().remove(this_feature, false);
+ }
+
+
+ // add the segments
+ //uk.ac.sanger.artemis.chado.ChadoTransactionManager.addSegments = false;
+ for(int i = 0; i < ranges.size(); i++)
+ {
+ final Range range = (Range)ranges.get(i);
+ final String segId = chadoGene.autoGenerateSplicedFeatureName(transcriptId);
+ id_range_store.put(segId,range);
+ ((GFFStreamFeature)merge_feature.getEmblFeature()).setSegmentRangeStore(id_range_store);
+
+ merge_feature.addSegment(range);
+ }
+ //uk.ac.sanger.artemis.chado.ChadoTransactionManager.addSegments = true;
+
+ // set the new ID for the joined feature
+ final String ID = ((GFFStreamFeature)merge_feature.getEmblFeature()).getSegmentID(
+ merge_feature.getLocation().getRanges());
+ final Qualifier qualifier = new Qualifier("ID", ID);
+ merge_feature.getEmblFeature().setQualifier(qualifier);
+ chadoGene.addSplicedFeatures(transcriptId, merge_feature.getEmblFeature(), true);
+ ((GFFStreamFeature)merge_feature.getEmblFeature()).setChadoGene(chadoGene);
+
+ if(chadoGene2 != null)
+ {
+ // add prev_sys_id
+ Qualifier q = new Qualifier("previous_systematic_id",
+ chadoGene2.getGeneUniqueName()+";current=false");
+ ((Feature)chadoGene.getGene().getUserData()).addQualifierValues(q);
+
+ logger4j.debug("Now DELETE "+chadoGene2.getGeneUniqueName());
+ GeneUtils.deleteAllFeature((Feature)chadoGene2.getGene().getUserData(), chadoGene2);
+ }
+ if(chadoGene != null)
+ {
+ logger4j.debug("Check gene boundaries of: "+chadoGene.getGeneUniqueName());
+ GeneUtils.checkGeneBoundary(chadoGene);
+ }
+ }
+ catch(ReadOnlyException e)
+ {
+ final String message = "one or more of the features is read-only or is in a "
+ + "read-only entry - cannot continue";
+ new MessageDialog(null, message);
+ return;
+ }
+ catch(InvalidRelationException ire){ ire.printStackTrace(); }
+ catch(EntryInformationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ catch(OutOfRangeException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Get the chado gene models for a list of features
+ * @param features
+ * @return
+ */
+ private static java.util.List<ChadoCanonicalGene> getGeneModels(final FeatureVector features)
+ {
+ final java.util.List<ChadoCanonicalGene> geneModels = new Vector<ChadoCanonicalGene>();
+ final java.util.List<String> geneModelNames = new Vector<String>();
+ for(int i=0; i< features.size(); i++)
+ {
+ if(features.elementAt(i).getEmblFeature() instanceof GFFStreamFeature)
+ {
+ final GFFStreamFeature this_feature =
+ (GFFStreamFeature)features.elementAt(i).getEmblFeature();
+
+ if(this_feature.getChadoGene() != null)
+ {
+ final String this_name =
+ this_feature.getChadoGene().getGeneUniqueName();
+ if(!geneModelNames.contains(this_name))
+ {
+ geneModels.add(this_feature.getChadoGene());
+ geneModelNames.add(this_name);
+ }
+ }
+ }
+ }
+ return geneModels;
+ }
+
+ /**
+ * Merge qualifiers from two features avoiding duplication of their values.
+ * @param f1 first feature that results in having the merged qualifiers
+ * @param f2 second feature
+ * @throws ReadOnlyException
+ * @throws EntryInformationException
+ */
+ private static void mergeQualifiers(final uk.ac.sanger.artemis.io.Feature f1,
+ final uk.ac.sanger.artemis.io.Feature f2) throws ReadOnlyException, EntryInformationException
+ {
+ if(f1 != null && f2 != null)
+ {
+ final QualifierVector qualifiers = f2.getQualifiers();
+ for(int i=0;i<qualifiers.size(); i++)
+ {
+ Qualifier qualifier = (Qualifier) qualifiers.get(i);
+ if(!TransferAnnotationTool.isNonTransferable(qualifier.getName()))
+ {
+ final Qualifier oldQualifier = f1.getQualifiers().getQualifierByName(qualifier.getName());
+ StringVector oldValues = null;
+ if(oldQualifier != null)
+ oldValues = oldQualifier.getValues();
+
+ final Qualifier newQualifier =
+ TransferAnnotationTool.getQualifierWithoutDuplicateValues(qualifier, oldValues);
+ ((Feature)f1.getUserData()).addQualifierValues(newQualifier);
+ }
+ }
+ }
+ }
+
+ /**
+ * If the selection contains exactly two segments and those segments are
+ * adjacent in the same feature, split the feature into two pieces. The
+ * orignal feature is truncated and a new feature is created. The
+ * qualifiers of the old feature are copied to new feature.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection containing the segments to unmerge.
+ * @param entry_group Used to get the ActionController for calling
+ * startAction() and endAction().
+ **/
+ protected static void unmergeFeature(final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group)
+ {
+ try
+ {
+ entry_group.getActionController ().startAction ();
+
+ final FeatureSegmentVector selected_segments =
+ selection.getSelectedSegments ();
+
+ if (selected_segments.size () != 2)
+ {
+ final String message =
+ "you need to select exactly two exons use unmerge";
+ new MessageDialog(frame, message);
+ return;
+ }
+
+ FeatureSegment first_segment = selected_segments.elementAt (0);
+ FeatureSegment second_segment = selected_segments.elementAt (1);
+
+ if(first_segment.getFeature () != second_segment.getFeature ())
+ {
+ final String message =
+ "you need to select two exons from the same feature to use unmerge";
+ new MessageDialog (frame, message);
+ return;
+ }
+
+ final Feature segment_feature = first_segment.getFeature ();
+
+ final FeatureSegmentVector all_feature_segments =
+ segment_feature.getSegments ();
+
+ int index_of_first_segment =
+ all_feature_segments.indexOf (first_segment);
+ int index_of_second_segment =
+ all_feature_segments.indexOf (second_segment);
+
+ if(index_of_first_segment - index_of_second_segment < -1 ||
+ index_of_first_segment - index_of_second_segment > 1)
+ {
+ final String message =
+ "you need to select two adjacent exons to use unmerge";
+ new MessageDialog (frame, message);
+ return;
+ }
+
+ if(index_of_second_segment < index_of_first_segment)
+ {
+ // swap the segments for consistency
+ final FeatureSegment temp_segment = first_segment;
+ final int temp_segment_index = index_of_first_segment;
+
+ first_segment = second_segment;
+ index_of_first_segment = index_of_second_segment;
+
+ second_segment = temp_segment;
+ index_of_second_segment = temp_segment_index;
+ }
+
+ try
+ {
+ final Feature new_feature;
+ if(segment_feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ final FeatureVector chadoGenes = new FeatureVector();
+ chadoGenes.add(segment_feature);
+ final Vector<ChadoCanonicalGene> duplicateGenes = duplicateGeneFeatures(frame, chadoGenes, entry_group);
+
+ // get the new duplicate spliced feature
+ ChadoCanonicalGene chado_gene = duplicateGenes.get(0);
+ // assumes single transcript
+ uk.ac.sanger.artemis.io.Feature transcript = chado_gene.getTranscripts().get(0);
+ // get the spliced feature with the same key as the segment
+ // selected to unmerge
+ uk.ac.sanger.artemis.io.Feature spliced =
+ chado_gene.getSpliceSitesOfTranscript(GeneUtils.getUniqueName(transcript),
+ first_segment.getFeature().getKey().getKeyString()).get(0);
+ new_feature = (Feature)spliced.getUserData();
+ }
+ else
+ new_feature = segment_feature.duplicate(true);
+ // we set the Selection later
+ selection.clear ();
+
+ // delete the segments starting at index_of_second_segment from
+ // segment_feature and delete the segments up to (and including)
+ // index_of_first_segment from new_feature
+
+ for(int i = all_feature_segments.size() - 1; i >= index_of_second_segment; --i)
+ segment_feature.getSegments ().elementAt (i).removeFromFeature ();
+
+ // remove the first segment of new_feature index_of_first_segment times
+ for (int i = 0; i <= index_of_first_segment; ++i)
+ new_feature.getSegments ().elementAt (0).removeFromFeature ();
+
+ if(segment_feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ final FeatureVector chadoGenes = new FeatureVector();
+ chadoGenes.add(segment_feature);
+ final Vector<ChadoCanonicalGene> duplicateGenes = duplicateGeneFeatures(frame, chadoGenes, entry_group);
+
+ final GFFStreamFeature orig_feature = (GFFStreamFeature)segment_feature.getEmblFeature();
+ final ChadoCanonicalGene orig_chado_gene = orig_feature.getChadoGene();
+ final String prevId = GeneUtils.getUniqueName(orig_chado_gene.getGene());
+ GeneUtils.deleteAllFeature(
+ ((uk.ac.sanger.artemis.Feature)orig_chado_gene.getGene().getUserData()), orig_chado_gene, false);
+
+ final ChadoCanonicalGene gene1 = duplicateGenes.get(0);
+ final ChadoCanonicalGene gene2 = ((GFFStreamFeature)new_feature.getEmblFeature()).getChadoGene();
+ if(!prevId.startsWith("DUP"))
+ {
+ // add prev_sys_id
+ final Qualifier synQualifier =
+ new Qualifier("previous_systematic_id", prevId+";current=false");
+
+ try
+ {
+ Qualifier originalQualifier =
+ ((Feature)gene1.getGene().getUserData()).getQualifierByName("previous_systematic_id");
+ if( originalQualifier == null ||
+ !originalQualifier.getValues().contains(prevId+";current=false"))
+ {
+ ((Feature)gene1.getGene().getUserData()).addQualifierValues(synQualifier);
+ ((Feature)gene2.getGene().getUserData()).addQualifierValues(synQualifier);
+ }
+
+ }
+ catch (Exception e){}
+ }
+
+ GeneUtils.checkGeneBoundary(gene1);
+ GeneUtils.checkGeneBoundary(gene2);
+ }
+ else
+ selection.set (segment_feature.getSegments ().lastElement ());
+ selection.add (new_feature.getSegments ().elementAt (0));
+ }
+ catch (ReadOnlyException e)
+ {
+ final String message =
+ "the selected exons (in " +
+ segment_feature.getIDString () +
+ ") are in a read only entry - cannot continue";
+ new MessageDialog (frame, message);
+ }
+ catch (LastSegmentException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ finally
+ {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * If the selection contains exactly one feature this routine will
+ * remove all the joins.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection containing the segments to unmerge.
+ * @param entry_group Used to get the ActionController for calling
+ * startAction() and endAction().
+ **/
+ private static void unmergeAllFeature(final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group)
+ {
+ try
+ {
+ entry_group.getActionController ().startAction ();
+
+ final FeatureVector delete_features = selection.getAllFeatures();
+ if(delete_features.size() > 1)
+ {
+ new MessageDialog (frame, "Select just one feature");
+ return;
+ }
+
+ final FeatureSegmentVector selected_segments =
+ delete_features.elementAt(0).getSegments();
+ try
+ {
+ Vector new_features = new Vector();
+ Vector segment_to_remove = new Vector();
+
+ FeatureSegment[] selected_segments_array = new FeatureSegment[selected_segments.size()];
+ for(int i=0; i<selected_segments.size(); i++)
+ {
+ FeatureSegment seg = selected_segments.elementAt(i);
+ int index_of_segment = selected_segments.indexOf(seg);
+ selected_segments_array[index_of_segment] = seg;
+ }
+
+ for(int i=0; i<selected_segments.size()-1; i++)
+ {
+ FeatureSegment segment = selected_segments_array[i];
+ Feature segment_feature = segment.getFeature();
+ final Feature new_feature = segment_feature.duplicate();
+ segment_to_remove.add(segment);
+
+ FeatureSegmentVector new_segments = new_feature.getSegments();
+
+ Vector removals = new Vector();
+ for(int j = 0 ; j <new_segments.size(); j++)
+ {
+ if(i != j)
+ removals.add(new_segments.elementAt(j));
+ }
+
+ for(int j = 0; j < removals.size(); j++)
+ new_feature.removeSegment( (FeatureSegment)removals.get(j) );
+
+ new_features.add(new_feature);
+ }
+
+ final int size = segment_to_remove.size();
+ for(int i=0; i<size; i++)
+ {
+ selected_segments_array[size-1].getFeature().removeSegment(
+ (FeatureSegment)segment_to_remove.get(i) );
+ }
+
+ Feature feature;
+ for(int i=0; i<new_features.size(); i++)
+ {
+ feature = (Feature)new_features.get(i);
+ selection.add(feature.getSegments().elementAt(0));
+
+ // set GFF ID's
+ if(feature.getEmblFeature() instanceof GFFStreamFeature)
+ setGffId(selected_segments_array[0].getFeature(), feature);
+ }
+
+ // set GFF ID's
+ feature = selected_segments_array[size-1].getFeature();
+ if(feature.getEmblFeature() instanceof GFFStreamFeature)
+ setGffId(selected_segments_array[0].getFeature(), feature);
+ }
+ catch(ReadOnlyException e)
+ {
+ final String message =
+ "the selected exons (in " +
+ delete_features.elementAt(0).getIDString () +
+ ") are in a read only entry - cannot continue";
+ new MessageDialog (frame, message);
+ }
+ catch (LastSegmentException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ finally
+ {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Sets the ID based on the new features ranges.
+ * @param feature_original
+ * @param feature_new
+ */
+ private static void setGffId(final Feature feature_original,
+ final Feature feature_new)
+ {
+ RangeVector ranges = feature_new.getLocation().getRanges();
+ GFFStreamFeature gff_feature =
+ (GFFStreamFeature)feature_original.getEmblFeature();
+ String id1 = gff_feature.getSegmentID(ranges);
+
+ try
+ {
+ feature_new.getEmblFeature().setQualifier(new Qualifier("ID", id1));
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+
+ }
+
+ /**
+ * Create a QualifierEditor JFrame that acts on the selected features.
+ * @param frame The JFrame to use for MessageDialog components.
+ **/
+ private void addQualifiers (final JFrame frame, final Selection selection) {
+ if (!checkForSelectionFeatures (frame, selection)) {
+ return;
+ }
+
+ final FeatureVector selected_features = selection.getAllFeatures ();
+
+ if (getReadOnlyFeatures (selected_features).size () > 0) {
+ new MessageDialog (frame,
+ "one or more of the selected features is read-only " +
+ "- cannot continue");
+ return;
+ }
+
+ final QualifierEditor qualifier_editor =
+ new QualifierEditor (selected_features, getEntryGroup ());
+
+ qualifier_editor.setVisible (true);
+ }
+
+ /**
+ * Offer the user a choice of qualifier to remove from the selected features
+ **/
+ private void removeQualifier (final JFrame frame, final Selection selection) {
+ if (!checkForSelectionFeatures (frame, selection)) {
+ return;
+ }
+
+ final FeatureVector selected_features = selection.getAllFeatures ();
+
+ final StringVector qualifier_names =
+ Feature.getAllQualifierNames (selected_features);
+
+ if (qualifier_names.size () == 0) {
+ new MessageDialog (getParentFrame (), "feature has no qualifiers");
+ return;
+ }
+
+ final ChoiceFrame choice_frame =
+ new ChoiceFrame ("Select a qualifer name", qualifier_names);
+
+ final JComboBox choice = choice_frame.getChoice ();
+
+
+ final int MAX_VISIBLE_ROWS = 30;
+
+ choice.setMaximumRowCount (MAX_VISIBLE_ROWS);
+
+ choice.addItemListener (new ItemListener () {
+ public void itemStateChanged (ItemEvent _) {
+ removeQualifierFromFeatures (selected_features,
+ (String) choice.getSelectedItem ());
+ choice_frame.setVisible (false);
+ choice_frame.dispose ();
+ }
+ });
+
+ choice_frame.getOKButton ().addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent _) {
+ removeQualifierFromFeatures (selected_features,
+ (String) choice.getSelectedItem ());
+ }
+ });
+
+ choice_frame.setVisible (true);
+ }
+
+
+ private void convertKeys(final JFrame frame,
+ final Selection selection)
+ {
+ if (!checkForSelectionFeatures (frame, selection))
+ return;
+
+ final FeatureVector selected_features = selection.getAllFeatures ();
+
+ Entry default_entry = getEntryGroup().getDefaultEntry();
+ if(default_entry == null)
+ default_entry = getEntryGroup().elementAt(0);
+
+ final EntryInformation default_entry_information =
+ default_entry.getEntryInformation();
+
+ final KeyChoice key_selector = new KeyChoice(default_entry_information);
+ final String options[] = { "Convert", "Cancel" };
+
+ final int opt = JOptionPane.showOptionDialog(frame,
+ key_selector, "Convert Key(s) of Selected Features",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
+
+ if(opt == 1)
+ return;
+
+ entry_group.getActionController ().startAction ();
+ try
+ {
+ for(int i=0; i<selected_features.size(); i++)
+ {
+ Feature feature = selected_features.elementAt(i);
+ feature.set(key_selector.getSelectedItem(),
+ feature.getLocation(), feature.getQualifiers());
+ }
+ }
+ catch(ReadOnlyException e)
+ {
+ JOptionPane.showMessageDialog(frame,
+ "Cannot convert read-only features.",
+ "Error Converting Key(s)", JOptionPane.ERROR_MESSAGE);
+ }
+ catch(EntryInformationException e)
+ {
+ JOptionPane.showMessageDialog(frame,
+ e.getMessage(),
+ "Error Converting Key(s)", JOptionPane.ERROR_MESSAGE);
+ }
+ catch(OutOfRangeException e)
+ {
+ JOptionPane.showMessageDialog(frame,
+ e.getMessage(),
+ "Error Converting Key(s)", JOptionPane.ERROR_MESSAGE);
+ }
+ finally
+ {
+ entry_group.getActionController ().endAction();
+ }
+ }
+
+ /**
+ * Offer the user a choice of qualifier to convert the name of
+ * from the selected features
+ * @param frame
+ * @param selection
+ */
+ private void convertQualifier (final JFrame frame,
+ final Selection selection)
+ {
+ if (!checkForSelectionFeatures (frame, selection))
+ return;
+
+ final FeatureVector selected_features = selection.getAllFeatures ();
+ final StringVector qualifier_names =
+ Feature.getAllQualifierNames (selected_features);
+
+ if(qualifier_names.size () == 0)
+ {
+ new MessageDialog (getParentFrame (), "feature has no qualifiers");
+ return;
+ }
+
+ Box yBox = Box.createVerticalBox();
+ final JComboBox convertFrom = new JComboBox(qualifier_names);
+ final QualifierChoice convertTo = new QualifierChoice(
+ getEntryGroup().getDefaultEntry().getEntryInformation(),
+ selected_features.elementAt(0).getKey(),null,
+ false);
+
+ Box xBox = Box.createHorizontalBox();
+ xBox.add(new JLabel("Convert all qualifiers of type:"));
+ xBox.add(Box.createHorizontalGlue());
+ yBox.add(xBox);
+ yBox.add(convertFrom);
+ xBox = Box.createHorizontalBox();
+ xBox.add(new JLabel("To:"));
+ xBox.add(Box.createHorizontalGlue());
+ yBox.add(xBox);
+ yBox.add(convertTo);
+
+ int select = JOptionPane.showConfirmDialog(frame, yBox,
+ "Convert Qualifiers",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ String oldQualifierName = (String)convertFrom.getSelectedItem();
+ String newQualifierName = (String)convertTo.getSelectedItem();
+
+ if(select == JOptionPane.CANCEL_OPTION ||
+ oldQualifierName.equals(newQualifierName))
+ return;
+
+ try
+ {
+ for(int i=0; i<selected_features.size(); i++)
+ {
+ Feature feature = selected_features.elementAt(i);
+ QualifierVector qualifiers = feature.getQualifiers();
+ int index = qualifiers.indexOfQualifierWithName(oldQualifierName);
+ if(index == -1)
+ continue;
+ StringVector values = feature.getValuesOfQualifier(oldQualifierName);
+ Qualifier newQualifier = new Qualifier(newQualifierName, values);
+ qualifiers.add(index, newQualifier);
+ qualifiers.removeQualifierByName(oldQualifierName);
+ }
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+
+ }
+
+ /**
+ * Remove the qualifier given by qualifier_name from the given features.
+ * Silently ignore features that don't have that qualifier. Warn the user
+ * about read-only features.
+ **/
+ private void removeQualifierFromFeatures (final FeatureVector features,
+ final String qualifier_name) {
+ boolean found_read_only = false;
+
+ try {
+ entry_group.getActionController ().startAction ();
+
+ for (int i = 0 ; i < features.size () ; ++i) {
+ final Feature this_feature = features.elementAt (i);
+ try {
+ this_feature.removeQualifierByName (qualifier_name);
+ } catch (OutOfDateException _) {
+ // ignore
+ } catch (EntryInformationException _) {
+ // ignore
+ } catch (ReadOnlyException _) {
+ found_read_only = true;
+ }
+ }
+
+ if (found_read_only) {
+ final String message =
+ "ignored one or more read-only features";
+ new MessageDialog (getParentFrame (), message);
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+
+ private static Vector<ChadoCanonicalGene> duplicateGeneFeatures(final JFrame frame,
+ final FeatureVector features,
+ final EntryGroup entry_group)
+ {
+ if (getReadOnlyFeatures (features).size () > 0)
+ {
+ new MessageDialog (frame,
+ "one or more of the selected features is read-only " +
+ "- cannot continue");
+ return null;
+ }
+
+ return GeneUtils.duplicateGeneModel(frame, features, entry_group);
+ }
+
+ /**
+ * Duplicate the selected Feature objects. If there are selected segments
+ * then the owning Feature of each segment will be duplicated.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection containing the features to merge.
+ * @param entry_group Used to get the ActionController for calling
+ * startAction() and endAction().
+ **/
+ protected static void duplicateFeatures (final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group) {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ if (getReadOnlyFeatures (selection.getAllFeatures ()).size () > 0) {
+ new MessageDialog (frame,
+ "one or more of the selected features is read-only " +
+ "- cannot continue");
+ return;
+ }
+
+ if (Options.getOptions ().isNoddyMode ()) {
+ final YesNoDialog dialog =
+ new YesNoDialog (frame,
+ "Are you sure you want to duplicate the selected " +
+ "features?");
+
+ if (!dialog.getResult ()) {
+ return;
+ }
+ } else {
+ if (!checkForSelectionFeatures (frame, selection,
+ 100, "really duplicate all (>100) " +
+ "selected features?")) {
+ return;
+ }
+ }
+
+ final FeatureVector features_to_duplicate =
+ selection.getAllFeatures ();
+
+ FeatureVector chadoGenes = null;
+ Vector chadoGeneNames = null;
+ for (int i = 0 ; i < features_to_duplicate.size () ; ++i)
+ {
+ final Feature this_feature = features_to_duplicate.elementAt (i);
+
+ if(this_feature.getEmblFeature() instanceof GFFStreamFeature &&
+ ((GFFStreamFeature)this_feature.getEmblFeature()).getChadoGene() != null)
+ {
+ if(chadoGenes == null)
+ chadoGenes = new FeatureVector();
+ if(chadoGeneNames == null)
+ chadoGeneNames = new Vector();
+
+ final String geneName =
+ ((GFFStreamFeature)this_feature.getEmblFeature()).getChadoGene().getGeneUniqueName();
+
+ if(!chadoGeneNames.contains(geneName))
+ {
+ chadoGenes.add(this_feature);
+ chadoGeneNames.add(geneName);
+ }
+ continue;
+ }
+
+ try
+ {
+ this_feature.duplicate (true);
+ } catch (ReadOnlyException e) {
+ final String message =
+ "one of the selected features (" + this_feature.getIDString () +
+ ") is read only - cannot continue";
+ new MessageDialog (frame, message);
+ return;
+ }
+ }
+
+ if(chadoGenes != null)
+ duplicateGeneFeatures(frame, chadoGenes, entry_group);
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Delete the selected features.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection containing the features to merge.
+ * @param entry_group Used to get the ActionController for calling
+ * startAction() and endAction().
+ **/
+ protected static void deleteSelectedFeatures (final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group)
+ {
+ try
+ {
+ entry_group.getActionController ().startAction ();
+
+ final FeatureVector features_to_delete = selection.getAllFeatures ();
+ final String feature_count_str = ( (features_to_delete.size () == 1) ?
+ "the selected feature" : features_to_delete.size () + " features");
+
+ if (!checkForSelectionFeatures (frame, selection, 0,
+ "really delete " + feature_count_str + "?"))
+ return;
+
+ // clear the selection now so it doesn't need updating as each
+ // feature is deleted
+ selection.clear ();
+
+ if (Options.getOptions ().isNoddyMode ())
+ {
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ {
+ Box boption = Box.createVerticalBox();
+ final JCheckBox delete = new JCheckBox("permanently delete",
+ !Options.getOptions().getPropertyTruthValue("set_obsolete_on_delete"));
+ boption.add(new JLabel("Make "+feature_count_str+" obsolete?"));
+ boption.add(delete);
+ final int res = JOptionPane.showConfirmDialog(frame,
+ boption, "Make obsolete", JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+ if(res == JOptionPane.CANCEL_OPTION)
+ return;
+
+ for(int i=0; i<features_to_delete.size(); i++)
+ {
+ try
+ {
+ GFFStreamFeature gffFeat =
+ (GFFStreamFeature)features_to_delete.elementAt(i).getEmblFeature();
+
+ // if a CDS the delete / obsolete the entire gene model
+ if(gffFeat.getKey().equals(Key.CDS) &&
+ gffFeat.getChadoGene() != null &&
+ gffFeat.getChadoGene().getGene() != null)
+ gffFeat = (GFFStreamFeature) gffFeat.getChadoGene().getGene();
+ final Feature f = (Feature) gffFeat.getUserData();
+
+ if(!delete.isSelected())
+ {
+ // make obsolete rather than permanently delete
+ f.setQualifier(new Qualifier("isObsolete", "true"));
+ PropertiesPanel.updateObsoleteSettings(gffFeat);
+ }
+ else if(gffFeat.getChadoGene() != null)
+ GeneUtils.deleteAllFeature(f, gffFeat.getChadoGene());
+ else
+ {
+ if(!deleteFeature(frame, f, selection, features_to_delete))
+ return;
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return;
+ }
+ }
+
+ while (features_to_delete.size () > 0)
+ {
+ // delete in reverse order for speed
+ final Feature current_selection_feature =
+ features_to_delete.lastElement ();
+ features_to_delete.removeElementAt (features_to_delete.size () - 1);
+
+ if(!deleteFeature(frame, current_selection_feature, selection, features_to_delete))
+ return;
+ }
+ }
+ finally
+ {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ private static boolean deleteFeature(final JFrame frame,
+ final Feature current_selection_feature,
+ final Selection selection,
+ final FeatureVector features_to_delete)
+ {
+ try
+ {
+ current_selection_feature.removeFromEntry ();
+ }
+ catch (ReadOnlyException e)
+ {
+ selection.set (current_selection_feature);
+ if (features_to_delete.size () == 1)
+ {
+ final String message =
+ "the selected feature (" +
+ current_selection_feature.getIDString () +
+ ") is read only - cannot continue";
+ new MessageDialog (frame, message);
+ }
+ else
+ {
+ final String message =
+ "one of the selected features (" +
+ current_selection_feature.getIDString () +
+ ") is read only - cannot continue";
+ new MessageDialog (frame, message);
+ }
+
+ features_to_delete.add (current_selection_feature);
+ // reset the select so that the user can see what it was
+ selection.set (features_to_delete);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Delete the selected feature segments.
+ **/
+ private void deleteSelectedSegments () {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ final FeatureSegmentVector segments_to_delete =
+ (FeatureSegmentVector) getSelection ().getAllSegments ().clone ();
+
+ if (Options.getOptions ().isNoddyMode ()) {
+ // 0 means always popup a YesNoDialog
+ if (!checkForSelectionFeatureSegments (0, "really delete " +
+ (segments_to_delete.size ()==1 ?
+ "the selected exon?" :
+ segments_to_delete.size () +
+ " exons?"))) {
+ return;
+ }
+ }
+
+ for (int i = 0 ; i < segments_to_delete.size () ; ++i) {
+ final FeatureSegment selection_segment =
+ segments_to_delete.elementAt (i);
+
+ try {
+ getSelection ().remove (selection_segment);
+ selection_segment.removeFromFeature ();
+ } catch (ReadOnlyException e) {
+ final String message =
+ "one of the selected exons (in " +
+ selection_segment.getFeature ().getIDString () +
+ ") is in a read only - cannot continue";
+ new MessageDialog (getParentFrame (), message);
+ } catch (LastSegmentException e) {
+ final String message =
+ "the last exon in this feature: " +
+ selection_segment.getFeature ().getIDString () +
+ " cannot be removed (all features must have at least one exon)";
+ new MessageDialog (getParentFrame (), message);
+ }
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Remove all introns from the selected features.
+ **/
+ private void removeIntrons () {
+ if (!checkForSelectionFeatures ()) {
+ return;
+ }
+
+ boolean found_read_only = false;
+
+ try {
+ entry_group.getActionController ().startAction ();
+
+ final FeatureVector features = getSelection ().getAllFeatures ();
+
+ for (int i = 0 ; i < features.size () ; ++i) {
+ final Feature this_feature = features.elementAt (i);
+ try {
+ final RangeVector new_ranges = new RangeVector ();
+ final Range new_range = this_feature.getMaxRawRange ();
+ new_ranges.add (new_range);
+ final Location old_location = this_feature.getLocation ();
+ final Location new_location =
+ new Location (new_ranges, old_location.isComplement ());
+ this_feature.setLocation (new_location);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (ReadOnlyException _) {
+ found_read_only = true;
+ }
+ }
+
+ if (found_read_only) {
+ final String message =
+ "ignored one or more read-only features";
+ new MessageDialog (getParentFrame (), message);
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Move the given features to the given destination entry.
+ **/
+ private void moveFeatures (final FeatureVector features,
+ final Entry destination_entry) {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ if (features.size () == 0) {
+ return;
+ }
+
+ final FeatureVector read_only_features =
+ getReadOnlyFeatures (features);
+
+ if (read_only_features.size () > 0) {
+ final String message =
+ (features.size () == 1 ?
+ "the selected feature (" +
+ read_only_features.elementAt (0).getIDString () +
+ ") is read only " :
+ "some of the selected features (eg. " +
+ read_only_features.elementAt (0).getIDString () +
+ ") are read only - ") +
+ "cannot continue";
+ new MessageDialog (getParentFrame (), message);
+ return;
+ }
+
+ FEATURES:
+ for (int i = 0 ; i < features.size () ; ++i) {
+ final Feature this_feature = features.elementAt (i);
+
+ // check that we don't loop forever (perhaps due to bugs in
+ // handleOpenException())
+ final int MAX_COUNT = 100;
+
+ int count = 0;
+
+ while (count++ < MAX_COUNT) {
+ try {
+ this_feature.moveTo (destination_entry, false);
+ continue FEATURES;
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ final EntryInformation dest_entry_information =
+ destination_entry.getEntryInformation ();
+ dest_entry_information.fixException (e);
+ continue;
+ } catch (ReadOnlyException e) {
+ final String message =
+ "either the source or destination for one of the features is " +
+ "read only - cannot continue";
+ new MessageDialog (getParentFrame (), message);
+ return;
+ }
+ }
+
+ final String message =
+ "internal error while copying";
+ new MessageDialog (getParentFrame (), message);
+
+ throw new Error ("internal error in EditMenu.copyFeatures() - infinite loop");
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Copy the given features to the given destination entry.
+ **/
+ private void copyFeatures (final FeatureVector features,
+ final Entry destination_entry) {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ FEATURES:
+ for (int i = 0 ; i < features.size () ; ++i) {
+ final Feature this_feature = features.elementAt (i);
+
+ // check that we don't loop forever (perhaps due to bugs in
+ // handleOpenException())
+ final int MAX_COUNT = 100;
+
+ int count = 0;
+
+ while (count++ < MAX_COUNT) {
+ try {
+ this_feature.copyTo (destination_entry);
+ continue FEATURES;
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ final EntryInformation dest_entry_information =
+ destination_entry.getEntryInformation ();
+ dest_entry_information.fixException (e);
+ continue;
+ } catch (ReadOnlyException e) {
+ final String message =
+ "the destination entry is read only - cannot continue";
+ new MessageDialog (getParentFrame (), message);
+ return;
+ }
+ }
+
+ final String message = "internal error while copying";
+ new MessageDialog (getParentFrame (), message);
+
+ throw new Error ("internal error in EditMenu.copyFeatures() - " +
+ "infinite loop");
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Calls fixStopCodon () on each selected feature.
+ **/
+ private void fixStopCodons () {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ if (!checkForSelectionFeatures ()) {
+ return;
+ }
+
+ // features that can't be fixed
+ final FeatureVector bad_features = new FeatureVector ();
+
+ final FeatureVector features_to_fix = getSelection ().getAllFeatures ();
+
+ for (int i = 0 ; i < features_to_fix.size () ; ++i) {
+ final Feature selection_feature = features_to_fix.elementAt (i);
+
+ if (!selection_feature.isCDS() &&
+ !(selection_feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL))) {
+ final String message =
+ "Warning: some of the selected features are not coding features. " +
+ "Continue?";
+
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog (getParentFrame (), message);
+
+ if (yes_no_dialog.getResult ()) {
+ break;
+ } else {
+ return;
+ }
+ }
+ }
+
+ for (int i = 0 ; i < features_to_fix.size () ; ++i) {
+ final Feature selection_feature = features_to_fix.elementAt (i);
+
+ try {
+ if (!selection_feature.fixStopCodon ()) {
+ bad_features.add (selection_feature);
+ }
+ } catch (ReadOnlyException e) {
+ final String message =
+ "one of the entries is read only - cannot continue";
+ new MessageDialog (getParentFrame (), message);
+ break;
+ }
+ }
+
+ if (bad_features.size () > 0) {
+ // select the bad feature
+ getSelection ().set (bad_features);
+
+ final String message =
+ "Warning: could not fix the stop codon for some of the features. " +
+ "View them now?";
+
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog (getParentFrame (), message);
+
+ if (yes_no_dialog.getResult ()) {
+ final FeaturePredicate predicate =
+ new FeatureFromVectorPredicate (bad_features);
+
+ final String filter_name =
+ "features that can't be fixed (filtered from: " +
+ getParentFrame ().getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ getSelection (),
+ goto_event_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Calls Feature.trimStart () on all selected Feature objects.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group Used to get the ActionController for calling
+ * startAction().
+ * @param trim_to_any If true then the features will be trimmed to ATG, GTG
+ * or TTG, otherwise the features will be trimmed to ATG only.
+ * @param trim_to_next If true then the features will be trimmed to the
+ * next start codon (dependent on trim_to_any) regardless of whether the
+ * feature currently start on a start codon. If false then the feature
+ * will only be trimmed if the feature doesn't start on a start codon.
+ **/
+ protected static void trimSelected (final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final boolean trim_to_any,
+ final boolean trim_to_next) {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ if (!checkForSelectionFeatures (frame, selection)) {
+ return;
+ }
+
+ final FeatureVector features_to_trim = selection.getAllFeatures ();
+
+ // this will contain the features that could not be trimmed
+ final FeatureVector failed_features = new FeatureVector ();
+
+ for (int i = 0 ; i < features_to_trim.size () ; ++i) {
+ final Feature selection_feature = features_to_trim.elementAt (i);
+
+ try {
+ if (!selection_feature.trimStart (trim_to_any, trim_to_next)) {
+ failed_features.add (selection_feature);
+ }
+ } catch (ReadOnlyException e) {
+ final String message =
+ "one or more of the of the selected features are read only " +
+ "- cannot continue";
+ new MessageDialog (frame, message);
+ break;
+ }
+ }
+
+ if (failed_features.size () > 0) {
+ selection.set (failed_features);
+
+ if (failed_features.size () == 1) {
+ final String message = "could not trim the feature";
+
+ new MessageDialog (frame, message);
+
+ } else {
+ final String message =
+ "some features could not be trimmed (they are now selected)";
+
+ new MessageDialog (frame, message);
+ }
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Extend the selected segments/features so that they reach to the start or
+ * end of their containing ORF.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group Used to get the ActionController for calling
+ * startAction() and endAction().
+ * @param extend_to_next_stop If true the feature is extended to the next
+ * stop codon (but it won't include the next stop). If false the feature
+ * is extended to the previous stop codon (but it won't include the
+ * previous stop).
+ **/
+ protected static void extendToORF(final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final boolean extend_to_next_stop)
+ {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ if (!checkForSelectionFeatures (frame, selection)) {
+ return;
+ }
+
+ final FeatureVector features_to_extend = selection.getAllFeatures ();
+
+ for (int i = 0 ; i < features_to_extend.size () ; ++i) {
+ final Feature selection_feature = features_to_extend.elementAt (i);
+
+ if (selection_feature.isReadOnly ()) {
+ final String message =
+ "one or more of the of the selected features are read only " +
+ "- cannot continue";
+ new MessageDialog (frame, message);
+ break;
+ }
+
+ if (selection_feature.getSegments ().size () < 1) {
+ continue;
+ }
+
+ final FeatureSegmentVector feature_segments =
+ selection_feature.getSegments ();
+
+ if (feature_segments.size () == 0) {
+ continue;
+ }
+
+ final Strand strand = selection_feature.getStrand ();
+
+ if (extend_to_next_stop) {
+
+ if(!(selection_feature.getEmblFeature() instanceof GFFStreamFeature))
+ {
+ if(selection_feature.hasValidStopCodon())
+ continue;
+ }
+ else
+ {
+ if(selection_feature.hasValidStopCodon(true))
+ continue;
+ }
+
+ final Marker feature_end_marker =
+ selection_feature.getLastBaseMarker ();
+
+ Marker end_marker_copy = null;
+
+ // get the marker of the first base of the last codon in the range
+ // (we want to keep in the same frame)
+ try {
+ if (selection_feature.getBaseCount () == 1) {
+ end_marker_copy = feature_end_marker;
+ } else {
+ if (selection_feature.getBaseCount () == 2) {
+ end_marker_copy = feature_end_marker.moveBy (-1);
+ } else {
+ // go to the start of the codon
+ end_marker_copy = feature_end_marker.moveBy (-2);
+ }
+ }
+
+ // adjust for /codon_start
+ final int frame_shift =
+ selection_feature.getCodonStart () - 1;
+
+ // the number of bases from the start of translation to the
+ // end of the feature
+ final int bases_from_start_pos_mod_3 =
+ (3 + selection_feature.getBaseCount () - frame_shift) % 3;
+
+ end_marker_copy =
+ end_marker_copy.moveBy (-bases_from_start_pos_mod_3);
+ } catch (OutOfRangeException e) {
+ end_marker_copy = feature_end_marker;
+ }
+
+ final MarkerRange end_orf_range =
+ strand.getORFAroundMarker (end_marker_copy, true);
+
+ if (end_orf_range == null) {
+ // end_marker_copy is at the start base of a stop codon
+ continue;
+ }
+
+ final Marker new_end_marker = end_orf_range.getEnd ();
+
+ try {
+ feature_end_marker.setPosition (new_end_marker.getPosition ());
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ } else {
+ final AminoAcidSequence translation =
+ selection_feature.getTranslation ();
+
+ if (translation.length () > 0) {
+ final char first_aa = translation.elementAt (0);
+
+ if (AminoAcidSequence.isStopCodon (first_aa)) {
+ continue;
+ }
+ }
+
+ final Marker feature_start_marker =
+ selection_feature.getFirstBaseMarker ();
+
+ Marker start_marker_copy = feature_start_marker;
+
+ final int frame_shift =
+ selection_feature.getCodonStart () - 1;
+
+ if (frame_shift != 0) {
+ try {
+ start_marker_copy =
+ feature_start_marker.moveBy (frame_shift);
+ } catch (OutOfRangeException e) {
+ // ignore and hope for the best
+ }
+ }
+
+ final MarkerRange start_orf_range =
+ strand.getORFAroundMarker (start_marker_copy, true);
+
+ final Marker new_start_marker = start_orf_range.getStart ();
+
+ try {
+ feature_start_marker.setPosition (new_start_marker.getPosition ());
+ selection_feature.removeQualifierByName ("codon_start");
+ } catch (EntryInformationException _) {
+ // ignore - if we can't remove codon_start, then it won't have
+ // been there to start with
+ } catch (OutOfDateException _) {
+ // ignore
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Reverse and complement the sequence and all the features in all Entry
+ * objects.
+ **/
+ private void reverseAndComplement ()
+ {
+ if (getEntryGroup ().isReadOnly ())
+ {
+ final String message =
+ "one or more of the entries or features are read only - " +
+ "cannot continue";
+ new MessageDialog (getParentFrame (), message);
+ return;
+ }
+
+ final YesNoDialog dialog =
+ new YesNoDialog (getParentFrame (),
+ "Are you sure you want to reverse complement all " +
+ "entries?");
+
+ if (!dialog.getResult ())
+ return;
+
+ try
+ {
+ if(getEntryGroup().getBases().getSequence() instanceof RawStreamSequence)
+ setFastaHeaderPositionsOnReverseComplement(
+ (RawStreamSequence) getEntryGroup().getBases().getSequence());
+
+ getEntryGroup ().reverseComplement ();
+ makeSelectionStartVisible ();
+ }
+ catch (ReadOnlyException e)
+ {
+ final String message =
+ "one of the entries is read only - cannot continue";
+ new MessageDialog (getParentFrame (), message);
+ return;
+ }
+ }
+
+ /**
+ * Set the fasta header positions.
+ * @param sequence
+ */
+ private void setFastaHeaderPositionsOnReverseComplement(final RawStreamSequence sequence)
+ {
+ // find all fasta_record features
+ final FeaturePredicate key_predicate_contig
+ = new FeatureKeyQualifierPredicate(new Key("fasta_record"),
+ null, // match any qialifier
+ false);
+
+ final RangeVector contigRanges = new RangeVector();
+ for(int i=0; i<getEntryGroup().getAllFeatures().size(); i++)
+ {
+ uk.ac.sanger.artemis.Feature contig =
+ getEntryGroup().getAllFeatures().elementAt(i);
+ if(key_predicate_contig.testPredicate(contig))
+ contigRanges.add(contig.getLocation().getTotalRange());
+ }
+ sequence.setFastaHeaderPositionsOnReverseComplement(contigRanges);
+ }
+
+ /**
+ * Delete the selected bases after asking the user for confimation.
+ **/
+ private boolean deleteSelectedBases (final String description) {
+ if (!checkForSelectionRange ()) {
+ return false;
+ }
+
+ final MarkerRange marker_range = getSelection ().getMarkerRange ();
+
+ if (marker_range.getCount () == getEntryGroup ().getSequenceLength ()) {
+ new MessageDialog (getParentFrame (), "You can't delete every base");
+ return false;
+ }
+
+ if (getEntryGroup ().isReadOnly ()) {
+ new MessageDialog (getParentFrame (),
+ "one of the current entries or features is " +
+ "read-only - connot continue");
+ return false;
+ }
+
+ final Range raw_range = marker_range.getRawRange ();
+
+ final FeatureVector features_in_range;
+
+ final EntryVector old_active_entries =
+ getEntryGroup ().getActiveEntries ();
+
+ // make all the entries active so that getFeaturesInRange () gets
+ // everything
+ for (int i = 0 ; i < getEntryGroup ().size () ; ++i) {
+ getEntryGroup ().setIsActive (i, true);
+ }
+
+ try {
+ features_in_range =
+ getEntryGroup ().getFeaturesInRange (raw_range);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ if (features_in_range.size () != 0) {
+ final FeatureVector read_only_features =
+ getReadOnlyFeatures (features_in_range);
+
+ if (read_only_features.size () > 0) {
+ getSelection ().set (read_only_features);
+ final String message =
+ "one or more of the features in the selected range are " +
+ "read only - cannot continue";
+ new MessageDialog (getParentFrame (), message);
+ getSelection ().setMarkerRange (marker_range);
+ return false;
+ }
+
+ for (int feature_index = 0 ;
+ feature_index < features_in_range.size () ;
+ ++feature_index) {
+ final Feature this_feature =
+ features_in_range.elementAt (feature_index);
+
+ final FeatureSegmentVector segments = this_feature.getSegments ();
+
+ for (int i = 0 ; i < segments.size () ; ++i) {
+ final FeatureSegment this_segment = segments.elementAt (i);
+
+ final Range this_segment_range = this_segment.getRawRange ();
+
+ if (raw_range.getStart () <= this_segment_range.getStart () &&
+ raw_range.getEnd () >= this_segment_range.getStart () ||
+ raw_range.getStart () <= this_segment_range.getEnd () &&
+ raw_range.getEnd () >= this_segment_range.getEnd ()) {
+ new MessageDialog (getParentFrame (),
+ "the selected range overlaps the " +
+ "start or end of an exon - cannot continue");
+ return false;
+ }
+ }
+ }
+ }
+
+ // reset the EntryGroup to the state it was in originally
+ for (int i = 0 ; i < getEntryGroup ().size () ; ++i) {
+ final Entry this_entry = getEntryGroup ().elementAt (i);
+ final boolean old_active_flag =
+ old_active_entries.contains (this_entry);
+ getEntryGroup ().setIsActive (i, old_active_flag);
+ }
+
+ if (Options.getOptions ().isNoddyMode ()) {
+ final YesNoDialog dialog =
+ new YesNoDialog (getParentFrame (), description);
+ if (!dialog.getResult ()) {
+ return false;
+ }
+ }
+
+ try {
+ getSelection ().setMarkerRange (null);
+
+ // deleteRange () is static and uses the strand reference from the
+ // MarkerRange to decide which Strand to edit
+ Strand.deleteRange (marker_range);
+ } catch (ReadOnlyException e) {
+ getSelection ().setMarkerRange (marker_range);
+
+ new MessageDialog (getParentFrame (),
+ "the bases are read only - cannot delete");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Ask the user for some bases and then add them at the start of the
+ * selected range.
+ **/
+ private void addBases () {
+ if (!checkForSelectionRange ()) {
+ return;
+ }
+
+ if (getEntryGroup ().isReadOnly ()) {
+ new MessageDialog (getParentFrame (),
+ "one of the current entries or features is " +
+ "read-only - connot continue");
+ return;
+ }
+
+ final MarkerRange range = getSelection ().getMarkerRange ();
+
+ final TextRequester text_requester =
+ new TextRequester ("enter the bases to insert before base " +
+ range.getStart ().getPosition () +
+ (range.isForwardMarker () ?
+ " on the forward strand" :
+ " on the reverse strand") + ":",
+ 18, "");
+
+ text_requester.addTextRequesterListener (new TextRequesterListener () {
+ public void actionPerformed (final TextRequesterEvent event) {
+ if (event.getType () == TextRequesterEvent.CANCEL) {
+ return;
+ }
+
+ final String bases_string = event.getRequesterText ().trim ();
+
+ if (bases_string.length () == 0) {
+ new MessageDialog (getParentFrame (), "no bases inserted");
+ return;
+ }
+
+ try {
+ // addBases () is static and uses the strand reference from the
+ // start Marker to decide which Strand to edit
+ Strand.addBases (range.getStart (), bases_string);
+ } catch (ReadOnlyException e) {
+ new MessageDialog (getParentFrame (),
+ "sorry the bases are read only");
+ return;
+ } catch (org.biojava.bio.symbol.IllegalSymbolException e) {
+ new MessageDialog (getParentFrame (),
+ "sorry - new sequence contains non-IUB base " +
+ "codes");
+ return;
+ }
+ }
+ });
+
+ text_requester.setVisible(true);
+ }
+
+
+ /**
+ * Ask the user for some bases and then add them at the start of the
+ * selected range.
+ **/
+ private void addBasesFromFile () {
+ if (!checkForSelectionRange ()) {
+ return;
+ }
+
+ if (getEntryGroup ().isReadOnly ()) {
+ new MessageDialog (getParentFrame (),
+ "one of the current entries or features is " +
+ "read-only - connot continue");
+ return;
+ }
+
+ final MarkerRange range = getSelection ().getMarkerRange ();
+
+ // XXX add an InputStreamProgressListener
+ final EntrySourceVector entry_sources =
+ Utilities.getEntrySources (getParentFrame (), null);
+
+ EntrySource filesystem_entry_source = null;
+
+ for (int source_index = 0 ;
+ source_index < entry_sources.size () ;
+ ++source_index) {
+ final EntrySource this_source =
+ entry_sources.elementAt (source_index);
+
+ if (this_source.isFullEntrySource ()) {
+ continue;
+ }
+
+ if (this_source.getSourceName ().equals ("Filesystem")) {
+ filesystem_entry_source = this_source;
+ }
+ }
+
+ if (filesystem_entry_source == null) {
+ throw new Error ("internal error - can't find a file system to read " +
+ "from");
+ }
+
+ try {
+ final Entry new_entry = filesystem_entry_source.getEntry (true);
+
+ final Bases bases = new_entry.getBases ();
+
+ final String bases_string = bases.toString ();
+
+ if (bases_string.length () == 0) {
+ new MessageDialog (getParentFrame (), "no bases inserted");
+ return;
+ }
+
+ // addBases () is static and uses the strand reference from the
+ // start Marker to decide which Strand to edit
+ Strand.addBases (range.getStart (), bases_string);
+ } catch (ReadOnlyException e) {
+ new MessageDialog (getParentFrame (),
+ "sorry the bases are read only");
+ return;
+ } catch (IOException e) {
+ new MessageDialog (getParentFrame (),
+ "error while reading: " + e.getMessage ());
+ return;
+ } catch (OutOfRangeException e) {
+ new MessageDialog (getParentFrame (),
+ "out of range exception while reading: " +
+ e.getMessage ());
+ return;
+ } catch (NoSequenceException e) {
+ new MessageDialog (getParentFrame (),
+ "sorry the file did not contain any sequence");
+ return;
+ } catch (org.biojava.bio.symbol.IllegalSymbolException e) {
+ new MessageDialog (getParentFrame (),
+ "sorry - new sequence contains non-IUB base " +
+ "codes");
+ return;
+ }
+ }
+
+ /**
+ * Add a qualifier to the given features. The qualifier will normally be a
+ * gene name.
+ * @param prefix_string prefix of the new qualifier eg. SPAC
+ * @param start_number The number to start counting at eg. 1 give SPAC0001
+ * for the first CDS
+ * @param increment The amount to increment by at each gene.
+ * @param qualifier_name The name of the qualifier to add
+ * @param tag_complement_names If True a lowercase "c" will be appended to
+ * the new qualifier values on the reverse strand.
+ **/
+ private void autoGeneNameHelper (final FeatureVector features_to_name,
+ final String prefix_string,
+ final int start_number,
+ final int increment,
+ final String qualifier_name,
+ final boolean tag_complement_names,
+ final int format_value) {
+ try {
+ entry_group.getActionController ().startAction ();
+
+ String fmt = "";
+ for(int i=0; i<format_value; i++)
+ fmt = fmt.concat("0");
+
+ NumberFormat formatter = new DecimalFormat(fmt);
+
+ int current_number = start_number;
+
+ for (int i = 0 ; i < features_to_name.size () ; ++i) {
+ final Feature this_feature = features_to_name.elementAt (i);
+
+ //final Key key = this_feature.getKey ();
+
+ final String number_string = formatter.format(current_number);
+
+ /*if (current_number < 10) {
+ number_string = "000" + current_number;
+ } else {
+ if (current_number < 100) {
+ number_string = "00" + current_number;
+ } else {
+ if (current_number < 1000) {
+ number_string = "0" + current_number;
+ } else {
+ number_string = String.valueOf (current_number);
+ }
+ }
+ }*/
+
+ try {
+ final Qualifier new_qualifier =
+ new Qualifier (qualifier_name, prefix_string +
+ number_string +
+ (!tag_complement_names ||
+ this_feature.isForwardFeature () ?
+ "" :
+ "c"));
+
+ this_feature.addQualifierValues (new_qualifier);
+ } catch (EntryInformationException e) {
+ new MessageDialog (getParentFrame (),
+ "/" + qualifier_name +
+ " not supported by this entry " +
+ "- cannot continue");
+ return;
+ } catch (ReadOnlyException e) {
+ new MessageDialog (getParentFrame (),
+ "one of the features is in a read-only " +
+ "entry - cannot continue");
+ return;
+ }
+
+ current_number += increment;
+// }
+ }
+ } finally {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Automatically create gene names for all CDS features in the active
+ * entries.
+ **/
+ private void autoGeneName () {
+ if (!checkForSelectionFeatures (getParentFrame (), getSelection ())) {
+ return;
+ }
+
+ final FeatureVector features_to_name = getSelection ().getAllFeatures ();
+
+ if (getReadOnlyFeatures (features_to_name).size () > 0) {
+ new MessageDialog (getParentFrame (),
+ "one or more of the current features is " +
+ "read-only - cannot continue");
+ return;
+ }
+
+ final TextDialog prefix_dialog =
+ new TextDialog (getParentFrame (),
+ "enter the start characters of the new gene names:",
+ 18, "");
+
+ final String prefix_text = prefix_dialog.getText ();
+
+ if (prefix_text == null) {
+ return;
+ }
+
+ final String prefix_string = prefix_text.trim ();
+
+ if (prefix_string.length () == 0) {
+ new MessageDialog (getParentFrame (), "no prefix given");
+ return;
+ }
+
+ final TextDialog start_number_dialog =
+ new TextDialog (getParentFrame (),
+ "start counting at:", 18, "1");
+
+ final String start_number_text = start_number_dialog.getText ();
+
+ if (start_number_text == null) {
+ return;
+ }
+
+ String start_string = start_number_text.trim ();
+
+ if (start_string.length () == 0) {
+ new MessageDialog (getParentFrame (), "no start given");
+ return;
+ }
+
+ final int start_value;
+
+ try {
+ start_value = Integer.valueOf (start_string).intValue ();
+ } catch (NumberFormatException e) {
+ new MessageDialog (getParentFrame (),
+ "this is not a number: " + start_string);
+ return;
+ }
+
+ final TextDialog increment_dialog =
+ new TextDialog (getParentFrame (),
+ "increment number by:", 18, "1");
+
+ final String increment_text = increment_dialog.getText ();
+
+ if (increment_text == null) {
+ return;
+ }
+
+ String increment_string = increment_text.trim ();
+
+ if (increment_string.length () == 0) {
+ new MessageDialog (getParentFrame (), "no increment given");
+ return;
+ }
+
+ final int increment_value;
+
+ try {
+ increment_value = Integer.valueOf (increment_string).intValue ();
+ } catch (NumberFormatException e) {
+ new MessageDialog (getParentFrame (),
+ "this is not a number: " + increment_string);
+ return;
+ }
+
+ final TextDialog qualifier_name_dialog =
+ new TextDialog (getParentFrame (),
+ "enter a qualifier name to use",
+ 18, "gene");
+
+ final String qualifier_name_text = qualifier_name_dialog.getText ();
+
+ if (qualifier_name_text == null) {
+ return;
+ }
+
+ final String qualifier_name_string = qualifier_name_text.trim ();
+
+ if (qualifier_name_string.length () == 0) {
+ new MessageDialog (getParentFrame (), "no qualifier name given");
+ return;
+ }
+
+ final YesNoDialog complement_tag_dialog =
+ new YesNoDialog (getParentFrame (),
+ "append \"c\" to names of reverse strand features?");
+
+ final TextDialog format_dialog =
+ new TextDialog (getParentFrame (),
+ "number of digits in the name, e.g. 5, pads with " +
+ "zeros: 00009, 00010 ....",
+ 18, "5");
+
+ int format_value;
+
+ try {
+ format_value = Integer.valueOf (format_dialog.getText().trim()).intValue ();
+ } catch (NumberFormatException e) {
+ new MessageDialog (getParentFrame (),
+ "this is not a number: " + format_dialog.getText());
+ return;
+ }
+
+ if(format_value < 0)
+ format_value = -format_value;
+
+ autoGeneNameHelper(features_to_name,
+ prefix_string, start_value, increment_value,
+ qualifier_name_string,
+ complement_tag_dialog.getResult(), format_value);
+ }
+
+ /**
+ * Helper function for fixGeneNames(). Add the gene name from the CDS to
+ * neighbouring/overlapping mRNA, intron, exon, gene, 5'UTR and 3'UTR
+ * features. Warn about inconsistencies.
+ * @return true if all went well, false if fixing should stop immediately
+ **/
+ private static boolean fixGeneNamesHelper(final JFrame frame,
+ final EntryGroup entry_group,
+ final Feature cds_to_fix,
+ final String name)
+ {
+ if(cds_to_fix.isReadOnly())
+ {
+ final String message =
+ "one or more of the of the selected features are read only " +
+ "- cannot continue";
+ new MessageDialog(frame, message);
+ return false;
+ }
+
+ try
+ {
+ final Strand cds_to_fix_strand = cds_to_fix.getStrand();
+ Marker cds_start_marker = cds_to_fix.getFirstBaseMarker();
+ Marker cds_end_marker = cds_to_fix.getLastBaseMarker();
+
+ // move the start one base back and the end one base forward so that
+ // when we call getFeaturesInRange() we get the UTRs
+
+ try
+ {
+ cds_start_marker = cds_start_marker.moveBy(-1);
+ }
+ catch(OutOfRangeException _)
+ {
+ // ignore and use the original cds_start_marker
+ }
+
+ try
+ {
+ cds_end_marker = cds_end_marker.moveBy(1);
+ }
+ catch(OutOfRangeException _)
+ {
+ // ignore and use the original cds_end_marker
+ }
+
+ final Range search_range;
+
+ if(cds_start_marker.getRawPosition() < cds_end_marker.getRawPosition())
+ {
+ search_range =
+ new Range(cds_start_marker.getRawPosition(),
+ cds_end_marker.getRawPosition());
+ }
+ else
+ {
+ search_range =
+ new Range(cds_end_marker.getRawPosition(),
+ cds_start_marker.getRawPosition());
+ }
+
+ final FeatureVector features_in_range =
+ entry_group.getFeaturesInRange(search_range);
+
+ final FeatureVector features_to_change =
+ new FeatureVector();
+
+ for(int i = 0 ; i < features_in_range.size() ; ++i)
+ {
+ final Feature this_feature = features_in_range.elementAt(i);
+
+ if(this_feature.getStrand() == cds_to_fix_strand &&
+ (this_feature.isCDS() ||
+ this_feature.getKey().equals("mRNA") ||
+ this_feature.getKey().equals("intron") ||
+ this_feature.getKey().equals("exon") ||
+ this_feature.getKey().equals("5'UTR") &&
+ (cds_to_fix.getFirstBase() ==
+ this_feature.getLastBase() + 1) ||
+ this_feature.getKey().equals("3'UTR") &&
+ (cds_to_fix.getLastBase() + 1 ==
+ this_feature.getFirstBase()) ||
+ this_feature.getKey().equals("gene")))
+ {
+ if(this_feature.isReadOnly())
+ {
+ final String message =
+ "one or more of the of the overlapping features are read only " +
+ "- cannot continue";
+ new MessageDialog(frame, message);
+ return false;
+ }
+
+
+ // for exons check they are in this cds's range
+ if(this_feature.getKey().equals("exon"))
+ {
+ Range exon_range = this_feature.getMaxRawRange();
+ RangeVector ranges = cds_to_fix.getLocation().getRanges();
+
+ for(int j=0; j<ranges.size(); j++)
+ {
+ Range range = (Range)ranges.get(j);
+ if(exon_range.equals(range))
+ {
+ features_to_change.add(this_feature);
+ break;
+ }
+ }
+ }
+ else
+ features_to_change.add(this_feature);
+ }
+ }
+
+ final FeatureVector overlapping_cds_features = new FeatureVector();
+ for(int i = 0 ; i < features_to_change.size() ; ++i)
+ {
+ final Feature this_test_feature = features_to_change.elementAt(i);
+ if(this_test_feature != cds_to_fix && this_test_feature.isCDS())
+ {
+ overlapping_cds_features.add(this_test_feature);
+ features_to_change.remove(this_test_feature);
+ }
+ }
+
+ if(overlapping_cds_features.size() > 0)
+ {
+ final String message =
+ "your CDS (" + cds_to_fix.getIDString() +
+ ") overlaps " + overlapping_cds_features.size() +
+ " other CDS feature" +
+ (overlapping_cds_features.size() == 1 ?
+ "" : "s") + " - continue?";
+
+ final YesNoDialog dialog =
+ new YesNoDialog(frame, message);
+
+ if(!dialog.getResult())
+ return false;
+ }
+
+ final StringVector gene_names = new StringVector ();
+ for(int i = 0; i < features_to_change.size(); ++i)
+ {
+ final Feature test_feature =
+ features_to_change.elementAt(i);
+
+ final StringVector test_feature_gene_names =
+ test_feature.getValuesOfQualifier(name);
+
+ if(test_feature_gene_names != null)
+ {
+ for(int j = 0; j < test_feature_gene_names.size(); ++j)
+ {
+ final String this_gene_name =
+ (String)test_feature_gene_names.elementAt(j);
+
+ if(!gene_names.contains(this_gene_name))
+ gene_names.add (this_gene_name);
+ }
+ }
+ }
+
+ // ignore this feature, but continue with the other features
+ if (gene_names.size () == 0)
+ return true;
+
+ for(int i = 0; i < features_to_change.size(); ++i)
+ {
+ final Feature this_feature = features_to_change.elementAt(i);
+ final Qualifier qualifier = new Qualifier(name, gene_names);
+ this_feature.setQualifier(qualifier);
+ }
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(InvalidRelationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(EntryInformationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(ReadOnlyException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ return true;
+ }
+
+ /**
+ * For each selected CDS, add the gene name from the CDS to
+ * neighbouring/overlapping mRNA, intron, exon, gene, 5'UTR and 3'UTR
+ * features. Warn about inconsistencies.
+ * @param frame The Frame to use for MessageDialog components.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ **/
+ private static void fixGeneNames(final JFrame frame,
+ final EntryGroup entry_group,
+ final Selection selection)
+ {
+ try
+ {
+ entry_group.getActionController().startAction();
+ final FeatureVector features_to_fix = selection.getAllFeatures();
+ int cds_features_found = 0;
+
+ StringVector names = Options.getOptions().getSystematicQualifierNames();
+ JList types = new JList(names);
+ types.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ types.setSelectedValue("gene", true);
+ JOptionPane.showMessageDialog(frame, types,
+ "Qualifier to Transfer", JOptionPane.QUESTION_MESSAGE);
+ String name = (String) types.getSelectedValue();
+
+ for(int i = 0; i < features_to_fix.size(); ++i)
+ {
+ final Feature selection_feature = features_to_fix.elementAt(i);
+
+ if(selection_feature.isCDS())
+ {
+ ++cds_features_found;
+ if(!fixGeneNamesHelper(frame, entry_group, selection_feature, name))
+ return;
+ }
+ }
+
+ if(cds_features_found == 0)
+ new MessageDialog(frame, "no CDS features selected");
+ }
+ finally
+ {
+ entry_group.getActionController ().endAction ();
+ }
+ }
+
+ /**
+ * Return the EntryGroup that was passed to the constructor.
+ **/
+ private EntryGroup getEntryGroup()
+ {
+ return entry_group;
+ }
+
+ /**
+ * This method sends an GotoEvent to all the GotoEvent listeners that will
+ * make the first base of the selection visible.
+ **/
+ private void makeSelectionStartVisible()
+ {
+ final GotoEvent new_event = new GotoEvent(this,
+ getSelection().getStartBaseOfSelection());
+ goto_event_source.sendGotoEvent(new_event);
+ }
+
+ /**
+ * Returns a Vector containing those features in the given vector of
+ * features which are read only or are in a read only entry.
+ **/
+ private static FeatureVector getReadOnlyFeatures(final FeatureVector features)
+ {
+ final FeatureVector return_vector = new FeatureVector();
+
+ for(int i = 0; i < features.size(); ++i)
+ {
+ final Feature this_feature = features.elementAt(i);
+ if(this_feature.isReadOnly())
+ return_vector.add(this_feature);
+ }
+
+ return return_vector;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/EntryActionListener.java b/uk/ac/sanger/artemis/components/EntryActionListener.java
new file mode 100644
index 0000000..057a82d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EntryActionListener.java
@@ -0,0 +1,63 @@
+/* EntryActionListener.java
+ *
+ * created: Mon Jan 31 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryActionListener.java,v 1.1 2004-06-09 09:46:21 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+import java.awt.event.*;
+
+/**
+ * This class is an implementation of the ActionListener interface that can
+ * remember an entry.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryActionListener.java,v 1.1 2004-06-09 09:46:21 tjc Exp $
+ **/
+
+abstract public class EntryActionListener implements ActionListener {
+ /**
+ * Make a new EntryActionListener from the given Entry.
+ **/
+ EntryActionListener (final EntryEdit entry_edit,
+ final Entry entry) {
+ this.entry = entry;
+ this.entry_edit = entry_edit;
+ }
+
+ abstract public void actionPerformed (final ActionEvent event);
+
+ public Entry getEntry () {
+ return entry;
+ }
+
+ public EntryEdit getEntryEdit () {
+ return entry_edit;
+ }
+
+ final private Entry entry;
+ final private EntryEdit entry_edit;
+}
+
diff --git a/uk/ac/sanger/artemis/components/EntryEdit.java b/uk/ac/sanger/artemis/components/EntryEdit.java
new file mode 100644
index 0000000..bb7388d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EntryEdit.java
@@ -0,0 +1,2615 @@
+/* EntryEdit.java
+ *
+ * created: Fri Oct 9 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryEdit.java,v 1.82 2009-09-24 12:42:16 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.chado.CommitFrame;
+import uk.ac.sanger.artemis.circular.DNADraw;
+import uk.ac.sanger.artemis.components.alignment.BamView;
+import uk.ac.sanger.artemis.components.alignment.FileSelectionDialog;
+import uk.ac.sanger.artemis.components.alignment.LookSeqPanel;
+import uk.ac.sanger.artemis.components.filetree.FileList;
+import uk.ac.sanger.artemis.components.filetree.FileManager;
+import uk.ac.sanger.artemis.components.variant.VCFview;
+import uk.ac.sanger.artemis.editor.BigPane;
+import uk.ac.sanger.artemis.editor.FastaTextPane;
+import uk.ac.sanger.artemis.editor.HitInfo;
+import uk.ac.sanger.artemis.sequence.SequenceChangeEvent;
+import uk.ac.sanger.artemis.sequence.SequenceChangeListener;
+
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.io.DatabaseInferredFeature;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.DocumentEntryFactory;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.GenbankTblOutputStream;
+import uk.ac.sanger.artemis.io.IndexFastaStream;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.MediaTracker;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.io.IOException;
+
+import javax.swing.*;
+
+import com.sshtools.j2ssh.sftp.FileAttributes;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ * Each object of this class is used to edit an EntryGroup object.
+ * @author Kim Rutherford
+ * @version $Id: EntryEdit.java,v 1.82 2009-09-24 12:42:16 tjc Exp $
+ */
+public class EntryEdit extends JFrame
+ implements EntryGroupChangeListener, EntryChangeListener
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /** The shortcut for Delete Selected Features. */
+ final static KeyStroke SAVE_DEFAULT_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_S,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); // InputEvent.CTRL_MASK);
+
+ /**
+ * A vector containing the Entry objects that this
+ * EntryEdit object knows about.
+ **/
+ private EntryGroup entry_group;
+
+ /**
+ * Created by the constructor to pass to those objects
+ * that are interested in GotoEvents.
+ **/
+ private GotoEventSource goto_event_source;
+
+ private final JMenuBar menu_bar = new JMenuBar();
+ private final JMenu file_menu = new JMenu("File");
+
+ private EntryGroupDisplay group_display;
+ private FeatureDisplay one_line_per_entry_display;
+ private FeatureDisplay feature_display;
+ private FeatureDisplay base_display;
+ private BasePlotGroup base_plot_group;
+ private FeatureList feature_list;
+
+ /** This Object contains the current selection. */
+ private Selection selection = null;
+
+ /** Alignment panel */
+ private BamView bamView;
+ private JPanel bamPanel;
+ private VCFview vcfView;
+ private JPanel vcfPanel;
+ private JSplitPane lowerSplitPane;
+ private JSplitPane ngSplitPane;
+
+ /**
+ * The EntrySourceVector reference that is created in the constructor.
+ **/
+ private EntrySourceVector entry_sources;
+
+ private SelectionInfoDisplay selection_info;
+
+ private CommitButton commitButton;
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(EntryEdit.class);
+
+ /**
+ * Create a new EntryEdit object and JFrame.
+ * @param entry_group The EntryGroup object that this component is editing.
+ */
+ public EntryEdit(final EntryGroup entry_group)
+ {
+ super("Artemis Entry Edit");
+
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ entry_group.ref();
+ this.entry_group = entry_group;
+
+ // XXX add a InputStreamProgressListener
+ this.entry_sources = Utilities.getEntrySources(this, null);
+ this.goto_event_source = new SimpleGotoEventSource(getEntryGroup());
+
+ selection = new Selection(null);
+
+ getEntryGroup().addFeatureChangeListener(selection);
+ getEntryGroup().addEntryChangeListener(selection);
+ getEntryGroup().addEntryGroupChangeListener(this);
+ getEntryGroup().addEntryChangeListener(this);
+
+ final Box box_panel = Box.createVerticalBox();
+ final Box xBox = Box.createHorizontalBox();
+ group_display = new EntryGroupDisplay(this);
+ xBox.add(group_display);
+ box_panel.add(xBox);
+
+ if(getEntryGroup().getDefaultEntry() != null)
+ {
+ final String name = getEntryGroup().getDefaultEntry().getName();
+ if(name != null)
+ setTitle("Artemis Entry Edit: " + name);
+
+ final JButton validate = new JButton("\u2713");
+ validate.setToolTipText("Validate selected entries");
+ validate.setFont(validate.getFont().deriveFont(18f));
+ validate.setPreferredSize(new Dimension(25,25));
+ validate.setMargin(new Insets(0, 0, 0, 0));
+ validate.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ new ValidateViewer(getEntryGroup(),
+ getEntryGroup().getAllFeatures());
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ });
+ }
+ });
+ xBox.add(validate);
+
+ if(getEntryGroup().getDefaultEntry().getEMBLEntry() instanceof DatabaseDocumentEntry)
+ {
+ ChadoTransactionManager ctm = getDatabaseDocumentEntry().getChadoTransactionManager();
+
+ getEntryGroup().addFeatureChangeListener(ctm);
+ getEntryGroup().addEntryChangeListener(ctm);
+ getEntryGroup().getBases().addSequenceChangeListener(ctm, 0);
+ ctm.setEntryGroup(getEntryGroup());
+
+ if(!getEntryGroup().getDefaultEntry().getEMBLEntry().isReadOnly())
+ {
+ commitButton = new CommitButton();
+ getEntryGroup().addFeatureChangeListener(commitButton);
+ getEntryGroup().addEntryChangeListener(commitButton);
+ getEntryGroup().getBases().addSequenceChangeListener(commitButton, 0);
+ xBox.add(commitButton);
+ }
+
+ if(DatabaseDocument.CHADO_INFER_CDS)
+ DatabaseInferredFeature.addListenersToEntryGroup(getEntryGroup());
+ }
+ }
+
+ final Font default_font = getDefaultFont();
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ closeEntryEdit();
+ }
+ });
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(box_panel, "North");
+
+ menu_bar.setFont(default_font);
+
+ selection_info =
+ new SelectionInfoDisplay(getEntryGroup(), getSelection());
+ box_panel.add(selection_info);
+
+
+ final boolean entry_buttons_option =
+ Options.getOptions().getPropertyTruthValue("show_entry_buttons");
+
+ group_display.setVisible(entry_buttons_option);
+
+
+ final JPanel mainPanel = new JPanel(new BorderLayout());
+ // set minimum size - this is then the smallest the split
+ // pane divider can make this panel
+ mainPanel.setMinimumSize(new Dimension(100,100));
+ final Box main_box_panel = Box.createVerticalBox();
+ mainPanel.add(main_box_panel, BorderLayout.NORTH);
+
+ base_plot_group =
+ new BasePlotGroup(getEntryGroup(), this, getSelection(),
+ getGotoEventSource());
+
+ bamPanel = new JPanel();
+ vcfPanel = new JPanel();
+ ngSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
+ bamPanel, vcfPanel);
+ ngSplitPane.setBorder(null);
+
+ Dimension minimumSize = new Dimension(0, 0);
+ bamPanel.setMinimumSize(minimumSize);
+ vcfPanel.setMinimumSize(minimumSize);
+
+
+ lowerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
+ ngSplitPane, mainPanel);
+ lowerSplitPane.setResizeWeight(0.);
+
+ setNGDivider();
+
+ final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
+ base_plot_group, lowerSplitPane);
+ splitPane.setDividerSize(0);
+ splitPane.setResizeWeight(0.);
+ splitPane.setBorder(null);
+ base_plot_group.setVisible(true);
+
+ // lookseq read alignment
+ LookSeqPanel lookseqPanel = null;
+ JScrollPane jspLookSeq = null;
+ if(Options.getOptions().getProperty("lookseq") != null)
+ {
+ lookseqPanel = new LookSeqPanel();
+ jspLookSeq = new JScrollPane(lookseqPanel,
+ JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+ JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ jspLookSeq.setPreferredSize(new Dimension(jspLookSeq.getPreferredSize().width, 200));
+ jspLookSeq.setVisible(false);
+ main_box_panel.add(jspLookSeq);
+ }
+
+
+ // one line per entry display
+ one_line_per_entry_display =
+ new FeatureDisplay(getEntryGroup(), getSelection(),
+ getGotoEventSource(), base_plot_group);
+
+ // read alignment panel
+ //main_box_panel.add(bamPanel);
+
+ one_line_per_entry_display.setShowLabels(false);
+ one_line_per_entry_display.setOneLinePerEntry(true);
+ one_line_per_entry_display.setVisible(false);
+
+ // one line per entry expander button
+ final JButton one_line_display_button = new JButton(">>");
+ final Box one_line_button_box_across = setExpanderButton(one_line_display_button);
+ one_line_display_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(one_line_per_entry_display.isVisible())
+ one_line_display_button.setText(">>");
+ else
+ one_line_display_button.setText("<<");
+ one_line_per_entry_display.setVisible(!one_line_per_entry_display.isVisible());
+ validate();
+ }
+ });
+ main_box_panel.add(one_line_button_box_across);
+ main_box_panel.add(one_line_per_entry_display);
+
+ // feature display
+ feature_display =
+ new FeatureDisplay(getEntryGroup(), getSelection(),
+ getGotoEventSource(), base_plot_group);
+
+ final Options options = Options.getOptions();
+
+ if(options.getProperty("overview_feature_labels") != null)
+ {
+ final boolean option_value =
+ options.getPropertyTruthValue("overview_feature_labels");
+ feature_display.setShowLabels(option_value);
+ }
+
+ if(options.getProperty("overview_one_line_per_entry") != null)
+ {
+ final boolean option_value =
+ options.getPropertyTruthValue("overview_one_line_per_entry");
+ feature_display.setOneLinePerEntry(option_value);
+ }
+
+ if(options.getProperty("overview_feature_stack_view") != null)
+ {
+ final boolean option_value =
+ options.getPropertyTruthValue("overview_feature_stack_view");
+ feature_display.setFeatureStackViewFlag(option_value);
+ }
+
+ feature_display.addDisplayAdjustmentListener(base_plot_group);
+ feature_display.addDisplayAdjustmentListener(one_line_per_entry_display);
+
+ one_line_per_entry_display.addDisplayAdjustmentListener(feature_display);
+
+ // feature display expander button
+ final JButton feature_display_button = new JButton("<<");
+ final Box feature_display_button_box_across = setExpanderButton(feature_display_button);
+ feature_display_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(feature_display.isVisible())
+ feature_display_button.setText(">>");
+ else
+ feature_display_button.setText("<<");
+ feature_display.setVisible(!feature_display.isVisible());
+ }
+ });
+ main_box_panel.add(feature_display_button_box_across);
+ main_box_panel.add(feature_display);
+ feature_display.setVisible(true);
+
+ // base display
+ base_display =
+ new FeatureDisplay(getEntryGroup(), getSelection(),
+ getGotoEventSource(), base_plot_group);
+ base_display.setShowLabels(false);
+ base_display.setScaleFactor(0);
+
+ // base display expander button
+ final JButton base_display_button = new JButton("<<");
+ final Box base_display_button_box_across = setExpanderButton(base_display_button);
+ base_display_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(base_display.isVisible())
+ base_display_button.setText(">>");
+ else
+ base_display_button.setText("<<");
+ base_display.setVisible(!base_display.isVisible());
+ }
+ });
+ main_box_panel.add(base_display_button_box_across);
+ main_box_panel.add(base_display);
+
+ final boolean show_base_view;
+
+ if(Options.getOptions().getProperty("show_base_view") != null)
+ show_base_view =
+ Options.getOptions().getPropertyTruthValue("show_base_view");
+ else
+ show_base_view = true;
+
+ if(!show_base_view)
+ base_display_button.setText(">>");
+ base_display.setVisible(show_base_view);
+
+ final JScrollPane jsp_feature_list;
+ feature_list =
+ new FeatureList(getEntryGroup(), getSelection(),
+ getGotoEventSource(), base_plot_group);
+ jsp_feature_list = new JScrollPane(feature_list);
+ feature_list.setFont(default_font);
+
+ // feature list expander button
+ final JButton scroll_button = new JButton(">>");
+ final Box box_across = setExpanderButton(scroll_button);
+ scroll_button.addActionListener(new ActionListener()
+ {
+ private int hgt = 0;
+ public void actionPerformed(ActionEvent event)
+ {
+ Dimension dim = getSize();
+ if(jsp_feature_list.isVisible())
+ {
+ Dimension dim_box = box_across.getPreferredSize();
+ jsp_feature_list.setVisible(false);
+ box_across.setPreferredSize(new Dimension(dim.width, dim_box.height));
+ scroll_button.setText(">>");
+ hgt = jsp_feature_list.getSize().height;
+ }
+ else
+ {
+ if(hgt == 0)
+ hgt = getEntryGroup().getAllFeaturesCount() *
+ feature_list.getLineHeight();
+
+ jsp_feature_list.setPreferredSize(new Dimension(dim.width,hgt));
+ jsp_feature_list.setVisible(true);
+ scroll_button.setText("<<");
+ }
+
+ pack();
+ }
+ });
+ main_box_panel.add(box_across);
+
+ if(Options.getOptions().getPropertyTruthValue("show_list"))
+ {
+ scroll_button.setText("<<");
+ feature_list.setVisible(true);
+ }
+ else
+ {
+ scroll_button.setText(">>");
+ feature_list.setVisible(false);
+ }
+
+ mainPanel.add(jsp_feature_list, "Center");
+ getContentPane().add(splitPane, BorderLayout.CENTER);
+
+ makeMenus(splitPane, jspLookSeq, lookseqPanel);
+ pack();
+
+ ClassLoader cl = this.getClass().getClassLoader();
+ ImageIcon icon = new ImageIcon(cl.getResource("images/icon.gif"));
+
+ if(icon != null)
+ {
+ final Image icon_image = icon.getImage();
+ MediaTracker tracker = new MediaTracker(this);
+ tracker.addImage(icon_image, 0);
+
+ try
+ {
+ tracker.waitForAll();
+ setIconImage(icon_image);
+ }
+ catch(InterruptedException e)
+ {
+ // ignore and continue
+ }
+ }
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ int screen_height = screen.height;
+ int screen_width = screen.width;
+
+ if(screen_width <= 900 || screen_height <= 800)
+ setSize(screen_width * 9 / 10, screen_height * 9 / 10);
+ else
+ setSize(900, 800);
+
+ final int hgt = getEntryGroup().getAllFeaturesCount() *
+ feature_list.getLineHeight();
+ feature_list.setPreferredSize(new Dimension(getSize().width*4,hgt));
+ jsp_feature_list.getVerticalScrollBar().setUnitIncrement(feature_list.getLineHeight());
+
+ Utilities.centreFrame(this);
+
+ if(System.getProperty("bam") != null || System.getProperty("bam1") != null)
+ {
+ SwingWorker worker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ EntryEdit.this.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ final String ngs[];
+ final int idx;
+
+ if(System.getProperty("bam") != null)
+ {
+ idx = 1;
+ ngs = System.getProperty("bam").split("[\\s,]");
+ }
+ else
+ {
+ idx = 2;
+ ngs = System.getProperty("bam1").split("[\\s,]");
+ System.setProperty("bam1", "");
+ }
+ FileSelectionDialog fileChooser = new FileSelectionDialog(ngs);
+ List<String> listBams = fileChooser.getFiles(".*\\.(bam|cram)$");
+ final List<String> vcfFiles = fileChooser.getFiles(VCFview.VCFFILE_SUFFIX);
+ loadBamAndVcf(listBams, vcfFiles);
+
+ for(int i=idx; i<20; i++)
+ {
+ if(System.getProperty("bam"+i) != null)
+ {
+ fileChooser = new FileSelectionDialog(
+ System.getProperty("bam"+i).split("[\\s,]"));
+
+ List<String> lBams = fileChooser.getFiles(".*\\.(bam|cram)$");
+ if(lBams.size() > 0)
+ bamView.openBamView(fileChooser.getFiles(".*\\.(bam|cram)$"));
+ System.setProperty("bam"+i, "");
+ }
+ }
+
+ if(System.getProperty("bamClone") != null)
+ {
+ int nclone = 2;
+ try
+ {
+ nclone = Integer.parseInt(System.getProperty("bamClone"));
+ }
+ catch(NumberFormatException ne){}
+ if(nclone > 10)
+ nclone = 10;
+ logger4j.debug("No. BamView clones = "+nclone+" bamClone = "+
+ System.getProperty("bamClone"));
+
+ for(int i=1;i<nclone;i++)
+ bamView.openBamView(listBams);
+ }
+ System.setProperty("bam", "");
+
+ EntryEdit.this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ return null;
+ }
+ };
+ worker.start();
+ }
+ }
+
+ private Box setExpanderButton(final JButton butt)
+ {
+ butt.setMargin(new Insets(0,1,0,1));
+ butt.setHorizontalAlignment(SwingConstants.LEFT);
+ butt.setPreferredSize(new Dimension( butt.getPreferredSize().width,9));
+ butt.setBorderPainted(false);
+ butt.setFont(new Font("SansSerif", Font.BOLD, 9));
+
+ final Box box_across = Box.createHorizontalBox();
+ box_across.add(butt);
+ box_across.add(Box.createHorizontalGlue());
+ return box_across;
+ }
+
+ protected void resetScrolls()
+ {
+ base_display.setFirstBase(1);
+ base_display.fixScrollbar();
+ feature_display.setFirstBase(1);
+ feature_display.fixScrollbar();
+ one_line_per_entry_display.setFirstBase(1);
+ one_line_per_entry_display.fixScrollbar();
+ }
+
+ /**
+ * Retrieve the feature list object.
+ */
+ protected FeatureList getFeatureList()
+ {
+ return feature_list;
+ }
+
+
+ /**
+ * Retrieve the base display object.
+ */
+ protected FeatureDisplay getBaseDisplay()
+ {
+ return base_display;
+ }
+
+ /**
+ * Retrieve the one line per entry object.
+ */
+ public FeatureDisplay getOneLinePerEntryDisplay()
+ {
+ return one_line_per_entry_display;
+ }
+
+
+ /**
+ * Retrieve the base plot display object.
+ */
+ protected BasePlotGroup getBasePlotGroup()
+ {
+ return base_plot_group;
+ }
+
+ protected JPanel getBamPanel()
+ {
+ return bamPanel;
+ }
+
+ protected BamView getJamView()
+ {
+ return bamView;
+ }
+
+ protected JPanel getVcfPanel()
+ {
+ return vcfPanel;
+ }
+
+ protected JPanel getVcfView()
+ {
+ return vcfView;
+ }
+
+
+ /**
+ * Retrieve the entry group display object.
+ */
+ public EntryGroupDisplay getEntryGroupDisplay()
+ {
+ return group_display;
+ }
+
+
+ /**
+ * Retrieve the selection info display object.
+ */
+ protected SelectionInfoDisplay getSelectionInfoDisplay()
+ {
+ return selection_info;
+ }
+
+
+ /**
+ * Retrieve the feature display object.
+ */
+ protected FeatureDisplay getFeatureDisplay()
+ {
+ return feature_display;
+ }
+
+ /**
+ * If there are no unsaved changes, close this EntryEdit. Otherwise ask
+ * the user first.
+ **/
+ private void closeEntryEdit()
+ {
+ if(getEntryGroup().hasUnsavedChanges() &&
+ getEntryGroup().refCount() == 1)
+ {
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog(EntryEdit.this,
+ "There are unsaved changes - really close?");
+
+ if(!yes_no_dialog.getResult())
+ return;
+ }
+
+ entryEditFinished();
+ }
+
+ /**
+ * Return an object that implements the GotoEventSource interface, and is
+ * the controlling object for Goto events associated with this object.
+ **/
+ public GotoEventSource getGotoEventSource()
+ {
+ return goto_event_source;
+ }
+
+ /**
+ * Return the EntryGroup object that was passed to the constructor.
+ **/
+ public EntryGroup getEntryGroup()
+ {
+ return entry_group;
+ }
+
+ /**
+ * Return the database document entry if present or null.
+ * @return
+ */
+ private DatabaseDocumentEntry getDatabaseDocumentEntry()
+ {
+ for(int i=0; i<entry_group.size(); i++)
+ if(entry_group.elementAt(i).getEMBLEntry() instanceof DatabaseDocumentEntry)
+ return (DatabaseDocumentEntry) entry_group.elementAt(i).getEMBLEntry();
+ return null;
+ }
+
+ /**
+ * Returns a Selection object containing the selected features/exons.
+ **/
+ public Selection getSelection()
+ {
+ return selection;
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can update the File menu when the
+ * EntryGroup changes.
+ **/
+ public void entryGroupChanged(final EntryGroupChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryGroupChangeEvent.ENTRY_ADDED:
+ case EntryGroupChangeEvent.ENTRY_DELETED:
+ makeFileMenu();
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so that we can update the File menu when an Entry
+ * changes.
+ **/
+ public void entryChanged(final EntryChangeEvent event)
+ {
+ if(event.getType() == EntryChangeEvent.NAME_CHANGED)
+ makeFileMenu();
+ }
+
+ /* private members: */
+
+ /**
+ * This method arranges for the EntryEdit JFrame to go away. This EntryEdit
+ * object was created by the main program, so the main program must be the
+ * one to delete us.
+ **/
+ private void entryEditFinished()
+ {
+ setVisible(false);
+
+ // chado transaction manager
+ if(getDatabaseDocumentEntry() != null)
+ {
+ ChadoTransactionManager ctm = getDatabaseDocumentEntry().getChadoTransactionManager();
+ getEntryGroup().removeFeatureChangeListener(ctm);
+ getEntryGroup().removeEntryChangeListener(ctm);
+ }
+
+ if(commitButton != null)
+ {
+ getEntryGroup().removeFeatureChangeListener(commitButton);
+ getEntryGroup().removeEntryChangeListener(commitButton);
+ commitButton.close();
+ }
+
+ getEntryGroup().removeFeatureChangeListener(selection);
+ getEntryGroup().removeEntryChangeListener(selection);
+
+ getEntryGroup().removeEntryGroupChangeListener(this);
+ getEntryGroup().removeEntryChangeListener(this);
+
+ getEntryGroup().unref();
+
+ dispose();
+ getEntryGroup().getBases().clearCodonCache();
+ //getEntryGroup().getBases().getSequence().clear();
+ }
+
+ /**
+ * Write the default Entry in the EntryGroup to the file it came from.
+ **/
+ private void saveDefaultEntry()
+ {
+ if(getEntryGroup().getDefaultEntry() == null)
+ new MessageDialog(EntryEdit.this, "There is no default entry");
+ else
+ saveEntry(entry_group.getDefaultEntry(), true, false, true,
+ DocumentEntryFactory.ANY_FORMAT);
+ }
+
+ /**
+ * Save the given entry, prompting for a file name if necessary.
+ * @param include_diana_extensions If true then any diana additions to
+ * the embl file format will be included in the output, otherwise they
+ * will be removed. Also possible problems that would cause an entry to
+ * bounce from the EMBL submission process will be flagged if this is
+ * true.
+ * @param ask_for_name If true then always prompt for a new filename,
+ * otherwise prompt only when the entry name is not set.
+ * @param keep_new_name If ask_for_name is true a file will be written with
+ * the new name the user selects - if keep_new_name is true as well, then
+ * the entry will have it's name set to the new name, otherwise it will
+ * be used for this save and then discarded.
+ * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
+ * ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the
+ * same format it was created, otherwise it will be saved in the given
+ * format.
+ **/
+ void saveEntry(final Entry entry,
+ final boolean include_diana_extensions,
+ final boolean ask_for_name, final boolean keep_new_name,
+ final int destination_type)
+ {
+ saveEntry(entry, include_diana_extensions,
+ ask_for_name, keep_new_name,
+ destination_type, true);
+ }
+
+ private void saveEntry(final Entry entry,
+ final boolean include_diana_extensions,
+ final boolean ask_for_name, final boolean keep_new_name,
+ final int destination_type, final boolean useSwingWorker)
+ {
+ if(!include_diana_extensions)
+ {
+ if(displaySaveWarnings(entry))
+ return;
+ }
+
+ if(destination_type != DocumentEntryFactory.ANY_FORMAT &&
+ entry.getHeaderText() != null)
+ {
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog(this, "header section will be lost. continue?");
+
+ if(!yes_no_dialog.getResult())
+ return;
+ }
+
+// if(!System.getProperty("os.arch").equals("alpha"))
+// {
+ if(entry.getEMBLEntry().getSequence() instanceof IndexFastaStream)
+ {
+ JOptionPane.showMessageDialog(null,
+ entry.getName()+" is an indexed sequence.\n"+
+ "This cannot be written out.",
+ "Write Option Not Available",
+ JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+/* if(useSwingWorker)
+ {
+ SwingWorker worker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ final EntryFileDialog file_dialog = new EntryFileDialog(
+ EntryEdit.this, false);
+ file_dialog.saveEntry(entry, include_diana_extensions, ask_for_name,
+ keep_new_name, destination_type);
+
+ return null;
+ }
+ };
+ worker.start();
+ }
+ else*/
+ {
+ final EntryFileDialog file_dialog = new EntryFileDialog(this,
+ false);
+
+ file_dialog.saveEntry(entry, include_diana_extensions, ask_for_name,
+ keep_new_name, destination_type);
+ }
+ }
+
+
+ /**
+ * Save the changes to all the Entry objects in the entry_group back to
+ * where the they came from.
+ **/
+ private void saveAllEntries()
+ {
+ SwingWorker worker = new SwingWorker()
+ {
+
+ public Object construct()
+ {
+ final int entry_group_size = entry_group.size();
+ for(int entry_index = 0; entry_index < entry_group_size;
+ ++entry_index)
+ saveEntry(entry_group.elementAt(entry_index), true, false, true,
+ DocumentEntryFactory.ANY_FORMAT, false);
+ return null;
+ }
+ };
+ worker.start();
+ }
+
+ /**
+ * Check the given Entry for invalid EMBL features(such as CDS features
+ * without a stop codon) then display a FeatureListFrame list the problem
+ * features.
+ * @return true if and only if the save should be aborted.
+ **/
+ private boolean displaySaveWarnings(final Entry entry)
+ {
+ final FeatureVector invalid_starts = entry.checkFeatureStartCodons();
+ final FeatureVector invalid_stops = entry.checkFeatureStopCodons();
+ final FeatureVector invalid_keys = entry.checkForNonEMBLKeys();
+ final FeatureVector duplicate_features = entry.checkForEMBLDuplicates();
+ final FeatureVector overlapping_cds_features =
+ entry.checkForOverlappingCDSs();
+ final FeatureVector missing_qualifier_features =
+ entry.checkForMissingQualifiers();
+
+ // this predicate will filter out those features that aren't in the
+ // entry we are trying to save
+ final FeaturePredicate predicate = new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ if(feature.getEntry() == entry)
+ return true;
+ else
+ return false;
+ }
+ };
+
+ String entry_name = entry.getName();
+
+ if(entry_name == null)
+ entry_name = "no name";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(getEntryGroup(), predicate,
+ "features from " + entry_name);
+
+ if(invalid_starts.size() + invalid_stops.size() +
+ invalid_keys.size() + duplicate_features.size() +
+ overlapping_cds_features.size() > 0)
+ {
+
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog(this,
+ "warning: some features may have problems. " +
+ "continue with save?");
+
+ if(!yes_no_dialog.getResult())
+ {
+ getSelection().clear();
+
+ if(invalid_starts.size() > 0)
+ {
+ getSelection().add(invalid_starts);
+
+ ViewMenu.showBadStartCodons(this,
+ getSelection(),
+ filtered_entry_group,
+ getGotoEventSource(),
+ base_plot_group);
+ }
+
+ if(invalid_stops.size() > 0)
+ {
+ getSelection().add(invalid_stops);
+
+ ViewMenu.showBadStopCodons(this, getSelection(),
+ filtered_entry_group,
+ getGotoEventSource(),
+ base_plot_group);
+ }
+
+ if(invalid_keys.size() > 0)
+ {
+ getSelection().add(invalid_keys);
+
+ ViewMenu.showNonEMBLKeys(this, getSelection(),
+ filtered_entry_group,
+ getGotoEventSource(),
+ base_plot_group);
+ }
+
+ if(duplicate_features.size() > 0)
+ {
+ getSelection().add(duplicate_features);
+
+ ViewMenu.showDuplicatedFeatures(this, getSelection(),
+ filtered_entry_group,
+ getGotoEventSource(),
+ base_plot_group);
+ }
+
+ if(overlapping_cds_features.size() > 0)
+ {
+ getSelection().add(overlapping_cds_features);
+
+ ViewMenu.showOverlappingCDSs(this, getSelection(),
+ filtered_entry_group,
+ getGotoEventSource(),
+ base_plot_group);
+ }
+
+ if(missing_qualifier_features.size() > 0)
+ {
+ getSelection().add(missing_qualifier_features);
+
+ ViewMenu.showMissingQualifierFeatures(this, getSelection(),
+ filtered_entry_group,
+ getGotoEventSource(),
+ base_plot_group);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Make and add the menus for this component.
+ * @param lookseqPanel
+ **/
+ private void makeMenus(final JSplitPane splitPane,
+ final JScrollPane jspLookSeq,
+ final LookSeqPanel lookseqPanel)
+ {
+ setJMenuBar(menu_bar);
+ makeFileMenu();
+ menu_bar.add(file_menu);
+
+ // don't add the menu if this is an applet and we have just one entry
+ if(Options.readWritePossible() || getEntryGroup().size() > 1)
+ {
+ JMenu entry_group_menu = new EntryGroupMenu(this, getEntryGroup());
+ entry_group_menu.setMnemonic(KeyEvent.VK_N);
+ menu_bar.add(entry_group_menu);
+ }
+
+ SelectMenu select_menu = new SelectMenu(this, getSelection(),
+ getGotoEventSource(), getEntryGroup(),
+ base_plot_group);
+ select_menu.setMnemonic(KeyEvent.VK_S);
+ menu_bar.add(select_menu);
+
+ ViewMenu view_menu = new ViewMenu(this, getSelection(),
+ getGotoEventSource(), getEntryGroup(),
+ base_plot_group);
+ view_menu.setMnemonic(KeyEvent.VK_V);
+ menu_bar.add(view_menu);
+
+ JMenu goto_menu = new GotoMenu(this, getSelection(),
+ getGotoEventSource(), getEntryGroup());
+ goto_menu.setMnemonic(KeyEvent.VK_O);
+ menu_bar.add(goto_menu);
+
+ if(Options.readWritePossible())
+ {
+ EditMenu edit_menu = new EditMenu(this, getSelection(),
+ getGotoEventSource(), getEntryGroup(),
+ base_plot_group, feature_display);
+ edit_menu.setMnemonic(KeyEvent.VK_E);
+ menu_bar.add(edit_menu);
+
+ AddMenu add_menu = new AddMenu(this, getSelection(), getEntryGroup(),
+ getGotoEventSource(), base_plot_group);
+ add_menu.setMnemonic(KeyEvent.VK_C);
+ menu_bar.add(add_menu);
+
+ /*JMenu write_menu = new WriteMenu(this, getSelection(), getEntryGroup());
+ write_menu.setMnemonic(KeyEvent.VK_W);
+ menu_bar.add(write_menu);*/
+
+ JMenu run_menu = new RunMenu(this, getSelection());
+ run_menu.setMnemonic(KeyEvent.VK_R);
+ menu_bar.add(run_menu);
+ }
+
+ JMenu graph_menu = new GraphMenu(this, getEntryGroup(),
+ base_plot_group,
+ feature_display, splitPane);
+ graph_menu.setMnemonic(KeyEvent.VK_G);
+ menu_bar.add(graph_menu);
+
+ final JMenu display_menu = new JMenu("Display");
+ display_menu.setMnemonic(KeyEvent.VK_D);
+ final JCheckBoxMenuItem show_entry_buttons_item =
+ new JCheckBoxMenuItem("Show Entry Buttons");
+ show_entry_buttons_item.setState(true);
+ show_entry_buttons_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ group_display.setVisible(show_entry_buttons_item.getState());
+ // XXX change to revalidate().
+ validate();
+ }
+ });
+ display_menu.add(show_entry_buttons_item);
+
+ final JCheckBoxMenuItem show_one_line =
+ new JCheckBoxMenuItem("Show One Line Per Entry View", false);
+ final JCheckBoxMenuItem show_feature_stack =
+ new JCheckBoxMenuItem("Show Feature Stack View", false);
+
+ show_one_line.setState(one_line_per_entry_display.isVisible());
+ show_one_line.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ if(show_one_line.getState())
+ show_feature_stack.setState(false);
+ one_line_per_entry_display.setFeatureStackViewFlag(false);
+ one_line_per_entry_display.setVisible(show_one_line.getState());
+ validate();
+ }
+ });
+ display_menu.add(show_one_line);
+
+ show_feature_stack.setState(one_line_per_entry_display.isVisible());
+ show_feature_stack.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ if(show_feature_stack.getState())
+ show_one_line.setState(false);
+ one_line_per_entry_display.setFeatureStackViewFlag(true);
+ one_line_per_entry_display.setVisible(show_feature_stack.getState());
+ validate();
+ one_line_per_entry_display.updateOneLinePerFeatureFlag();
+ }
+ });
+ display_menu.add(show_feature_stack);
+
+ final JCheckBoxMenuItem show_base_display_item =
+ new JCheckBoxMenuItem("Show Base View");
+
+ show_base_display_item.setState(base_display.isVisible());
+ show_base_display_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ base_display.setVisible(show_base_display_item.getState());
+ // XXX change to revalidate().
+ validate();
+ }
+ });
+ display_menu.add(show_base_display_item);
+
+ final JCheckBoxMenuItem show_feature_list_item =
+ new JCheckBoxMenuItem("Show Feature List");
+ show_feature_list_item.setState(feature_list.isVisible());
+ show_feature_list_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ feature_list.setVisible(show_feature_list_item.getState());
+ // XXX change to revalidate().
+ validate();
+ }
+ });
+ display_menu.add(show_feature_list_item);
+
+ if (lookseqPanel != null)
+ {
+ final JCheckBoxMenuItem show_lookseq_item = new JCheckBoxMenuItem(
+ "Show lookseq");
+ show_lookseq_item.setState(jspLookSeq.isVisible());
+ show_lookseq_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ if (show_lookseq_item.getState())
+ {
+ if (lookseqPanel.getQueryStr() == null)
+ {
+ String urlStr = Options.getOptions().getProperty("lookseq");
+ // String urlStr =
+ // "http://www.sanger.ac.uk/cgi-bin/teams/team112/lookseq/get_data.pl?";
+ String queryStr = "from="
+ + feature_display.getForwardBaseAtLeftEdge()
+ + "&to=8000"
+ + "&chr=MAL1&output=image&width=1024&lane=sample_2a&view=indel&display=|perfect|snps|inversions|pairlinks|potsnps|uniqueness|&debug=0";
+
+ if(Options.getOptions().getProperty("lookseq_chr") != null)
+ queryStr = queryStr.replaceFirst(
+ "chr=[^&]+", "chr="+Options.getOptions().getProperty("lookseq_chr").trim());
+
+ if(Options.getOptions().getProperty("lookseq_lane") != null)
+ queryStr = queryStr.replaceFirst(
+ "lane=[^&]+", "lane="+Options.getOptions().getProperty("lookseq_lane").trim());
+
+ lookseqPanel.setUrl(urlStr, queryStr);
+ }
+
+ lookseqPanel.setFeatureDisplay(feature_display);
+ lookseqPanel.showOptions();
+ feature_display.addDisplayAdjustmentListener(lookseqPanel);
+ }
+ else
+ feature_display.removeDisplayAdjustmentListener(lookseqPanel);
+
+ jspLookSeq.setVisible(show_lookseq_item.getState());
+
+ if (show_lookseq_item.getState())
+ lookseqPanel.setDisplay(feature_display.getForwardBaseAtLeftEdge(),
+ feature_display.getLastVisibleForwardBase(), null);
+
+ validate();
+ }
+ });
+ display_menu.add(show_lookseq_item);
+ }
+
+ display_menu.addSeparator();
+ final JMenuItem show_Jam_item = new JMenuItem("BAM Alignment");
+ show_Jam_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(bamView == null)
+ return;
+
+ if (!bamPanel.isVisible())
+ {
+ bamPanel.setVisible(true);
+ bamView.setDisplay(feature_display.getFirstVisibleForwardBase(),
+ feature_display.getLastVisibleForwardBase(), null);
+ bamView.revalidate();
+ one_line_per_entry_display.addDisplayAdjustmentListener(bamView);
+ feature_display.addDisplayAdjustmentListener(bamView);
+ feature_display.getSelection().addSelectionChangeListener(bamView);
+ }
+ else
+ {
+ one_line_per_entry_display.removeDisplayAdjustmentListener(bamView);
+ feature_display.removeDisplayAdjustmentListener(bamView);
+ feature_display.getSelection().removeSelectionChangeListener(bamView);
+ bamPanel.setVisible(false);
+ }
+ setNGDivider();
+ }
+ });
+ display_menu.add(show_Jam_item);
+
+
+ final JMenuItem show_Vcf_item = new JMenuItem("VCF");
+ show_Vcf_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(vcfView == null)
+ return;
+
+ if (!vcfView.isVisible())
+ {
+ vcfView.setVisible(true);
+ vcfView.revalidate();
+ feature_display.addDisplayAdjustmentListener(vcfView);
+ feature_display.getSelection().addSelectionChangeListener(vcfView);
+ }
+ else
+ {
+ feature_display.removeDisplayAdjustmentListener(vcfView);
+ feature_display.getSelection().removeSelectionChangeListener(vcfView);
+ vcfView.setVisible(false);
+ }
+ setNGDivider();
+ }
+ });
+ display_menu.add(show_Vcf_item);
+
+ menu_bar.add(display_menu);
+ }
+
+
+ /**
+ * Make a new File menureplacing the current one (if any).
+ **/
+ private void makeFileMenu()
+ {
+ file_menu.removeAll();
+ file_menu.setMnemonic(KeyEvent.VK_F);
+
+ if(Options.readWritePossible())
+ {
+ boolean db = false;
+ final EntryVector entries = getEntryGroup().getActiveEntries();
+ for(int i=0; i<entries.size(); i++)
+ {
+ Entry entry = entries.elementAt(i);
+ if(entry.getEMBLEntry() instanceof DatabaseDocumentEntry)
+ db = true;
+ }
+
+ if(db && !getEntryGroup().getDefaultEntry().getEMBLEntry().isReadOnly())
+ {
+ final JMenuItem commit = new JMenuItem("Commit to Database");
+ commit.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ commitToDatabase(entry_group,
+ getDatabaseDocumentEntry().getChadoTransactionManager(), EntryEdit.this,
+ selection, goto_event_source, base_plot_group);
+ }
+ });
+ file_menu.add(commit);
+ file_menu.addSeparator();
+ }
+
+ JMenuItem popFileManager = new JMenuItem("Show File Manager ...");
+ popFileManager.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(ArtemisMain.filemanager == null)
+ ArtemisMain.filemanager = new FileManager(EntryEdit.this);
+ else
+ ArtemisMain.filemanager.setVisible(true);
+ }
+ });
+ file_menu.add(popFileManager);
+
+ // only the standalone version can save or read
+ EntrySource filesystem_entry_source = null;
+ final int entry_sources_size = entry_sources.size();
+
+ for(int source_index = 0; source_index < entry_sources_size;
+ ++source_index)
+ {
+ final EntrySource this_source =
+ entry_sources.elementAt(source_index);
+
+ if(this_source.isFullEntrySource())
+ continue;
+
+ if(this_source.getSourceName().equals("Filesystem"))
+ filesystem_entry_source = this_source;
+
+ String entry_source_name = this_source.getSourceName();
+ String menu_item_name = null;
+
+ if(entry_source_name.equals("Filesystem"))
+ menu_item_name = "Read An Entry ...";
+ else
+ menu_item_name = "Read An Entry From " + entry_source_name + " ...";
+
+ final JMenuItem read_entry = new JMenuItem(menu_item_name);
+
+ read_entry.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ readAnEntry(this_source);
+ }
+ });
+
+ file_menu.add(read_entry);
+ }
+
+ JMenu read_features_menu = null;
+
+ if(filesystem_entry_source != null &&
+ entry_group != null && entry_group.size() > 0)
+ {
+ read_features_menu = new JMenu("Read Entry Into");
+ file_menu.add(read_features_menu);
+ }
+
+ file_menu.addSeparator();
+
+ JMenuItem read_bam_file = new JMenuItem("Read BAM / VCF ...");
+ read_bam_file.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ FileSelectionDialog fileChooser = new FileSelectionDialog(
+ null, false, "BAM / VCF View", "BAM / VCF");
+ List<String> listBams = fileChooser.getFiles(".*\\.(bam|cram)$");
+ List<String> vcfFiles = fileChooser.getFiles(VCFview.VCFFILE_SUFFIX);
+ loadBamAndVcf(listBams, vcfFiles);
+ }
+ });
+ file_menu.add(read_bam_file);
+
+ file_menu.addSeparator();
+
+ final JMenuItem save_default =
+ new JMenuItem("Save Default Entry");
+ save_default.setAccelerator(SAVE_DEFAULT_KEY);
+ save_default.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ saveDefaultEntry();
+ }
+ });
+
+ file_menu.add(save_default);
+
+ if(entry_group == null || entry_group.size() == 0)
+ {
+ // do nothing
+ }
+ else
+ {
+ final JMenu save_entry_menu = new JMenu("Save An Entry");
+ final JMenu save_as_menu = new JMenu("Save An Entry As");
+ final JMenu save_as = new JMenu("New File");
+ final JMenu save_as_embl = new JMenu("EMBL Format");
+ final JMenu save_as_genbank = new JMenu("GENBANK Format");
+ final JMenu save_as_genbank_only = new JMenu("Sequin Table Format");
+ final JMenu save_as_gff = new JMenu("GFF Format");
+ final JMenu save_embl_only = new JMenu("EMBL Submission Format");
+ final int entry_group_size = getEntryGroup().size();
+
+ for(int i = 0; i < entry_group_size; ++i)
+ {
+ final Entry this_entry = getEntryGroup().elementAt(i);
+ String entry_name = this_entry.getName();
+
+ if(entry_name == null)
+ entry_name = "no name";
+
+ final ActionListener save_entry_listener =
+ new SaveEntryActionListener(this, this_entry);
+
+ final JMenuItem save_entry_item = new JMenuItem(entry_name);
+
+ save_entry_item.addActionListener(save_entry_listener);
+
+ final ActionListener save_as_listener =
+ new SaveEntryAsActionListener(this, this_entry);
+
+ final JMenuItem save_as_item = new JMenuItem(entry_name);
+
+ save_as_item.addActionListener(save_as_listener);
+
+ final ActionListener save_as_embl_listener =
+ new SaveEntryAsEMBLActionListener(this, this_entry);
+
+ final JMenuItem save_as_embl_item = new JMenuItem(entry_name);
+
+ save_as_embl_item.addActionListener(save_as_embl_listener);
+
+ final ActionListener save_as_genbank_listener =
+ new SaveEntryAsGenbankActionListener(this, this_entry);
+
+ final JMenuItem save_as_genbank_item = new JMenuItem(entry_name);
+ save_as_genbank_item.addActionListener(save_as_genbank_listener);
+
+ final JMenuItem save_as_genbank_only_item = new JMenuItem(entry_name);
+ save_as_genbank_only_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ GenbankTblOutputStream.writeEntryAsTbl(this_entry, EntryEdit.this);
+ }
+ });
+
+ final ActionListener save_as_gff_listener =
+ new SaveEntryAsGFFActionListener(this, this_entry);
+
+ final JMenuItem save_as_gff_item = new JMenuItem(entry_name);
+
+ save_as_gff_item.addActionListener(save_as_gff_listener);
+
+ final ActionListener save_embl_only_listener =
+ new SaveEntryAsSubmissionActionListener(this, this_entry);
+
+ final JMenuItem save_embl_only_item = new JMenuItem(entry_name);
+
+ save_embl_only_item.addActionListener(save_embl_only_listener);
+
+ if(read_features_menu != null)
+ {
+ final ActionListener read_into_listener =
+ new ReadFeaturesActionListener(this, filesystem_entry_source,
+ this_entry);
+
+ final JMenuItem read_into_item = new JMenuItem(entry_name);
+ read_into_item.addActionListener(read_into_listener);
+ read_features_menu.add(read_into_item);
+ }
+
+ save_entry_menu.add(save_entry_item);
+
+ save_as.add(save_as_item);
+ save_as_embl.add(save_as_embl_item);
+ save_as_genbank.add(save_as_genbank_item);
+ save_as_genbank_only.add(save_as_genbank_only_item);
+ save_as_gff.add(save_as_gff_item);
+ save_embl_only.add(save_embl_only_item);
+ }
+
+ save_as_menu.add(save_as);
+ save_as_menu.add(save_as_embl);
+ save_as_menu.add(save_as_genbank);
+ save_as_menu.add(save_as_genbank_only);
+ save_as_menu.add(save_as_gff);
+ save_as_menu.addSeparator();
+ save_as_menu.add(save_embl_only);
+
+ file_menu.add(save_entry_menu);
+ file_menu.add(save_as_menu);
+ }
+
+ final JMenuItem save_all = new JMenuItem("Save All Entries");
+ save_all.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ saveAllEntries();
+ }
+ });
+
+ file_menu.add(save_all);
+ file_menu.addSeparator();
+
+ final WriteMenu write_menu = new WriteMenu(this, getSelection(), getEntryGroup());
+ write_menu.setMnemonic(KeyEvent.VK_W);
+ file_menu.add(write_menu);
+ file_menu.addSeparator();
+ }
+
+ final JMenuItem clone_entry_edit = new JMenuItem("Clone This Window");
+ clone_entry_edit.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ EntryEdit clone = new EntryEdit(getEntryGroup());
+ clone.setVisible(true);
+ }
+ });
+
+ file_menu.add(clone_entry_edit);
+ file_menu.addSeparator();
+
+ printMenu();
+
+ file_menu.addSeparator();
+ final JMenuItem open_dna_viewer = new JMenuItem("Open in DNAPlotter");
+ open_dna_viewer.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ Entry entry = getEntryGroup().getSequenceEntry();
+
+ final DNADraw dna =
+ uk.ac.sanger.artemis.circular.Wizard.getDNADrawFromArtemisEntry(null,
+ getEntryGroup(), entry);
+ final String version = dna.getVersion();
+ final JFrame f = new JFrame();
+ if(version == null)
+ f.setTitle("DNAPlotter");
+ else
+ f.setTitle("DNAPlotter :: "+version);
+ dna.setCloseAndDispose(true, f);
+
+ JScrollPane jsp = new JScrollPane(dna);
+ jsp.getViewport().setBackground(Color.white);
+ f.getContentPane().add(jsp);
+ f.setJMenuBar(dna.createMenuBar());
+ f.pack();
+ Utilities.centreFrame(f);
+ f.setVisible(true);
+ }
+ });
+ file_menu.add(open_dna_viewer);
+
+ file_menu.addSeparator();
+ final JMenuItem prefs = new JMenuItem("Preferences");
+ prefs.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ int nmenus = EntryEdit.super.getJMenuBar().getMenuCount();
+ final JTabbedPane shortcut_pane = new JTabbedPane();
+
+ Vector<String> sc = new Vector<String>();
+ for(int i=0; i<nmenus; i++)
+ {
+ JMenu menu = EntryEdit.super.getJMenuBar().getMenu(i);
+ if(menu instanceof SelectionMenu &&
+ ((SelectionMenu)menu).isEditableShortCutMenu() )
+ sc.addAll( ((SelectionMenu)menu).getUsedShortCutKeys() );
+ }
+
+ for(int i=0; i<nmenus; i++)
+ {
+ JMenu menu = EntryEdit.super.getJMenuBar().getMenu(i);
+ if(menu instanceof SelectionMenu &&
+ ((SelectionMenu)menu).isEditableShortCutMenu() )
+ shortcut_pane.add(menu.getText()+" Menu", ((SelectionMenu)menu).getShortCuts(sc));
+ }
+
+ shortcut_pane.setPreferredSize(new Dimension(
+ shortcut_pane.getPreferredSize().width+50,
+ screen.height/2));
+
+ final JTabbedPane tabPane = new JTabbedPane();
+
+ //
+ // Display names
+ //
+ final Object display_names[] =
+ Options.getOptions().getDisplayQualifierNames().toArray();
+
+ String[] description =
+ { "Names of qualifiers to search when attempting to find",
+ "the display name. These qualifier names are searched in order." };
+ ListSelectionPanel displayListSelectionPanel =
+ new ListSelectionPanel(entry_group, display_names,
+ description, true);
+
+ //
+ //
+ //
+ tabPane.add("Feature Display Labels", displayListSelectionPanel);
+
+ //
+ // Display names
+ //
+ final Object systematic_names[] =
+ Options.getOptions().getSystematicQualifierNames().toArray();
+
+ String[] description2 =
+ { "Names of qualifiers to search when attempting to find",
+ "the systematic name of a gene." } ;
+ ListSelectionPanel systematicListSelectionPanel =
+ new ListSelectionPanel(entry_group, systematic_names,
+ description2, true);
+
+ //
+ //
+ //
+ tabPane.add("Systematic Name Labels", systematicListSelectionPanel);
+
+ //
+ // Object Editor - accessing entries via SRS or mfetch
+ //
+ JTextField cacheSize = null;
+ if(System.getProperty("j2ssh") != null)
+ {
+ boolean remoteMfetch = false;
+ if(FileList.isConnected() && !FastaTextPane.isForceUrl())
+ {
+ FileList fileList = new FileList();
+ FileAttributes attr = fileList.stat("/nfs/disk100/pubseq/bin/mfetch");
+ if(attr != null)
+ remoteMfetch = attr.isFile();
+ }
+
+ Box yBox = Box.createVerticalBox();
+ final String srsUrl = (String)Options.getOptions().getOptionValues("srs_url").elementAt(0);
+ final JCheckBox useMfetch =
+ new JCheckBox("Use Sanger Server (Sanger Users Only)", remoteMfetch);
+
+ useMfetch.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(useMfetch.isSelected())
+ {
+ FileList fileList = new FileList();
+ FileAttributes attr = fileList.stat("/nfs/disk100/pubseq/bin/mfetch");
+ if(attr != null)
+ {
+ FastaTextPane.setForceUrl(false);
+ FastaTextPane.setRemoteMfetch(useMfetch.isSelected());
+ }
+ }
+ else
+ {
+ FastaTextPane.setForceUrl(true);
+ FastaTextPane.setRemoteMfetch(useMfetch.isSelected());
+ }
+ }
+ });
+ final JCheckBox useSrs = new JCheckBox("Use SRS "+srsUrl, !remoteMfetch);
+
+ ButtonGroup group = new ButtonGroup();
+ group.add(useMfetch);
+ group.add(useSrs);
+ yBox.add(new JLabel("Select the method for retrieving entry information "+
+ "in the object editor:"));
+ yBox.add(useMfetch);
+ yBox.add(useSrs);
+ yBox.add(Box.createVerticalStrut(5));
+
+ // cache
+ cacheSize = new JTextField(Integer.toString(BigPane.CACHE_SIZE), 6);
+ cacheSize.setMaximumSize(new Dimension(cacheSize.getMaximumSize().width,
+ cacheSize.getPreferredSize().height));
+ yBox.add(Box.createVerticalStrut(5));
+ yBox.add(new JLabel("Size of cache for retrieved entried (max "+
+ BigPane.MAX_CACHE_SIZE+"):"));
+ yBox.add(cacheSize);
+
+ JButton buttClear = new JButton("Empty Cache");
+ buttClear.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Arrays.fill(FastaTextPane.cacheHits, null);
+ }
+ });
+ yBox.add(buttClear);
+
+ yBox.add(Box.createVerticalGlue());
+ tabPane.add("Object Editor", yBox);
+ }
+
+ tabPane.add("Short Cuts", shortcut_pane);
+
+ //
+ // Contig ordering options
+ //
+ final Vector<String> contigKeys = FeatureDisplay.getContigKeys();
+ final Vector<String> allPossibleContigKeys = FeatureDisplay.getAllPossibleContigKeys();
+ final Vector<String> nonContigKeys = new Vector<String>();
+
+ for(int i=0; i<allPossibleContigKeys.size(); i++)
+ {
+ String keyStr = allPossibleContigKeys.get(i);
+ if( !contigKeys.contains(keyStr) )
+ nonContigKeys.add(keyStr);
+ }
+
+ final DefaultListModel showListModel = new DefaultListModel();
+ for(int i=0; i<contigKeys.size(); i++)
+ showListModel.addElement(contigKeys.get(i));
+ final JList displayList = new JList(showListModel);
+
+
+ final DefaultListModel hideListModel = new DefaultListModel();
+ for(int i=0; i<nonContigKeys.size(); i++)
+ hideListModel.addElement(nonContigKeys.get(i));
+ final JList hideList = new JList(hideListModel);
+
+
+ final JButton hide_butt = new JButton(">>");
+ hide_butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ while(!displayList.isSelectionEmpty())
+ {
+ final String hideKey = (String)displayList.getSelectedValue();
+
+ nonContigKeys.add(hideKey);
+ if(contigKeys.contains(hideKey))
+ contigKeys.remove(hideKey);
+
+ hideListModel.add(nonContigKeys.indexOf(hideKey), hideKey);
+ showListModel.removeElement(hideKey);
+ }
+ }
+ });
+
+
+ final JPanel panel = new JPanel(new BorderLayout());
+ final JPanel contigPanel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.anchor = GridBagConstraints.NORTHWEST;
+
+ c.ipadx = 50;
+ c.gridx = 0;
+ c.gridy = 0;
+ contigPanel.add(new JLabel("Contig Ordering Features:"),c);
+ c.gridy = 1;
+ JScrollPane jspContig = new JScrollPane(displayList);
+ Dimension d = new Dimension(300, jspContig.getPreferredSize().height);
+ jspContig.setPreferredSize(d);
+ contigPanel.add(jspContig,c);
+ c.gridy = 2;
+ contigPanel.add(hide_butt,c);
+
+ final JButton show_butt = new JButton("<<");
+ show_butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ while(!hideList.isSelectionEmpty())
+ {
+ final String showKey = (String)hideList.getSelectedValue();
+
+ if(nonContigKeys.contains(showKey))
+ nonContigKeys.remove(showKey);
+ contigKeys.add(showKey);
+
+ showListModel.add(contigKeys.indexOf(showKey), showKey);
+ hideListModel.removeElement(showKey);
+ }
+ }
+ });
+
+ c.gridx = 1;
+ c.gridy = 0;
+ contigPanel.add(new JLabel("Non-Contig Ordering Features:"),c);
+ c.gridy = 1;
+ JScrollPane jspNonContig = new JScrollPane(hideList);
+ jspNonContig.setPreferredSize(d);
+ contigPanel.add(jspNonContig,c);
+ c.gridy = 2;
+ contigPanel.add(show_butt,c);
+
+ panel.add(contigPanel, BorderLayout.CENTER);
+ tabPane.add("Contig Tool Options", panel);
+
+
+ /*String urlString = (String)Options.getOptions().getOptionValues("srs_url").elementAt(0);
+ Box yBox = Box.createVerticalBox();
+ JTextField srsField = new JTextField(urlString);
+ yBox.add(srsField);
+ yBox.add(Box.createVerticalGlue());
+
+ srsField.setSelectionStart(0);
+ srsField.setSelectionEnd(urlString.length());
+
+ tabPane.add("SRS Site", yBox);*/
+
+ JOptionPane.showMessageDialog(null,
+ tabPane,
+ "Preferences",
+ JOptionPane.PLAIN_MESSAGE);
+ tabPane.removeAll();
+
+ Options.getOptions().setDisplayNameQualifiers(
+ displayListSelectionPanel.getResultString());
+
+ if(displayListSelectionPanel.isSaveOption())
+ Splash.save_display_name = true;
+
+ Options.getOptions().setSystematicQualifierNames(
+ systematicListSelectionPanel.getResultString());
+
+ if(systematicListSelectionPanel.isSaveOption())
+ Splash.save_systematic_names = true;
+
+ //if(!srsField.getText().equals(urlString))
+ // Options.getOptions().setProperty("srs_url", srsField.getText().trim());
+
+ if(cacheSize != null &&
+ Integer.parseInt(cacheSize.getText()) != BigPane.CACHE_SIZE)
+ {
+ int cacheSizeValue = Integer.parseInt(cacheSize.getText());
+
+ if(cacheSizeValue <= 0)
+ return;
+ if(cacheSizeValue > BigPane.MAX_CACHE_SIZE)
+ BigPane.CACHE_SIZE = BigPane.MAX_CACHE_SIZE;
+ else
+ BigPane.CACHE_SIZE = Integer.parseInt(cacheSize.getText());
+ HitInfo[] cacheHits = FastaTextPane.cacheHits;
+
+ FastaTextPane.cacheHits = new HitInfo[BigPane.CACHE_SIZE];
+ for(int i = 0; i < cacheHits.length; i++)
+ {
+ if(i >= FastaTextPane.cacheHits.length)
+ break;
+ FastaTextPane.cacheHits[i] = cacheHits[i];
+ }
+ }
+ }
+ });
+ file_menu.add(prefs);
+
+ file_menu.addSeparator();
+
+ final JMenuItem close = new JMenuItem("Close");
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ closeEntryEdit();
+ }
+ });
+
+ file_menu.add(close);
+ }
+
+ private void loadBamAndVcf(List<String> listBams, List<String> vcfFiles)
+ {
+ logger4j.debug("No. BAM FILES="+listBams.size());
+
+ if(listBams.size() > 0)
+ {
+ bamPanel.removeAll();
+ try
+ {
+ bamView = new BamView(listBams, null, feature_display.getMaxVisibleBases(), this,
+ feature_display, getEntryGroup().getBases(), bamPanel, null);
+
+
+ if(entry_group.getSequenceEntry().getEMBLEntry().getSequence() instanceof IndexFastaStream)
+ {
+ // add reference sequence selection listeners
+ getEntryGroupDisplay().getIndexFastaCombo().addIndexReferenceListener(bamView.getCombo());
+ bamView.getCombo().addIndexReferenceListener(getEntryGroupDisplay().getIndexFastaCombo());
+ }
+
+ }
+ catch (Exception ex)
+ {
+ logger4j.warn("EntryEdit.loadBamAndVcf() "+ex.getMessage());
+
+ if(ex.getMessage() != null)
+ JOptionPane.showMessageDialog(null, ex.getMessage(), "Error",
+ JOptionPane.ERROR_MESSAGE);
+ else
+ ex.printStackTrace();
+ return;
+ }
+
+ bamView.getJspView().getVerticalScrollBar().setValue(
+ bamView.getJspView().getVerticalScrollBar().getMaximum());
+ bamView.setDisplay(feature_display.getFirstVisibleForwardBase(),
+ feature_display.getLastVisibleForwardBase(), null);
+
+ one_line_per_entry_display.addDisplayAdjustmentListener(bamView);
+ feature_display.addDisplayAdjustmentListener(bamView);
+ feature_display.getSelection().addSelectionChangeListener(bamView);
+
+ setNGDivider();
+ }
+
+ logger4j.debug("No. VCF FILES="+vcfFiles.size());
+ if (vcfFiles.size() > 0)
+ {
+ vcfPanel.removeAll();
+ vcfView = new VCFview(null, vcfPanel, vcfFiles,
+ feature_display.getMaxVisibleBases(), 1, null, null,
+ this, feature_display);
+
+ feature_display.addDisplayAdjustmentListener(vcfView);
+ feature_display.getSelection().addSelectionChangeListener(vcfView);
+
+ if(entry_group.getSequenceEntry().getEMBLEntry().getSequence() instanceof IndexFastaStream)
+ {
+ // add reference sequence selection listeners
+ getEntryGroupDisplay().getIndexFastaCombo().addIndexReferenceListener(vcfView.getCombo());
+ vcfView.getCombo().addIndexReferenceListener(getEntryGroupDisplay().getIndexFastaCombo());
+ }
+
+ setNGDivider();
+ }
+ }
+
+ /**
+ * Handle the split panes divider positions for BamView and VcfView.
+ */
+ public void setNGDivider()
+ {
+ if( (bamPanel.getComponents().length > 0 && bamPanel.isVisible()) &&
+ (vcfPanel.getComponents().length > 0 && vcfView.isVisible()))
+ {
+ ngSplitPane.setVisible(true);
+ lowerSplitPane.setDividerSize(3);
+ lowerSplitPane.setDividerLocation(0.4d);
+
+ ngSplitPane.setResizeWeight(0.5);
+ ngSplitPane.setDividerSize(3);
+ ngSplitPane.setDividerLocation(0.5);
+
+ logger4j.debug("BAM & VCF visible");
+ }
+ else if(vcfPanel.getComponents().length > 0 && vcfView.isVisible())
+ {
+ ngSplitPane.setVisible(true);
+ lowerSplitPane.setDividerSize(3);
+ lowerSplitPane.setDividerLocation(0.25d);
+
+ ngSplitPane.setResizeWeight(0);
+ ngSplitPane.setDividerSize(0);
+ ngSplitPane.setDividerLocation(0.);
+ logger4j.debug("VCF visible");
+ }
+ else if(bamPanel.getComponents().length > 0 && bamPanel.isVisible())
+ {
+ ngSplitPane.setVisible(true);
+ lowerSplitPane.setDividerSize(3);
+ lowerSplitPane.setDividerLocation(0.3d);
+
+ ngSplitPane.setResizeWeight(1);
+ ngSplitPane.setDividerSize(1);
+ ngSplitPane.setDividerLocation(1.d);
+ logger4j.debug("BAM visible");
+ }
+ else
+ {
+ lowerSplitPane.setResizeWeight(0);
+ lowerSplitPane.setDividerSize(0);
+ lowerSplitPane.setDividerLocation(0);
+ logger4j.debug("BAM & VCF not visible");
+ }
+ }
+
+ private void printMenu()
+ {
+ JMenuItem printImage = new JMenuItem("Save As Image Files (png/svg)...");
+ printImage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintArtemis part = new PrintArtemis(EntryEdit.this);
+ part.print();
+ }
+ });
+ file_menu.add(printImage);
+
+ JMenuItem printPS = new JMenuItem("Print...");
+ printPS.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintArtemis part = new PrintArtemis(EntryEdit.this);
+ part.validate();
+ part.doPrintActions();
+ }
+ });
+ file_menu.add(printPS);
+
+// print preview
+ JMenuItem printPreview = new JMenuItem("Print Preview");
+ file_menu.add(printPreview);
+ printPreview.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintArtemis part = new PrintArtemis(EntryEdit.this);
+ part.printPreview();
+ }
+ });
+
+ }
+
+ private static boolean canCommitChecks(final EntryGroup entry_group,
+ final ChadoTransactionManager ctm,
+ final JFrame frame,
+ final Selection selection,
+ final GotoEventSource getGotoEventSource,
+ final BasePlotGroup base_plot_group)
+ {
+ if(entry_group.getDefaultEntry() == null)
+ {
+ JOptionPane.showMessageDialog(frame,
+ "No default entry selected.\n"+
+ "Select the default from the entry bar at the top.");
+ return false;
+ }
+ else if(!ctm.hasTransactions())
+ {
+ JOptionPane.showMessageDialog(frame,
+ "No changes to commit to the database.");
+ return false;
+ }
+
+ if(System.getProperty("nocommit") == null ||
+ System.getProperty("nocommit").equals("false"))
+ {
+ int select = JOptionPane.showConfirmDialog(frame,
+ "Commit "+ctm.getTransactionCount()+" change(s) to the database?",
+ "Commit", JOptionPane.OK_CANCEL_OPTION);
+ if(select == JOptionPane.CANCEL_OPTION)
+ return false;
+ }
+
+ if(!isUniqueID(entry_group, ctm, selection,
+ getGotoEventSource, base_plot_group))
+ return false;
+ return true;
+ }
+
+ public static boolean commitToDatabase(final EntryGroup entry_group,
+ final ChadoTransactionManager ctm,
+ final JFrame frame,
+ final Selection selection,
+ final GotoEventSource getGotoEventSource,
+ final BasePlotGroup base_plot_group)
+ {
+ return commitToDatabase(entry_group, ctm, frame, selection,
+ getGotoEventSource, base_plot_group, false);
+ }
+
+ /**
+ * Commit back to the database.
+ * @param entry_group
+ * @param ctm
+ * @param frame
+ * @param selection
+ * @param getGotoEventSource
+ * @param base_plot_group
+ * @param force force the commit - i.e. commit everything that doesn't
+ * throw an exception
+ * @return
+ */
+ private static boolean commitToDatabase(final EntryGroup entry_group,
+ final ChadoTransactionManager ctm,
+ final JFrame frame,
+ final Selection selection,
+ final GotoEventSource getGotoEventSource,
+ final BasePlotGroup base_plot_group,
+ final boolean force)
+ {
+ try
+ {
+ if(!canCommitChecks(entry_group, ctm, frame, selection,
+ getGotoEventSource, base_plot_group))
+ return false;
+
+ final Document dbDoc =
+ ((DocumentEntry)entry_group.getDefaultEntry().getEMBLEntry()).getDocument();
+
+ if(dbDoc instanceof DatabaseDocument)
+ {
+ frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ ChadoTransactionManager.commit((DatabaseDocument)dbDoc, force, ctm);
+
+ final String nocommit = System.getProperty("nocommit");
+ if(!force && ctm.hasTransactions() &&
+ (nocommit == null || nocommit.equals("false")))
+ {
+ int forceCommit = JOptionPane.showConfirmDialog(frame,
+ "Force commit (saving any changes that can be\n"+
+ "committed to the database and ignoring those that fail)?",
+ "Force Database Commit", JOptionPane.OK_CANCEL_OPTION);
+
+ if(forceCommit == JOptionPane.OK_OPTION)
+ ChadoTransactionManager.commit((DatabaseDocument)dbDoc, true, ctm);
+ }
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ else
+ new MessageDialog(frame,
+ "No database associated with the default entry");
+ }
+ catch(NullPointerException npe)
+ {
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ new MessageDialog(frame,
+ "Problem writing to the database - check log window");
+ npe.printStackTrace();
+ logger4j.error(npe.getMessage());
+ }
+
+ if(ctm.hasTransactions())
+ return false;
+ return true;
+ }
+
+
+ /**
+ * Test to ensure ID (chado uniquename) is unique.
+ * @param entry_group
+ * @return
+ */
+ private static boolean isUniqueID(final EntryGroup entry_group,
+ final ChadoTransactionManager ctm,
+ final Selection selection,
+ final GotoEventSource getGotoEventSource,
+ final BasePlotGroup base_plot_group)
+ {
+ // only need to check if the Feature table has been changed
+ List<String> changed_features = ctm.getFeatureInsertUpdate();
+ if(changed_features == null)
+ return true;
+
+ // filter out non-db entries
+ FilteredEntryGroup db_entry_group = new FilteredEntryGroup(entry_group,
+ new FeatureDatabasePredicate(),
+ "Database Entries");
+ FeatureVector features = db_entry_group.getAllFeatures();
+ FeatureVector duplicateIDs = new FeatureVector();
+ for(int i=0; i<features.size()-1; i++)
+ {
+ Feature ifeature = features.elementAt(i);
+ String id;
+ try
+ {
+ id = (String)ifeature.getQualifierByName("ID").getValues().get(0);
+ if(!changed_features.contains(id))
+ continue;
+ }
+ catch(InvalidRelationException e)
+ {
+ continue;
+ }
+ catch(NullPointerException npe)
+ {
+ npe.printStackTrace();
+ logger4j.error(ifeature.getLocation().toStringShort());
+ continue;
+ }
+
+ FeaturePredicate predicate =
+ new FeatureKeyQualifierPredicate(null, "ID", id,
+ false, true);
+
+ for(int j=i+1; j<features.size(); j++)
+ {
+ Feature jfeature = features.elementAt(j);
+ if(predicate.testPredicate(jfeature))
+ {
+ duplicateIDs.add(ifeature);
+ duplicateIDs.add(jfeature);
+ }
+ }
+
+ }
+
+ if(duplicateIDs.size() > 0)
+ {
+ String filtern_name = "Features with non-unique feature ID's";
+ selection.set(duplicateIDs);
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(entry_group, duplicateIDs,
+ filtern_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame(filtern_name, selection,
+ getGotoEventSource, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Read an entry
+ **/
+ private void readAnEntry(final EntrySource this_source)
+ {
+ try
+ {
+ final Entry new_entry = this_source.getEntry(entry_group.getBases(),
+ true);
+ if(new_entry != null)
+ getEntryGroup().add(new_entry);
+ }
+ catch(final OutOfRangeException e)
+ {
+ new MessageDialog(EntryEdit.this,
+ "read failed: one of the features " +
+ "in the entry has an out of " +
+ "range location: " +
+ e.getMessage());
+ }
+ catch(final IOException e)
+ {
+ new MessageDialog(EntryEdit.this,
+ "read failed due to an IO error: " +
+ e.getMessage());
+ }
+ }
+
+
+ /**
+ * Return the current default font(from Diana.options).
+ **/
+ private Font getDefaultFont()
+ {
+ return Options.getOptions().getFont();
+ }
+
+ class CommitButton extends JButton
+ implements FeatureChangeListener, SequenceChangeListener, EntryChangeListener
+ {
+ private static final long serialVersionUID = 1L;
+ private Color DEFAULT_FOREGROUND;
+ private CommitFrame commitFrame;
+ private final ChadoTransactionManager ctm;
+
+ public CommitButton()
+ {
+ super("Commit");
+
+ ctm = getDatabaseDocumentEntry().getChadoTransactionManager();
+ setBackground(EntryGroupDisplay.background_colour);
+ addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(commitToDatabase(entry_group, ctm, EntryEdit.this,
+ selection, goto_event_source, base_plot_group, false))
+ {
+ setForeground(DEFAULT_FOREGROUND);
+ setFont(getFont().deriveFont(Font.PLAIN));
+
+ if(commitFrame != null)
+ commitFrame.setList(ctm);
+ }
+ }
+ });
+
+ addMouseListener(new MouseAdapter()
+ {
+ public void mouseReleased(MouseEvent e)
+ {
+ if(e.getButton() == MouseEvent.BUTTON3)
+ {
+ commitFrame =
+ new CommitFrame(ctm, entry_group, EntryEdit.this,
+ selection, goto_event_source, base_plot_group);
+ }
+ }
+ });
+
+ setMargin(new Insets(0,2,0,2));
+ setToolTipText("commit to database");
+
+ DEFAULT_FOREGROUND = super.getForeground();
+ }
+
+ public String getToolTipText()
+ {
+ if(ctm.hasTransactions())
+ return Integer.toString(ctm.getTransactionCount());
+ else
+ return "no transaction to commit";
+ }
+
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ if(commitFrame != null)
+ commitFrame.setList(ctm);
+ if(ctm.hasTransactions())
+ {
+ setFont(getFont().deriveFont(Font.BOLD));
+ setForeground(Color.red);
+ }
+ }
+
+ public void sequenceChanged(SequenceChangeEvent event)
+ {
+ if(commitFrame != null)
+ commitFrame.setList(ctm);
+ if(ctm.hasTransactions())
+ {
+ setForeground(Color.red);
+ setFont(getFont().deriveFont(Font.BOLD));
+ }
+ }
+
+ public void entryChanged(EntryChangeEvent event)
+ {
+ if(commitFrame != null)
+ commitFrame.setList(ctm);
+ if(ctm.hasTransactions())
+ {
+ setForeground(Color.red);
+ setFont(getFont().deriveFont(Font.BOLD));
+ }
+ }
+
+ protected void close()
+ {
+ if(commitFrame != null)
+ commitFrame.dispose();
+ }
+ }
+
+}
+
+/**
+ * This is an EntryActionListener that will get an entry from entry_source
+ * and then copy them to destination_entry when actionPerformed() is called.
+ **/
+class ReadFeaturesActionListener extends EntryActionListener
+{
+ final EntrySource entry_source;
+
+ ReadFeaturesActionListener(final EntryEdit entry_edit,
+ final EntrySource entry_source,
+ final Entry destination_entry)
+ {
+ super(entry_edit, destination_entry);
+ this.entry_source = entry_source;
+ }
+
+ public void actionPerformed(final ActionEvent event)
+ {
+ try
+ {
+ if(getEntry().isReadOnly())
+ {
+ final String message =
+ "the default entry is read only - cannot continue";
+ new MessageDialog(getEntryEdit(), message);
+ }
+
+ final Entry source_entry =
+ entry_source.getEntry(getEntryEdit().getEntryGroup().getBases(),
+ true);
+
+ for(int i = 0; i < source_entry.getFeatureCount(); ++i)
+ {
+ final Feature this_feature = source_entry.getFeature(i);
+ try
+ {
+ this_feature.copyTo(getEntry());
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(EntryInformationException e)
+ {
+ final String message =
+ "couldn't move one of the features(" +
+ this_feature.getIDString() + "): " + e.getMessage();
+ new MessageDialog(getEntryEdit(), message);
+ }
+ catch(ReadOnlyException e)
+ {
+ final String message =
+ "the default entry is read only - cannot continue";
+ new MessageDialog(getEntryEdit(), message);
+ return;
+ }
+ }
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(getEntryEdit(),
+ "read failed: one of the features in " +
+ "the source entry has an out of range location");
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getEntryEdit(),
+ "read failed due to an IO error: " +
+ e.getMessage());
+ }
+ catch(NullPointerException e)
+ {
+ new MessageDialog(getEntryEdit(),
+ "read failed due to a null pointer error: " +
+ e.getMessage());
+ }
+
+ }
+
+}
+
+/**
+ * This is an EntryActionListener that will call saveEntry() when
+ * actionPerformed() is called.
+ **/
+class SaveEntryActionListener extends EntryActionListener
+{
+ SaveEntryActionListener(final EntryEdit entry_edit,
+ final Entry entry)
+ {
+ super(entry_edit, entry);
+ }
+
+ public void actionPerformed(final ActionEvent event)
+ {
+ getEntryEdit().saveEntry(getEntry(), true, false, true,
+ DocumentEntryFactory.ANY_FORMAT);
+ }
+}
+
+/**
+ * This is an EntryActionListener that will call saveEntry() when
+ * actionPerformed() is called.
+ **/
+class SaveEntryAsActionListener extends EntryActionListener
+{
+ SaveEntryAsActionListener(final EntryEdit entry_edit,
+ final Entry entry)
+ {
+ super(entry_edit, entry);
+ }
+
+ public void actionPerformed(final ActionEvent event)
+ {
+ getEntryEdit().saveEntry(getEntry(), true, true, true,
+ DocumentEntryFactory.ANY_FORMAT);
+ }
+}
+
+/**
+ * This is an EntryActionListener that will call saveEntry() when
+ * actionPerformed() is called. The output file type will be EMBL.
+ **/
+class SaveEntryAsEMBLActionListener extends EntryActionListener
+{
+ SaveEntryAsEMBLActionListener(final EntryEdit entry_edit,
+ final Entry entry)
+ {
+ super(entry_edit, entry);
+ }
+
+ public void actionPerformed(final ActionEvent event)
+ {
+ getEntryEdit().saveEntry(getEntry(), true, true, false,
+ DocumentEntryFactory.EMBL_FORMAT);
+ }
+}
+
+/**
+ * This is an EntryActionListener that will call saveEntry() when
+ * actionPerformed() is called. The output file type will be GENBANK.
+ **/
+class SaveEntryAsGenbankActionListener extends EntryActionListener
+{
+ SaveEntryAsGenbankActionListener(final EntryEdit entry_edit,
+ final Entry entry)
+ {
+ super(entry_edit, entry);
+ }
+
+ public void actionPerformed(final ActionEvent event)
+ {
+ getEntryEdit().saveEntry(getEntry(), true, true, false,
+ DocumentEntryFactory.GENBANK_FORMAT);
+ }
+}
+
+/**
+ * This is an EntryActionListener that will call saveEntry() when
+ * actionPerformed() is called. The output file type will be GFF, with the
+ * sequence(if any) in FASTA format.
+ **/
+class SaveEntryAsGFFActionListener extends EntryActionListener
+{
+ SaveEntryAsGFFActionListener(final EntryEdit entry_edit,
+ final Entry entry)
+ {
+ super(entry_edit, entry);
+ }
+
+ public void actionPerformed(final ActionEvent event)
+ {
+ getEntryEdit().saveEntry(getEntry(), true, true, false,
+ DocumentEntryFactory.GFF_FORMAT);
+ }
+}
+
+/**
+ * This is an EntryActionListener that will call saveEntry() when
+ * actionPerformed() is called.
+ **/
+class SaveEntryAsSubmissionActionListener extends EntryActionListener
+{
+ SaveEntryAsSubmissionActionListener(final EntryEdit entry_edit,
+ final Entry entry)
+ {
+ super(entry_edit, entry);
+ }
+
+ public void actionPerformed(final ActionEvent event)
+ {
+ getEntryEdit().saveEntry(getEntry(), false, true, false,
+ DocumentEntryFactory.EMBL_FORMAT);
+ }
+}
+
+class FeatureDatabasePredicate implements FeaturePredicate
+{
+
+ public FeatureDatabasePredicate()
+ {
+ }
+
+ public boolean testPredicate(final Feature feature)
+ {
+ if(feature.getEntry().getEMBLEntry() instanceof DatabaseDocumentEntry)
+ return true;
+ else
+ return false;
+ }
+
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/EntryEditVector.java b/uk/ac/sanger/artemis/components/EntryEditVector.java
new file mode 100644
index 0000000..23f7bec
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EntryEditVector.java
@@ -0,0 +1,77 @@
+/* EntryEditVector.java
+ *
+ * created: Sat Oct 17 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryEditVector.java,v 1.1 2004-06-09 09:46:27 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.util.Vector;
+
+/**
+ * This class implements a Vector of EntryEdit objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryEditVector.java,v 1.1 2004-06-09 09:46:27 tjc Exp $
+ *
+ **/
+
+public class EntryEditVector {
+
+ /**
+ * Performs the same function as Vector.addElement ()
+ */
+
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ */
+ public EntryEdit elementAt (int index) {
+ return vector.elementAt (index);
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public boolean removeElement (EntryEdit entry_edit) {
+ return vector.removeElement (entry_edit);
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public int indexOf (EntryEdit entry_edit) {
+ return vector.indexOf (entry_edit);
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ */
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Storage for EntryEdit objects.
+ */
+ final private Vector<EntryEdit> vector = new Vector<EntryEdit> ();
+}
diff --git a/uk/ac/sanger/artemis/components/EntryFileDialog.java b/uk/ac/sanger/artemis/components/EntryFileDialog.java
new file mode 100644
index 0000000..20896d1
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EntryFileDialog.java
@@ -0,0 +1,595 @@
+/* EntryFileDialog.java
+ *
+ * created: Mon Dec 7 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998-2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryFileDialog.java,v 1.15 2009-04-01 12:23:20 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.Entry;
+import uk.ac.sanger.artemis.io.DocumentEntryFactory;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.ReadFormatException;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.ReadAndWriteEntry;
+
+import java.io.*;
+import javax.swing.*;
+
+/**
+ * This class is a JFileChooser that can read EMBL Entry objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryFileDialog.java,v 1.15 2009-04-01 12:23:20 tjc Exp $
+ **/
+
+public class EntryFileDialog extends StickyFileChooser
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+ /** JFrame reference that was passed to the constructor. */
+ private JFrame owner = null;
+
+ /**
+ * Create a new EntryFileDialog component.
+ * @param owner The component where this dialog was created.
+ * @param sequence_only If true default to showing only file that have
+ * suffixes that suggest that the files contain sequence(eg. .embl,
+ * .seq, .dna). Of false show all files that can be read.
+ **/
+ public EntryFileDialog(final JFrame owner,
+ final boolean show_sequence_only)
+ {
+ super();
+ this.owner = owner;
+
+ setFileSelectionMode(JFileChooser.FILES_ONLY);
+ setMultiSelectionEnabled(false);
+
+ final StringVector sequence_suffixes =
+ Options.getOptions().getOptionValues("sequence_file_suffixes");
+
+ final StringVector feature_suffixes =
+ Options.getOptions().getOptionValues("feature_file_suffixes");
+
+ final javax.swing.filechooser.FileFilter artemis_filter =
+ new javax.swing.filechooser.FileFilter()
+ {
+ public boolean accept(final File file)
+ {
+ if(file.isDirectory())
+ return true;
+
+ for(int i = 0; i<sequence_suffixes.size(); ++i)
+ {
+ final String this_suffix = (String)sequence_suffixes.elementAt(i);
+
+ if(file.getName().endsWith("." + this_suffix) ||
+ file.getName().endsWith("." + this_suffix + ".gz"))
+ return true;
+ }
+
+ for(int i = 0; i<feature_suffixes.size(); ++i)
+ {
+ final String this_suffix = (String)feature_suffixes.elementAt(i);
+
+ if(file.getName().endsWith("." + this_suffix) ||
+ file.getName().endsWith("." + this_suffix + ".gz"))
+ return true;
+ }
+ return false;
+ }
+
+ public String getDescription()
+ {
+ return "Artemis files";
+ }
+ };
+
+ final javax.swing.filechooser.FileFilter feature_filter =
+ new javax.swing.filechooser.FileFilter()
+ {
+ public boolean accept(final File file)
+ {
+ if(file.isDirectory())
+ return true;
+
+ for(int i = 0 ; i < feature_suffixes.size() ; ++i)
+ {
+ final String this_suffix = (String)feature_suffixes.elementAt(i);
+
+ if(file.getName().endsWith("." + this_suffix) ||
+ file.getName().endsWith("." + this_suffix + ".gz"))
+ return true;
+ }
+
+ return false;
+ }
+
+ public String getDescription()
+ {
+ return "Feature files";
+ }
+ };
+
+ final javax.swing.filechooser.FileFilter sequence_filter =
+ new javax.swing.filechooser.FileFilter()
+ {
+ public boolean accept(final File file)
+ {
+ if(file.isDirectory())
+ return true;
+
+ for(int i = 0 ; i<sequence_suffixes.size() ; ++i)
+ {
+ final String this_suffix = (String)sequence_suffixes.elementAt(i);
+
+ if(file.getName().endsWith("." + this_suffix) ||
+ file.getName().endsWith("." + this_suffix + ".gz"))
+ return true;
+ }
+
+ return false;
+ }
+
+ public String getDescription()
+ {
+ return "Sequence files";
+ }
+ };
+
+ addChoosableFileFilter(artemis_filter);
+ addChoosableFileFilter(feature_filter);
+ addChoosableFileFilter(sequence_filter);
+
+ if(show_sequence_only)
+ setFileFilter(sequence_filter);
+ else
+ setFileFilter(artemis_filter);
+ }
+
+ /**
+ * Return an uk.ac.sanger.artemis.io.Entry object representing the file
+ * the user has selected with the dialog or null if the read failed for any
+ * reason.
+ * @param entry_information The EntryInformation to use when reading. This
+ * supplies the list of valid keys and qualifiers. If a key or qualifier
+ * is read that is incompatible with this EntryInformation object the
+ * EntryInformation will be changed to cope.
+ * @param listener The object to which InputStreamProgressEvents will be
+ * send while reading.
+ * @exception EntryInformationException Thrown if an Entry using the given
+ * EntryInformation object cannot contain the Key, Qualifier or
+ * Key/Qualifier combination of one of the features in the Document.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading.
+ **/
+ public Entry getEntry(final EntryInformation entry_information,
+ final InputStreamProgressListener listener,
+ final boolean show_progress)
+ {
+ setDialogTitle("Select a file ...");
+ setDialogType(JFileChooser.OPEN_DIALOG);
+ SecurityManager sm = System.getSecurityManager();
+ System.setSecurityManager(null);
+
+ final int status = showOpenDialog(owner);
+
+ System.setSecurityManager(sm);
+
+ if(status != JFileChooser.APPROVE_OPTION ||
+ getSelectedFile() == null)
+ return null;
+
+ final File file = new File(getCurrentDirectory(),
+ getSelectedFile().getName());
+
+ return getEntryFromFile(owner, new FileDocument(file),
+ entry_information, show_progress);
+ }
+
+ /**
+ * This exists only to get around a bug in the 1.1/1.2 code generator,
+ * which generates unverifiable code.
+ * @param frame Used when creating MessageDialog components.
+ * @param file_document The Document to read the Entry from(should be made
+ * from entry_file).
+ * @param entry_information The EntryInformation to use when reading. This
+ * supplies the list of valid keys and qualifiers. If a key or qualifier
+ * is read that is incompatible with this EntryInformation object the
+ * EntryInformation will be changed to cope.
+ **/
+ private static Entry getEntryFromFileHelper(final JFrame frame,
+ final Document file_document,
+ final EntryInformation entry_information)
+ throws ReadFormatException, IOException
+ {
+
+ final LogReadListener read_event_logger =
+ new LogReadListener(file_document.getName());
+
+ final Entry new_entry;
+
+ try
+ {
+ new_entry =
+ DocumentEntryFactory.makeDocumentEntry(entry_information,
+ file_document,read_event_logger);
+ }
+ catch(EntryInformationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ if(read_event_logger.seenMessage() && !Options.isBlackBeltMode())
+ {
+ final YesNoDialog yes_no_dialog = new YesNoDialog(frame,
+ "there were warnings while reading - view now?");
+
+ if(yes_no_dialog.getResult())
+ Splash.showLog();
+ }
+
+ return new_entry;
+ }
+
+ /**
+ * Read and return an Entry from the given File.
+ * @param frame Used when creating MessageDialog components.
+ * @param entry_file The file to read the Entry from.
+ * @param entry_information The EntryInformation to use when reading. This
+ * supplies the list of valid keys and qualifiers. If a key or qualifier
+ * is read that is incompatible with this EntryInformation object the
+ * EntryInformation will be changed to cope.
+ * @param listener The object to which InputStreamProgressEvents will be
+ * send while reading.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading.
+ **/
+ public static Entry getEntryFromFile(final JFrame frame,
+ final Document entry_document,
+ final EntryInformation entry_information,
+ final boolean show_progress)
+ {
+ InputStreamProgressDialog progress_dialog = null;
+
+ if(show_progress)
+ {
+// XXX FIXME
+
+// This doesn't work because getEntryFromFile() is called from the Swing
+// thread so the Dialog never gets updated
+
+ progress_dialog =
+ new InputStreamProgressDialog(frame, "Reading ...",
+ "Reading from " +
+ entry_document.getName(), false);
+ final InputStreamProgressListener listener =
+ progress_dialog.getInputStreamProgressListener();
+
+ entry_document.addInputStreamProgressListener(listener);
+ }
+
+ try
+ {
+ return getEntryFromFileHelper(frame, entry_document,
+ entry_information);
+ }
+ catch(ReadFormatException e)
+ {
+ final String message =
+ "failed to open " + entry_document.getName() + ": " +
+ e.getMessage() +(e.getLineNumber() > 1 ?
+ " at line: " + e.getLineNumber() :
+ "");
+ System.out.println(message);
+ new MessageDialog(frame, message);
+ }
+ catch(FileNotFoundException e)
+ {
+ final String message =
+ "failed to open " + entry_document.getName() + ": file not found";
+ new MessageDialog(frame, message);
+ }
+ catch(IOException e)
+ {
+ final String message =
+ "failed to open " + entry_document.getName() + ": " + e.getMessage();
+ new MessageDialog(frame, message);
+ }
+ finally
+ {
+ if(progress_dialog != null)
+ progress_dialog.dispose();
+ }
+ return null;
+ }
+
+ /**
+ * Save the given entry, prompting for a file name if necessary.
+ * @param include_diana_extensions If true then the any diana additions to
+ * the embl file format will be included in the output, otherwise they
+ * will be removed. Also possible problems that would cause an entry to
+ * bounce from the EMBL submission process will be flagged if this is
+ * true.
+ * @param ask_for_name If true then always prompt for a new filename,
+ * otherwise prompt only when the entry name is not set.
+ * @param keep_new_name If ask_for_name is true a file will be written with
+ * the new name the user selects - if keep_new_name is true as well, then
+ * the entry will have it's name set to the new name, otherwise it will
+ * be used for this save and then discarded.
+ * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
+ * ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the
+ * same format it was created, otherwise it will be saved in the given
+ * format.
+ **/
+ void saveEntry(final uk.ac.sanger.artemis.Entry entry,
+ final boolean include_diana_extensions,
+ final boolean ask_for_name,
+ final boolean keep_new_name,
+ final int destination_type)
+ {
+
+ JCheckBox remoteSave = new JCheckBox("Ssh/Remote Save",
+ true);
+ File file = null;
+
+ try
+ {
+ if(ask_for_name || entry.getName() == null)
+ {
+ Box yBox = Box.createVerticalBox();
+ boolean useAccessory = false;
+
+ JCheckBox emblHeader = new JCheckBox("Add EMBL Header", false);
+
+ JCheckBox removeProductForPseudo = new JCheckBox(
+ "Remove products from pseudogenes", false);
+
+ setDialogTitle("Save to ...");
+ setDialogType(JFileChooser.SAVE_DIALOG);
+
+ if( destination_type == DocumentEntryFactory.EMBL_FORMAT )
+ {
+ if((entry.getHeaderText() == null || !isHeaderEMBL(entry.getHeaderText())))
+ yBox.add(emblHeader);
+
+ if(!include_diana_extensions)
+ yBox.add(removeProductForPseudo);
+ useAccessory = true;
+ }
+
+ final JCheckBox flattenGeneModel = new JCheckBox("Flatten Gene Model",
+ false);
+ final JCheckBox ignoreObsoleteFeatures = new JCheckBox(
+ "Ignore obsolete features", true);
+ if(((DocumentEntry)entry.getEMBLEntry()).getDocument()
+ instanceof RemoteFileDocument)
+ {
+ yBox.add(remoteSave);
+ useAccessory = true;
+ }
+ else if(entry.getEMBLEntry() instanceof DatabaseDocumentEntry ||
+ entry.getEMBLEntry() instanceof GFFDocumentEntry)
+ {
+ yBox.add(flattenGeneModel);
+ if(entry.getEMBLEntry() instanceof DatabaseDocumentEntry)
+ yBox.add(ignoreObsoleteFeatures);
+ useAccessory = true;
+ }
+
+ if(useAccessory)
+ setAccessory(yBox);
+
+ switch(destination_type) // provide a suffix
+ {
+ case DocumentEntryFactory.EMBL_FORMAT:
+ super.setSelectedFile(new File(".embl"));
+ flattenGeneModel.setSelected(true);
+ break;
+ case DocumentEntryFactory.GENBANK_FORMAT:
+ super.setSelectedFile(new File(".gbk"));
+ flattenGeneModel.setSelected(true);
+ break;
+ case DocumentEntryFactory.GFF_FORMAT:
+ super.setSelectedFile(new File(".gff"));
+ break;
+ default:
+ break;
+ }
+ int status = showSaveDialog(owner);
+
+ if(status != JFileChooser.APPROVE_OPTION ||
+ getSelectedFile() == null)
+ return;
+
+ if(emblHeader.isSelected())
+ {
+ Box bdown = Box.createVerticalBox();
+ JTextField idField = new JTextField("");
+ bdown.add(idField);
+
+ int n = JOptionPane.showConfirmDialog(null, bdown,
+ "Enter the entry ID",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(n != JOptionPane.CANCEL_OPTION &&
+ !idField.getText().trim().equals(""))
+ {
+ int length = entry.getBases().getLength();
+ String header = "ID "+idField.getText().trim()+"; SV ; ; ; ; ; "+length+" BP.";
+ //String header = "ID "+idField.getText().trim();
+ if(entry.getFeatureCount() > 0)
+ header = header.concat("\nFH Key "+
+ "Location/Qualifiers\nFH\n");
+ entry.setHeaderText(header);
+ }
+ }
+
+ file = new File(getCurrentDirectory(),
+ getSelectedFile().getName());
+
+ if(file.exists())
+ {
+ final YesNoDialog yes_no_dialog = new YesNoDialog(owner,
+ "this file exists: " + file.getName() +
+ " overwrite it?");
+
+ if(!yes_no_dialog.getResult())
+ return;
+ }
+
+ final MessageDialog message = new MessageDialog(owner,
+ "saving to " + file.getName() + " ...",
+ false);
+ try
+ {
+ DocumentEntryFactory.REMOVE_PRODUCT_FROM_PSEUDOGENE = removeProductForPseudo.isSelected();
+ if(entry.getEMBLEntry() instanceof DatabaseDocumentEntry ||
+ entry.getEMBLEntry() instanceof GFFDocumentEntry)
+ ReadAndWriteEntry.writeDatabaseEntryToFile(entry, file,
+ flattenGeneModel.isSelected(),
+ ignoreObsoleteFeatures.isSelected(), false,
+ include_diana_extensions, destination_type, owner);
+ else if(include_diana_extensions)
+ entry.save(file, destination_type, false);
+ else
+ entry.saveStandardOnly(file, destination_type, true);
+ }
+ catch(EntryInformationException e)
+ {
+ final YesNoDialog yes_no_dialog = new YesNoDialog(owner,
+ "destination format can't handle all " +
+ "keys/qualifiers - continue?");
+
+ if(yes_no_dialog.getResult())
+ {
+ try
+ {
+ if(entry.getEMBLEntry() instanceof DatabaseDocumentEntry)
+ ReadAndWriteEntry.writeDatabaseEntryToFile(entry, file,
+ flattenGeneModel.isSelected(),
+ ignoreObsoleteFeatures.isSelected(), true,
+ include_diana_extensions, destination_type, null);
+ else if(include_diana_extensions)
+ entry.save(file, destination_type, true);
+ else
+ entry.saveStandardOnly(file, destination_type, true);
+ }
+ catch(EntryInformationException e2)
+ {
+ throw new Error("internal error - unexpected exception: "+ e);
+ }
+ }
+ else
+ return;
+ }
+ finally
+ {
+ DocumentEntryFactory.REMOVE_PRODUCT_FROM_PSEUDOGENE = false;
+ if(message != null)
+ message.dispose();
+ }
+
+ if(keep_new_name)
+ entry.setName(file.getName());
+ }
+ else
+ {
+ final MessageDialog message = new MessageDialog(owner,
+ "saving to " + entry.getName() + " ...",
+ false);
+ try
+ {
+ if(include_diana_extensions)
+ entry.save(destination_type);
+ else
+ entry.saveStandardOnly(destination_type);
+ }
+ finally
+ {
+ message.dispose();
+ }
+ }
+ }
+ catch(ReadOnlyException e)
+ {
+ new MessageDialog(owner, "this entry is read only");
+ return;
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(owner, "error while saving: " + e);
+ return;
+ }
+ catch(EntryInformationException e)
+ {
+ new MessageDialog(owner, "error while saving: " + e);
+ return;
+ }
+
+ // save it back to ssh server
+ if(((DocumentEntry)entry.getEMBLEntry()).getDocument()
+ instanceof RemoteFileDocument &&
+ remoteSave.isSelected())
+ {
+ RemoteFileDocument node =
+ (RemoteFileDocument)(((DocumentEntry)entry.getEMBLEntry()).getDocument());
+
+ if(file == null)
+ file = new File( ((DocumentEntry)entry.getEMBLEntry()).getDocument().getLocation().toString() );
+ node.saveEntry(file);
+ }
+
+ }
+
+ /**
+ *
+ * Test for that the header of an EMBL entry begins
+ * with an ID line.
+ * @param header embl header
+ *
+ */
+ private boolean isHeaderEMBL(String header)
+ {
+ StringReader reader = new StringReader(header);
+ BufferedReader buff_reader = new BufferedReader(reader);
+
+ try
+ {
+ if(!buff_reader.readLine().startsWith("ID"))
+ return false;
+ }
+ catch(IOException ioe){}
+ return true;
+ }
+
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/EntryGroupDisplay.java b/uk/ac/sanger/artemis/components/EntryGroupDisplay.java
new file mode 100644
index 0000000..e9b42ab
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EntryGroupDisplay.java
@@ -0,0 +1,301 @@
+/* EntryGroupDisplay.java
+ *
+ * created: Mon Dec 7 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryGroupDisplay.java,v 1.5 2009-05-29 10:16:15 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryChangeEvent;
+import uk.ac.sanger.artemis.EntryChangeListener;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.EntryGroupChangeEvent;
+import uk.ac.sanger.artemis.EntryGroupChangeListener;
+import uk.ac.sanger.artemis.EntryVector;
+import uk.ac.sanger.artemis.io.IndexFastaStream;
+import uk.ac.sanger.artemis.io.IndexedGFFDocumentEntry;
+
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.Graphics;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import java.util.Vector;
+
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * This component allows the user to change the "active" setting of the
+ * objects in an EntryGroup.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryGroupDisplay.java,v 1.5 2009-05-29 10:16:15 tjc Exp $
+ **/
+
+public class EntryGroupDisplay extends JPanel
+ implements EntryGroupChangeListener, EntryChangeListener
+{
+ private static final long serialVersionUID = 1L;
+
+ final protected static Color background_colour = new Color(200, 200, 200);
+
+ /**
+ * This is a reference to the EntryEdit component that created this
+ * EntryGroupDisplay. We need this reference so that we can watch the
+ * entry group.
+ **/
+ private EntryEdit owning_component;
+
+ /**
+ * Contains Entry objects that this EntryEdit object knows
+ * about. This reference is obtained from owning_component.
+ **/
+ private EntryGroup entry_group;
+
+ /**
+ * Contains one JCheckBox or Label for each Entry in the
+ * EntryGroup object.
+ **/
+ private Vector<JCheckBox> entry_components = new Vector<JCheckBox>();
+
+ private JLabel label = new JLabel("Entry: ");
+ private SequenceComboBox indexFastaCombo;
+
+ /**
+ * Create a new EntryGroupDisplay object.
+ * @param owning_component The EntryEdit object that this EntryGroupDisplay
+ * component is in.
+ **/
+ public EntryGroupDisplay(final EntryEdit owning_component)
+ {
+ this.owning_component = owning_component;
+ this.entry_group = owning_component.getEntryGroup();
+
+ entry_group.addEntryGroupChangeListener(this);
+ entry_group.addEntryChangeListener(this);
+
+ setLayout(new FlowLayout(FlowLayout.LEFT,2,1));
+ refreshButtons();
+ setBackground(background_colour);
+ }
+
+
+ protected void printComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ try
+ {
+ super.printChildren(g);
+ }catch(Exception e){}
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can update the display if entries
+ * are added or deleted.
+ **/
+ public void entryGroupChanged(final EntryGroupChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryGroupChangeEvent.ENTRY_ADDED:
+ case EntryGroupChangeEvent.ENTRY_DELETED:
+ refreshButtons();
+ break;
+ case EntryGroupChangeEvent.ENTRY_INACTIVE:
+ case EntryGroupChangeEvent.ENTRY_ACTIVE:
+ updateActive();
+ break;
+ case EntryGroupChangeEvent.NEW_DEFAULT_ENTRY:
+ highlightDefaultEntry(event);
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface.
+ **/
+ public void entryChanged(final EntryChangeEvent event)
+ {
+ if(event.getType() == EntryChangeEvent.NAME_CHANGED)
+ refreshButtons();
+ }
+
+ /**
+ * Remove and then recreate the Buttons to the reflect the current contents
+ * of the EntryGroup.
+ **/
+ private void refreshButtons()
+ {
+ removeAll();
+ add(label);
+
+ entry_components = new Vector<JCheckBox>();
+ if(entry_group == null)
+ return;
+ else
+ {
+ for(int i = 0; i < entry_group.size(); ++i)
+ add(entry_group.elementAt(i));
+ }
+
+ validate();
+ repaint();
+ }
+
+ /**
+ * Update the buttons to reflect the current state of the EntryGroup.
+ **/
+ private void updateActive()
+ {
+ for(int i = 0 ; i < entry_group.size() ; ++i)
+ {
+ final JCheckBox menu_item = entry_components.elementAt(i);
+ menu_item.setSelected(entry_group.isActive(entry_group.elementAt(i)));
+ }
+ }
+
+ /**
+ * Add a JLabel or JCheckBox for the given Entry to this component.
+ **/
+ private void add(final Entry entry)
+ {
+ final JCheckBox new_component;
+ String entry_name = entry.getName();
+
+ if(entry_name == null)
+ entry_name = "no name";
+
+ new_component = new JCheckBox(entry_name, entry_group.isActive(entry));
+ new_component.setOpaque(true);
+ setEntryHighlight(entry, new_component);
+
+ new_component.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ final int button_idx =
+ entry_components.indexOf(event.getSource());
+
+ if(event.getStateChange() == ItemEvent.SELECTED)
+ entry_group.setIsActive(button_idx, true);
+ else
+ entry_group.setIsActive(button_idx, false);
+ }
+ });
+
+ new_component.addMouseListener(new MouseAdapter()
+ {
+ /**
+ * Listen for mouse press events so that we can change the default
+ * Entry when the popup trigger is pressed.
+ **/
+ public void mousePressed(MouseEvent event)
+ {
+ if(event.isPopupTrigger())
+ entry_group.setDefaultEntry(entry);
+ }
+ });
+
+ entry_components.add(new_component);
+ add(new_component);
+
+ if(entry.getEMBLEntry().getSequence() instanceof IndexFastaStream)
+ {
+ if(indexFastaCombo == null)
+ {
+ final Vector<String> contigs =
+ ((IndexFastaStream)entry.getEMBLEntry().getSequence()).getContigs();
+
+ indexFastaCombo = new SequenceComboBox(contigs){
+ private static final long serialVersionUID = 1L;
+ public void update(IndexReferenceEvent event)
+ {
+ IndexFastaStream is = (IndexFastaStream)entry.getEMBLEntry().getSequence();
+ is.setContigByIndex(indexFastaCombo.getSelectedIndex());
+
+ EntryVector entries = entry_group.getActiveEntries();
+ for(int i=0; i<entries.size(); i++)
+ {
+ Entry entry = entries.elementAt(i);
+ if(entry.getEMBLEntry() instanceof IndexedGFFDocumentEntry)
+ ((IndexedGFFDocumentEntry)entry.getEMBLEntry()).updateReference(is.getContig(), false);
+ }
+ owning_component.resetScrolls();
+ owning_component.getFeatureDisplay().getBases().clearCodonCache();
+ owning_component.getFeatureDisplay().needVisibleFeatureVectorUpdate();
+ owning_component.repaint();
+ owning_component.getBasePlotGroup().displayAdjustmentValueChanged(
+ new DisplayAdjustmentEvent(
+ owning_component,
+ owning_component.getFeatureDisplay().getFirstVisibleForwardBase(),
+ owning_component.getFeatureDisplay().getLastVisibleForwardBase(),
+ owning_component.getFeatureDisplay().getMaxVisibleBases(),
+ owning_component.getFeatureDisplay().getDisplayWidth(),
+ owning_component.getFeatureDisplay().getScaleFactor(),
+ owning_component.getFeatureDisplay().isRevCompDisplay(),
+ DisplayAdjustmentEvent.IDX_SEQUENCE_CHANGE));
+ }
+ };
+ }
+ add(indexFastaCombo);
+ }
+ }
+
+ /**
+ * Given a EntryGroupChangeEvent, this method will highlight the new
+ * default entry
+ **/
+ private void highlightDefaultEntry(final EntryGroupChangeEvent event)
+ {
+ for(int i = 0 ; i < entry_group.size() ; ++i)
+ setEntryHighlight(entry_group.elementAt(i), entry_components.elementAt(i));
+ }
+
+ /**
+ * Highlight the given JCheckBox/Entry appropriately. The default Entry
+ * will look different to the others
+ **/
+ private void setEntryHighlight(final Entry entry,
+ final JCheckBox component)
+ {
+ if(entry_group.getDefaultEntry() == entry)
+ component.setBackground(Color.yellow);
+ else
+ component.setBackground(background_colour);
+ }
+
+ /**
+ * @return the indexFastaCombo
+ */
+ public SequenceComboBox getIndexFastaCombo()
+ {
+ return indexFastaCombo;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/EntryGroupInfoDisplay.java b/uk/ac/sanger/artemis/components/EntryGroupInfoDisplay.java
new file mode 100644
index 0000000..6a5080e
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EntryGroupInfoDisplay.java
@@ -0,0 +1,629 @@
+/* EntryGroupInfoDisplay.java
+ *
+ * created: Fri Mar 12 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryGroupInfoDisplay.java,v 1.8 2009-03-06 09:00:39 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryChangeEvent;
+import uk.ac.sanger.artemis.EntryChangeListener;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.EntryGroupChangeEvent;
+import uk.ac.sanger.artemis.EntryGroupChangeListener;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.FeatureEnumeration;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.FilteredEntryGroup;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.sequence.SequenceChangeEvent;
+import uk.ac.sanger.artemis.sequence.SequenceChangeListener;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.FuzzyRange;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+import javax.swing.JFrame;
+
+import org.apache.log4j.Level;
+
+/**
+ * This component will show general information about an EntryGroup. It will
+ * show the sequence length, GC content and a summary of the active entries.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryGroupInfoDisplay.java,v 1.8 2009-03-06 09:00:39 tjc Exp $
+ **/
+
+public class EntryGroupInfoDisplay
+ implements FeatureChangeListener, EntryChangeListener,
+ EntryGroupChangeListener, SequenceChangeListener
+{
+ /** Summarize both strands. */
+ final static public int BOTH = 3;
+
+ /** This is the EntryGroup object that we are viewing. */
+ private EntryGroup entry_group;
+
+ /** The strand indicator that was passed to the constructor. */
+ private int strand_flag;
+
+ /** The FileViewer object that is displaying the EntryGroup. */
+ private FileViewer file_viewer;
+
+ /** The Frame that was passed to the constructor. */
+ private JFrame parent_frame;
+
+ /**
+ * Create a new EntryGroupInfoDisplay object to summarize all features on
+ * both strands of the given EntryGroup.
+ * @param parent_frame The reference of the parent JFrame.
+ * @param entry_group The EntryGroup to view.
+ **/
+ public EntryGroupInfoDisplay(final JFrame parent_frame,
+ final EntryGroup entry_group)
+ {
+ this(parent_frame, entry_group, BOTH);
+ }
+
+ /**
+ * Create a new EntryGroupInfoDisplay object to summarize the given
+ * EntryGroup.
+ * @param parent_frame The reference of the parent JFrame.
+ * @param entry_group The EntryGroup to view.
+ * @param strand_flag Indicates which strand to summarize. BOTH means
+ * summarize all features on both strands. FORWARD means summarize
+ * forward strand only. REVERSE means summarize reverse strand only.
+ **/
+ public EntryGroupInfoDisplay(final JFrame parent_frame,
+ EntryGroup entry_group,
+ final int strand_flag)
+ {
+ this.strand_flag = strand_flag;
+ this.parent_frame = parent_frame;
+ this.entry_group = entry_group;
+
+ if(strand_flag == Bases.FORWARD)
+ {
+ final FeaturePredicate forward_feature_predicate =
+ new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature test_feature)
+ {
+ return test_feature.isForwardFeature();
+ }
+ };
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(entry_group, forward_feature_predicate,
+ "forward strand features");
+
+ this.entry_group = filtered_entry_group;
+ }
+ else
+ {
+ if(strand_flag == Bases.REVERSE)
+ {
+ final FeaturePredicate reverse_feature_predicate =
+ new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature test_feature)
+ {
+ return !test_feature.isForwardFeature();
+ }
+ };
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(entry_group, reverse_feature_predicate,
+ "reverse strand features");
+
+ this.entry_group = filtered_entry_group;
+ }
+ else
+ {
+ if(strand_flag != BOTH)
+ throw new Error("internal error - illegal argument");
+ }
+ }
+
+ file_viewer = new FileViewer(getFrameName());
+ updateView();
+
+ this.entry_group.addEntryChangeListener(this);
+ this.entry_group.addFeatureChangeListener(this);
+ this.entry_group.addEntryGroupChangeListener(this);
+ this.entry_group.getBases().addSequenceChangeListener(this,
+ Bases.MAX_PRIORITY);
+
+ file_viewer.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosed(WindowEvent event)
+ {
+ stopListening();
+ }
+ });
+ }
+
+ /**
+ * Return the String that should be used for the title of the Overview Frame
+ **/
+ private String getFrameName()
+ {
+ if(entry_group instanceof FilteredEntryGroup)
+ {
+ final FilteredEntryGroup filtered_entry_group =
+ (FilteredEntryGroup)entry_group;
+
+ final String filter_name = filtered_entry_group.getFilterName();
+
+ if(filter_name == null)
+ return "Artemis overview of: " + parent_frame.getTitle();
+ else
+ return "Artemis overview of: " + parent_frame.getTitle() +
+ " (" + filter_name + ")";
+ }
+ else
+ {
+ final StringBuffer buffer = new StringBuffer("Artemis Overview");
+
+ if(entry_group.size() > 0)
+ {
+ buffer.append(" of:");
+
+ for(int i = 0 ; i < entry_group.size() ; ++i)
+ {
+ final Entry this_entry = entry_group.elementAt(i);
+
+ if(this_entry.getName() == null)
+ buffer.append(" [no name]");
+ else
+ buffer.append(" " + this_entry.getName());
+ }
+ }
+
+ return buffer.toString();
+ }
+ }
+
+ /**
+ * Remove this object as a selection change listener.
+ **/
+ private void stopListening()
+ {
+ entry_group.removeEntryChangeListener(this);
+ entry_group.removeEntryGroupChangeListener(this);
+ entry_group.removeFeatureChangeListener(this);
+ entry_group.getBases().removeSequenceChangeListener(this);
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We listen to
+ * FeatureChange events so that we can update the display if qualifiers
+ * change.
+ **/
+ public void featureChanged(final FeatureChangeEvent event)
+ {
+ updateView();
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can update the display if entries
+ * are added, deleted, activated or deactivated.
+ **/
+ public void entryGroupChanged(final EntryGroupChangeEvent event)
+ {
+ if(event.getType() == EntryGroupChangeEvent.DONE_GONE)
+ {
+ stopListening();
+ file_viewer.dispose();
+ }
+ else
+ updateView();
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so that we can update the display if features are
+ * added or deleted.
+ **/
+ public void entryChanged(final EntryChangeEvent event)
+ {
+ updateView();
+ }
+
+ /**
+ * Implementation of the SequenceChangeListener interface. We listen to
+ * SequenceChange events so that we can update the display if the sequence
+ * changes.
+ **/
+ public void sequenceChanged(final SequenceChangeEvent event)
+ {
+ updateView();
+ }
+
+ /**
+ * Update the FileViewer component to reflect the current state of the
+ * EntryGroup.
+ **/
+ private void updateView()
+ {
+ final String frame_name = getFrameName();
+ file_viewer.setTitle(frame_name);
+
+ file_viewer.appendString(frame_name + "\n\n", Level.INFO);
+ file_viewer.appendString("Number of bases: " +
+ entry_group.getSequenceLength() + "\n");
+ file_viewer.appendString("Number of features in the active entries: " +
+ entry_group.getAllFeaturesCount() + "\n\n");
+
+ final String otherKeys[] =
+ { "misc_RNA", "rRNA",
+ "snoRNA", "tRNA",
+ "snRNA", "3'UTR", "5'UTR",
+ "misc_feature" };
+
+ int others_count[] = new int[otherKeys.length];
+ StringBuffer other_bases_buffer[] = new StringBuffer[otherKeys.length];
+ int other_bases_count_with_introns[] = new int[otherKeys.length];
+ for(int i=0; i<others_count.length; i++)
+ {
+ others_count[i] = 0;
+ other_bases_count_with_introns[i] = 0;
+ other_bases_buffer[i] = new StringBuffer();
+ }
+
+ int gene_count = 0;
+ int cds_count = 0;
+ int spliced_gene_count = 0;
+ int spliced_gene_bases_count = 0;
+
+ StringBuffer gene_bases_buffer = new StringBuffer();
+ int gene_bases_count_with_introns = 0;
+ int cds_bases_count = 0;
+ int exon_count = 0;
+ int intron_count = 0;
+ int partial_count = 0;
+
+ final Hashtable<String, Hashtable<String, Integer>> table = new Hashtable<String, Hashtable<String, Integer>>();
+ final FeatureEnumeration feature_enumerator = entry_group.features();
+
+ int index;
+ while(feature_enumerator.hasMoreFeatures())
+ {
+ final Feature this_feature = feature_enumerator.nextFeature();
+
+ final Key key = this_feature.getKey();
+ final String key_string = key.toString();
+
+ final Location location = this_feature.getLocation();
+ final RangeVector ranges = location.getRanges();
+
+ for(int i = 0; i < ranges.size(); ++i)
+ if(ranges.elementAt(i) instanceof FuzzyRange)
+ ++partial_count;
+
+ try
+ {
+ String colour = this_feature.getValueOfQualifier("colour");
+
+ if(colour == null || colour.length() == 0)
+ colour = "no colour";
+
+ if(table.containsKey(key_string))
+ {
+ final Hashtable<String, Integer> colour_table = table.get(key_string);
+ final Integer colour_value = colour_table.get(colour);
+
+ if(colour_value == null)
+ colour_table.put(colour, new Integer(1));
+ else
+ {
+ final int old_value = ((Integer)colour_value).intValue();
+ colour_table.put(colour, new Integer(old_value + 1));
+ }
+ }
+ else
+ {
+ final Hashtable<String, Integer> colour_table = new Hashtable<String, Integer>();
+ colour_table.put(colour, new Integer(1));
+ table.put(key_string, colour_table);
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ if(this_feature.isCDS())
+ {
+ cds_count++;
+ cds_bases_count += this_feature.getBaseCount();
+
+ Qualifier pseudo_qualifier;
+ try
+ {
+ pseudo_qualifier = this_feature.getQualifierByName("pseudo");
+ if(pseudo_qualifier == null)
+ pseudo_qualifier = this_feature.getQualifierByName("pseudogene");
+ }
+ catch(InvalidRelationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ if(pseudo_qualifier == null)
+ {
+ ++gene_count;
+
+ final FeatureSegmentVector segments = this_feature.getSegments();
+ if(segments.size() > 1)
+ {
+ ++spliced_gene_count;
+ spliced_gene_bases_count += this_feature.getBaseCount();
+ intron_count += segments.size() - 1;
+ }
+
+ exon_count += segments.size();
+ gene_bases_buffer.append(this_feature.getBases());
+ gene_bases_count_with_introns += this_feature.getRawLastBase() -
+ this_feature.getRawFirstBase() + 1;
+ }
+ }
+ else if((index=containsKey(otherKeys, key_string)) > -1)
+ {
+ others_count[index]++;
+ other_bases_buffer[index].append(this_feature.getBases());
+ other_bases_count_with_introns[index] += this_feature.getRawLastBase() -
+ this_feature.getRawFirstBase() + 1;
+ }
+ }
+
+
+ final String gene_bases = gene_bases_buffer.toString();
+ final int gene_bases_count = gene_bases.length();
+
+ final int pseudo_gene_count = cds_count - gene_count;
+ final int pseudo_gene_bases_count = cds_bases_count - gene_bases_count;
+
+ if(cds_count > 0)
+ {
+ if(gene_count > 0)
+ {
+ file_viewer.appendString("Genes (CDS features without a /pseudo or /pseudogene qualifier):\n", Level.INFO);
+
+ final int non_spliced_gene_count = gene_count - spliced_gene_count;
+ final int non_spliced_gene_bases_count =
+ gene_bases_count - spliced_gene_bases_count;
+
+ if(spliced_gene_count > 0)
+ {
+ file_viewer.appendString(" spliced:\n");
+ file_viewer.appendString(" count: " + spliced_gene_count + "\n");
+ file_viewer.appendString(" bases: " + spliced_gene_bases_count + "\n");
+ file_viewer.appendString(" introns: " + intron_count + "\n");
+ }
+
+ if(non_spliced_gene_count > 0)
+ {
+ file_viewer.appendString(" non-spliced:\n");
+ file_viewer.appendString(" count: " + non_spliced_gene_count + "\n");
+ file_viewer.appendString(" bases: " + non_spliced_gene_bases_count +
+ "\n");
+ }
+
+ file_viewer.appendString(" all:\n");
+ file_viewer.appendString(" count: " + gene_count + "\n");
+ file_viewer.appendString(" partials: " + partial_count + "\n");
+ if(exon_count == gene_count)
+ {
+ file_viewer.appendString(" bases: "
+ + gene_bases_count + "\n");
+ }
+ else
+ {
+ file_viewer.appendString(" bases(excluding introns): "
+ + gene_bases_count + "\n");
+ file_viewer.appendString(" bases(including introns): "
+ + gene_bases_count_with_introns + "\n");
+ file_viewer.appendString(" exons: " + exon_count + "\n");
+ file_viewer.appendString(" average exon length: " +
+ 10L * gene_bases_count / exon_count / 10.0 + "\n");
+ file_viewer.appendString(" average intron length: " +
+ 10L * (gene_bases_count_with_introns -
+ gene_bases_count) /
+ (exon_count - gene_count) / 10.0 + "\n");
+ file_viewer.appendString(" average number of exons per gene: " +
+ 100L * exon_count / gene_count / 100.00 + "\n");
+ }
+ file_viewer.appendString(" density: " +
+ 1000000L * gene_count /
+ entry_group.getSequenceLength() / 1000.0 +
+ " genes per kb (" +
+ entry_group.getSequenceLength() / gene_count +
+ " bases per gene)\n");
+ file_viewer.appendString(" average length: " +
+ gene_bases_count / gene_count + "\n");
+ file_viewer.appendString(" average length (including introns): " +
+ gene_bases_count_with_introns / gene_count + "\n");
+ file_viewer.appendString(" coding percentage: " +
+ 1000L * gene_bases_count /
+ entry_group.getSequenceLength() / 10.0 + "\n");
+ file_viewer.appendString(" coding percentage (including introns): " +
+ 1000L * gene_bases_count_with_introns /
+ entry_group.getSequenceLength() / 10.0 + "\n\n");
+
+ file_viewer.appendString(" gene sequence composition:\n\n");
+
+ final StringVector gene_base_summary =
+ SelectionViewer.getBaseSummary(gene_bases);
+
+ for(int i = 0 ; i < gene_base_summary.size() ; ++i)
+ {
+ file_viewer.appendString(" ");
+ file_viewer.appendString(gene_base_summary.elementAt(i)+"\n");
+ }
+
+ file_viewer.appendString("\n");
+ }
+
+ if(pseudo_gene_count > 0)
+ {
+ file_viewer.appendString("Pseudo genes (CDS features with a /pseudo or /pseudogene qualifier):\n", Level.INFO);
+
+ file_viewer.appendString(" count: " + pseudo_gene_count + "\n");
+ file_viewer.appendString(" bases: " + pseudo_gene_bases_count + "\n");
+ file_viewer.appendString(" average length: " +
+ pseudo_gene_bases_count / pseudo_gene_count + "\n\n");
+ }
+
+ if(pseudo_gene_count > 0)
+ {
+ file_viewer.appendString("All CDS features:\n",Level.INFO);
+ file_viewer.appendString(" count: " + cds_count + "\n");
+ file_viewer.appendString(" bases: " + cds_bases_count + "\n");
+ file_viewer.appendString(" average length: " +
+ cds_bases_count / cds_count + "\n\n");
+ }
+ }
+
+ //Vector none = null;
+ for(int i=0; i<others_count.length; i++)
+ {
+ if(others_count[i]>0)
+ {
+ file_viewer.appendString(otherKeys[i]+":", Level.INFO);
+ file_viewer.appendString("\n bases(excluding introns): "
+ + other_bases_buffer[i].length() + "\n");
+ file_viewer.appendString(" bases(including introns): "
+ + other_bases_count_with_introns[i]);
+
+ file_viewer.appendString("\n\n "+otherKeys[i]+" sequence composition:\n\n");
+ final StringVector other_base_summary =
+ SelectionViewer.getBaseSummary(other_bases_buffer[i].toString());
+
+ for(int j = 0 ; j < other_base_summary.size() ; ++j)
+ {
+ file_viewer.appendString(" ");
+ file_viewer.appendString(other_base_summary.elementAt(j)+"\n");
+ }
+ file_viewer.appendString("\n");
+ }
+ }
+
+
+ final Strand strand;
+ if(strand_flag == Bases.FORWARD || strand_flag == BOTH)
+ strand = entry_group.getBases().getForwardStrand();
+ else
+ strand = entry_group.getBases().getReverseStrand();
+
+ final StringVector base_summary =
+ SelectionViewer.getBaseSummary(strand.getStrandBases());
+
+ file_viewer.appendString("\nOverall sequence composition:\n\n", Level.INFO);
+
+ for(int i = 0 ; i < base_summary.size() ; ++i)
+ file_viewer.appendString(base_summary.elementAt(i)+"\n");
+
+ file_viewer.appendString("\nSummary of the active entries:\n", Level.INFO);
+
+ final Enumeration<String> e = table.keys();
+
+ while(e.hasMoreElements())
+ {
+ final String this_key = e.nextElement();
+ final Hashtable<String, Integer> colour_table = table.get(this_key);
+
+ file_viewer.appendString(this_key + ": ");
+
+ final StringBuffer colour_string = new StringBuffer();
+ final Enumeration<String> colour_enum = colour_table.keys();
+
+ int total = 0;
+ while(colour_enum.hasMoreElements())
+ {
+ final String this_colour = colour_enum.nextElement();
+ final int colour_count = colour_table.get(this_colour);
+
+ total += colour_count;
+
+ final String end_string;
+ if(this_colour.equals("no colour"))
+ end_string = "no colour";
+ else
+ end_string = "colour: " + this_colour;
+
+ if(colour_count == 1)
+ colour_string.append(" one has " + end_string + "\n");
+ else
+ colour_string.append(" " + colour_count + " have " +
+ end_string + "\n");
+ }
+
+ file_viewer.appendString(total + "\n" + colour_string.toString());
+ }
+ }
+
+ private int containsKey(final String keys[], final String keyStr)
+ {
+ for(int i=0; i<keys.length; i++)
+ {
+ if(keys[i].equals(keyStr))
+ return i;
+ }
+ return -1;
+ }
+
+ public static void main(final String args[])
+ {
+ final FileDialogEntrySource entry_source = new FileDialogEntrySource(null, null);
+
+ try
+ {
+ final Entry entry = entry_source.getEntry(true);
+ final EntryGroup entry_group =
+ new SimpleEntryGroup(entry.getBases());
+ entry_group.add(entry);
+ new EntryGroupInfoDisplay(new JFrame(), entry_group);
+ }
+ catch(OutOfRangeException e) {}
+ catch(NoSequenceException e) {}
+
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/EntryGroupMenu.java b/uk/ac/sanger/artemis/components/EntryGroupMenu.java
new file mode 100644
index 0000000..3722e74
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EntryGroupMenu.java
@@ -0,0 +1,336 @@
+/* EntryGroupMenu.java
+ *
+ * created: Fri Aug 27 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryGroupMenu.java,v 1.2 2004-12-03 18:11:28 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+import java.util.*;
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+/**
+ * A menu containing the current entries in an EntryGroup.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryGroupMenu.java,v 1.2 2004-12-03 18:11:28 tjc Exp $
+ **/
+
+public class EntryGroupMenu extends JMenu
+ implements EntryGroupChangeListener, EntryChangeListener {
+ /**
+ * Create a new EntryGroupMenu object that will show the entries in the
+ * given EntryGroup.
+ * @param frame The JFrame that owns this JMenu.
+ * @param entry_group The EntryGroup object to display.
+ * @param menu_name The name of the new menu.
+ **/
+ public EntryGroupMenu (final JFrame frame,
+ final EntryGroup entry_group,
+ final String menu_name) {
+ super (menu_name);
+
+ this.frame = frame;
+ this.entry_group = entry_group;
+
+ entry_group.addEntryGroupChangeListener (this);
+ entry_group.addEntryChangeListener (this);
+
+ refreshMenu ();
+ }
+
+ /**
+ * Create a new EntryGroupMenu object that will show the entries in the
+ * given EntryGroup.
+ * @param frame The JFrame that owns this JMenu.
+ * @param entry_group The EntryGroup object to display.
+ **/
+ public EntryGroupMenu (final JFrame frame,
+ final EntryGroup entry_group) {
+ this (frame, entry_group, "Entries");
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can update the display if entries
+ * are added or deleted.
+ **/
+ public void entryGroupChanged (final EntryGroupChangeEvent event) {
+ switch (event.getType ()) {
+ case EntryGroupChangeEvent.ENTRY_ADDED:
+ case EntryGroupChangeEvent.ENTRY_DELETED:
+ case EntryGroupChangeEvent.ENTRY_INACTIVE:
+ case EntryGroupChangeEvent.ENTRY_ACTIVE:
+ case EntryGroupChangeEvent.NEW_DEFAULT_ENTRY:
+ refreshMenu ();
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface.
+ **/
+ public void entryChanged (final EntryChangeEvent event) {
+ if (event.getType () == EntryChangeEvent.NAME_CHANGED) {
+ refreshMenu ();
+ }
+ }
+
+ /**
+ * Update the menus to the reflect the current contents of the EntryGroup.
+ **/
+ private void refreshMenu () {
+ removeAll ();
+
+ if (entry_group == null || entry_group.size () == 0) {
+ add (new JMenuItem ("(No Entries Currently)"));
+ return;
+ }
+
+ final JMenu set_entry_name_menu = new JMenu ("Set Name Of Entry");
+
+ add (set_entry_name_menu);
+
+ final JMenu set_default_menu = new JMenu ("Set Default Entry");
+ final JMenu delete_entry_menu = new JMenu ("Remove An Entry");
+
+ addSeparator ();
+
+ add (set_default_menu);
+ add (delete_entry_menu);
+
+ final JMenuItem delete_active_entries_menu =
+ new JMenuItem ("Remove Active Entries");
+
+ delete_active_entries_menu.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ deleteActiveEntries ();
+ }
+ });
+
+ add (delete_active_entries_menu);
+
+ final JMenuItem deactivate_all = new JMenuItem ("Deactivate All Entries");
+
+ deactivate_all.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ for (int i = 0 ; i < entry_group.size () ; ++i) {
+ final Entry this_entry = entry_group.elementAt (i);
+
+ entry_group.setIsActive (this_entry, false);
+ }
+ }
+ });
+
+ add (deactivate_all);
+
+ entry_components = new Vector ();
+
+ addSeparator ();
+
+ for (int i = 0 ; i < entry_group.size () ; ++i) {
+ final Entry this_entry = entry_group.elementAt (i);
+
+ String entry_name = this_entry.getName ();
+
+ if (entry_name == null) {
+ entry_name = "no name";
+ }
+
+ final JMenuItem set_entry_name_item = new JMenuItem (entry_name);
+ set_entry_name_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ setEntryName (this_entry);
+ }
+ });
+
+ set_entry_name_menu.add (set_entry_name_item);
+
+ final JMenuItem delete_entry_item = new JMenuItem (entry_name);
+ delete_entry_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ if (this_entry.hasUnsavedChanges ()) {
+ final String message;
+
+ if (this_entry.getName () != null) {
+ message = "there are unsaved changes in " +
+ this_entry.getName () + " - really remove?";
+ } else {
+ message = "there are unsaved changes in entry #" +
+ entry_group.indexOf (this_entry) + " - really remove?";
+ }
+
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog (frame, message);
+
+ if (!yes_no_dialog.getResult ()) {
+ return;
+ }
+ }
+ entry_group.remove (this_entry);
+ }
+ });
+
+ delete_entry_menu.add (delete_entry_item);
+
+ final JMenuItem set_default_entry_item = new JMenuItem (entry_name);
+ set_default_entry_item.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ entry_group.setDefaultEntry (this_entry);
+ }
+ });
+
+ set_default_menu.add (set_default_entry_item);
+
+ add (entry_group.elementAt (i));
+ }
+ }
+
+ /**
+ * Add a Label or Checkbox for the given Entry to this component.
+ **/
+ private void add (final Entry entry) {
+ String entry_name = entry.getName ();
+
+ if (entry_name == null) {
+ entry_name = "no name";
+ }
+
+ if (entry_group.getDefaultEntry () == entry) {
+ entry_name = entry_name + " (default entry)";
+ }
+
+ final JCheckBoxMenuItem new_component =
+ new JCheckBoxMenuItem (entry_name, entry_group.isActive (entry));
+
+ new_component.addItemListener (new ItemListener () {
+ public void itemStateChanged (ItemEvent event) {
+ final int button_index =
+ entry_components.indexOf (event.getSource ());
+
+ if (event.getStateChange () == ItemEvent.SELECTED) {
+ entry_group.setIsActive (button_index, true);
+ } else {
+ entry_group.setIsActive (button_index, false);
+ }
+ }
+ });
+
+ entry_components.addElement (new_component);
+ add (new_component);
+ }
+
+ /**
+ * Create a new TextRequester component and set the name of the default
+ * Entry to whatever the user types.
+ **/
+ private void setEntryName (final Entry entry) {
+ final TextRequester text_requester =
+ new TextRequester ("New name for the entry?", 18, "");
+
+ text_requester.addTextRequesterListener (new TextRequesterListener () {
+ public void actionPerformed (final TextRequesterEvent event) {
+ if (event.getType () == TextRequesterEvent.CANCEL) {
+ return;
+ }
+
+ final String requester_text = event.getRequesterText ().trim ();
+ if (requester_text.length () > 0) {
+ if (entry.setName (requester_text)) {
+ // it worked
+ } else {
+ new MessageDialog (frame,
+ "could not set the name of the default entry");
+ }
+ }
+ }
+ });
+
+ text_requester.setVisible(true);
+ }
+
+ /**
+ * Delete the active entries after asking the user.
+ **/
+ private void deleteActiveEntries () {
+ if (Options.getOptions ().isNoddyMode ()) {
+ final YesNoDialog dialog =
+ new YesNoDialog (frame,
+ "Are you sure you want to remove the " +
+ "active entries?");
+
+ if (!dialog.getResult ()) {
+ return;
+ }
+ }
+
+ for (int i = entry_group.size () - 1 ; i >= 0 ; --i) {
+ final Entry this_entry = entry_group.elementAt (i);
+
+ if (this_entry.hasUnsavedChanges ()) {
+ final String message;
+
+ if (this_entry.getName () != null) {
+ message = "there are unsaved changes in " +
+ this_entry.getName () + " - really remove?";
+ } else {
+ message = "there are unsaved changes in entry #" +
+ (i + 1) + " - really remove?";
+ }
+
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog (frame, message);
+
+ if (!yes_no_dialog.getResult ()) {
+ continue;
+ }
+ }
+
+ if (entry_group.isActive (this_entry)) {
+ entry_group.remove (this_entry);
+ }
+ }
+ }
+
+ /**
+ * A vector containing the Entry objects that this EntryEdit object knows
+ * about. This reference is obtained from owning_component.
+ **/
+ private EntryGroup entry_group;
+
+ /**
+ * A vector containing one Checkbox or Label for each Entry in the
+ * EntryGroup object.
+ **/
+ private Vector entry_components = new Vector ();
+
+ /**
+ * The JFrame reference that was passed to the constructor.
+ **/
+ private JFrame frame = null;
+}
+
diff --git a/uk/ac/sanger/artemis/components/EntryGroupPanel.java b/uk/ac/sanger/artemis/components/EntryGroupPanel.java
new file mode 100644
index 0000000..6df1913
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EntryGroupPanel.java
@@ -0,0 +1,410 @@
+/* EntryGroupPanel.java
+ *
+ * created: Wed Jun 21 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryGroupPanel.java,v 1.9 2008-10-08 15:31:48 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.sequence.*;
+
+import java.awt.Component;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+ * A JPanel that can show an EntryGroup(in some way).
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: EntryGroupPanel.java,v 1.9 2008-10-08 15:31:48 tjc Exp $
+ **/
+
+abstract public class EntryGroupPanel extends CanvasPanel
+{
+ /** shortcut for View FASTA in browser. */
+ final int fasta_in_browser_key = KeyEvent.VK_F;
+
+ /** shortcut for View FASTA. */
+ final int view_fasta_key = KeyEvent.VK_R;
+
+ /** shortcut for View BLASTP in browser. */
+ final int blastp_in_browser_key = KeyEvent.VK_B;
+
+ /** shortcut for View BLASTP. */
+ final int view_blastp_key = KeyEvent.VK_TAB;
+
+ /** EntryGroup this component is displaying */
+ private EntryGroup entry_group;
+
+ /**
+ * This is a reference to the Selection object that was passed to the
+ * constructor.
+ **/
+ private Selection selection;
+
+ /**
+ * This is a reference to the GotoEventSource object that was passed to the
+ * constructor.
+ **/
+ private GotoEventSource goto_event_source;
+
+ /**
+ * The BasePlotGroup object that was passed to the constructor.
+ **/
+ private BasePlotGroup base_plot_group;
+
+ /**
+ * Create a new EntryGroupPanel for the given EntryGroup.
+ * @param entry_group The EntryGroup that this component will display.
+ * @param selection The Selection object for this component. Selected
+ * objects will be highlighted.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ public EntryGroupPanel(final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group)
+ {
+ this.entry_group = entry_group;
+ this.selection = selection;
+ this.goto_event_source = goto_event_source;
+ this.base_plot_group = base_plot_group;
+
+ addKeyListener(new KeyAdapter()
+ {
+ public void keyPressed(final KeyEvent event)
+ {
+ handleKeyPress(event);
+ }
+ });
+ }
+
+ /**
+ * Return an object that implements the GotoEventSource interface and which
+ * is relative to the sequence that this DisplayComponent is displaying.
+ **/
+ public GotoEventSource getGotoEventSource()
+ {
+ return goto_event_source;
+ }
+
+ /**
+ * Walk up through parents of this JComponent and return the JFrame at the
+ * top.
+ **/
+ public JFrame getParentFrame()
+ {
+ Component parent = this.getParent();
+
+ while(parent != null && !(parent instanceof JFrame))
+ parent = parent.getParent();
+
+ return (JFrame)parent;
+ }
+
+ /**
+ * Return the EntryGroup object that this FeatureDisplay is displaying.
+ **/
+ public EntryGroup getEntryGroup()
+ {
+ return entry_group;
+ }
+
+ /**
+ * Returns the Selection that was passed to the constructor.
+ **/
+ public Selection getSelection()
+ {
+ return selection;
+ }
+
+ /**
+ * Returns the BasePlotGroup that was passed to the constructor.
+ **/
+ public BasePlotGroup getBasePlotGroup()
+ {
+ return base_plot_group;
+ }
+
+ /**
+ * This method sends an GotoEvent to all the GotoEvent listeners that will
+ * make the start of the first selected Feature or FeatureSegment visible.
+ **/
+ protected void makeSelectionVisible()
+ {
+ final Marker first_base = getSelection().getStartBaseOfSelection();
+
+ final GotoEvent new_event = new GotoEvent(this, first_base);
+
+ getGotoEventSource().sendGotoEvent(new_event);
+ }
+
+ /**
+ * Return true if and only if the given MouseEvent(a mouse press) should
+ * pop up a JPopupMenu.
+ **/
+ protected boolean isMenuTrigger(final MouseEvent event)
+ {
+ if(event.isPopupTrigger() ||
+ (event.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Handle key press events.
+ **/
+ private void handleKeyPress(final KeyEvent event)
+ {
+ // this is done so that menu shortcuts don't cause each action to be
+ // performed twice
+ if(!event.isShiftDown() && event.getModifiers() != 0)
+ {
+ if(getParentFrame() instanceof MultiComparator &&
+ event.getModifiers() == InputEvent.ALT_MASK)
+ {
+ int event_key_code = event.getKeyCode();
+ switch(event_key_code)
+ {
+ case EditMenu.TRIM_FEATURES_KEY_CODE:
+ EditMenu.trimSelected(getParentFrame(), getSelection(),
+ getEntryGroup(), false, true);
+ break;
+ case EditMenu.TRIM_FEATURES_TO_NEXT_ANY_KEY_CODE:
+ EditMenu.trimSelected(getParentFrame(), getSelection(),
+ getEntryGroup(), true, true);
+ break;
+ case EditMenu.EXTEND_TO_PREVIOUS_STOP_CODON_KEY_CODE:
+ EditMenu.extendToORF(getParentFrame(), getSelection(),
+ getEntryGroup(), false);
+ break;
+ case EditMenu.UNDO_KEY_CODE:
+ EditMenu.undo(getParentFrame(), getSelection(), getEntryGroup());
+ break;
+ }
+ }
+ return;
+ }
+
+ final FeatureVector selected_features = getSelection().getAllFeatures();
+
+ if(selected_features.size() == 0)
+ {
+ final MarkerRange marker_range = getSelection().getMarkerRange();
+
+ if(marker_range != null)
+ {
+ switch(event.getKeyCode())
+ {
+ case AddMenu.CREATE_FROM_BASE_RANGE_KEY_CODE:
+ if(!GeneUtils.isDatabaseEntry(entry_group))
+ AddMenu.createFeatureFromBaseRange(getParentFrame(),
+ getSelection(),
+ entry_group,
+ getGotoEventSource());
+ else
+ {
+ entry_group.getActionController ().startAction ();
+ GeneUtils.createGeneModel(getParentFrame(), getSelection(),
+ entry_group, getGotoEventSource());
+ entry_group.getActionController ().endAction ();
+ }
+ break;
+ }
+ }
+
+ }
+ else
+ {
+ final Feature first_selected_feature = selected_features.elementAt(0);
+
+ final int feature_index =
+ getEntryGroup().indexOf(first_selected_feature);
+
+ final int event_key_code = event.getKeyCode();
+
+ switch(event_key_code)
+ {
+ case KeyEvent.VK_UP:
+ case KeyEvent.VK_DOWN:
+ case KeyEvent.VK_LEFT:
+ case KeyEvent.VK_RIGHT:
+ {
+ if((event_key_code == KeyEvent.VK_LEFT ||
+ event_key_code == KeyEvent.VK_RIGHT) &&
+ !event.isShiftDown())
+ break;
+
+ final FeatureSegmentVector selected_segments =
+ getSelection().getSelectedSegments();
+
+ if(event.isShiftDown())
+ {
+ if(selected_segments.size() > 0)
+ {
+ final FeatureSegment selected_segment =
+ selected_segments.elementAt(0);
+
+ final Feature selected_segment_feature =
+ selected_segment.getFeature();
+
+ final FeatureSegmentVector feature_segments =
+ selected_segment_feature.getSegments();
+
+ final int segment_index =
+ feature_segments.indexOf(selected_segment);
+
+ if(event_key_code == KeyEvent.VK_UP ||
+ event_key_code == KeyEvent.VK_LEFT &&
+ !selected_segment_feature.isForwardFeature() ||
+ event_key_code == KeyEvent.VK_RIGHT &&
+ selected_segment_feature.isForwardFeature())
+ {
+ final int segment_count = feature_segments.size();
+ final int new_index = segment_index + 1;
+ if(segment_index < segment_count - 1)
+ selection.set(feature_segments.elementAt(new_index));
+ }
+ else
+ {
+ if(segment_index > 0)
+ {
+ final int new_index = segment_index - 1;
+ selection.set(feature_segments.elementAt(new_index));
+ }
+ }
+ }
+ }
+ else
+ {
+ if(event_key_code == KeyEvent.VK_DOWN)
+ {
+ if(feature_index <
+ getEntryGroup().getAllFeaturesCount() - 1)
+ {
+ selection.set(getEntryGroup().featureAt(feature_index + 1));
+ makeSelectionVisible();
+ }
+ }
+ else
+ {
+ if(feature_index > 0)
+ {
+ selection.set(getEntryGroup().featureAt(feature_index - 1));
+ makeSelectionVisible();
+ }
+ }
+ }
+ }
+ break;
+ case EditMenu.EDIT_FEATURES_KEY_CODE:
+ EditMenu.editSelectedFeatures(getParentFrame(),
+ getEntryGroup(),
+ getSelection(),
+ getGotoEventSource());
+ break;
+// case EditMenu.UNDO_KEY_CODE:
+// EditMenu.undo(getParentFrame(), getSelection(), getEntryGroup());
+// break;
+ case EditMenu.MERGE_FEATURES_KEY_CODE:
+ EditMenu.mergeFeatures(getParentFrame(), getSelection(),
+ getEntryGroup());
+ break;
+ case EditMenu.DUPLICATE_KEY_CODE:
+ EditMenu.duplicateFeatures(getParentFrame(), getSelection(),
+ getEntryGroup());
+ break;
+ case EditMenu.DELETE_FEATURES_KEY_CODE:
+ EditMenu.deleteSelectedFeatures(getParentFrame(), getSelection(),
+ getEntryGroup());
+ break;
+// case EditMenu.TRIM_FEATURES_KEY_CODE:
+// EditMenu.trimSelected(getParentFrame(), getSelection(),
+// getEntryGroup(), false, true);
+// break;
+// case EditMenu.TRIM_FEATURES_TO_NEXT_ANY_KEY_CODE:
+// EditMenu.trimSelected(getParentFrame(), getSelection(),
+// getEntryGroup(), true, true);
+// break;
+// case EditMenu.EXTEND_TO_PREVIOUS_STOP_CODON_KEY_CODE:
+// EditMenu.extendToORF(getParentFrame(), getSelection(),
+// getEntryGroup(), false);
+// break;
+ case AddMenu.CREATE_FROM_BASE_RANGE_KEY_CODE:
+ AddMenu.createFeatureFromBaseRange(getParentFrame(), getSelection(),
+ entry_group,
+ getGotoEventSource());
+ break;
+ case ViewMenu.VIEW_FEATURES_KEY_CODE:
+ ViewMenu.viewSelectedFeatures(getParentFrame(), getSelection());
+ break;
+ case ViewMenu.PLOT_FEATURES_KEY_CODE:
+ ViewMenu.plotSelectedFeatures(getParentFrame(), getSelection());
+ break;
+ case ViewMenu.OVERVIEW_KEY_CODE:
+ new EntryGroupInfoDisplay(getParentFrame(), entry_group);
+ break;
+ case ViewMenu.FASTA_IN_BROWSER_KEY_CODE:
+ viewResults("fasta", true);
+ break;
+ case ViewMenu.BLASTP_IN_BROWSER_KEY_CODE:
+ viewResults("blastp", true);
+ break;
+ case ViewMenu.VIEW_FASTA_KEY_CODE:
+ viewResults("fasta", false);
+ break;
+ case ViewMenu.VIEW_BLASTP_KEY_CODE:
+ viewResults("blastp", false);
+ break;
+ case ViewMenu.VIEW_HTH_KEY_CODE:
+ viewResults("hth", false);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Show the output file from an external program (like fasta) for the
+ * selected Feature objects. The name of the file to read is stored in a
+ * feature qualifier. The qualifier used is the program name plus "_file".
+ * @param send_to_browser if true the results should be sent straight to
+ * the web browser rather than using a SearchResultViewer object.
+ **/
+ private void viewResults(final String program_name,
+ final boolean send_to_browser)
+ {
+ final boolean sanger_options =
+ Options.getOptions().getPropertyTruthValue("sanger_options");
+
+ if(sanger_options && send_to_browser)
+ ViewMenu.viewExternalResults(getParentFrame(), getSelection(),
+ program_name, true);
+ else
+ ViewMenu.viewExternalResults(getParentFrame(), getSelection(),
+ program_name, false);
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/EntryHeaderEdit.java b/uk/ac/sanger/artemis/components/EntryHeaderEdit.java
new file mode 100644
index 0000000..8f29c1c
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/EntryHeaderEdit.java
@@ -0,0 +1,205 @@
+/* EntryHeaderEdit.java
+ *
+ * created: Wed Jun 16 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/EntryHeaderEdit.java,v 1.2 2007-03-23 14:15:54 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+
+/**
+ * Objects of this class are used to edit the header of an Entry.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryHeaderEdit.java,v 1.2 2007-03-23 14:15:54 tjc Exp $
+ **/
+
+public class EntryHeaderEdit
+ implements EntryChangeListener, EntryGroupChangeListener,
+ DocumentListener {
+ /**
+ * Create a new EntryHeaderEdit object for the given Entry.
+ **/
+ public EntryHeaderEdit (final EntryGroup entry_group,
+ final Entry edit_entry) {
+
+ this.edit_entry = edit_entry;
+ this.entry_group = entry_group;
+
+ file_viewer = new FileViewer ("Artemis Entry Header Editor: " +
+ edit_entry.getName () == null ?
+ "" : edit_entry.getName ());
+
+ file_viewer.getContentPane ().add (error_text, "North");
+
+ readHeader ();
+
+ file_viewer.getTextPane().setEditable (true);
+ file_viewer.getTextPane().getDocument ().addDocumentListener (this);
+
+ getEntry ().addEntryChangeListener (this);
+ entry_group.addEntryGroupChangeListener (this);
+
+ file_viewer.addWindowListener (new WindowAdapter () {
+ public void windowClosed (WindowEvent event) {
+ stopListening ();
+ }
+ });
+ }
+
+
+ /**
+ * Remove this object as a feature and entry change listener.
+ **/
+ public void stopListening () {
+ getEntry ().removeEntryChangeListener (this);
+ entry_group.removeEntryGroupChangeListener (this);
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so we can update this component if the header changes
+ * for another reason.
+ **/
+ public void entryChanged (EntryChangeEvent event) {
+ switch (event.getType ()) {
+ case EntryChangeEvent.HEADER_CHANGED:
+
+ if (event.getSource () == current_text) {
+ // don't bother with events from us
+ return;
+ }
+
+ // re-read the information from the entry
+ readHeader ();
+ break;
+ default:
+ // do nothing
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so we can notify the user if of this component
+ * if the entry gets deleted.
+ **/
+ public void entryGroupChanged (final EntryGroupChangeEvent event) {
+ switch (event.getType ()) {
+ case EntryGroupChangeEvent.ENTRY_DELETED:
+ if (event.getEntry () == edit_entry) {
+ stopListening ();
+ file_viewer.dispose ();
+ }
+ break;
+ default:
+ // do nothing
+ break;
+ }
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ textValueChanged ();
+ }
+ public void insertUpdate(DocumentEvent e) {
+ textValueChanged ();
+ }
+ public void removeUpdate(DocumentEvent e) {
+ textValueChanged ();
+ }
+
+ /**
+ * Implementation of the TextListener interface. When the text changes we
+ * update the Feature object that we are showing.
+ **/
+ public void textValueChanged () {
+ if (current_text.equals (file_viewer.getText ())) {
+ // the text hasn't really changed
+ return;
+ }
+
+ current_text = file_viewer.getText ();
+
+ try {
+ // ignore text change events while reading
+ edit_entry.setHeaderText (current_text);
+ error_text.setText ("");
+ } catch (uk.ac.sanger.artemis.io.ReadFormatException e) {
+ error_text.setText (e + (e.getLineNumber () > 1 ?
+ " at line: " + e.getLineNumber () :
+ ""));
+ } catch (java.io.IOException e) {
+ error_text.setText (e.toString ());
+ }
+ }
+
+ /**
+ * Read the header of edit_entry into this component.
+ **/
+ public void readHeader () {
+ final String header = edit_entry.getHeaderText ();
+
+ if (header != null) {
+ file_viewer.setText (header);
+
+ current_text = file_viewer.getText ();
+ }
+ }
+
+ /**
+ * Return the Entry that this object is displaying.
+ **/
+ private Entry getEntry () {
+ return edit_entry;
+ }
+
+ /**
+ * The EntryGroup that passed to the constructor.
+ **/
+ private EntryGroup entry_group;
+
+ /**
+ * The Entry that contains the Feature this object is displaying.
+ **/
+ private Entry edit_entry;
+
+ /**
+ * The FileViewer object that is displaying the feature.
+ **/
+ private FileViewer file_viewer;
+
+ /**
+ * The text version of the Feature we are currently displaying.
+ **/
+ private String current_text = "";
+
+ /**
+ * A Label for showing errors and messages.
+ **/
+ private JLabel error_text = new JLabel ("");
+}
+
diff --git a/uk/ac/sanger/artemis/components/ExternalProgramOptions.java b/uk/ac/sanger/artemis/components/ExternalProgramOptions.java
new file mode 100644
index 0000000..f159b9a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ExternalProgramOptions.java
@@ -0,0 +1,64 @@
+/* ExternalProgramOptions.java
+ *
+ * created: Mon Oct 4 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ExternalProgramOptions.java,v 1.3 2009-05-13 15:45:04 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+/**
+ * This component allows the user to set the options of an ExternalProgram.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ExternalProgramOptions.java,v 1.3 2009-05-13 15:45:04 tjc Exp $
+ **/
+
+public class ExternalProgramOptions {
+ /**
+ * Create a new ExternalProgramOptions object for the given
+ * ExternalProgram.
+ **/
+ public ExternalProgramOptions (final ExternalProgram external_program) {
+ if (external_program.getType () == ExternalProgram.AA_PROGRAM ||
+ external_program.getType () == ExternalProgram.DNA_PROGRAM) {
+
+ final TextRequester requester =
+ new TextRequester ("Options for " + external_program.getName () + ":",
+ 18, external_program.getProgramOptions ());
+
+ requester.addTextRequesterListener (new TextRequesterListener () {
+ public void actionPerformed (final TextRequesterEvent event) {
+ final String requester_text = event.getRequesterText ().trim ();
+ if (requester_text.length () > 0) {
+ external_program.setProgramOptions (requester_text);
+ }
+ }
+ });
+
+ requester.setVisible(true);
+ } else {
+ throw new Error ("internal error - please hit the programmer");
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/FeatureAminoAcidViewer.java b/uk/ac/sanger/artemis/components/FeatureAminoAcidViewer.java
new file mode 100644
index 0000000..02254ea
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeatureAminoAcidViewer.java
@@ -0,0 +1,166 @@
+/* FeatureAminoAcidViewer.java
+ *
+ * created: Sat Dec 19 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureAminoAcidViewer.java,v 1.2 2008-11-12 16:49:58 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import java.awt.event.*;
+
+/**
+ * A component for viewing the amino acids of a protein feature. Once
+ * created this component listens for FeatureChange events to keep the the
+ * sequence up to date.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureAminoAcidViewer.java,v 1.2 2008-11-12 16:49:58 tjc Exp $
+ **/
+
+public class FeatureAminoAcidViewer
+ implements EntryChangeListener, FeatureChangeListener {
+ /**
+ * Create a new FeatureAminoAcidViewer component to display the amino acids
+ * of the given Feature.
+ * @param feature The feature to view.
+ * @param include_numbers If true then the amino acids will be numbered
+ * (every second line of the display will be numbers rather than
+ * sequence).
+ **/
+ public FeatureAminoAcidViewer (final Feature feature,
+ final boolean include_numbers) {
+ this.feature = feature;
+ this.entry = feature.getEntry ();
+
+ sequence_viewer =
+ new SequenceViewer ("Feature base viewer for feature:" +
+ getFeature ().getIDString (), include_numbers);
+
+ redisplay ();
+
+ getFeature ().getEntry ().addEntryChangeListener (this);
+ getFeature ().addFeatureChangeListener (this);
+
+ sequence_viewer.addWindowListener (new WindowAdapter () {
+ public void windowClosed (WindowEvent event) {
+ stopListening ();
+ }
+ });
+ }
+
+ /**
+ * Remove this object as a entry and feature change listener.
+ **/
+ private void stopListening () {
+ getEntry ().removeEntryChangeListener (this);
+ getFeature ().removeFeatureChangeListener (this);
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so we can delete this component if the feature gets
+ * deleted.
+ **/
+ public void entryChanged (EntryChangeEvent event) {
+ switch (event.getType ()) {
+ case EntryChangeEvent.FEATURE_DELETED:
+ if (event.getFeature () == getFeature ()) {
+ stopListening ();
+ sequence_viewer.dispose ();
+ }
+ break;
+ default:
+ // do nothing;
+ break;
+ }
+ }
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events from the Features in this object so that
+ * we can keep the display up to date.
+ * @param event The change event.
+ **/
+ public void featureChanged (FeatureChangeEvent event) {
+// System.out.println ("FeatureViewer: feature change event type: " +
+// event.getType ());
+
+ // re-read the information from the feature
+ redisplay ();
+ }
+
+ /**
+ * Redisplay the amino acids.
+ **/
+ private void redisplay () {
+ String small_note = getFeature ().getNote ();
+
+ if (small_note == null) {
+ small_note = getFeature ().getIDString ();
+ } else {
+ if (small_note.length () > 50) {
+ small_note = small_note.substring (0, 50);
+ }
+ }
+
+ final String comment = ">" + small_note + " - " +
+ getFeature ().getFirstBase () + ": " +
+ getFeature ().getLastBase () + " MW: " +
+ getFeature ().getMolecularWeight ();
+
+ final String sequence =
+ getFeature ().getTranslation ().toString ().toUpperCase ();
+
+ sequence_viewer.setSequence (comment, sequence);
+ }
+
+
+ /**
+ * Return the feature this component is showing information about.
+ **/
+ private Feature getFeature () {
+ return feature;
+ }
+
+ /**
+ * Return the Entry that contains the Feature this object is displaying.
+ **/
+ private Entry getEntry () {
+ return entry;
+ }
+
+ /**
+ * The Feature that this component is showing information about.
+ **/
+ private Feature feature = null;
+
+ /**
+ * The SequenceViewer object that is displaying the feature bases.
+ **/
+ private SequenceViewer sequence_viewer;
+
+ /**
+ * The Entry that contains the Feature this object is displaying.
+ **/
+ private Entry entry;
+
+}
diff --git a/uk/ac/sanger/artemis/components/FeatureBaseViewer.java b/uk/ac/sanger/artemis/components/FeatureBaseViewer.java
new file mode 100644
index 0000000..5e08129
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeatureBaseViewer.java
@@ -0,0 +1,177 @@
+/* FeatureBaseViewer.java
+ *
+ * created: Sat Dec 19 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureBaseViewer.java,v 1.2 2008-12-16 11:46:15 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import uk.ac.sanger.artemis.*;
+
+/**
+ * A component for viewing the bases of a feature. Once created this
+ * component listens for FeatureChange events to keep the the sequence up to
+ * date.
+ * @author Kim Rutherford
+ **/
+
+public class FeatureBaseViewer
+ implements EntryChangeListener, FeatureChangeListener
+{
+ /** The Feature that this component is showing information about. */
+ private Feature feature = null;
+
+ /** The SequenceViewer object that is displaying the feature bases. */
+ private SequenceViewer sequence_viewer;
+
+ /** The Entry that contains the Feature this object is displaying. */
+ private Entry entry;
+
+ private FeatureSegmentVector segments;
+
+ /**
+ * Create a new FeatureBaseViewer component to display the bases of the
+ * given Feature.
+ * @param feature The feature to view.
+ * @param include_numbers If true then the sequence will be numbered
+ * (every second line of the display will be numbers rather than
+ * sequence).
+ **/
+ public FeatureBaseViewer (final Feature feature,
+ final boolean include_numbers,
+ final FeatureSegmentVector segments)
+ {
+ this.feature = feature;
+ this.entry = feature.getEntry ();
+ this.segments = segments;
+
+ sequence_viewer =
+ new SequenceViewer ("Feature base viewer for feature:" +
+ getFeature ().getIDString (), include_numbers);
+ redisplay ();
+ getFeature ().getEntry ().addEntryChangeListener (this);
+ getFeature ().addFeatureChangeListener (this);
+ sequence_viewer.addWindowListener (new WindowAdapter ()
+ {
+ public void windowClosed (WindowEvent event)
+ {
+ stopListening ();
+ }
+ });
+ }
+
+ /**
+ * Remove this object as a entry and feature change listener.
+ **/
+ private void stopListening ()
+ {
+ getEntry ().removeEntryChangeListener (this);
+ getFeature ().removeFeatureChangeListener (this);
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so we can delete this component if the feature gets
+ * deleted.
+ **/
+ public void entryChanged (EntryChangeEvent event)
+ {
+ switch (event.getType ())
+ {
+ case EntryChangeEvent.FEATURE_DELETED:
+ if (event.getFeature () == getFeature ())
+ {
+ stopListening ();
+ sequence_viewer.dispose ();
+ }
+ break;
+ default:
+ // do nothing;
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events from the Features in this object so that
+ * we can keep the display up to date.
+ * @param event The change event.
+ **/
+ public void featureChanged (FeatureChangeEvent event)
+ {
+ // re-read the information from the feature
+ redisplay ();
+ }
+
+ /**
+ * Redisplay the bases.
+ **/
+ private void redisplay ()
+ {
+ final String product = getFeature().getProductString();
+ final StringBuilder hdr = new StringBuilder();
+ hdr.append(getFeature().getSystematicName()).append(" ");
+ hdr.append(getFeature().getIDString()).append(" ");
+ hdr.append(product == null ? "undefined product" : product);
+
+ final String bases;
+ if(segments != null) // display just for selected segments
+ {
+ final StringBuilder buffer = new StringBuilder();
+ segments.sortByPosition();
+ for(int i = 0; i < segments.size(); ++i)
+ {
+ final FeatureSegment segment = segments.elementAt(i);
+ buffer.append(segment.getBases());
+ hdr.append(i == 0 ? " " : ",").append(segment.getRawRange().toString());
+ }
+ hdr.append(getFeature ().isForwardFeature() ? " forward" : " reverse");
+ bases = buffer.toString();
+ }
+ else
+ {
+ hdr.append(" ").append(getFeature().getWriteRange());
+ bases = getFeature ().getBases ();
+ }
+
+ sequence_viewer.setSequence (">" + hdr.toString(), bases.toUpperCase ());
+ }
+
+ /**
+ * Return the feature this component is showing information about.
+ **/
+ private Feature getFeature ()
+ {
+ return feature;
+ }
+
+ /**
+ * Return the Entry that contains the Feature this object is displaying.
+ **/
+ private Entry getEntry ()
+ {
+ return entry;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/FeatureDisplay.java b/uk/ac/sanger/artemis/components/FeatureDisplay.java
new file mode 100644
index 0000000..b64d1a4
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeatureDisplay.java
@@ -0,0 +1,5656 @@
+/* FeatureDisplay.java
+ *
+ * created: Fri Oct 9 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureDisplay.java,v 1.66 2009-06-12 13:50:35 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.components.filetree.*;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.RemoteFileDocument;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.GFFUtils;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.SimpleEntryInformation;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.RawStreamSequence;
+import uk.ac.sanger.artemis.io.FastaStreamSequence;
+import uk.ac.sanger.artemis.io.Sequence;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.components.genebuilder.*;
+
+import java.io.IOException;
+import java.io.File;
+import java.awt.event.*;
+import java.awt.*;
+import java.lang.Math;
+import java.util.Vector;
+import java.util.Comparator;
+import javax.swing.border.Border;
+import javax.swing.border.BevelBorder;
+
+import java.awt.datatransfer.*;
+import java.awt.dnd.*;
+import javax.swing.Box;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollBar;
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+
+import org.apache.batik.svggen.SVGGraphics2D;
+
+/**
+ * This component is used for displaying an Entry.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureDisplay.java,v 1.66 2009-06-12 13:50:35 tjc Exp $
+ **/
+
+public class FeatureDisplay extends EntryGroupPanel
+ implements EntryGroupChangeListener,
+ EntryChangeListener, FeatureChangeListener,
+ SelectionChangeListener, GotoListener, SequenceChangeListener,
+ DisplayComponent, OptionChangeListener, DisplayAdjustmentListener,
+ DragGestureListener, DropTargetListener,
+ DragSourceListener
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ private int highlight_drop_base = -1;
+
+ /** Key code for calling zoomToSelection(). */
+ private final static int ZOOM_TO_SELECTION_KEY = KeyEvent.VK_Z;
+ private final static int ARROW_LEFT = KeyEvent.VK_LEFT;
+ private final static int ARROW_RIGHT = KeyEvent.VK_RIGHT;
+
+ protected final static int SCROLLBAR_AT_TOP = 1;
+ protected final static int SCROLLBAR_AT_BOTTOM = 2;
+
+ private final static int FORWARD = Bases.FORWARD;
+ private final static int REVERSE = Bases.REVERSE;
+
+ private final static int NO_FRAME = FeatureSegment.NO_FRAME;
+ private final static int FORWARD_STRAND = FeatureSegment.FORWARD_STRAND;
+ private final static int REVERSE_STRAND = FeatureSegment.REVERSE_STRAND;
+ private final static int FORWARD_FRAME_1 = FeatureSegment.FORWARD_FRAME_1;
+ private final static int FORWARD_FRAME_2 = FeatureSegment.FORWARD_FRAME_2;
+ private final static int FORWARD_FRAME_3 = FeatureSegment.FORWARD_FRAME_3;
+ private final static int REVERSE_FRAME_3 = FeatureSegment.REVERSE_FRAME_3;
+ private final static int REVERSE_FRAME_2 = FeatureSegment.REVERSE_FRAME_2;
+ private final static int REVERSE_FRAME_1 = FeatureSegment.REVERSE_FRAME_1;
+ private final static int SCALE_LINE = FeatureSegment.SCALE_LINE;
+
+
+ /**
+ * The JScrollBar for this FeatureDisplay object. We create the scrollbar
+ * as part of this object rather than in the EntryEdit component because we
+ * may need to change the parameters of the scrollbar later.
+ **/
+ private JScrollBar scrollbar = null;
+
+ /** A scroll bar for changing the viewing scale. */
+ private ZoomScrollBar scale_changer = null;
+
+ /** Used to colour the frames. */
+ private Color light_grey = new Color(240, 240, 240);
+
+ /** Used to colour sequence line. */
+ private Color not_so_light_grey = new Color(200, 200, 200);
+
+ /**
+ * The colour used for the active entry line when
+ * one_line_per_entry is set.
+ **/
+ private Color active_entry_colour = new Color(255, 255, 140);
+
+ /**
+ * This Vector containing the references of those features that are
+ * currently visible.
+ **/
+ private FeatureVector visible_features = new FeatureVector();
+
+ /**
+ * If true updateVisibleFeatureVector() will be called by paint().
+ * updateVisibleFeatureVector() sets this to false,
+ * needVisibleFeatureVectorUpdate() sets this to true.
+ **/
+ private boolean update_visible_features = true;
+
+ /** Contains those objects listening for adjustment events. */
+ final private Vector<DisplayAdjustmentListener> adjustment_listener_list =
+ new Vector<DisplayAdjustmentListener>();
+
+ /**
+ * The index of the first base that we are displaying.
+ * Can be negative if hard_left_edge is true.
+ **/
+ private int left_edge_base = 1;
+
+ /** See getScaleFactor(). */
+ private int scale_factor = 3;
+
+ /** See getScaleFactor(). */
+ private float scale_value = 1;
+
+ /** true if labels should be shown. */
+ private boolean show_labels = true;
+
+ /**
+ * This variable is true if the forward frame lines(or forward entry lines
+ * - see one_line_per_entry) should be drawn.
+ **/
+ private boolean show_forward_lines = true;
+
+ /**
+ * This variable is true if the reverse frame lines(or reverse entry lines
+ * - see one_line_per_entry) should be drawn.
+ **/
+ private boolean show_reverse_lines = true;
+
+ /**
+ * If true draw all features, sequence and scale lines reverse complemented.
+ **/
+ private boolean rev_comp_display = false;
+
+ /**
+ * This variable is true if(for each strand) each entry should be on a
+ * separate line.
+ **/
+ private boolean one_line_per_entry = false;
+
+ private boolean feature_stack_view = false;
+
+ private short MAX_LINES_FEATURE_STACK = 10;
+
+ /** expand / collapse stack view, 2 or 1 respectively */
+ private int STACK_EXPAND_FACTOR = 1;
+
+ /** visible features sorted by size */
+ private FeatureVector visibleFeaturesSortBySize;
+ /** visible features sorted by position */
+ private FeatureVector visibleFeaturesSortByPosition;
+
+ /**
+ * If true the there will never be a gap between the left edge of the
+ * screen and the first visible base.
+ **/
+ private boolean hard_left_edge = true;
+
+ /** true if source features should be shown. */
+ private boolean show_source_features = false;
+
+ /** true if stop codons should be shown. */
+ private boolean show_stop_codons = true;
+
+ /** true if start codons should be shown. */
+ private boolean show_start_codons = false;
+
+ /** true if directional arrows should be shown on features. */
+ private boolean show_feature_arrows;
+
+ /** true a black border will be drawn around each feature. */
+ private boolean show_feature_borders;
+
+ /**
+ * This variable is true if each base should be drawn in a different colour
+ * at scale feature 1.
+ **/
+ private boolean show_base_colours = false;
+
+ /**
+ * All features(not just CDS features) will be drawn on the frame lines if
+ * and only if this variable is true. See setFrameFeaturesFlag() and
+ * getFrameFeaturesFlag().
+ **/
+ private boolean frame_features_flag = false;
+
+ /**
+ * The position(s) of the last mouse click on the dna line. The
+ * MarkerRange contains a reference to the appropriate Strand and contains
+ * the base positions. See getMarkerRangeFromPosition() to understand why
+ * one click can give multiple bases.
+ **/
+ private MarkerRange click_range = null;
+
+ /**
+ * The last(FeatureSegment) Marker that the user clicked on. This is used
+ * for dragging the ends of segments.
+ **/
+ private Marker click_segment_marker = null;
+
+ /**
+ * This is true if click_segment_marker is the Marker at the start of
+ * segment false otherwise. The value is only useful if
+ * click_segment_marker is set.
+ **/
+ private boolean click_segment_marker_is_start_marker = false;
+
+ /**
+ * When a FeatureSegment Marker drag starts, this is set to the Marker at
+ * the other end of the segment. This is used to check that the drag has
+ * not move the Marker too far(past the end of the segment).
+ **/
+ private Marker other_end_of_segment_marker = null;
+
+ /**
+ * Features with a /score qualifier less than this value will not be shown.
+ **/
+ private int current_min_score = 0;
+
+ /**
+ * Features with a /score qualifier greater than this value will not be
+ * shown.
+ **/
+ private int current_max_score = 100;
+
+ private MouseEvent last_mouse_press_event;
+
+ /**
+ * If set no DisplayAdjustment events will be sent. This is set by
+ * displayAdjustmentValueChanged() to prevent an event we send from
+ * returning to us(a FeatureDisplay can listen for DisplayAdjustment
+ * events from another FeatureDisplay).
+ **/
+ private boolean disable_display_events = false;
+
+ /**
+ * Set to true by selectionChanged() to tell updateVisibleFeatureVector()
+ * to raise the contents of the select before updating.
+ **/
+ private boolean raise_selection_flag = false;
+
+ /** the minimum distance in pixels between the labels. */
+ private final static int MINIMUM_LABEL_SPACING = 80;
+
+ /** colour used for A. */
+ private static Color dark_green = new Color(0, 150, 0);
+
+ private final int scrollbar_style;
+
+ private static Vector<String> contigKeys;
+ private static Vector<String> allPossibleContigKeys;
+
+ private Object[] protein_keys = { "CDS",
+ "BLASTCDS",
+ "polypeptide",
+ "pseudogenic_exon"};
+
+ /**
+ * Create a new FeatureDisplay object with the horizontal scrollbar at the
+ * bottom of the component.
+ * @param entry_group The EntryGroup that this component will display.
+ * @param selection The Selection object for this component. Selected
+ * objects will be highlighted.
+ * @param goto_event_source The object to use when we need to call
+ * gotoBase().
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ public FeatureDisplay(final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group)
+ {
+ this(entry_group, selection, goto_event_source,
+ base_plot_group, SCROLLBAR_AT_BOTTOM);
+ }
+
+ /**
+ * Create a new FeatureDisplay object.
+ * @param entry_group The EntryGroup that this component will display.
+ * @param owning_component The EntryEdit object that contains the selection
+ * that this component uses.
+ * @param scrollbar_at_top If true the horizontal scrollbar will be at the
+ * top of component.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ * @param scrollbar_style Controls the type of horizontal scrollbar. Must
+ * be one of SCROLLBAR_AT_TOP, SCROLLBAR_AT_BOTTOM or NO_SCROLLBAR.
+ **/
+ public FeatureDisplay(final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group,
+ final int scrollbar_style)
+ {
+ super(entry_group, selection, goto_event_source, base_plot_group);
+
+ this.scrollbar_style = scrollbar_style;
+
+ show_feature_arrows =
+ Options.getOptions().getPropertyTruthValue("draw_feature_arrows");
+
+ show_feature_borders =
+ Options.getOptions().getPropertyTruthValue("draw_feature_borders");
+
+ frame_features_flag =
+ Options.getOptions().getPropertyTruthValue("features_on_frame_lines");
+
+ one_line_per_entry =
+ Options.getOptions().getPropertyTruthValue("one_line_per_entry");
+
+ feature_stack_view =
+ Options.getOptions().getPropertyTruthValue("feature_stack_view");
+
+ show_labels =
+ Options.getOptions().getPropertyTruthValue("feature_labels");
+
+ show_reverse_lines =
+ Options.getOptions().getPropertyTruthValue("show_reverse_lines");
+
+ show_forward_lines =
+ Options.getOptions().getPropertyTruthValue("show_forward_lines");
+
+ final StringVector frame_line_features =
+ Options.getOptions().getOptionValues("frame_line_features");
+ if(frame_line_features != null)
+ protein_keys = frame_line_features.toArray();
+
+ addComponentListener(new ComponentAdapter()
+ {
+ public void componentResized(ComponentEvent e)
+ {
+ // update the scroll bar as soon as we know the size of the canvas
+ fixScrollbar();
+ needVisibleFeatureVectorUpdate();
+ fireAdjustmentEvent(DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT);
+ }
+
+ public void componentShown(ComponentEvent e)
+ {
+ // update the scroll bar as soon as we know the size of the canvas
+ fixScrollbar();
+ needVisibleFeatureVectorUpdate();
+ fireAdjustmentEvent(DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT);
+ }
+ });
+
+ setScaleValue();
+
+ if(scrollbar_style == SCROLLBAR_AT_TOP)
+ createScrollbar(true);
+ else if(scrollbar_style == SCROLLBAR_AT_BOTTOM)
+ createScrollbar(false);
+
+ createScaleScrollbar();
+ addListeners();
+
+ needVisibleFeatureVectorUpdate();
+
+ getSelection().addSelectionChangeListener(this);
+ getGotoEventSource().addGotoListener(this);
+
+ getEntryGroup().addEntryGroupChangeListener(this);
+ getEntryGroup().addEntryChangeListener(this);
+ getEntryGroup().addFeatureChangeListener(this);
+
+ getBases().addSequenceChangeListener(this, Bases.MIN_PRIORITY);
+
+ Options.getOptions().addOptionChangeListener(this);
+ setBackground(Color.white);
+
+ DragSource dragSource = DragSource.getDefaultDragSource();
+
+ dragSource.createDefaultDragGestureRecognizer(
+ this, // component where drag originates
+ DnDConstants.ACTION_COPY_OR_MOVE, // actions
+ this); // drag gesture recognizer
+
+ setDropTarget(new DropTarget(this,this));
+ }
+
+
+ /**
+ * Overriden to call fixCanvasSize()
+ **/
+ public void setVisible(final boolean visible)
+ {
+ super.setVisible(visible);
+ fixCanvasSize();
+ }
+
+ /**
+ * Set value of the show label flag.
+ * @param show_label Show labels if and only if this argument is true.
+ **/
+ protected void setShowLabels(boolean show_labels)
+ {
+ if(this.show_labels != show_labels)
+ {
+ this.show_labels = show_labels;
+ fixCanvasSize();
+ }
+ }
+
+ /**
+ * Get the value of the "show label" flag.
+ **/
+ protected boolean getShowLabels()
+ {
+ return show_labels;
+ }
+
+ /**
+ * Set value of the "show forward frame lines" flag.
+ * @param show_forward_lines Show forward frame lines if and only if
+ * this argument is true.
+ **/
+ protected void setShowForwardFrameLines(boolean show_forward_lines)
+ {
+ if(this.show_forward_lines != show_forward_lines)
+ {
+ this.show_forward_lines = show_forward_lines;
+ fixCanvasSize();
+ }
+ }
+
+ /**
+ * Get the value of the "show forward frame lines" flag.
+ **/
+ protected boolean getShowForwardFrameLines()
+ {
+ return show_forward_lines;
+ }
+
+ /**
+ * Set value of the "show reverse frame lines" flag.
+ * @param show_reverse_lines Show frame lines if and only if this
+ * argument is true.
+ **/
+ protected void setShowReverseFrameLines(boolean show_reverse_lines)
+ {
+ if(this.show_reverse_lines != show_reverse_lines)
+ {
+ this.show_reverse_lines = show_reverse_lines;
+ fixCanvasSize();
+ }
+ }
+
+ /**
+ * Get the value of the "show source features" flag.
+ **/
+ protected boolean getShowSourceFeatures()
+ {
+ return show_source_features;
+ }
+
+ /**
+ * Set value of the "show source features" flag.
+ * @param show_source_features Show features with a "source" key if and
+ * only if this argument is true.
+ **/
+ protected void setShowSourceFeatures(boolean show_source_features)
+ {
+ if(this.show_source_features != show_source_features)
+ {
+ this.show_source_features = show_source_features;
+ needVisibleFeatureVectorUpdate();
+ repaint();
+ }
+ }
+
+ /**
+ * Get the value of the "show frame lines" flag.
+ **/
+ protected boolean getShowReverseFrameLines()
+ {
+ return show_reverse_lines;
+ }
+
+ /**
+ * Set value of the show base colours flag.
+ * @param show_base_colours At scale_factor less than two show each base in
+ * a different colour if and only if this argument is true.
+ **/
+ protected void setShowBaseColours(boolean show_base_colours)
+ {
+ if(this.show_base_colours != show_base_colours)
+ {
+ this.show_base_colours = show_base_colours;
+ if(getScaleFactor() > 1)
+ setScaleFactor(1);
+ repaint();
+ }
+ }
+
+ /**
+ * Get the value of the "show base colours" flag.
+ **/
+ protected boolean getShowBaseColours()
+ {
+ return show_base_colours;
+ }
+
+ /**
+ * Set value of the "one line per entry" flag.
+ * @param one_line_per_entry If true then each entry will be shown on a
+ * different line, instead of showing frame lines.
+ **/
+ protected void setOneLinePerEntry(final boolean one_line_per_entry)
+ {
+ if(this.one_line_per_entry != one_line_per_entry)
+ {
+ this.one_line_per_entry = one_line_per_entry;
+ fixCanvasSize();
+ }
+ }
+
+ /**
+ * Get the value of the "one line per entry" flag.
+ **/
+ protected boolean getOneLinePerEntryFlag()
+ {
+ return one_line_per_entry;
+ }
+
+
+ /**
+ * Set value of the feature stack view flag.
+ * @param feature_stack_view If true then each CDS will be shown on a
+ * different line, instead of showing frame lines.
+ **/
+ protected void setFeatureStackViewFlag(final boolean feature_stack_view)
+ {
+ if(this.feature_stack_view != feature_stack_view)
+ {
+ this.feature_stack_view = feature_stack_view;
+ fixCanvasSize();
+ }
+ }
+
+ /**
+ * Get the value of the "one line per entry" flag.
+ **/
+ protected boolean getFeatureStackViewFlag()
+ {
+ return feature_stack_view;
+ }
+
+ /**
+ * Set value of the "hard left edge" flag.
+ * @param hard_left_edge If true the there will never be a gap between the
+ * left edge of the screen and the first visible base. If false base one
+ * can be moved to the centre of the display.
+ **/
+ protected void setHardLeftEdge(final boolean hard_left_edge)
+ {
+ if(this.hard_left_edge != hard_left_edge)
+ {
+ this.hard_left_edge = hard_left_edge;
+ if(hard_left_edge && getForwardBaseAtLeftEdge() < 1)
+ setFirstVisibleForwardBase(1);
+
+ fixScrollbar();
+ }
+ }
+
+ /**
+ * Set value of the show stop codons flag.
+ * @param show_stop_codons Show stop codons if and only if this argument is
+ * true.
+ **/
+ protected void setShowStopCodons(boolean show_stop_codons)
+ {
+ if(this.show_stop_codons != show_stop_codons)
+ {
+ this.show_stop_codons = show_stop_codons;
+ repaint();
+ }
+ }
+
+ /**
+ * Return the value of the "show stop codons" flag.
+ **/
+ protected boolean getShowStopCodons()
+ {
+ return show_stop_codons;
+ }
+
+ /**
+ * Set value of the show start codons flag.
+ * @param show_start_codons Show start codons if and only if this argument
+ * is true.
+ **/
+ protected void setShowStartCodons(boolean show_start_codons)
+ {
+ if(this.show_start_codons != show_start_codons)
+ {
+ this.show_start_codons = show_start_codons;
+ repaint();
+ }
+ }
+
+ /**
+ * Return the value of the "show start codons" flag.
+ **/
+ protected boolean getShowStartCodons()
+ {
+ return show_start_codons;
+ }
+
+ /**
+ * Set value of the reverse complement display flag.
+ * @param show_start_codons Draw all features and sequence reverse
+ * complemented if and only if this argument is true.
+ **/
+ protected void setRevCompDisplay(boolean rev_comp_display)
+ {
+ if(this.rev_comp_display != rev_comp_display)
+ {
+ this.rev_comp_display = rev_comp_display;
+ int remember_position = getCentreForwardBase();
+
+ // we want to keep the selection visible after the flip, so
+ // that will override the centre position
+ final Marker first_base_marker =
+ getSelection().getStartBaseOfSelection();
+
+ if(first_base_marker != null && baseVisible(first_base_marker))
+ remember_position = first_base_marker.getRawPosition();
+
+ final Marker last_base_marker =
+ getSelection().getStartBaseOfSelection();
+
+ if(last_base_marker != null && baseVisible(last_base_marker))
+ remember_position = last_base_marker.getRawPosition();
+
+ fireAdjustmentEvent(DisplayAdjustmentEvent.REV_COMP_EVENT);
+
+ makeBaseVisibleInternal(remember_position, isRevCompDisplay(), true);
+
+ needVisibleFeatureVectorUpdate();
+ fixScrollbar();
+ repaint();
+ }
+ }
+
+ /**
+ * Return the value of the "reverse complement display" flag.
+ **/
+ protected boolean isRevCompDisplay()
+ {
+ return rev_comp_display;
+ }
+
+ /**
+ * Set value of the show feature arrows flag.
+ * @param show_feature_arrows Show directional arrows if and only if this
+ * argument is true.
+ **/
+ protected void setShowFeatureArrows(boolean show_feature_arrows)
+ {
+ if(this.show_feature_arrows != show_feature_arrows)
+ {
+ this.show_feature_arrows = show_feature_arrows;
+ repaint();
+ }
+ }
+
+ /**
+ * Return the value of the "show feature arrows" flag.
+ **/
+ protected boolean getShowFeatureArrows()
+ {
+ return show_feature_arrows;
+ }
+
+ /**
+ * Set value of the show feature borders flag.
+ * @param show_feature_borders Draw a border around each feature if and
+ * only if this argument is true.
+ **/
+ protected void setShowFeatureBorders(boolean show_feature_borders)
+ {
+ if(this.show_feature_borders != show_feature_borders)
+ {
+ this.show_feature_borders = show_feature_borders;
+ repaint();
+ }
+ }
+
+ /**
+ * Return the value of the "show feature borders" flag.
+ **/
+ protected boolean getShowFeatureBorders()
+ {
+ return show_feature_borders;
+ }
+
+ /**
+ * Set value of the show frame features flag.
+ * @param frame_features_flag All features(not just CDS features) will be
+ * drawn on the frame lines if and only if this argument is true.
+ **/
+ protected void setFrameFeaturesFlag(boolean frame_features_flag)
+ {
+ if(this.frame_features_flag != frame_features_flag)
+ {
+ this.frame_features_flag = frame_features_flag;
+ repaint();
+ }
+ }
+
+ /**
+ * Return the value of the "show frame features" flag.
+ **/
+ protected boolean getFrameFeaturesFlag()
+ {
+ return frame_features_flag;
+ }
+
+ /**
+ * Set the value of the minimum score for this FeatureDisplay - features
+ * that have a /score lower than this value are never shown.
+ **/
+ protected void setMinimumScore(final int minimum_score)
+ {
+ current_min_score = minimum_score;
+ needVisibleFeatureVectorUpdate();
+ repaint();
+ }
+
+ /**
+ * Return the value of the minimum score for this FeatureDisplay - see
+ * setMinimumScore().
+ **/
+ private int getMinimumScore()
+ {
+ return current_min_score;
+ }
+
+ /**
+ * Set the value of the maximum score for this FeatureDisplay - features
+ * that have a /score higher than this value are never shown.
+ **/
+ protected void setMaximumScore(final int maximum_score)
+ {
+ current_max_score = maximum_score;
+ needVisibleFeatureVectorUpdate();
+ repaint();
+ }
+
+ /**
+ * Return the value of the maximum score for this FeatureDisplay - see
+ * setMaximumScore().
+ **/
+ private int getMaximumScore()
+ {
+ return current_max_score;
+ }
+
+ /**
+ * Adds the specified event adjustment listener to receive adjustment
+ * change events from this object.
+ * @param l the event change listener.
+ **/
+ public void addDisplayAdjustmentListener(DisplayAdjustmentListener l)
+ {
+ adjustment_listener_list.addElement(l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * adjustment change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeDisplayAdjustmentListener(DisplayAdjustmentListener l)
+ {
+ adjustment_listener_list.removeElement(l);
+ }
+
+ /**
+ * Handle key press events. This is static because making it non-static
+ * triggered a java.lang.VerifyError
+ **/
+ private static void handleKeyPress(final FeatureDisplay feature_display,
+ final KeyEvent event)
+ {
+ // this is done so that menu shortcuts don't cause each action to be
+ // performed twice
+ if(event.getModifiers() != 0)
+ return;
+
+ switch(event.getKeyCode())
+ {
+ case ZOOM_TO_SELECTION_KEY:
+ FeaturePopup.zoomToSelection(feature_display);
+ break;
+ case ARROW_LEFT:
+ feature_display.setFirstBase(
+ feature_display.getFirstVisibleForwardBase()-
+ feature_display.scrollbar.getUnitIncrement());
+ break;
+ case ARROW_RIGHT:
+ feature_display.setFirstBase(
+ feature_display.getFirstVisibleForwardBase()+
+ feature_display.scrollbar.getUnitIncrement());
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Set the scale factor and update the display if the scale factor has
+ * changed. A factor of zero means the full translation will be visible.
+ * At higher scale factors only stop codons are visible, and a bigger
+ * number will mean more bases are visible.
+ **/
+ protected void setScaleFactor(int scale_factor)
+ {
+ if(this.scale_factor != scale_factor)
+ {
+ // we will try to keep the base in the centre of the view to stay where
+ // it is, so we save it's position in remember_position.
+ int remember_position = getCentreForwardBase();
+
+ // if the first base is visible then keep it visible
+ if(hard_left_edge && getFirstVisibleForwardBase() == 1)
+ remember_position = 1;
+
+ if(!getSelection().isEmpty())
+ {
+ // but, we want to keep the selection visible after a scale change, so
+ // that will override the centre position
+ final Marker first_base_marker =
+ getSelection().getStartBaseOfSelection();
+
+ final int first_base_marker_raw_position =
+ first_base_marker.getRawPosition();
+ final int first_base_marker_position;
+
+ if(isRevCompDisplay())
+ first_base_marker_position =
+ getBases().getComplementPosition(first_base_marker_raw_position);
+ else
+ first_base_marker_position = first_base_marker_raw_position;
+
+ final Marker last_base_marker =
+ getSelection().getEndBaseOfSelection();
+
+ final int last_base_marker_raw_position =
+ last_base_marker.getRawPosition();
+
+ final int last_base_marker_position;
+
+ if(isRevCompDisplay())
+ last_base_marker_position =
+ getBases().getComplementPosition(last_base_marker_raw_position);
+ else
+ last_base_marker_position = last_base_marker_raw_position;
+
+ final int lowest_visible_base = getFirstVisibleForwardBase();
+ final int highest_visible_base = getLastVisibleForwardBase();
+
+ // first selected base or first visible base, whichever is greater
+ int restricted_first_selected_base = lowest_visible_base;
+
+ // last selected base or last visible base, whichever is smaller
+ int restricted_last_selected_base = highest_visible_base;
+
+ if(first_base_marker != null)
+ {
+ if(first_base_marker_position > lowest_visible_base &&
+ first_base_marker_position < highest_visible_base)
+ restricted_first_selected_base = first_base_marker_position;
+ }
+
+ if(last_base_marker != null)
+ {
+ if(last_base_marker_position < highest_visible_base &&
+ last_base_marker_position > lowest_visible_base)
+ restricted_last_selected_base = last_base_marker_position;
+ }
+
+ if(getSelection().getMarkerRange() == null)
+ remember_position = restricted_first_selected_base;
+ else
+ {
+ // keep the centre of the selection in the middle of the display if
+ // a range of bases is selected
+ remember_position = restricted_first_selected_base +
+ (restricted_last_selected_base -
+ restricted_first_selected_base) / 2;
+ }
+ }
+
+ this.scale_factor = scale_factor;
+
+ setScaleValue();
+
+ if(scale_changer != null)
+ scale_changer.setValue(scale_factor);
+ setCentreVisibleForwardBase(remember_position);
+ fixScrollbar();
+ fireAdjustmentEvent(DisplayAdjustmentEvent.SCALE_ADJUST_EVENT);
+ needVisibleFeatureVectorUpdate();
+
+ repaint();
+ }
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We listen to
+ * FeatureChange events so that we can update the display if qualifiers
+ * change.
+ **/
+ public void featureChanged(final FeatureChangeEvent event)
+ {
+ final Feature event_feature = event.getFeature();
+
+ if( event.getType() == FeatureChangeEvent.LOCATION_CHANGED &&
+ event_feature.getEmblFeature() instanceof GFFStreamFeature &&
+ !GeneUtils.isDatabaseEntry(event_feature.getEmblFeature()))
+ {
+ try
+ {
+ GFFUtils.updateSegmentRangeStore((GFFStreamFeature)event_feature.getEmblFeature(),
+ event.getOldLocation(), event.getNewLocation());
+ } catch(Exception e) {}
+ }
+
+ // the feature isn't in an active entry
+ if(!getEntryGroup().contains(event_feature))
+ return;
+
+ // if the feature is visible now or is in the list of visible features
+ //(ie. it was visible previously) then redisplay.
+ if(featureVisible(event_feature) ||
+ getVisibleFeatures().contains(event_feature))
+ {
+ // update the visible_features vector
+ if(getVisibleFeatures().contains(event_feature) &&
+ !featureVisible(event_feature))
+ getVisibleFeatures().remove(event_feature);
+ else
+ {
+ // the visibility of the feature has changed
+ if(!getVisibleFeatures().contains(event_feature) &&
+ featureVisible(event_feature))
+ getVisibleFeatures().add(event_feature);
+ }
+
+ repaint();
+ }
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can update the display if entries
+ * are added or deleted.
+ **/
+ public void entryGroupChanged(final EntryGroupChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryGroupChangeEvent.ENTRY_ADDED:
+ case EntryGroupChangeEvent.ENTRY_ACTIVE:
+ case EntryGroupChangeEvent.ENTRY_DELETED:
+ case EntryGroupChangeEvent.ENTRY_INACTIVE:
+ if(getOneLinePerEntryFlag())
+ fixCanvasSize();
+
+ needVisibleFeatureVectorUpdate();
+ break;
+ }
+
+ repaint();
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so that we can update the display if features are
+ * added or deleted.
+ **/
+ public void entryChanged(final EntryChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryChangeEvent.FEATURE_DELETED:
+ remove(event.getFeature());
+ break;
+ case EntryChangeEvent.FEATURE_ADDED:
+ add(event.getFeature());
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the SelectionChangeListener interface. We listen to
+ * SelectionChange events so that we can update the list to reflect the
+ * current selection.
+ **/
+ public void selectionChanged(final SelectionChangeEvent event)
+ {
+ // don't bother with events we sent ourself
+ if(event.getSource() == this)
+ return;
+
+ needVisibleFeatureVectorUpdate();
+
+ if(event.getType() == SelectionChangeEvent.SELECTION_CHANGED)
+ raise_selection_flag = true;
+
+ repaint();
+ }
+
+ /**
+ * Implementation of the SequenceChangeListener interface.
+ **/
+ public void sequenceChanged(final SequenceChangeEvent event)
+ {
+ visible_features = new FeatureVector();
+
+ if(event.getType() == SequenceChangeEvent.REVERSE_COMPLEMENT)
+ {
+ final int old_centre_position = getCentreForwardBase();
+
+ final int new_centre_position =
+ getBases().getComplementPosition(old_centre_position);
+
+ makeBaseVisibleInternal(new_centre_position, true, false);
+ }
+ else if(event.getType() != SequenceChangeEvent.CONTIG_REVERSE_COMPLEMENT)
+ makeBaseVisibleInternal(event.getPosition(), true, false);
+
+ fixScrollbar();
+ needVisibleFeatureVectorUpdate();
+ repaint();
+
+ if(event.getType() == SequenceChangeEvent.REVERSE_COMPLEMENT )
+ fireAdjustmentEvent(DisplayAdjustmentEvent.REV_COMP_EVENT);
+ else if(event.getType() == SequenceChangeEvent.CONTIG_REVERSE_COMPLEMENT )
+ {
+ final Range range = event.getRange();
+
+ fireAction(adjustment_listener_list,
+ new DisplayAdjustmentEvent(FeatureDisplay.this,
+ range.getStart(),
+ range.getEnd(),
+ getMaxVisibleBases(),
+ getScaleValue(), getScaleFactor(),
+ isRevCompDisplay(),
+ DisplayAdjustmentEvent.CONTIG_REV_COMP_EVENT));
+ }
+ else if(event.getType() == SequenceChangeEvent.CONTIG_REORDER)
+ {
+ final Range range = event.getRange();
+ final int drop_position = event.getPosition();
+
+ fireAction(adjustment_listener_list,
+ new DisplayAdjustmentEvent(
+ FeatureDisplay.this,
+ range.getStart(), range.getEnd(),
+ drop_position,
+ DisplayAdjustmentEvent.CONTIG_REORDER));
+ }
+ else
+ fireAdjustmentEvent(DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT);
+ }
+
+ /**
+ * Invoked when an Option is changed.
+ **/
+ public void optionChanged(OptionChangeEvent event)
+ {
+ AminoAcidSequence.setGeneCode();
+ getBases().clearCodonCache();
+ repaint();
+ }
+
+ /**
+ * Implementation of the GotoListener interface. Invoked when the listener
+ * should goto to the given base.
+ **/
+ public void performGoto(final GotoEvent event)
+ {
+ makeBaseVisible(event.getMarker());
+ }
+
+ /**
+ * Implementation of the DisplayAdjustmentListener interface. Invoked when
+ * a component(FeatureDisplay) scrolls or changes the scale. Set the
+ * position and scale of this FeatureDisplay to match the event
+ **/
+ public void displayAdjustmentValueChanged(DisplayAdjustmentEvent event)
+ {
+ if(event.getType() == DisplayAdjustmentEvent.CONTIG_REORDER)
+ return;
+
+ disable_display_events = true;
+ try
+ {
+ setScaleFactor(event.getScaleFactor());
+ setFirstVisibleForwardBase(event.getStart());
+ }
+ finally
+ {
+ disable_display_events = false;
+ }
+ }
+
+ /**
+ * Return a MarkerRange that exactly covers the visible bases
+ **/
+ protected MarkerRange getVisibleMarkerRange()
+ {
+ final int first_base = getFirstVisibleForwardBase();
+ final int last_base = getLastVisibleForwardBase();
+
+ final Strand strand = getBases().getForwardStrand();
+
+ try
+ {
+ return strand.makeMarkerRangeFromPositions(first_base, last_base);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Arrange for the given feature to be drawn last - ie. so that it will
+ * appear at the front of the other features.
+ **/
+ protected void raiseFeature(Feature feature)
+ {
+ if(getVisibleFeatures().remove(feature))
+ {
+ getVisibleFeatures().addElementAtEnd(feature);
+ repaint();
+ }
+ }
+
+ /**
+ * Arrange for the given feature to be drawn first - ie. so that it will
+ * appear at the back of the other features.
+ **/
+ protected void lowerFeature(Feature feature)
+ {
+ if(getVisibleFeatures().remove(feature))
+ {
+ getVisibleFeatures().insertElementAt(feature, 0);
+ repaint();
+ }
+ }
+
+ /**
+ * Redraw the display with the smallest features on top. Selected features
+ * will always be on top.
+ **/
+ protected void smallestToFront()
+ {
+ visible_features = new FeatureVector();
+ needVisibleFeatureVectorUpdate();
+ repaint();
+ }
+
+ /**
+ * Scroll the display so the given base on the Strand currently display on
+ * top will be in the centre of the display. No events are sent.
+ * @param base_position The new centre base position.
+ **/
+ private void setCentreVisibleForwardBase(final int base_position)
+ {
+ //int max_visible_bases = getVisibleRange().getCount()-1;
+ final int max_visible_bases = getMaxVisibleBases();
+ final int possible_base_position = base_position - max_visible_bases / 2;
+ int real_base_position;
+
+ if(possible_base_position < 1 && hard_left_edge)
+ real_base_position = 1;
+ else
+ real_base_position = possible_base_position;
+
+ if(real_base_position > getSequenceLength())
+ real_base_position = getSequenceLength();
+
+ setFirstVisibleForwardBase(real_base_position);
+ }
+
+ /**
+ * Scroll the display so that the given base is in the middle of the
+ * screen. This method obeys the rev_comp_display flag.
+ * @param base_position The base position to make visible.
+ * @param forward true means FORWARD - the base_position refers to a
+ * position on the forward strand, false means REVERSE - the
+ * base_position refers to the reverse strand.
+ * @param send_event Send a DisplayAdjustmentEvent if and only if this is
+ * true
+ **/
+ private void makeBaseVisibleInternal(final int base_position,
+ final boolean forward,
+ final boolean send_event)
+ {
+ int forward_base_position = base_position;
+
+ if(!forward ^ isRevCompDisplay())
+ forward_base_position =
+ getBases().getComplementPosition(forward_base_position);
+
+ setCentreVisibleForwardBase(forward_base_position);
+
+ if(send_event)
+ fireAdjustmentEvent(DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT);
+ }
+
+ /**
+ * Scroll the display so that the given base is in the middle of the screen
+ * @param base_marker The Marker of base to make visible.
+ **/
+ private void makeBaseVisible(final Marker base_marker)
+ {
+ makeBaseVisibleInternal(base_marker.getPosition(),
+ base_marker.getStrand().isForwardStrand(),
+ true);
+ }
+
+ /**
+ * Scroll the display so that the given base is in the middle of the screen.
+ * @param base The base to scroll to.
+ **/
+ public void makeBaseVisible(final int base)
+ {
+ makeBaseVisibleInternal(base, true, true);
+ }
+
+ /**
+ * This method is called to add a single feature to the display by adding it
+ * to the vector of visible features (if it is visible that is).
+ * @param feature The object to add
+ **/
+ private void add(Feature feature)
+ {
+ if(getEntryGroup().isActive(feature.getEntry()) &&
+ featureVisible(feature))
+ {
+ if(!visible_features.contains(feature))
+ getVisibleFeatures().addElementAtEnd(feature);
+ }
+
+ repaint();
+ }
+
+ /**
+ * The method is called when a Feature should be removed from the display.
+ * The feature is removed from the vector of visible features.
+ * @param feature The object to remove
+ **/
+ private void remove(Feature feature)
+ {
+ if(visible_features != null)
+ visible_features.remove(feature);
+
+ repaint();
+ }
+
+ /**
+ * Returns the vector containing those features that are currently visible.
+ **/
+ private FeatureVector getVisibleFeatures()
+ {
+ return visible_features;
+ }
+
+ /**
+ * Returns a copy of the vector containing those features that are
+ * currently visible.
+ **/
+ protected FeatureVector getCurrentVisibleFeatures()
+ {
+ return (FeatureVector)visible_features.clone();
+ }
+
+ /**
+ * Returns a Range that starts at the first visible base and ends at the
+ * last visible base.
+ **/
+ private Range getVisibleRange()
+ {
+ final int first_visible_base = getFirstVisibleForwardBase();
+ final int last_visible_base = getLastVisibleForwardBase();
+
+ if(first_visible_base <= last_visible_base)
+ return newRange(first_visible_base, last_visible_base);
+ else
+ return null;
+ }
+
+ /**
+ * This is called after scrolling to add all features that have become
+ * visible and remove those that have become invisible. The method changes
+ * visible_features as little as possible so that features that were at the
+ * end(and hence drawn last/on top) stay there. Selected features and
+ * segments will always be on top of unselected ones.
+ **/
+ private void updateVisibleFeatureVector()
+ {
+ final Range visible_range;
+
+ if(getSize().width == 0)
+ {
+ // don't bother doing any thinking
+ visible_features = new FeatureVector();
+ return;
+ }
+
+ if(raise_selection_flag)
+ {
+ final FeatureVector all_features = getSelection().getAllFeatures();
+
+ final int all_features_size = all_features.size();
+ for(int i = 0 ; i < all_features_size; ++i)
+ raiseFeature(all_features.elementAt(i));
+
+ raise_selection_flag = false;
+ }
+
+ if(isRevCompDisplay())
+ {
+ final int first_visible_base = getFirstVisibleReverseBase();
+ final int last_visible_base = getLastVisibleReverseBase();
+ visible_range = newRange(first_visible_base, last_visible_base);
+ }
+ else
+ visible_range = getVisibleRange();
+
+ if(visible_range == null)
+ {
+ visible_features = new FeatureVector();
+ return;
+ }
+
+ final FeatureVector real_visible_features =
+ getSortedFeaturesInRange(visible_range);
+
+ final FeatureVector new_visible_features = new FeatureVector();
+
+ // add features that are in visible_features and
+ // real_visible_features - ie features that are still visible
+ final int visible_features_size = visible_features.size();
+ for(int i = 0 ; i < visible_features_size; ++i)
+ {
+ final Feature new_feature = visible_features.elementAt(i);
+ if(real_visible_features.contains(new_feature))
+ new_visible_features.addElementAtEnd(new_feature);
+ }
+
+ // add features that are in real_visible_features and not currently
+ // in visible_features and are not selected(selected features will be
+ // added last so that they stay on top).
+ final int real_visible_features_size = real_visible_features.size();
+ for(int i = 0 ; i < real_visible_features_size; ++i)
+ {
+ final Feature new_feature = real_visible_features.elementAt(i);
+
+ if(!visible_features.contains(new_feature) &&
+ !getSelection().contains(new_feature))
+ new_visible_features.addElementAtEnd(new_feature);
+ }
+
+ final FeatureVector selection_features = getSelection().getAllFeatures();
+
+ // now add features that are in real_visible_features, are not in
+ // visible_features and are selected (selected features are added last so
+ // that they stay on top).
+ for(int i = 0 ; i < real_visible_features_size; ++i)
+ {
+ final Feature new_feature = real_visible_features.elementAt(i);
+ if(!visible_features.contains(new_feature) &&
+ selection_features.contains(new_feature))
+ {
+ try
+ {
+ new_visible_features.addElementAtEnd(new_feature);
+ }catch(Error e){}
+ }
+ }
+
+ visible_features = new_visible_features;
+ update_visible_features = false;
+ }
+
+ /**
+ * This is used by getSortedFeaturesInRange().
+ **/
+ final private static Comparator<Feature> feature_comparator = new Comparator<Feature>()
+ {
+ /**
+ * Compare two Objects with respect to ordering.
+ * @return a negative number if feature1_object is less than
+ * feature2_object ; a positive number if feature1_object is greater
+ * than feature2_object; else 0
+ **/
+ public int compare(final Feature feature1,
+ final Feature feature2)
+ {
+ final int feature1_size = feature1.getBaseCount();
+ final int feature2_size = feature2.getBaseCount();
+
+ if(feature1_size > feature2_size)
+ return -1;
+ else if(feature1_size < feature2_size)
+ return 1;
+ else if(feature1.hashCode() < feature2.hashCode()) // use hash value as a last resort
+ return -1;
+ else if(feature1.hashCode() == feature2.hashCode())
+ return 0;
+ else
+ return 1;
+ }
+ };
+
+ final private static Comparator<Feature> feature_position_comparator = new Comparator<Feature>()
+ {
+ /**
+ * Compare two Objects with respect to ordering.
+ * @return a negative number if feature1_object is less than
+ * feature2_object ; a positive number if feature1_object is greater
+ * than feature2_object; else 0
+ **/
+ public int compare(final Feature f1,
+ final Feature f2)
+ {
+ final int pos1 = f1.getFirstBase();
+ final int pos2 = f2.getFirstBase();
+
+ if(pos1 > pos2)
+ return -1;
+ else if(pos1 < pos2)
+ return 1;
+ else if(f1.hashCode() < f2.hashCode()) // use hash value as a last resort
+ return -1;
+ else if(f1.hashCode() == f2.hashCode())
+ return 0;
+ else
+ return 1;
+ }
+ };
+
+ /**
+ * Return a vector containing the references of the Feature objects in the
+ * EntryGroup that are within the given range are which should be
+ * displayed. source features are not returned unless show_source_features
+ * is true. features with a score less than getMinimumScore() or higher
+ * than getMaximumScore() aren't returned.
+ *
+ * GFFStreamFeature features check whether they are set to be visible.
+ *
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The features the are within the given range. The returned
+ * object is a copy - changes will not effect the EntryGroup object
+ * itself. The Features are sorted so that the features with the least
+ * bases come last, which is the reverse of Entry.getFeaturesInRange().
+ **/
+ private FeatureVector getSortedFeaturesInRange(final Range range)
+ {
+ try
+ {
+ FeatureVector features_from_entry =
+ getEntryGroup().getFeaturesInRange(range);
+
+ final int min_score = getMinimumScore();
+ final int max_score = getMaximumScore();
+
+ final FeatureVector filtered_features = new FeatureVector();
+
+ // filter out low and high scoring features and (possibly) source
+ // features
+ for(int i = features_from_entry.size() - 1; i >= 0; --i)
+ {
+ final Feature this_feature = features_from_entry.elementAt(i);
+
+ if(this_feature.getKey().equals("source") &&
+ !getShowSourceFeatures() &&
+ !getSelection().contains(this_feature))
+ continue;
+
+ if(this_feature.getEmblFeature() instanceof GFFStreamFeature &&
+ !((GFFStreamFeature)this_feature.getEmblFeature()).isVisible())
+ continue;
+
+ if(min_score > 0 || max_score < 100)
+ {
+ final int this_feature_score = this_feature.getScore();
+
+ // features with no /score are always shown
+ if(this_feature_score != -1 &&
+ (this_feature_score < min_score ||
+ this_feature_score > max_score) &&
+ !getSelection().contains(this_feature))
+ continue;
+ }
+
+ filtered_features.add(this_feature);
+ }
+
+ features_from_entry = filtered_features;
+
+ final FeatureVector sorted_features =
+ features_from_entry.sort(feature_comparator);
+
+ return sorted_features;
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * The main paint function for the canvas. An off screen image
+ * used for double buffering when drawing the canvas.
+ * @param g The Graphics object of the canvas.
+ **/
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ if(!isVisible())
+ return;
+
+// System.out.println("1 "+ System.currentTimeMillis());
+ int scrollbar_hgt = 0;
+ if(scrollbar_style == SCROLLBAR_AT_TOP)
+ {
+ scrollbar_hgt = scrollbar.getPreferredSize().height;
+ ((Graphics2D)g).translate(0,scrollbar_hgt);
+ }
+
+ if(update_visible_features)
+ {
+ updateVisibleFeatureVector();
+ visibleFeaturesSortBySize = null;
+ visibleFeaturesSortByPosition = null;
+ }
+
+ fillBackground(g);
+
+ g.setFont(getFont());
+// System.out.println("2 "+ System.currentTimeMillis());
+ final Selection selection = getSelection();
+ final FeatureVector selected_features = selection.getAllFeatures();
+
+ final FeatureSegmentVector selected_segments =
+ selection.getSelectedSegments();
+
+ int num_visible_features = getVisibleFeatures().size();
+ final int segment_height = getFeatureHeight() - 1;
+ final int seq_length = getSequenceLength();
+
+// System.out.println("3 "+ System.currentTimeMillis());
+ FontMetrics fm = g.getFontMetrics();
+
+ // we draw the feature backgrounds first then the visible indication
+ // that there is a MarkerRange selected, then the feature outlines.
+ final Vector<SegmentBorder> segment_borders =
+ new Vector<SegmentBorder>(num_visible_features);
+
+ for(int i = 0; i < num_visible_features; ++i)
+ drawFeature(g, segment_borders, getVisibleFeatures().elementAt(i),
+ true, selected_features, selected_segments,
+ segment_height, seq_length, fm);
+
+// System.out.println("4 "+ System.currentTimeMillis());
+ drawBaseSelection(g);
+ drawScale(g);
+ drawCodons(g);
+ drawBases(g);
+
+// System.out.println("5 "+ System.currentTimeMillis());
+
+ // draw the segment borders
+ g.setColor(Color.black);
+ final int arrowWidth = getFontWidth() * 8 / 10;
+ for(SegmentBorder sb: segment_borders)
+ sb.drawSegmentBorder(g, segment_height, arrowWidth);
+
+// System.out.println("6 "+ System.currentTimeMillis());
+
+ if(scrollbar_style == SCROLLBAR_AT_TOP)
+ ((Graphics2D)g).translate(0,-scrollbar_hgt);
+
+// draw drag and drop line
+ if(highlight_drop_base > 0)
+ {
+ g.setColor(Color.red);
+ final int draw_x_position = getLowXPositionOfBase(highlight_drop_base);
+ int nlines = 16;
+
+ if(!show_forward_lines)
+ nlines -= 6;
+ if(!show_reverse_lines)
+ nlines -= 6;
+
+ g.drawLine(draw_x_position, 0,
+ draw_x_position, (nlines*getFontHeight()));
+ }
+// Thread.yield();
+
+ if(getFeatureStackViewFlag())
+ {
+ int w = fm.charWidth('+');
+ g.setColor(Color.RED);
+ if(STACK_EXPAND_FACTOR == 1)
+ g.drawString("+", getDisplayWidth()-w-2, 10);
+ else
+ g.drawString("-", getDisplayWidth()-w-2, 10);
+ }
+ }
+
+ /**
+ * Draw the background colour of the frames.
+ **/
+ private void fillBackground(Graphics g)
+ {
+ final int first_visible_base_coord =
+ getLowXPositionOfBase(getFirstVisibleForwardBase());
+
+ final int last_visible_base_coord =
+ getHighXPositionOfBase(getLastVisibleForwardBase());
+
+ if(getFeatureStackViewFlag())
+ {
+
+ }
+ else if(getOneLinePerEntryFlag())
+ {
+ final int group_size = getEntryGroup().size();
+ for(int i = 0 ; i < group_size; ++i)
+ {
+ final int forward_entry_line = getDisplayLineOfEntryIndex(i, true);
+ final int reverse_entry_line = getDisplayLineOfEntryIndex(i, false);
+
+ final Entry current_entry = getEntryGroup().elementAt(i);
+
+ if(getEntryGroup().getDefaultEntry() == current_entry &&
+ Options.getOptions().highlightActiveEntryFlag())
+ {
+ g.setColor(active_entry_colour);
+ fillLane(g, forward_entry_line,
+ first_visible_base_coord, last_visible_base_coord);
+ fillLane(g, reverse_entry_line,
+ first_visible_base_coord, last_visible_base_coord);
+ }
+ else
+ {
+ g.setColor(light_grey);
+ fillLane(g, forward_entry_line,
+ first_visible_base_coord, last_visible_base_coord);
+ fillLane(g, reverse_entry_line,
+ first_visible_base_coord, last_visible_base_coord);
+ }
+ }
+ }
+ else
+ {
+ g.setColor(light_grey);
+ if(show_forward_lines)
+ {
+ fillLane(g, getFrameDisplayLine(FORWARD_FRAME_1),
+ first_visible_base_coord, last_visible_base_coord);
+ fillLane(g, getFrameDisplayLine(FORWARD_FRAME_2),
+ first_visible_base_coord, last_visible_base_coord);
+ fillLane(g, getFrameDisplayLine(FORWARD_FRAME_3),
+ first_visible_base_coord, last_visible_base_coord);
+ }
+
+ if(show_reverse_lines)
+ {
+ fillLane(g, getFrameDisplayLine(REVERSE_FRAME_1),
+ first_visible_base_coord, last_visible_base_coord);
+ fillLane(g, getFrameDisplayLine(REVERSE_FRAME_2),
+ first_visible_base_coord, last_visible_base_coord);
+ fillLane(g, getFrameDisplayLine(REVERSE_FRAME_3),
+ first_visible_base_coord, last_visible_base_coord);
+ }
+ }
+
+ g.setColor(not_so_light_grey);
+ fillLane(g, getFrameDisplayLine(FORWARD_STRAND),
+ first_visible_base_coord, last_visible_base_coord);
+ fillLane(g, getFrameDisplayLine(REVERSE_STRAND),
+ first_visible_base_coord, last_visible_base_coord);
+ }
+
+ /**
+ * Fill one lane/frame line with the given colour.
+ **/
+ private void fillLane(Graphics g, int fill_line_number,
+ int first_visible_base_coord, int last_visible_base_coord)
+ {
+ int fill_line_top = fill_line_number*getFontHeight() + 1;
+
+ g.fillRect(first_visible_base_coord, fill_line_top,
+ last_visible_base_coord - first_visible_base_coord + 1,
+ getFeatureHeight());
+ }
+
+
+ /**
+ * Draw the line showing the base numbers in the middle of the canvas.
+ * The smallest numbers will be to the left.
+ **/
+ private void drawScale(Graphics g)
+ {
+ g.setColor(Color.black);
+
+ final int scale_line = getFrameDisplayLine(SCALE_LINE);
+ final int scale_number_y_pos = scale_line * getFontHeight();
+
+ final float bases_per_pixel =
+ (float)getMaxVisibleBases()/getWidth();
+
+ final int base_label_spacing;
+
+ if(getScaleFactor() == 0)
+ {
+ // set the spacing so that the labels are at multiples of ten at the
+ // lowest scale factor
+ base_label_spacing =
+ (int)Math.ceil(MINIMUM_LABEL_SPACING * bases_per_pixel / 10) * 10;
+ }
+ else
+ {
+ // set the spacing so that the labels are at multiples of 100 otherwise
+ base_label_spacing =
+ (int)Math.ceil(MINIMUM_LABEL_SPACING * bases_per_pixel / 100) * 100;
+ }
+
+ //final int label_spacing = (int)(base_label_spacing / bases_per_pixel);
+
+ final int possible_index_of_first_label;
+ final int seq_length = getSequenceLength();
+
+ if(isRevCompDisplay())
+ possible_index_of_first_label = (seq_length -
+ getLastVisibleForwardBase() + 1) / base_label_spacing;
+ else
+ possible_index_of_first_label =
+ getFirstVisibleForwardBase() / base_label_spacing;
+
+ final int index_of_first_label;
+
+ if(possible_index_of_first_label <= 0)
+ index_of_first_label = 1;
+ else
+ index_of_first_label = possible_index_of_first_label;
+
+ final int index_of_last_label;
+
+ if(isRevCompDisplay())
+ index_of_last_label = (seq_length -
+ getFirstVisibleForwardBase() + 1) / base_label_spacing;
+ else
+ index_of_last_label =
+ getLastVisibleForwardBase() / base_label_spacing;
+
+ final int font_ascent = getFontAscent();
+ final int font_width = getFontWidth();
+ final int font_line_hgt = getFontHeight();
+
+ for(int i = index_of_first_label; i <= index_of_last_label; ++i)
+ {
+ final String label_string=
+ String.valueOf((int)(i * base_label_spacing));
+ final int scale_number_x_pos;
+
+ if(isRevCompDisplay())
+ scale_number_x_pos =
+ getLowXPositionOfBase(seq_length -
+ i * base_label_spacing + 1);
+ else
+ scale_number_x_pos =
+ getLowXPositionOfBase(i * base_label_spacing);
+
+ g.drawString(label_string,
+ scale_number_x_pos + 2,
+ scale_number_y_pos + font_ascent + 1);
+
+ g.drawLine(scale_number_x_pos, scale_number_y_pos,
+ scale_number_x_pos, scale_number_y_pos + font_line_hgt);
+
+ if(isRevCompDisplay())
+ g.drawLine(scale_number_x_pos, scale_number_y_pos,
+ scale_number_x_pos + font_width, scale_number_y_pos);
+ else
+ g.drawLine(scale_number_x_pos,
+ scale_number_y_pos + font_line_hgt,
+ scale_number_x_pos + font_width,
+ scale_number_y_pos + font_line_hgt);
+ }
+ }
+
+ /**
+ * Draw the bases of the forward and reverse strands into a Graphics
+ * object.
+ **/
+ private void drawBases(Graphics g)
+ {
+ if(getScaleFactor() > 1 ||
+ getScaleFactor() == 1 && !show_base_colours)
+ return;
+
+ if(getFeatureStackViewFlag())
+ return;
+
+ final Strand forward_strand;
+ final Strand reverse_strand;
+
+ if(isRevCompDisplay())
+ {
+ reverse_strand = getBases().getForwardStrand();
+ forward_strand = getBases().getReverseStrand();
+ }
+ else
+ {
+ forward_strand = getBases().getForwardStrand();
+ reverse_strand = getBases().getReverseStrand();
+ }
+
+ final Range forward_range =
+ newRange(getFirstVisibleForwardBase(),
+ getLastVisibleForwardBase());
+
+ String forward_visible_bases =
+ forward_strand.getSubSequence(forward_range).toUpperCase();
+
+ final int forward_frame_line = getFrameDisplayLine(FORWARD_STRAND);
+ final int forward_sequence_length = forward_visible_bases.length();
+ final int offset;
+
+ if(getForwardBaseAtLeftEdge() < 1)
+ offset = 1 - getForwardBaseAtLeftEdge();
+ else
+ offset = 0;
+
+ g.setFont(getFont());
+ int yposition = forward_frame_line * getFontHeight();
+
+ // draw fwd bases
+ if(getScaleFactor() == 0)
+ {
+ if(!(g instanceof SVGGraphics2D))
+ g.drawString(forward_visible_bases, offset * getFontWidth(),
+ yposition + getFontAscent() + 1);
+ else
+ {
+ // for svg graphics
+ for(int i=0;i<forward_visible_bases.length();i++)
+ g.drawString(String.valueOf(forward_visible_bases.charAt(i)),
+ (offset+i)*getFontWidth(),
+ yposition + getFontAscent() + 1);
+ }
+ }
+ else
+ {
+ for(int base_index = 0; base_index < forward_sequence_length;
+ ++base_index)
+ drawOnePixelBase(g, forward_visible_bases.charAt(base_index),
+ offset + base_index, yposition);
+ }
+
+ final Range reverse_range = newRange(getFirstVisibleReverseBase(),
+ getLastVisibleReverseBase());
+
+ String reverse_visible_bases =
+ reverse_strand.getSubSequence(reverse_range).toUpperCase();
+
+ reverse_visible_bases = reverse(reverse_visible_bases);
+
+ final int reverse_frame_line = getFrameDisplayLine(REVERSE_STRAND);
+ final int reverse_sequence_length = reverse_visible_bases.length();
+ yposition = reverse_frame_line * getFontHeight();
+
+ // draw bwd bases
+ if(getScaleFactor() == 0)
+ {
+ if(!(g instanceof SVGGraphics2D))
+ g.drawString(reverse_visible_bases, offset * getFontWidth(),
+ yposition + getFontAscent() + 1);
+ else
+ {
+ // for svg graphics
+ for(int i=0;i<reverse_visible_bases.length();i++)
+ g.drawString(String.valueOf(reverse_visible_bases.charAt(i)),
+ (offset+i)*getFontWidth(),
+ yposition + getFontAscent() + 1);
+ }
+ }
+ else
+ {
+ for(int base_index = 0; base_index < reverse_sequence_length;
+ ++base_index)
+ drawOnePixelBase(g, reverse_visible_bases.charAt(base_index),
+ offset + base_index, yposition);
+ }
+ }
+
+
+ /**
+ *
+ * Reverse the String.
+ *
+ */
+ private String reverse(String string)
+ {
+ StringBuffer sb = new StringBuffer(string);
+ return sb.reverse().toString();
+ }
+
+
+ /**
+ * Draw the codons of the sequence into a graphics object. If the scale is
+ * 0 then the codon letters will be drawn, otherwise just the stop codons
+ * will marked.
+ * @param g The object to draw into.
+ **/
+ private void drawCodons(Graphics g)
+ {
+ if(getOneLinePerEntryFlag() || getFeatureStackViewFlag())
+ return;
+
+ g.setColor(Color.black);
+
+ if(getScaleFactor() == 0)
+ {
+ if(show_forward_lines)
+ drawForwardCodonLetters(g);
+
+ if(show_reverse_lines)
+ drawReverseCodonLetters(g);
+ }
+ else
+ {
+ final int MAX_STOP_CODON_SCALE_FACTOR = 7;
+ if((show_stop_codons || show_start_codons) &&
+ getScaleFactor() <= MAX_STOP_CODON_SCALE_FACTOR)
+ {
+ if(show_forward_lines)
+ drawCodons(g,true);
+
+ if(show_reverse_lines)
+ drawCodons(g,false);
+ }
+ }
+ }
+
+
+ /**
+ * Mark the start and stop codons on the three forward frame lines.
+ * @param g The object to draw into.
+ **/
+ private void drawCodons(Graphics g, boolean fwd)
+ {
+ final Strand strand;
+ final int first_visible_base;
+ final int end_base;
+ final int direction;
+
+ if(fwd)
+ {
+ direction = FORWARD;
+ if(isRevCompDisplay())
+ strand = getBases().getReverseStrand();
+ else
+ strand = getBases().getForwardStrand();
+ first_visible_base = getForwardBaseAtLeftEdge();
+ // base to end translation at
+ // we + 3 to the upper bound because partial codons do not get translated
+ // by getTranslation()
+ end_base = getLastVisibleForwardBase() + 3;
+ }
+ else
+ {
+ direction = REVERSE;
+ if(isRevCompDisplay())
+ strand = getBases().getForwardStrand();
+ else
+ strand = getBases().getReverseStrand();
+ first_visible_base = getFirstVisibleReverseBase();
+ end_base = getLastVisibleReverseBase() + 3;
+ }
+
+ final int frame_shift = (first_visible_base - 1) % 3;
+
+ // base to start translation at - we start slightly off the
+ // left of the screen
+ int start_base = first_visible_base - frame_shift;
+
+ // not used if show_stop_codons is false
+ int [][] stop_codons = null;
+
+ if(show_stop_codons)
+ {
+ if(fwd && start_base < 1)
+ start_base = 1;
+
+ stop_codons = strand.getStopOrStartCodons(newRange(start_base, end_base),
+ null);
+// stop_codons = new int [][]
+// {
+// strand.getStopCodons(newRange(start_base, end_base)),
+// strand.getStopCodons(newRange(start_base + 1, end_base)),
+// strand.getStopCodons(newRange(start_base + 2, end_base))
+// };
+ }
+
+ // not used if show_start_codons is false
+ int [][] start_codons = null;
+
+ if(show_start_codons)
+ {
+ final StringVector starts = Options.getOptions().getStartCodons();
+
+ start_codons = strand.getStopOrStartCodons(newRange(start_base, end_base),
+ starts);
+// start_codons = new int [][]
+// {
+// strand.getMatchingCodons(newRange(start_base, end_base),
+// starts),
+// strand.getMatchingCodons(newRange(start_base + 1, end_base),
+// starts),
+// strand.getMatchingCodons(newRange(start_base + 2, end_base),
+// starts)
+// };
+ }
+
+ for(int i = 0 ; i < 3 ; ++i)
+ {
+ final int this_frame_line;
+ if(fwd)
+ this_frame_line = getFrameDisplayLine(FORWARD_FRAME_1 + i);
+ else
+ this_frame_line = getFrameDisplayLine(REVERSE_FRAME_1 - i);
+
+ if(show_start_codons)
+ {
+ final int[] this_frame_start_codons = start_codons[i];
+
+ drawCodonMarkLine(g, this_frame_line,
+ this_frame_start_codons,
+ direction, 80);
+ }
+
+ if(show_stop_codons)
+ {
+ final int[] this_frame_stop_codons = stop_codons[i];
+
+ drawCodonMarkLine(g, this_frame_line,
+ this_frame_stop_codons,
+ direction, 100);
+ }
+ }
+ }
+
+
+ /**
+ * Draw the codon letters into the three forward frame lines.
+ * @param g The object to draw into.
+ **/
+ private void drawForwardCodonLetters(Graphics g)
+ {
+ final Strand strand;
+
+ if(isRevCompDisplay())
+ strand = getBases().getReverseStrand();
+ else
+ strand = getBases().getForwardStrand();
+
+ final int first_visible_base = getFirstVisibleForwardBase();
+
+ // base to end translation at - we end slightly off the
+ // right of the screen
+ final int end_base = getLastVisibleForwardBase() + 1;
+
+ for(int i = 0 ; i < 3 ; ++i)
+ {
+ final int frame_shift = 1 - (first_visible_base + 3 - i) % 3;
+
+ // base to start translation at - we start slightly off the left of the
+ // screen so that the first partial codon is translated as '.'
+ final int start_base = first_visible_base + frame_shift;
+ final int frame_line = getFrameDisplayLine(FORWARD_FRAME_1 + i);
+
+ final AminoAcidSequence this_frame_translation =
+ strand.getSpacedTranslation(newRange(start_base, end_base), false);
+
+ final String this_frame_translation_string =
+ this_frame_translation.toString();
+
+ drawCodonLine(g, frame_shift, frame_line,
+ this_frame_translation_string,
+ FORWARD);
+ }
+ }
+
+ /**
+ * Draw the codon letters into the three reverse frame lines.
+ * @param g The object to draw into.
+ **/
+ private void drawReverseCodonLetters(Graphics g)
+ {
+ final Strand strand;
+
+ if(isRevCompDisplay())
+ strand = getBases().getForwardStrand();
+ else
+ strand = getBases().getReverseStrand();
+
+ // base to end translation at - we end slightly off the
+ // left of the screen
+ final int first_visible_base = getFirstVisibleReverseBase();
+ final int end_base = getLastVisibleReverseBase() + 1;
+
+ for(int i = 0 ; i < 3 ; ++i)
+ {
+ final int frame_shift = 1 - (first_visible_base + 3 - i) % 3;
+
+ // base to start translation at - we start slightly off the right of the
+ // screen so that the first partial codon is translated as '.'
+ final int start_base = first_visible_base + frame_shift;
+ final int frame_line = getFrameDisplayLine(REVERSE_FRAME_1 - i);
+
+ final AminoAcidSequence this_frame_translation =
+ strand.getSpacedTranslation(newRange(start_base, end_base), false);
+
+ final String this_frame_translation_string =
+ this_frame_translation.toString();
+
+ drawCodonLine(g, frame_shift, frame_line,
+ this_frame_translation_string,
+ REVERSE);
+ }
+ }
+
+
+ /**
+ * Draw one line of codons.
+ * @param g The object to draw into.
+ * @param frame_start When drawing in the FORWARD direction this is the
+ * offset from the left of the screen at which to start drawing the line.
+ * For REVERSE it is the offset from the last visible base.
+ * @param line_number The frame line to draw into. (see
+ * getFrameDisplayLine() for more details about these line numbers).
+ * @param codons A String containing the codon letters to draw.
+ * @param direction The direction to draw the letters in(see the
+ * frame_start parameter).
+ **/
+ private void drawCodonLine(Graphics g, int frame_start,
+ int line_number, String codons,
+ int direction)
+ {
+ final int offset;
+
+ if(getForwardBaseAtLeftEdge() < 1 && direction != REVERSE)
+ offset = 1 - getForwardBaseAtLeftEdge();
+ else
+ offset = 0;
+
+// codons = codons.toUpperCase();
+ final int draw_y_position = line_number * getFontHeight();
+ final int draw_x_position;
+
+ if(direction == REVERSE)
+ {
+ draw_x_position = getLowXPositionOfBase(getLastVisibleForwardBase()) -
+ (int)((offset+frame_start+codons.length()) * getScaleValue());
+
+ codons = reverse(codons);
+ }
+ else
+ draw_x_position = (int)((offset + frame_start + 1) * getScaleValue());
+
+ if(!(g instanceof SVGGraphics2D))
+ g.drawString(codons, draw_x_position,
+ draw_y_position + getFontAscent() + 1);
+ else
+ {
+ for(int i=0;i<codons.length();i++)
+ g.drawString(String.valueOf(codons.charAt(i)), draw_x_position+(i*getFontWidth() ),
+ draw_y_position + getFontAscent() + 1);
+ }
+ }
+
+ /**
+ * Draw one line of codons marks (stop codons or start codons).
+ * @param g The object to draw into.
+ * @param line_number The frame line to draw into. (see
+ * getFrameDisplayLine() for more details about these line numbers).
+ * @param codon_positions A vector containing the base positions of the
+ * codons to mark.
+ * @param direction The direction to draw the letters in(see the
+ * frame_start parameter).
+ * @param height_percent The height of the mark as a percentage of the
+ * frame line height.
+ **/
+ private void drawCodonMarkLine(final Graphics g,
+ final int line_number,
+ final int [] codon_positions,
+ final int direction,
+ final int height_percent)
+ {
+
+ //final int first_visible_forward_base = getForwardBaseAtLeftEdge();
+ //final int first_visible_reverse_base = getFirstVisibleReverseBase();
+ //final int last_visible_forward_base = getLastVisibleForwardBase();
+ //final double scale_value = getScaleValue();
+
+ final int line_height = getFontHeight();
+
+ final Integer colour_number =
+ Options.getOptions().getIntegerProperty("colour_of_start_codon");
+
+ final Color start_codon_colour;
+
+ if(colour_number != null)
+ {
+ final int int_colour_number = colour_number.intValue();
+ start_codon_colour =
+ Options.getOptions().getColorFromColourNumber(int_colour_number);
+ }
+ else
+ start_codon_colour = Color.blue;
+
+ final int draw_y_position = line_number * line_height +
+ (int)(1.0 * line_height *(100 - height_percent) / 100 / 2 + 0.5);
+
+ final int mark_height = height_percent * line_height / 100;
+
+ // used to remember the position of the previously drawn codon
+ int last_x_position = -1;
+
+ if(height_percent < 100)
+ g.setColor(start_codon_colour);
+ else
+ g.setColor(Color.black);
+
+
+ int length = 0;
+ if(direction != FORWARD)
+ length = getBases().getLength();
+
+ final int codon_positions_length = codon_positions.length;
+ for(int i = 0; i < codon_positions_length; ++i)
+ {
+ final int codon_base_start = codon_positions[i];
+
+ // zero is the end of data marker
+ if(codon_base_start == 0)
+ break;
+
+ int draw_x_position;
+
+ if(direction == FORWARD)
+ draw_x_position = getLowXPositionOfBase(codon_base_start+2);
+ else
+ {
+ final int raw_base_position = length - codon_base_start + 1;
+// final int raw_base_position =
+// getBases().getComplementPosition(codon_base_start);
+ draw_x_position = getLowXPositionOfBase(raw_base_position);
+ }
+
+ // draw only if we haven't drawn on the position already
+ if(draw_x_position != last_x_position || last_x_position == -1)
+ {
+ drawOneCodonMark(g, draw_x_position, draw_y_position,
+ direction, mark_height);
+
+ last_x_position = draw_x_position;
+ }
+ }
+ }
+
+ /**
+ * Return the colour in which the given base should be drawn: blue for C,
+ * red for T, green for A and black for G.
+ **/
+ private static Color getColourOfBase(final char base_char)
+ {
+ switch(base_char)
+ {
+ case 'c': case 'C':
+ return Color.blue;
+ case 't': case 'T':
+ return Color.red;
+ case 'a': case 'A':
+ return dark_green;
+ case 'g': case 'G':
+ return Color.black;
+ default:
+ return Color.white;
+ }
+ }
+
+
+ /**
+ * Draw a codon mark at the given position. The codon is represented
+ * by a one pixel wide line for scale factor greater than 1 and 3 pixels
+ * wide if the scale factor is one.
+ * @param g The object to draw into.
+ * @param x_pos The x position at which to draw the codon.
+ * @param y_pos The y position at which to draw the codon. The line
+ * is drawn down from the given x,y position.
+ * @param direction If this is FORWARD the line is draw from the y position
+ * to the right, otherwise it is drawn to the left.
+ * @param height The height in pixels of the codon mark.
+ **/
+ private void drawOneCodonMark(final Graphics g,
+ final int x_pos, final int y_pos,
+ final int direction,
+ final int height)
+ {
+ if(getScaleFactor() == 1)
+ {
+ // draw a line three pixels/bases wide
+ if(direction == FORWARD)
+ {
+ g.drawLine(x_pos + 1, y_pos,
+ x_pos + 1, y_pos + height - 1);
+ g.drawLine(x_pos + 2, y_pos,
+ x_pos + 2, y_pos + height - 1);
+ }
+ else
+ {
+ g.drawLine(x_pos - 1, y_pos,
+ x_pos - 1, y_pos + height - 1);
+ g.drawLine(x_pos - 2, y_pos,
+ x_pos - 2, y_pos + height - 1);
+ }
+ }
+ g.drawLine(x_pos, y_pos, x_pos, y_pos + height - 1);
+ }
+
+ /**
+ * Draw a one pixel wide line representing a single base on the forward or
+ * reverse strand. The base is coloured according to which base is passed
+ * to the method: blue for C, red for T, green for A and black for G.
+ * @param g The object to draw into.
+ * @param aa_char The character to draw.
+ * @param x_pos The x position at which to draw the line representing the
+ * base.
+ * @param y_pos The y position at which to draw the top of the line
+ * representing the base.
+ **/
+ private void drawOnePixelBase(Graphics g, char base_char,
+ int x_pos, int y_pos)
+ {
+ g.setColor(getColourOfBase(base_char));
+ g.drawLine(x_pos, y_pos, x_pos, y_pos + getFontHeight() - 1);
+ }
+
+ /**
+ * Get the 'feature stack view' line number for a feature.
+ * @param thisCDSFeature
+ * @param parentId
+ * @param predicate
+ * @param sortByPosition
+ * @return
+ */
+ private int getFeatureStackLineNumber(final Feature thisCDSFeature,
+ final String parentId,
+ final FeaturePredicate predicate,
+ final boolean sortByPosition)
+ {
+ final String sysName = thisCDSFeature.getSystematicName();
+ final Location loc = thisCDSFeature.getLocation();
+ final Range range = loc.getTotalRange();
+ final FeatureVector features;
+
+ if(sortByPosition)
+ {
+ if(visibleFeaturesSortByPosition == null)
+ visibleFeaturesSortByPosition = getVisibleFeatures().sort(feature_position_comparator);
+ features = visibleFeaturesSortByPosition;
+ }
+ else
+ {
+ if(visibleFeaturesSortBySize == null)
+ visibleFeaturesSortBySize = getVisibleFeatures().sort(feature_comparator);
+ features = visibleFeaturesSortBySize;
+ }
+
+ int cnt = 0;
+ for(int i=0; i<features.size(); i++)
+ {
+ final Feature f = features.elementAt(i);
+
+ if( range.getStart() == f.getLocation().getTotalRange().getStart() )
+ {
+ if(f.getSystematicName().equals(sysName))
+ {
+ if(f.getKey().equals(thisCDSFeature.getKey()))
+ {
+ if(parentId != null && parentId.equals(getParentQualifier(f)))
+ break;
+ else if(thisCDSFeature == f)
+ break;
+ }
+ }
+ }
+
+ if(predicate.testPredicate(f) &&
+ range.fuzzyOverlaps(f.getLocation().getTotalRange(), 10))
+ cnt++;
+
+ if(cnt > MAX_LINES_FEATURE_STACK)
+ break;
+ }
+
+ if(((cnt*STACK_EXPAND_FACTOR)+8) > MAX_LINES_FEATURE_STACK)
+ cnt = (MAX_LINES_FEATURE_STACK-8)/STACK_EXPAND_FACTOR;
+ return cnt;
+ }
+
+ /**
+ * Return locus_tag, Parent (GFF) or transcript_id (GTF) qualifier or
+ * null.
+ * @param f
+ * @return
+ */
+ public static String getParentQualifier(final Feature f)
+ {
+ try
+ {
+ if(f.getQualifierByName("locus_tag") != null)
+ return (String) f.getQualifierByName("locus_tag").getValues().get(0);
+ else if(f.getQualifierByName("Parent") != null)
+ return (String) f.getQualifierByName("Parent").getValues().get(0);
+ else if(f.getQualifierByName("transcript_id") != null)
+ return (String) f.getQualifierByName("transcript_id").getValues().get(0);
+ }
+ catch (InvalidRelationException e){}
+ return null;
+ }
+
+ /**
+ * Return true if the feature is a CDS, pseudogenic_exon or an exon
+ * that has been added as a frame line feature. This defines what
+ * feature types get stacked in the feature stack view.
+ * @param f feature
+ * @param keyStr feature key
+ * @return
+ */
+ private boolean isStackingFeature(final Feature f)
+ {
+ if( isProteinFeature(f) )
+ return true;
+ return false;
+ }
+
+ /**
+ * Return the line on the canvas where this feature segment should be drawn.
+ * @param segment The segment in question.
+ * @return The line to draw into.
+ **/
+ private int getSegmentDisplayLine(FeatureSegment segment)
+ {
+ if(getFeatureStackViewFlag())
+ {
+ final FeaturePredicate predicate = new FeaturePredicate(){
+ public boolean testPredicate(Feature f)
+ {
+ if(isStackingFeature(f))
+ return true;
+ return false;
+ }
+ };
+
+ String keyStr = segment.getFeature().getKey().toString();
+ if(keyStr.equals("gene") || keyStr.equals("pseudogene"))
+ return getLineCount()-2;
+ else if( isStackingFeature(segment.getFeature()) )
+ {
+ final String parentId = getParentQualifier(segment.getFeature());
+ return getLineCount()-4-( getFeatureStackLineNumber(
+ segment.getFeature(), parentId, predicate, false) * STACK_EXPAND_FACTOR);
+ }
+ else if(keyStr.indexOf("utr") > -1 || keyStr.indexOf("UTR") > -1 )
+ {
+ try
+ {
+ // try to identify with associated CDS and return same line
+ final String parentId = getParentQualifier(segment.getFeature());
+ final FeatureVector visibleFeatures = getVisibleFeatures().sort(feature_comparator);
+ for(int i=0; i<visibleFeatures.size(); i++)
+ {
+ final Feature f = visibleFeatures.elementAt(i);
+ if(isStackingFeature(f))
+ {
+ if(parentId.equals( getParentQualifier(f) ))
+ {
+ int ln = getLineCount()-4-( getFeatureStackLineNumber(
+ f, parentId, predicate, false) * STACK_EXPAND_FACTOR );
+
+ if(ln < 1)
+ ln = STACK_EXPAND_FACTOR;
+ if(STACK_EXPAND_FACTOR == 2)
+ ln--;
+ return ln;
+ }
+ }
+ }
+ }
+ catch (Exception e){}
+ return MAX_LINES_FEATURE_STACK-5;
+ }
+ else if(getAllPossibleContigKeys().contains(keyStr))
+ {
+ if(keyStr.equals("gap"))
+ return 1;
+ return 2;
+ }
+
+ ///
+ ///
+ final FeaturePredicate predicate2 = new FeaturePredicate(){
+ public boolean testPredicate(Feature f)
+ {
+ final String thisKeyStr = f.getKey().getKeyString();
+ if( !thisKeyStr.equals("gene") && !thisKeyStr.equals("pseudogene") &&
+ !isStackingFeature(f) &&
+ (thisKeyStr.indexOf("utr") == -1 || thisKeyStr.indexOf("UTR") == -1 ) &&
+ !getAllPossibleContigKeys().contains(thisKeyStr) )
+ return true;
+ return false;
+ }
+ };
+ int ln = getFeatureStackLineNumber(segment.getFeature(), null, predicate2, true);
+ if(ln % 2 == 0)
+ return 3;
+
+ return 4;
+ }
+ else if(getOneLinePerEntryFlag())
+ {
+ final Feature feature = segment.getFeature();
+ if((isProteinFeature(feature) || frame_features_flag) &&
+ (show_forward_lines && (segment.isForwardSegment() ^
+ isRevCompDisplay()) ||
+ show_reverse_lines && (!segment.isForwardSegment() ^
+ isRevCompDisplay())))
+ {
+ return getFeatureDisplayLine(feature,segment);
+ }
+ else
+ {
+ if(segment.isForwardSegment() ^ isRevCompDisplay())
+ return getFrameDisplayLine(FORWARD_STRAND);
+ else
+ return getFrameDisplayLine(REVERSE_STRAND);
+ }
+ }
+ else
+ {
+ final int frame_id =
+ maybeFlipFrameDirection(getSegmentFrameID(segment));
+
+ return getFrameDisplayLine(frame_id);
+ }
+ }
+
+
+ protected Object[] getProteinKeys()
+ {
+ return protein_keys;
+ }
+
+ protected void setProteinKeys(Object[] protein_keys)
+ {
+ this.protein_keys = protein_keys;
+ repaint();
+ }
+
+ private boolean isProteinFeature(Feature feature)
+ {
+ final String key = feature.getKey().toString();
+
+ if(isExonOfNonCodingTranscript(feature, key))
+ return false;
+
+ for(int i=0; i<protein_keys.length; i++)
+ {
+ if(key.equals((String)protein_keys[i]))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if this feature is an exon and is a child of a non-coding transcript
+ * and is a GFF3 feature.
+ * @param feature
+ * @param key
+ * @return
+ */
+ private boolean isExonOfNonCodingTranscript(final Feature feature, final String key)
+ {
+ if(key.equals(DatabaseDocument.EXONMODEL) &&
+ feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ final String nonCodingTranscripts[] = GeneUtils.getNonCodingTranscripts();
+ try
+ {
+ Qualifier qualifier = feature.getQualifierByName("Parent");
+ if(qualifier != null)
+ {
+ final ChadoCanonicalGene chadoGene =
+ ((GFFStreamFeature)feature.getEmblFeature()).getChadoGene();
+ final String transcriptName = qualifier.getValues().get(0);
+ final GFFStreamFeature transcript =
+ (GFFStreamFeature)chadoGene.getFeatureFromId(transcriptName);
+ final String transcriptKey = transcript.getKey().getKeyString();
+
+ for(int i=0; i<nonCodingTranscripts.length; i++)
+ if(nonCodingTranscripts[i].equals(transcriptKey))
+ return true;
+ }
+ }
+ catch(Exception e){}
+ }
+ return false;
+ }
+
+ /**
+ * Return the frame ID of to use when drawing the given segment.
+ **/
+ private int getSegmentFrameID(final FeatureSegment segment)
+ {
+ final int frame_id = segment.getFrameID();
+ final Feature feature = segment.getFeature();
+
+ if((isProteinFeature(feature) || frame_features_flag) &&
+ (show_forward_lines && (segment.isForwardSegment() ^
+ isRevCompDisplay())||
+ show_reverse_lines && (!segment.isForwardSegment() ^
+ isRevCompDisplay())))
+ {
+ return frame_id;
+ }
+ else
+ {
+ switch(frame_id)
+ {
+ case FORWARD_FRAME_1:
+ // fall through
+ case FORWARD_FRAME_2:
+ // fall through
+ case FORWARD_FRAME_3:
+ // fall through
+ case FORWARD_STRAND:
+ return FORWARD_STRAND;
+ case REVERSE_FRAME_1:
+ // fall through
+ case REVERSE_FRAME_2:
+ // fall through
+ case REVERSE_FRAME_3:
+ // fall through
+ case REVERSE_STRAND:
+ return REVERSE_STRAND;
+ default:
+ return frame_id;
+ }
+ }
+ }
+
+ /**
+ * Return the line on the canvas where the given feature should be drawn
+ * when one_line_per_entry is true. Call getLineCount() to find out the
+ * total number of lines. Each line is getFontHeight() pixels high.
+ * @return The line to draw into.
+ **/
+ private int getFeatureDisplayLine(final Feature feature,
+ final FeatureSegment segment)
+ {
+ final int entry_index = getEntryGroup().indexOf(feature.getEntry());
+ return getDisplayLineOfEntryIndex(entry_index,
+ segment.isForwardSegment() ^
+ isRevCompDisplay());
+ }
+
+ /**
+ * Return the line in the display where features from the entry specified
+ * by the given entry_index should be drawn.
+ * @param is_forward_feature indicates whether the feature of interest is
+ * on the forward or reverse strand.
+ **/
+ private int getDisplayLineOfEntryIndex(final int entry_index,
+ final boolean is_forward_feature)
+ {
+ if(is_forward_feature)
+ {
+ if(getShowForwardFrameLines())
+ {
+ if(show_labels)
+ return entry_index * 2;
+ else
+ return entry_index;
+ }
+ else
+ return 0;
+ }
+ else
+ {
+ int return_value = 0;
+ if(show_labels)
+ {
+ return_value = getEntryGroup().size() * 2 + 3 - entry_index * 2;
+
+ if(getShowForwardFrameLines())
+ return_value += getEntryGroup().size() * 2;
+ }
+ else
+ {
+ return_value = getEntryGroup().size() + 2 - entry_index;
+
+ if(getShowForwardFrameLines())
+ return_value += getEntryGroup().size();
+ }
+ return return_value;
+ }
+ }
+
+ /**
+ * If rev_comp_display is true return the corresponding frame_id from the
+ * opposite strand(eg. FORWARD_FRAME_1 gives REVERSE_FRAME_3) otherwise
+ * return the frame_id unchanged.
+ **/
+ private int maybeFlipFrameDirection(final int frame_id)
+ {
+ if(isRevCompDisplay())
+ {
+ // flip the frame so that forward becomes reverse and reverse becomes
+ // forward
+ switch(frame_id)
+ {
+ case FORWARD_FRAME_1:
+ return REVERSE_FRAME_1;
+ case FORWARD_FRAME_2:
+ return REVERSE_FRAME_2;
+ case FORWARD_FRAME_3:
+ return REVERSE_FRAME_3;
+ case FORWARD_STRAND:
+ return REVERSE_STRAND;
+ case REVERSE_FRAME_1:
+ return FORWARD_FRAME_1;
+ case REVERSE_FRAME_2:
+ return FORWARD_FRAME_2;
+ case REVERSE_FRAME_3:
+ return FORWARD_FRAME_3;
+ case REVERSE_STRAND:
+ return FORWARD_STRAND;
+ default:
+ return frame_id;
+ }
+ }
+ return frame_id;
+ }
+
+ /**
+ * Return the line on the canvas where this frame ID should be drawn. Call
+ * getLineCount() to find out the total number of lines. Each line is
+ * getFontHeight() pixels high.
+ * @param frame_id The frame ID.
+ * @return The line to draw into.
+ **/
+ private int getFrameDisplayLine(int frame_id)
+ {
+ if(getFeatureStackViewFlag())
+ {
+ return 0;
+ }
+ if(getOneLinePerEntryFlag())
+ {
+ int return_value;
+ switch(frame_id)
+ {
+ case FORWARD_STRAND:
+ return_value = 0;
+ break;
+ case REVERSE_STRAND:
+ if(show_labels)
+ return_value = 3;
+ else
+ return_value = 2;
+ break;
+ case SCALE_LINE:
+ if(show_labels)
+ return_value = 2;
+ else
+ return_value = 1;
+ break;
+ default:
+ throw new Error("internal error - unexpected value: " + frame_id);
+ }
+
+ if(show_forward_lines)
+ {
+ if(show_labels)
+ return_value += getEntryGroup().size();
+
+ return return_value + getEntryGroup().size();
+ }
+ else
+ return return_value;
+ }
+
+ final int line_number;
+
+ switch(frame_id)
+ {
+ case FORWARD_FRAME_1:
+ line_number = 0;
+ break;
+ case FORWARD_FRAME_2:
+ if(show_labels)
+ line_number = 2;
+ else
+ line_number = 1;
+ break;
+ case FORWARD_FRAME_3:
+ if(show_labels)
+ line_number = 4;
+ else
+ line_number = 2;
+ break;
+ case FORWARD_STRAND:
+ if(show_forward_lines)
+ {
+ if(show_labels)
+ line_number = 6;
+ else
+ line_number = 3;
+ }
+ else
+ line_number = 0;
+ break;
+ case REVERSE_STRAND:
+ if(show_forward_lines)
+ {
+ if(show_labels)
+ line_number = 9;
+ else
+ line_number = 5;
+ }
+ else
+ {
+ if(show_labels)
+ line_number = 3;
+ else
+ line_number = 2;
+ }
+ break;
+ case REVERSE_FRAME_3:
+ if(show_forward_lines)
+ {
+ if(show_labels)
+ line_number = 11;
+ else
+ line_number = 6;
+ }
+ else
+ {
+ if(show_labels)
+ line_number = 5;
+ else
+ line_number = 3;
+ }
+ break;
+ case REVERSE_FRAME_2:
+ if(show_forward_lines)
+ {
+ if(show_labels)
+ line_number = 13;
+ else
+ line_number = 7;
+ }
+ else
+ {
+ if(show_labels)
+ line_number = 7;
+ else
+ line_number = 4;
+ }
+ break;
+ case REVERSE_FRAME_1:
+ if(show_forward_lines)
+ {
+ if(show_labels)
+ line_number = 15;
+ else
+ line_number = 8;
+ }
+ else
+ {
+ if(show_labels)
+ line_number = 9;
+ else
+ line_number = 5;
+ }
+ break;
+ default:
+ if(show_forward_lines)
+ {
+ if(show_labels)
+ line_number = 8;
+ else
+ line_number = 4;
+ }
+ else
+ {
+ if(show_labels)
+ line_number = 2;
+ else
+ line_number = 1;
+ }
+ break;
+ }
+
+ return line_number;
+ }
+
+ /**
+ * Return the number of lines of text we need to fit on the canvas. This
+ * is used to set the height of the canvas.
+ **/
+ private int getLineCount()
+ {
+ int line_count;
+
+ if(show_labels)
+ line_count = 5;
+ else
+ line_count = 3;
+
+ int extra_line_count;
+
+ if(getFeatureStackViewFlag())
+ return MAX_LINES_FEATURE_STACK;
+ else if(getOneLinePerEntryFlag()) // some number of entry lines
+ extra_line_count = getEntryGroup().size();
+ else // three frame line
+ extra_line_count = 3;
+
+ if(show_labels)
+ extra_line_count *= 2;
+
+ if(show_forward_lines)
+ line_count += extra_line_count;
+
+ if(show_reverse_lines)
+ line_count += extra_line_count;
+
+ return line_count;
+ }
+
+ /**
+ * Return the vertical offset from the top of the canvas for this feature.
+ * The returned value will be the y-coordinate of the top of the lane that
+ * this feature should be draw into.
+ **/
+ private int getSegmentVerticalOffset(FeatureSegment segment)
+ {
+ // one "line" is font_height pixels high,(the number of lines times the
+ // font_height is the height of the height of canvas)
+ final int line = getSegmentDisplayLine(segment);
+ return getLineOffset(line);
+ }
+
+ /**
+ * Given a line return the vertical offset(in pixels) from the top of the
+ * canvas.
+ **/
+ private int getLineOffset(final int line)
+ {
+ return line * getFontHeight();
+ }
+
+ /**
+ * Return the lowest on screen(with respect to the canvas) x coordinate of
+ * a base. If the scale_factor is zero then one base will be font_width
+ * wide and this method will return a different value than
+ * getHighXPositionOfBase(). If scale_factor is greater than one then
+ * the two methods will return the same thing.
+ * @param base_number The(forward) base to calculate the position of.
+ **/
+ private int getLowXPositionOfBase(final int base_number)
+ {
+ return (int)((base_number - getForwardBaseAtLeftEdge()) *
+ getScaleValue());
+ }
+
+ /**
+ * Return the highest on screen(ie with respect to the canvas) x
+ * coordinate of a base. See comment on getLowXPositionOfBase().
+ * @param base_number The(forward) base to calculate the position of.
+ **/
+ private int getHighXPositionOfBase(final int base_number)
+ {
+ if(getScaleFactor() == 0)
+ return getLowXPositionOfBase(base_number) + getFontWidth() - 1;
+ else
+ return getLowXPositionOfBase(base_number);
+ }
+
+ /**
+ * Return the low on screen x coordinate of the base at the given Marker
+ * position. If the scale_factor is zero then one base will be font_width
+ * wide so that this method will return a different position than
+ * getHighXPositionOfMarker().
+ * @param marker The Marker position to return the screen position of.
+ **/
+ private int getLowXPositionOfMarker(Marker marker)
+ {
+ int position = marker.getRawPosition();
+
+ if(isRevCompDisplay())
+ position = getSequenceLength() - position + 1;
+
+ if(marker.getStrand().isForwardStrand() ^ isRevCompDisplay())
+ return getLowXPositionOfBase(position);
+ else
+ return getHighXPositionOfBase(position);
+ }
+
+ /**
+ * Return the high on screen x coordinate of the base at the given Marker
+ * position. If the scale_factor is zero then one base will be font_width
+ * wide so that this method will return a different position than
+ * getLowXPositionOfMarker().
+ * @param marker The Marker position to return the screen position of.
+ **/
+ private int getHighXPositionOfMarker(Marker marker)
+ {
+ int position = marker.getRawPosition();
+
+ if(isRevCompDisplay())
+ position = getSequenceLength() - position + 1;
+
+ if(marker.getStrand().isForwardStrand() ^ isRevCompDisplay())
+ return getHighXPositionOfBase(position);
+ else
+ return getLowXPositionOfBase(position);
+ }
+
+
+ /**
+ * This is the approximate opposite of getLowXPositionOfBase() - it returns
+ * MarkerRange corresponding to the given screen position. It returns a
+ * MarkerRange rather than a Marker because if the user clicks on a FRAME
+ * line then we want to select a whole codon not just one base.
+ * @param position A position on the canvas.
+ * @return A MarkerRange covering the clicked on bases.
+ **/
+ private MarkerRange getMarkerRangeFromPosition(Point position)
+ {
+ return getMarkerRangeFromPosition(position, true);
+ }
+
+ /**
+ * Given a Point and a direction(either FORWARD_STRAND or REVERSE_STRAND),
+ * return a the corresponding base position on that Strand.
+ **/
+ private int getBasePositionOfPoint(final Point position,
+ final int direction)
+ {
+ if(direction == FORWARD_STRAND)
+ {
+ return(int)(1.0 * position.x / getScaleValue() +
+ getForwardBaseAtLeftEdge());
+ }
+ else
+ {
+ if(direction == REVERSE_STRAND)
+ {
+ final int raw_base_position =
+ (int)(1.0 * position.x / getScaleValue() +
+ getForwardBaseAtLeftEdge());
+ return getBases().getComplementPosition(raw_base_position);
+ }
+ else
+ throw new Error("internal error - unexpected value: " + direction);
+ }
+ }
+
+ /**
+ * This is the approximate opposite of getLowXPositionOfBase() - it returns
+ * MarkerRange corresponding to the given screen position. It returns a
+ * MarkerRange rather than a Marker because if the user clicks on a FRAME
+ * line then we want to select a whole codon not just one base.
+ * @param position A position on the canvas.
+ * @param whole_codon If true then return a range that covers a whole codon
+ * if the click was on a frame line.
+ * @return A MarkerRange covering the clicked on bases.
+ **/
+ private MarkerRange getMarkerRangeFromPosition(final Point position,
+ final boolean whole_codon)
+ {
+ final int frame_id;
+ int base_position;
+ final Strand strand;
+
+// if(scrollbar_style == SCROLLBAR_AT_TOP)
+// position.x += scrollbar.getPreferredSize().height;
+
+ if(getOneLinePerEntryFlag())
+ {
+ final int scale_line = getFrameDisplayLine(SCALE_LINE);
+ final int position_line = getLineFromPoint(position);
+
+ if(position_line < scale_line)
+ {
+ if(isRevCompDisplay())
+ strand = getBases().getReverseStrand();
+ else
+ strand = getBases().getForwardStrand();
+
+ frame_id = FORWARD_STRAND;
+ }
+ else
+ {
+ if(position_line > scale_line)
+ {
+ if(isRevCompDisplay())
+ strand = getBases().getForwardStrand();
+ else
+ strand = getBases().getReverseStrand();
+
+ frame_id = REVERSE_STRAND;
+ }
+ else
+ return null;
+ }
+
+ base_position = getBasePositionOfPoint(position, frame_id);
+ }
+ else
+ {
+ frame_id = getFrameFromPoint(position);
+
+ // calculate the frame/strand line and base position
+ switch(frame_id)
+ {
+ case FORWARD_FRAME_1:
+ case FORWARD_FRAME_2:
+ case FORWARD_FRAME_3:
+ case FORWARD_STRAND:
+ if(isRevCompDisplay())
+ strand = getBases().getReverseStrand();
+ else
+ strand = getBases().getForwardStrand();
+
+ base_position = getBasePositionOfPoint(position, FORWARD_STRAND);
+ break;
+ case REVERSE_FRAME_1:
+ case REVERSE_FRAME_2:
+ case REVERSE_FRAME_3:
+ case REVERSE_STRAND:
+ if(isRevCompDisplay())
+ strand = getBases().getForwardStrand();
+ else
+ strand = getBases().getReverseStrand();
+
+ base_position = getBasePositionOfPoint(position, REVERSE_STRAND);
+ break;
+ default:
+ base_position = 0;
+ strand = null;
+ }
+ }
+
+ final int start_base_position;
+ final int end_base_position;
+
+ if(whole_codon)
+ {
+ start_base_position =
+ adjustBasePositionForFrame(frame_id, base_position, true);
+ end_base_position =
+ adjustBasePositionForFrame(frame_id, base_position, false);
+ }
+ else
+ {
+ start_base_position = base_position;
+ end_base_position = base_position;
+ }
+
+ if(strand == null)
+ return null;
+ else
+ {
+ try
+ {
+ return strand.makeMarkerRangeFromPositions(start_base_position,
+ end_base_position);
+ }
+ catch(OutOfRangeException e)
+ {
+ // XXX
+ return null;
+ }
+ }
+ }
+
+
+ /**
+ * Helper method for getMarkerRangeFromPosition(). Returns a base
+ * position offset to the start/end of the codon if the given frame id on a
+ * FRAME.
+ * @param get_start If true then the base position returned is base
+ * position of the start of the codon, otherwise the end base is
+ * returned.
+ **/
+ private int adjustBasePositionForFrame(int frame_id,
+ final int base_position,
+ final boolean get_start)
+ {
+ final int return_base_position;
+ final int end_offset;
+
+ if(get_start)
+ end_offset = 0;
+ else
+ {
+ // the end is two bases ahead of the start
+ end_offset = 2;
+ }
+
+ switch(frame_id)
+ {
+ case FORWARD_FRAME_1:
+ case REVERSE_FRAME_1:
+ {
+ final int base_pos_mod3 =(base_position - 1) % 3;
+ return_base_position = base_position + end_offset - base_pos_mod3;
+ }
+ break;
+ case FORWARD_FRAME_2:
+ case REVERSE_FRAME_2:
+ {
+ final int base_pos_mod3 =(base_position - 2) % 3;
+ return_base_position = base_position + end_offset - base_pos_mod3;
+ }
+ break;
+ case FORWARD_FRAME_3:
+ case REVERSE_FRAME_3:
+ {
+ final int base_pos_mod3 =(base_position - 3) % 3;
+ return_base_position = base_position + end_offset - base_pos_mod3;
+ }
+ break;
+ default:
+ return_base_position = base_position;
+ // do nothing
+ }
+
+ return return_base_position;
+ }
+
+ /**
+ * Draw one feature at the correct place in a Graphics object.
+ * @param g The Graphics object on which to draw.
+ * @param draw_feature_fill If true then draw just the solid block of colour
+ * inside the segments. If false then draw just the outline.
+ * @param feature The feature to draw.
+ **/
+ private void drawFeature(final Graphics g,
+ final Vector<SegmentBorder> segment_borders,
+ final Feature feature,
+ final boolean draw_feature_fill,
+ final FeatureVector selected_features,
+ final FeatureSegmentVector selected_segments,
+ final int segment_height,
+ final int seq_length,
+ final FontMetrics fm)
+ {
+ final FeatureSegmentVector this_feature_segments = feature.getSegments();
+ final int num_segs = this_feature_segments.size();
+
+ // don't try to draw a feature with no segments
+ if(num_segs == 0)
+ return;
+
+ // set to true if and only if the whole of this feature should be
+ // highlighted
+ final boolean highlight_feature_flag;
+
+ if(selected_features.contains(feature))
+ {
+ // ignore the possibility that a feature and a segment from the same
+ // feature could be in the selection vector at the same time
+ highlight_feature_flag = true;
+ }
+ else
+ {
+ // if the feature border flag is off and the feature is not selected
+ // then don't draw the border
+ highlight_feature_flag = false;
+ }
+
+ boolean highlight_segment_flag;
+ boolean draw_direction_arrow_flag;
+ FeatureSegment current_segment;
+
+ if(show_labels)
+ drawFeatureLabel(g, feature, seq_length, fm);
+
+ // draw each segment/exon
+ boolean trans_spliced = false;
+ for(int i = 0; i < num_segs; ++i)
+ {
+ current_segment = this_feature_segments.elementAt(i);
+
+ if(selected_segments.indexOf(current_segment) == -1)
+ highlight_segment_flag = false;
+ else
+ highlight_segment_flag = true;
+
+ // draw an arrow only on the last segment
+ if(i == num_segs - 1 && show_feature_arrows)
+ draw_direction_arrow_flag = true;
+ else
+ draw_direction_arrow_flag = false;
+
+ SegmentBorder fb = drawSegment(g, current_segment,
+ highlight_feature_flag, highlight_segment_flag,
+ draw_direction_arrow_flag, segment_height);
+ if(fb != null)
+ segment_borders.add(fb);
+
+ // draw a line between the segments
+ if(i + 1 < num_segs)
+ {
+ final FeatureSegment next_segment =
+ this_feature_segments.elementAt(i + 1);
+
+ trans_spliced = drawSegmentConnection(g, current_segment,
+ next_segment, trans_spliced);
+ }
+ }
+
+ // draw the label last if the is no label line because in this case the
+ // label is draw on top of the feature segments
+ if(!show_labels)
+ drawFeatureLabel(g, feature, seq_length, fm);
+ }
+
+ /**
+ * Draw a bent line between two segments which represents the connection
+ * between exons in feature.
+ * @param g The Graphics object on which to draw.
+ * @param lower_segment The reference of the segment that is closest to the
+ * start of the Strand. The connection line will start at the beginning
+ * of this segment.
+ * @param upper_segment The reference of the segment that is furthest from
+ * the start of the Strand. The connection line will finish at the end
+ * of this segment.
+ **/
+ private boolean drawSegmentConnection(Graphics g,
+ FeatureSegment lower_segment,
+ FeatureSegment upper_segment,
+ boolean trans_spliced)
+ {
+ Marker upper_segment_start_marker = upper_segment.getStart();
+ Marker lower_segment_end_marker = lower_segment.getEnd();
+
+ // trans-spliced
+ if((upper_segment.isForwardSegment() ^ lower_segment.isForwardSegment()) ||
+ trans_spliced)
+ {
+ trans_spliced = true;
+ int start = upper_segment.getStart().getRawPosition();
+ int end = upper_segment.getEnd().getRawPosition();
+ if(end < start)
+ upper_segment_start_marker = upper_segment.getEnd();
+
+ start = lower_segment.getStart().getRawPosition();
+ end = lower_segment.getEnd().getRawPosition();
+
+ if(end < start)
+ lower_segment_end_marker = lower_segment.getStart();
+ }
+
+ int next_segment_start_coord =
+ getLowXPositionOfMarker(upper_segment_start_marker);
+
+ // make sure we don't wrap around when drawing
+ if(next_segment_start_coord > 16000)
+ next_segment_start_coord = 16000;
+
+ // make sure we don't wrap around when drawing
+ if(next_segment_start_coord < -16000)
+ next_segment_start_coord = -16000;
+
+ int this_segment_end_coord =
+ getHighXPositionOfMarker(lower_segment_end_marker);
+
+ // make sure we don't wrap around when drawing
+ if(this_segment_end_coord > 16000)
+ this_segment_end_coord = 16000;
+
+ // make sure we don't wrap around when drawing
+ if(this_segment_end_coord < -16000)
+ this_segment_end_coord = -16000;
+
+ final int this_segment_vertical_offset =
+ getSegmentVerticalOffset(lower_segment);
+ final int next_segment_vertical_offset =
+ getSegmentVerticalOffset(upper_segment);
+
+ // we draw the line with a bend in the middle - this is the vertical
+ // position of the bend
+ final int line_y_position_for_centre;
+
+ if(this_segment_vertical_offset < next_segment_vertical_offset)
+ line_y_position_for_centre = this_segment_vertical_offset;
+ else
+ line_y_position_for_centre = next_segment_vertical_offset;
+
+ final int line_y_position_for_this =
+ this_segment_vertical_offset + getFeatureHeight() / 2;
+ final int line_y_position_for_next =
+ next_segment_vertical_offset + getFeatureHeight() / 2;
+
+ final int horizontal_position_of_centre =
+ (this_segment_end_coord + next_segment_start_coord) / 2;
+
+ final Feature segment_feature = lower_segment.getFeature();
+ final Color feature_colour = segment_feature.getColour();
+
+ // draw in black if no colour is specified or if the feature is selected
+ if(feature_colour == null ||
+ getSelection().contains(lower_segment.getFeature()))
+ g.setColor(Color.black);
+ else
+ g.setColor(feature_colour);
+
+ g.drawLine(this_segment_end_coord,
+ line_y_position_for_this,
+ horizontal_position_of_centre,
+ line_y_position_for_centre);
+
+ g.drawLine(horizontal_position_of_centre,
+ line_y_position_for_centre,
+ next_segment_start_coord,
+ line_y_position_for_next);
+
+ return trans_spliced;
+ }
+
+ /**
+ * Draw a label a feature. This is a helper function for drawFeature().
+ * If show_labels is true the labels will be drawn below the features,
+ * otherwise they will be drawn within the features.
+ * @param g The Graphics object on which to draw.
+ * @param feature The feature to draw the label for.
+ **/
+ private void drawFeatureLabel(Graphics g, final Feature feature,
+ final int seq_length,
+ final FontMetrics fm)
+ {
+ // the three frame translation is visible when the scale factor is 0,
+ // don't draw labels over it
+ if(!show_labels && getScaleFactor() == 0)
+ return;
+
+ final String label = feature.getLabel();
+
+ // special case - don't display a label if the label qualifier is "*"
+ if(label != null && label.equals("*"))
+ return;
+
+ final String label_or_gene = feature.getIDString();
+
+ // don't waste time drawing nothing
+ if(label_or_gene.length() == 0)
+ return;
+
+ final int string_width = fm.stringWidth(label_or_gene);
+ final FeatureSegment first_segment = feature.getSegments().elementAt(0);
+ final int label_x_coord;
+
+ if(first_segment.isForwardSegment() ^ isRevCompDisplay())
+ {
+ int segment_start_pos =
+ first_segment.getStart().getRawPosition();
+
+ if(isRevCompDisplay())
+ segment_start_pos = seq_length - segment_start_pos + 1;
+
+ label_x_coord = getLowXPositionOfBase(segment_start_pos);
+ }
+ else
+ {
+ int segment_end_pos =
+ first_segment.getEnd().getRawPosition();
+
+ if(isRevCompDisplay())
+ segment_end_pos = seq_length - segment_end_pos + 1;
+
+ // reverse the start and end on the complementary strand
+ label_x_coord = getLowXPositionOfBase(segment_end_pos);
+ }
+
+ if(label_x_coord >= getSize().width)
+ return;
+
+// if(label_x_coord + string_width <= 0) {
+ // don't draw the label if it is not visible on screen
+// }
+
+ int vertical_offset =
+ getSegmentVerticalOffset(first_segment);
+
+ if(show_labels)
+ vertical_offset += getFontHeight(); // move to the label line
+
+ // save this so we can restore it later
+ Shape saved_clip = null;
+ if(!show_labels)
+ saved_clip = g.getClip();
+
+ // if we have a labels line draw a white background behind the
+ // label
+ if(show_labels)
+ {
+ g.setColor(Color.white);
+
+ g.fillRect(label_x_coord - getFontWidth(),
+ vertical_offset+2,
+ string_width + getFontWidth() * 2,
+ getFontHeight()-1);
+ }
+ else
+ {
+ // if there is no label line clip to the size of the first segment
+ // and draw in there
+ final int segment_start_coord =
+ getSegmentStartCoord(first_segment);
+ final int segment_end_coord =
+ getSegmentEndCoord(first_segment);
+
+ if(Math.abs(segment_end_coord - segment_start_coord) > 5)
+ {
+ if(segment_end_coord > segment_start_coord)
+ g.setClip(segment_start_coord, vertical_offset,
+ segment_end_coord - segment_start_coord,
+ getFeatureHeight());
+ else
+ g.setClip(segment_end_coord, vertical_offset,
+ segment_start_coord - segment_end_coord,
+ getFeatureHeight());
+ }
+ else
+ return; // don't draw small labels if there is no room
+ }
+
+ g.setColor(Color.black);
+ g.drawString(label_or_gene, label_x_coord + 1,
+ vertical_offset + getFontAscent() + 1);
+
+ if(!show_labels)
+ g.setClip(saved_clip);
+
+ }
+
+ /**
+ * Return the position on the canvas where this segment starts. The
+ * returned value will be -1 if the position is off the left of the screen
+ * and will be (width of the canvas) if the position is off the right of
+ * the screen.
+ **/
+ private int getSegmentStartCoord(FeatureSegment segment)
+ {
+ final Marker segment_start_marker = segment.getStart();
+ int segment_start_coord =
+ getLowXPositionOfMarker(segment_start_marker);
+
+ // make sure we don't wrap around when drawing
+ if(segment_start_coord > getSize().width)
+ segment_start_coord = getSize().width;
+ else if(segment_start_coord < 0)
+ segment_start_coord = -1;
+
+ return segment_start_coord;
+ }
+
+ /**
+ * Return the position on the canvas where this segment ends. The returned
+ * value will be -1 if the position is off the left of the screen and will
+ * be(width of the canvas) if the position is off the right of the screen.
+ **/
+ private int getSegmentEndCoord(FeatureSegment segment)
+ {
+ final Marker segment_end_marker = segment.getEnd();
+ int segment_end_coord = getHighXPositionOfMarker(segment_end_marker);
+
+ // make sure we don't wrap around when drawing
+ if(segment_end_coord > getSize().width)
+ segment_end_coord = getSize().width;
+
+ // make sure we don't wrap around when drawing
+ if(segment_end_coord < 0)
+ segment_end_coord = -1;
+
+ return segment_end_coord;
+ }
+
+ /**
+ * Return true if the base given by the Marker is visible.
+ **/
+ private boolean baseVisible(Marker marker)
+ {
+ final int first_visible_base = getForwardBaseAtLeftEdge();
+ final int last_visible_base = getLastVisibleForwardBase();
+
+ int marker_position = marker.getRawPosition();
+
+ if(isRevCompDisplay())
+ marker_position = getBases().getComplementPosition(marker_position);
+
+ if(marker_position < first_visible_base ||
+ marker_position > last_visible_base)
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Return if and only if the segment is (partly) within the range of bases
+ * we are currently displaying.
+ * @param segment The FeatureSegment to check.
+ **/
+ private boolean segmentVisible(FeatureSegment segment)
+ {
+ final int segment_start_coord = getSegmentStartCoord(segment);
+ final int segment_end_coord = getSegmentEndCoord(segment);
+ final int width = getSize().width;
+
+ if(segment_end_coord < 0 && segment_start_coord < 0 ||
+ segment_start_coord >= width &&
+ segment_end_coord >= width)
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Return if and only if some part of a feature is(partly) within the
+ * range of bases we are currently displaying.
+ * @param feature The Feature to check.
+ **/
+ private boolean featureVisible(Feature feature)
+ {
+ if(getMinimumScore() > 0)
+ {
+ final int feature_score = feature.getScore();
+
+ if(feature_score != -1 && feature_score < getMinimumScore())
+ return false;
+ }
+
+ final FeatureSegmentVector segments = feature.getSegments();
+
+ for(int i = 0 ; i < segments.size() ; ++i)
+ {
+ if(segmentVisible(segments.elementAt(i)))
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Draw one FeatureSegment into a Graphics object.
+ * @param g The Graphics object on which to draw.
+ * @param segment The feature segment to draw
+ * @param highlight_feature If true draw an extra thick line
+ * @param highlight_segment If true draw the segment with a doubly thick
+ * line.
+ * @param draw_feature_fill If true then just the solid block of colour
+ * inside the segments will be drawn. If false then only the feature
+ * outline is drawn.
+ * @param draw_arrow If true draw a direction arrow at the end of the
+ * segment.
+ **/
+ private SegmentBorder drawSegment(Graphics g, FeatureSegment segment,
+ final boolean highlight_feature,
+ final boolean highlight_segment,
+ final boolean draw_arrow,
+ final int segment_height)
+ {
+ // not on screen
+ if(!segmentVisible(segment))
+ return null;
+
+ final Feature segment_feature = segment.getFeature();
+ final int vertical_offset = getSegmentVerticalOffset(segment) + 1;
+
+ int segment_start_coord = getSegmentStartCoord(segment);
+ int segment_end_coord = getSegmentEndCoord(segment);
+
+ // this is 1 if the feature is on the forward strand or on a forward frame
+ // and -1 otherwise. this used to draw the feature arrow in the right
+ // direction.
+ final int feature_direction;
+ if(segment.isForwardSegment() ^ isRevCompDisplay())
+ {
+ feature_direction = 1;
+ if(segment_end_coord < segment_start_coord)
+ {
+ int tmp = segment_start_coord;
+ segment_start_coord = segment_end_coord;
+ segment_end_coord = tmp;
+ }
+ }
+ else
+ {
+ feature_direction = -1;
+ if(segment_end_coord > segment_start_coord)
+ {
+ int tmp = segment_start_coord;
+ segment_start_coord = segment_end_coord;
+ segment_end_coord = tmp;
+ }
+ }
+
+
+ final Color feature_colour = segment_feature.getColour();
+
+ // no colour means draw in white
+ if(feature_colour == null)
+ g.setColor(Color.white);
+ else
+ g.setColor(feature_colour);
+
+ final int segment_width;
+ if(feature_direction == 1)
+ {
+ segment_width = segment_end_coord - segment_start_coord + 1;
+
+ g.fillRect(segment_start_coord, vertical_offset,
+ segment_width, segment_height + 1);
+ }
+ else
+ {
+ segment_width = segment_start_coord - segment_end_coord + 1;
+
+ g.fillRect(segment_end_coord, vertical_offset,
+ segment_width, segment_height + 1);
+ }
+
+ if(!show_feature_borders && !highlight_feature)
+ return null;
+
+ if(feature_direction == 1)
+ return new SegmentBorder(highlight_feature, highlight_segment, draw_arrow,
+ segment_start_coord, vertical_offset,
+ segment_width,
+ feature_direction);
+ else
+ return new SegmentBorder(highlight_feature, highlight_segment, draw_arrow,
+ segment_end_coord, vertical_offset,
+ segment_width,
+ feature_direction);
+ }
+
+
+ /**
+ * Draw/highlight the selected range of bases on the FORWARD_STRAND or
+ * REVERSE_STRAND lines.
+ **/
+ private void drawBaseSelection(Graphics g)
+ {
+ final MarkerRange selection_range = getSelection().getMarkerRange();
+
+ if(selection_range == null)
+ return;
+
+ final Marker raw_start_base;
+ final Marker raw_end_base;
+
+ if(selection_range.getStrand().isForwardStrand())
+ {
+ raw_start_base = selection_range.getRawStart();
+ raw_end_base = selection_range.getRawEnd();
+ }
+ else
+ {
+ raw_start_base = selection_range.getRawEnd();
+ raw_end_base = selection_range.getRawStart();
+ }
+
+ final int start_coord = getLowXPositionOfMarker(raw_start_base);
+ final int end_coord = getHighXPositionOfMarker(raw_end_base);
+
+ // not on screen
+ if(start_coord > getSize().width &&
+ end_coord > getSize().width)
+ return;
+
+ // not on screen
+ if(start_coord < 0 && end_coord < 0)
+ return;
+
+ // the ID of the strand on which to draw the highlighting - this will be
+ // FORWARD_STRAND or REVERSE_STRAND.
+ final int strand_id;
+
+ if(selection_range.getStrand().isForwardStrand() ^
+ isRevCompDisplay())
+ strand_id = FORWARD_STRAND;
+ else
+ strand_id = REVERSE_STRAND;
+
+ if(!getOneLinePerEntryFlag())
+ {
+ // the ID of the frame on which to draw the highlighting - this will be
+ // FORWARD_FRAME_1, 2 or 3 or REVERSE_FRAME_1, 2 or 3.
+ final int frame_id;
+
+ if(selection_range.getStrand().isForwardStrand() ^
+ isRevCompDisplay())
+ {
+ switch((selection_range.getStart().getPosition() - 1) % 3)
+ {
+ case 0:
+ frame_id = FORWARD_FRAME_1;
+ break;
+ case 1:
+ frame_id = FORWARD_FRAME_2;
+ break;
+ case 2:
+ frame_id = FORWARD_FRAME_3;
+ break;
+ default:
+ frame_id = NO_FRAME;
+ }
+ }
+ else
+ {
+ switch((selection_range.getStart().getPosition() - 1) % 3)
+ {
+ case 0:
+ frame_id = REVERSE_FRAME_1;
+ break;
+ case 1:
+ frame_id = REVERSE_FRAME_2;
+ break;
+ case 2:
+ frame_id = REVERSE_FRAME_3;
+ break;
+ default:
+ frame_id = NO_FRAME;
+ }
+ }
+
+ if(show_forward_lines && strand_id == FORWARD_STRAND ||
+ show_reverse_lines && strand_id == REVERSE_STRAND)
+ drawBaseRange(g, start_coord, end_coord, frame_id, Color.pink);
+ }
+
+ drawBaseRange(g, start_coord, end_coord, strand_id, Color.yellow);
+ }
+
+ /**
+ * Draw a rectangle representing the currently selected bases.
+ * @param g The Graphics object on which to draw.
+ * @param start_coord The start x coordinate.
+ * @param start_coord The end x coordinate.
+ * @param frame_id The ID of the frame line where the box should be drawn.
+ * @param fill_colour The colour to use to draw the box(the border will be
+ * black).
+ **/
+ private void drawBaseRange(Graphics g,
+ int start_coord, int end_coord,
+ int frame_id,
+ Color fill_colour)
+ {
+ if(start_coord < -1)
+ start_coord = -1;
+
+ if(end_coord < -1)
+ end_coord = -1;
+
+ if(end_coord > getWidth())
+ end_coord = getWidth();
+
+ if(start_coord > getWidth())
+ start_coord = getWidth();
+
+ final int frame_line = getFrameDisplayLine(frame_id);
+
+ final int frame_line_y_coord = getLineOffset(frame_line);
+
+ final int [] x_points = {
+ start_coord, end_coord, end_coord, start_coord
+ };
+
+ final int [] y_points = {
+ frame_line_y_coord, frame_line_y_coord,
+ frame_line_y_coord + getFeatureHeight() + 1,
+ frame_line_y_coord + getFeatureHeight() + 1
+ };
+
+ g.setColor(fill_colour);
+
+ if(start_coord > end_coord)
+ g.fillRect(end_coord, frame_line_y_coord + 1,
+ start_coord - end_coord + 1, getFeatureHeight());
+ else
+ g.fillRect(start_coord, frame_line_y_coord + 1,
+ end_coord - start_coord + 1, getFeatureHeight());
+
+ g.setColor(Color.black);
+ g.drawPolygon(x_points, y_points, 4);
+ }
+
+ /**
+ * Return the number of button the user has down.
+ **/
+ private int buttonDownCount(final MouseEvent event)
+ {
+ int count = 0;
+ if((event.getModifiers() & InputEvent.BUTTON1_MASK) > 0)
+ ++count;
+
+ if((event.getModifiers() & InputEvent.BUTTON2_DOWN_MASK) > 0)
+ ++count;
+
+ if((event.getModifiers() & InputEvent.BUTTON3_MASK) > 0)
+ ++count;
+
+ return count;
+ }
+
+ /**
+ * Add mouse and key listeners to the canvas.
+ **/
+ private void addListeners()
+ {
+ addMouseListener(new MouseAdapter()
+ {
+ private FeaturePopup popup = null;
+
+ public void mouseEntered(MouseEvent event)
+ {
+ // grab focus to enable scrolling with ARROW_LEFT/RIGHT
+ requestFocusInWindow();
+ }
+
+ /**
+ * Listen for mouse press events so that we can do popup menus and
+ * selection.
+ **/
+ public void mousePressed(MouseEvent event)
+ {
+ // adjust for scrollbar
+ if(scrollbar_style == SCROLLBAR_AT_TOP)
+ event.translatePoint(0,-scrollbar.getPreferredSize().height);
+
+ // finish dragging if the user presses any other button because
+ // we may not get a mouseReleased() call on some platforms
+ if(click_segment_marker != null)
+ {
+ getEntryGroup().getActionController().endAction();
+ click_segment_marker = null;
+ }
+
+ //ignore
+ if(buttonDownCount(event) > 1)
+ return;
+
+ last_mouse_press_event = event;
+ handleCanvasMousePress(event);
+
+ if(isMenuTrigger(event))
+ {
+ if(popup == null)
+ popup = new FeaturePopup(FeatureDisplay.this,
+ getEntryGroup(),
+ getSelection(),
+ getGotoEventSource(),
+ getBasePlotGroup());
+ final JComponent parent = (JComponent)event.getSource();
+
+ parent.add(popup);
+ popup.show(parent, event.getX(), event.getY());
+ }
+ }
+
+ /**
+ * Listen for mouse release events so that we can call endAction().
+ **/
+ public void mouseReleased(MouseEvent event)
+ {
+ // adjust for scrollbar
+ if(scrollbar_style == SCROLLBAR_AT_TOP)
+ event.translatePoint(0,-scrollbar.getPreferredSize().height);
+
+ last_mouse_press_event = null;
+
+ if(click_segment_marker != null)
+ {
+ getEntryGroup().getActionController().endAction();
+ click_segment_marker = null;
+ }
+ }
+ });
+
+ // Listen for mouse motion events so that we can select ranges of bases.
+ addMouseMotionListener(new MouseMotionAdapter()
+ {
+ public void mouseDragged(MouseEvent event)
+ {
+ // adjust for scrollbar
+ if(scrollbar_style == SCROLLBAR_AT_TOP)
+ event.translatePoint(0,-scrollbar.getPreferredSize().height);
+
+ if(last_mouse_press_event != null &&
+ !isMenuTrigger(last_mouse_press_event))
+ handleCanvasMouseDrag(event);
+ }
+ });
+
+ addKeyListener(new KeyAdapter()
+ {
+ public void keyPressed(final KeyEvent event)
+ {
+ handleKeyPress(FeatureDisplay.this, event);
+ }
+ });
+ }
+
+ /**
+ * Handle a mouse press event on the drawing canvas - select on click,
+ * select and broadcast it on double click.
+ **/
+ private void handleCanvasMousePress(MouseEvent event)
+ {
+ if(event.getID() != MouseEvent.MOUSE_PRESSED)
+ return;
+
+ requestFocus();
+
+ if(event.getClickCount() == 2)
+ handleCanvasDoubleClick(event);
+ else
+ handleCanvasSingleClick(event);
+
+ repaint();
+ }
+
+ /**
+ * Handle a double click on the canvas.
+ **/
+ private void handleCanvasDoubleClick(MouseEvent event)
+ {
+ if(isMenuTrigger(event))
+ return;
+
+ if((event.getModifiers() & InputEvent.BUTTON2_MASK) != 0 ||
+ event.isAltDown())
+ {
+ final Selectable clicked_thing = getThingAtPoint(event.getPoint());
+
+ if(clicked_thing == null)
+ {
+ // select the ORF around the click position
+ final MarkerRange new_click_range =
+ getMarkerRangeFromPosition(event.getPoint());
+
+ if(new_click_range == null)
+ return;
+
+ final MarkerRange orf_range =
+ Strand.getORFAroundMarker(new_click_range.getStart(), true);
+
+ // user clicked on a stop codon
+ if(orf_range == null)
+ return;
+ else
+ getSelection().setMarkerRange(orf_range);
+ }
+ else
+ {
+ // edit the selected Feature
+
+ final Feature clicked_feature = getFeatureOf(clicked_thing);
+
+ getSelection().set(clicked_feature);
+
+ if(Options.readWritePossible())
+ {
+ if(clicked_feature.getEmblFeature() instanceof GFFStreamFeature &&
+ ((GFFStreamFeature)clicked_feature.getEmblFeature()).getChadoGene() != null)
+ new GeneBuilderFrame(clicked_feature, getEntryGroup(),
+ getSelection(), getGotoEventSource());
+ else
+ {
+ final JFrame frame = new JFrame("Artemis Feature Edit: " +
+ clicked_feature.getIDString() +
+ (clicked_feature.isReadOnly() ?
+ " - (read only)" :
+ ""));
+
+ final FeatureEdit fe = new FeatureEdit(clicked_feature, getEntryGroup(),
+ getSelection(), getGotoEventSource(), frame);
+
+ frame.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ fe.stopListening();
+ frame.dispose();
+ }
+ });
+
+ frame.getContentPane().add(fe);
+ frame.pack();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ frame.setLocation(new Point((screen.width - getSize().width)/2,
+ (screen.height - getSize().height)/2));
+ frame.setVisible(true);
+ }
+ }
+ }
+ }
+
+ makeSelectionVisible();
+ }
+
+ /**
+ * Handle a single click on the canvas.
+ **/
+ private void handleCanvasSingleClick(MouseEvent event)
+ {
+ if(getFeatureStackViewFlag())
+ {
+ if(event.getPoint().y < 10 &&
+ event.getPoint().x > getDisplayWidth()-10)
+ {
+ if(STACK_EXPAND_FACTOR == 1) // expand/collapse
+ STACK_EXPAND_FACTOR = 2;
+ else
+ STACK_EXPAND_FACTOR = 1;
+
+ updateOneLinePerFeatureFlag();
+ return;
+ }
+ }
+
+ click_segment_marker = null;
+
+ final Selectable clicked_thing = getThingAtPoint(event.getPoint());
+
+ // note: ALT_MASK and BUTTON2_MASK are the same value
+ // so check this is not BUTTON1 to allow BUTTON1+ALT to drag
+ if( clicked_thing == null ||
+ (event.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
+ {
+ // if the user presses the mouse button 2 on feature or segment we treat
+ // it like the feature/segment isn't there
+
+ // if the user hasn't clicked on anything then don't change the
+ // selection
+ if(isMenuTrigger(event))
+ return;
+
+ // something is selected but the user clicked on nothing with the
+ // shift key down - do nothing
+ if(event.isShiftDown() &&
+ getSelection().getAllFeatures().size() > 0)
+ return;
+
+ final MarkerRange old_selected_range = getSelection().getMarkerRange();
+
+ final MarkerRange new_click_range =
+ getMarkerRangeFromPosition(event.getPoint());
+
+ if(new_click_range == null)
+ {
+ click_range = null;
+ getSelection().clear();
+ return;
+ }
+
+ final MarkerRange new_selection_range;
+
+ if(!event.isShiftDown() || old_selected_range == null)
+ new_selection_range = new_click_range;
+ else
+ {
+ if(old_selected_range.getStrand() ==
+ new_click_range.getStrand())
+ {
+ // extend the selection
+ new_selection_range =
+ old_selected_range.combineRanges(new_click_range, true);
+ }
+ else
+ new_selection_range = old_selected_range;
+ }
+
+ getSelection().setMarkerRange(new_selection_range);
+ click_range = new_selection_range;
+ }
+ else
+ {
+ // can't select a feature and a MarkerRange
+ if(getSelection().getMarkerRange() != null &&
+ event.isShiftDown())
+ return;
+
+ // clear the MarkerRange because this isn't the start of a range drag
+ click_range = null;
+
+ getSelection().setMarkerRange(null);
+ final Feature clicked_feature = getFeatureOf(clicked_thing);
+ raiseFeature(clicked_feature);
+
+ if(clicked_thing instanceof Feature)
+ {
+ // toggle the feature in or out of the selection unless this event
+ // is a popup trigger in which case we should just make sure the
+ // feature is in the selection
+ if(getSelection().contains(clicked_feature))
+ {
+ if(! isMenuTrigger(event))
+ {
+ // change the selection only if the user isn't popping up a menu
+ if(event.isShiftDown())
+ getSelection().remove(clicked_feature);
+ else
+ getSelection().set(clicked_feature);
+ }
+ }
+ else
+ {
+ if(event.isShiftDown())
+ getSelection().add(clicked_feature);
+ else
+ getSelection().set(clicked_feature);
+ }
+ }
+ else
+ {
+ // must be a FeatureSegment
+
+ // toggle the feature segment in or out of the selection unless
+ // this event is a popup trigger in which case we should just make
+ // sure the segment is in the selection
+ final FeatureSegment clicked_segment =
+ (FeatureSegment) clicked_thing;
+
+ final FeatureSegmentVector selected_feature_segments =
+ getSelection().getSelectedSegments();
+
+ if(selected_feature_segments.contains(clicked_segment))
+ {
+ if(! isMenuTrigger(event))
+ {
+ if(event.isShiftDown())
+ getSelection().remove(clicked_segment);
+ else
+ getSelection().set(clicked_segment);
+ }
+ }
+ else
+ {
+ if(event.isShiftDown())
+ {
+ final FeatureVector selected_features =
+ getSelection().getSelectedFeatures();
+
+ if(selected_features.contains(clicked_feature))
+ getSelection().remove(clicked_feature);
+ else
+ getSelection().add(clicked_segment);
+ }
+ else
+ getSelection().set(clicked_segment);
+ }
+ }
+
+ if(Options.getOptions().canDirectEdit() &&
+ !clicked_feature.isReadOnly())
+ {
+ if(getFeatureStackViewFlag())
+ return;
+
+ // check to see if the click was on a marker, the second argument is
+ // false because we want the range to cover just one base
+
+ final MarkerRange new_click_range =
+ getMarkerRangeFromPosition(event.getPoint(), false);
+
+ // search the feature to find if the start Marker of any of the
+ // segments matches the start Marker of new_click_range or if an end
+ // Marker matches the end Marker of new_click_range
+
+ final FeatureSegmentVector segments = clicked_feature.getSegments();
+
+ for(int i = 0 ; i < segments.size() ; ++i)
+ {
+ final FeatureSegment this_segment = segments.elementAt(i);
+
+ if(this_segment.getStart().getStrand() == new_click_range.getStrand() &&
+ this_segment.canDirectEdit())
+ {
+ if(event.isShiftDown() && getSelection().getSelectedSegments().size() == 0)
+ {
+ int baseGrab = 15;
+ if(getScaleFactor() > 2)
+ baseGrab = 30;
+
+ if( (new_click_range.getStart().getPosition() >= this_segment.getStart().getPosition() &&
+ new_click_range.getStart().getPosition() <= this_segment.getStart().getPosition()+baseGrab) ||
+ (new_click_range.getEnd().getPosition() <= this_segment.getEnd().getPosition() &&
+ new_click_range.getEnd().getPosition() >= this_segment.getEnd().getPosition()-baseGrab) )
+ {
+ int distFromBeg = new_click_range.getStart().getPosition()-this_segment.getStart().getPosition();
+ int distFromEnd = this_segment.getEnd().getPosition()-new_click_range.getEnd().getPosition();
+ if(distFromBeg < distFromEnd)
+ {
+ click_segment_marker = this_segment.getStart();
+ click_segment_marker_is_start_marker = true;
+ other_end_of_segment_marker = this_segment.getEnd();
+ }
+ else
+ {
+ click_segment_marker = this_segment.getEnd();
+ click_segment_marker_is_start_marker = false;
+ other_end_of_segment_marker = this_segment.getStart();
+ }
+
+ getSelection().add(this_segment);
+ getEntryGroup().getActionController().startAction();
+ break;
+ }
+ }
+ else
+ {
+ if (new_click_range.getStart().equals(this_segment.getStart()))
+ {
+ click_segment_marker = this_segment.getStart();
+ click_segment_marker_is_start_marker = true;
+ other_end_of_segment_marker = this_segment.getEnd();
+ getEntryGroup().getActionController().startAction();
+ break;
+ }
+
+ if (new_click_range.getEnd().equals(this_segment.getEnd()))
+ {
+ click_segment_marker = this_segment.getEnd();
+ click_segment_marker_is_start_marker = false;
+ other_end_of_segment_marker = this_segment.getStart();
+ getEntryGroup().getActionController().startAction();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This is a helper method for handleCanvasSingleClick() and
+ * handleCanvasDoubleClick().
+ * @param object The should be an instance of Feature or FeatureSegment.
+ * @return If the the argument is a Feature it is returned, if it is a
+ * FeatureSegment the owning Feature is returned.
+ **/
+ private Feature getFeatureOf(final Object object)
+ {
+ if(object instanceof Feature)
+ return (Feature)object;
+ else
+ // object must be a FeatureSegment
+ return ((FeatureSegment)object).getFeature();
+ }
+
+ /**
+ * Handle a mouse drag that started on a Marker from a FeatureSegment.
+ **/
+ private void handleMarkerDrag(final MouseEvent event)
+ {
+ MarkerRange drag_range =
+ getMarkerRangeFromPosition(event.getPoint(), false);
+
+ if(drag_range == null)
+ return;
+
+ final int new_position;
+
+ if(click_segment_marker_is_start_marker)
+ {
+ // the Marker is at the start of the segment
+ new_position = drag_range.getStart().getPosition();
+
+ // don't go past the other end of the segment
+ if(new_position > other_end_of_segment_marker.getPosition())
+ return;
+ }
+ else
+ {
+ new_position = drag_range.getEnd().getPosition();
+
+ // don't go past the other end of the segment
+ if(new_position < other_end_of_segment_marker.getPosition())
+ return;
+ }
+
+ try
+ {
+ click_segment_marker.setPosition(new_position);
+
+ if(!baseVisible(click_segment_marker))
+ makeBaseVisible(click_segment_marker);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected OutOfRangeException");
+ }
+ }
+
+ /**
+ * Handle a mouse drag event on the drawing canvas.
+ **/
+ private void handleCanvasMouseDrag(final MouseEvent event)
+ {
+ if( event.isShiftDown() &&
+ (getSelection().getAllFeatures().size() > 1 ||
+ getSelection().getSelectedSegments().size() > 1))
+ return;
+
+ if(click_segment_marker != null)
+ {
+ handleMarkerDrag(event);
+ return;
+ }
+
+ MarkerRange drag_range = getMarkerRangeFromPosition(event.getPoint());
+
+ if(drag_range == null)
+ return;
+
+ final MarkerRange selected_range = getSelection().getMarkerRange();
+
+ // if the start and current positions of the drag are not on the
+ // same Strand then ignore this event
+ if(selected_range != null &&
+ drag_range.getStrand() != selected_range.getStrand())
+ return;
+
+ try
+ {
+ // user has dragged off the screen so use a marker at position 1
+ if(drag_range.getStart().getPosition() < 1)
+ drag_range = new MarkerRange(drag_range.getStrand(), 1, 1);
+
+ // user has dragged off the screen so use a marker at position the
+ // end of the sequence
+ if(drag_range.getEnd().getPosition() > getSequenceLength())
+ drag_range = new MarkerRange(drag_range.getStrand(),
+ getSequenceLength(),
+ getSequenceLength());
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected OutOfRangeException");
+ }
+
+ final boolean start_base_is_visible = baseVisible(drag_range.getStart());
+ final boolean end_base_is_visible = baseVisible(drag_range.getEnd());
+
+ if(!start_base_is_visible)
+ makeBaseVisible(drag_range.getStart());
+
+ if(!end_base_is_visible)
+ makeBaseVisible(drag_range.getEnd());
+
+ // scrolling was necessary so update the visible features vector
+ if(!start_base_is_visible || !end_base_is_visible)
+ needVisibleFeatureVectorUpdate();
+
+ final MarkerRange new_marker_range;
+
+ if(click_range == null)
+ new_marker_range = drag_range;
+ else
+ new_marker_range = selected_range.combineRanges(drag_range, true);
+
+ getSelection().setMarkerRange(new_marker_range);
+
+ repaint();
+ }
+
+ /**
+ * Return the reference of the Object at p on the canvas or null if there
+ * is none. The returned Object will be a Feature or a FeatureSegment.
+ **/
+ private Selectable getThingAtPoint(Point p)
+ {
+ final int line_of_point = getLineFromPoint(p);
+
+ // point is not on a forward or reverse frame or strand
+ if(line_of_point == -1)
+ return null;
+
+ // go through the features in reverse order because the feature that is
+ // drawn last will be on top
+ for(int i = getVisibleFeatures().size() - 1 ; i >= 0 ; --i)
+ {
+ final Feature current_feature = getVisibleFeatures().elementAt(i);
+ final FeatureSegmentVector segments = current_feature.getSegments();
+
+ for(int segment_index = 0; segment_index < segments.size();
+ ++segment_index)
+ {
+ final FeatureSegment current_segment =
+ segments.elementAt(segment_index);
+
+ final int line_of_segment = getSegmentDisplayLine(current_segment);
+
+ if(line_of_segment == line_of_point ||
+ show_labels && line_of_segment + 1 == line_of_point)
+ {
+ // this segment is in the right frame so check that it is between
+ // the start and end positions of the segment
+
+ if(p.x >= getSegmentStartCoord(current_segment) &&
+ p.x <= getSegmentEndCoord(current_segment) ||
+ p.x <= getSegmentStartCoord(current_segment) &&
+ p.x >= getSegmentEndCoord(current_segment))
+ {
+ final Feature segment_feature = current_segment.getFeature();
+
+ // special case - if there is only one segment then select the
+ // whole feature
+ if(segment_feature.getSegments().size() == 1)
+ {
+ //
+ // exon-model returns the segment - this is a fix for merging chado
+ if(segment_feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL))
+ return current_segment;
+ return segment_feature;
+ }
+ else
+ return current_segment;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Create and add a new scroll bar to this FeatureDisplay.
+ **/
+ private void createScrollbar(final boolean scrollbar_at_top)
+ {
+ scrollbar = new JScrollBar(Scrollbar.HORIZONTAL);
+
+ scrollbar.addAdjustmentListener(new AdjustmentListener()
+ {
+ public void adjustmentValueChanged(AdjustmentEvent e)
+ {
+ if(left_edge_base != e.getValue())
+ {
+ if(e.getValue() < getSequenceLength())
+ left_edge_base = e.getValue();
+ else
+ {
+ if(left_edge_base == getSequenceLength())
+ return;
+ else
+ left_edge_base = getSequenceLength();
+ }
+
+ needVisibleFeatureVectorUpdate();
+ updateOneLinePerFeatureFlag();
+
+ fireAdjustmentEvent(DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT);
+ repaint();
+ }
+ }
+ });
+
+ Box box = Box.createHorizontalBox();
+ box.add(scrollbar);
+
+ if(UIManager.getLookAndFeel().getName().equals("Mac OS X Aqua"))
+ box.add(Box.createHorizontalStrut(scrollbar.getPreferredSize().height));
+
+ if(scrollbar_at_top)
+ add(box, "North");
+ else
+ add(box, "South");
+ }
+
+
+ protected void updateOneLinePerFeatureFlag()
+ {
+ if(getFeatureStackViewFlag())
+ {
+ final Range visRange;
+ if(isRevCompDisplay())
+ {
+ final int first_visible_base = getFirstVisibleReverseBase();
+ final int last_visible_base = getLastVisibleReverseBase();
+ visRange = newRange(first_visible_base, last_visible_base);
+ }
+ else
+ visRange = getVisibleRange();
+ final FeatureVector visFeatures = getSortedFeaturesInRange(visRange);
+
+ short maxOverlaps = 2;
+ for(int i=0; i<visFeatures.size(); i++)
+ {
+ final Feature f1 = visFeatures.elementAt(i);
+ if(!isProteinFeature(f1))
+ continue;
+
+ final Range r = f1.getMaxRawRange();
+ short cnt = 0;
+ for(int j=0; j<visFeatures.size(); j++)
+ {
+ final Feature f2 = visFeatures.elementAt(j);
+ if( f1 == f2 )
+ break;
+
+ if(isProteinFeature(f2) && r.overlaps(f2.getMaxRawRange()))
+ cnt++;
+ }
+ if(cnt > maxOverlaps)
+ maxOverlaps = cnt;
+ }
+
+ if(((maxOverlaps*STACK_EXPAND_FACTOR)+10) != MAX_LINES_FEATURE_STACK)
+ {
+ MAX_LINES_FEATURE_STACK = (short) ((maxOverlaps*STACK_EXPAND_FACTOR)+10);
+ if(MAX_LINES_FEATURE_STACK > 42)
+ MAX_LINES_FEATURE_STACK = 42;
+ fixCanvasSize();
+ }
+ }
+ }
+
+ /**
+ * Send a DisplayAdjustmentEvent with the current start and end base to
+ * each of the listeners. This has package scope because EntryEdit
+ * components need to send this event when a new BasePlot component is
+ * added. The BasePlot needs to know the first visible base, last visible
+ * base and the maximum number of visible bases before the plot can be
+ * drawn.
+ * @param type the type of event: DisplayAdjustmentEvent.SCALE_ADJUST_EVENT,
+ * DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT or
+ * DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT.
+ **/
+ void fireAdjustmentEvent(final int type)
+ {
+ if(disable_display_events)
+ return;
+
+ final DisplayAdjustmentEvent event =
+ new DisplayAdjustmentEvent(this,
+ getForwardBaseAtLeftEdge(),
+ getLastVisibleForwardBase(),
+ getMaxVisibleBases(),
+ getScaleValue(), getScaleFactor(),
+ isRevCompDisplay(), type);
+
+ fireAction(adjustment_listener_list, event);
+ }
+
+ /**
+ * Send an event to those object listening for it.
+ * @param listeners A Vector of the objects that the event should be sent to.
+ * @param event The event to send
+ **/
+ private void fireAction(final Vector<DisplayAdjustmentListener> listeners, final ChangeEvent event)
+ {
+ final Vector<DisplayAdjustmentListener> targets;
+ // copied from a book - synchronizing the whole method might cause a
+ // deadlock
+ synchronized(this)
+ {
+ //targets = (Vector)listeners.clone();
+ targets = new Vector<DisplayAdjustmentListener>(listeners);
+ }
+
+ final int ntargets = targets.size();
+ for(int i = 0; i < ntargets; ++i)
+ {
+ DisplayAdjustmentListener target = targets.elementAt(i);
+ target.displayAdjustmentValueChanged((DisplayAdjustmentEvent)event);
+ }
+ }
+
+ /**
+ * Create the scroll bar used for changing the scale and add it to the
+ * FeatureDisplay.
+ **/
+ private void createScaleScrollbar()
+ {
+ scale_changer = new ZoomScrollBar(this);
+
+ if(System.getProperty("autohide") == null ||
+ System.getProperty("autohide").equals("false") )
+ add(scale_changer, "East");
+ else
+ scale_changer.addMouseMotionListenerToFeatureDisplay();
+ }
+
+ /**
+ * Update the parameters of the scrollbar taking changes to the entry_group
+ * into account.
+ **/
+ protected void fixScrollbar()
+ {
+ if(scrollbar == null)
+ return;
+
+ final int sequence_length = getSequenceLength();
+ final int max_visible_bases;
+
+ // make sure max_visible_bases is at least 1 to stop setBlockIncrement()
+ // from complaining
+ if(getMaxVisibleBases() > 0)
+ max_visible_bases = getMaxVisibleBases();
+ else
+ max_visible_bases = 1;
+
+ // initial_value, visible, minimum, maximum
+ scrollbar.setValues(getForwardBaseAtLeftEdge(),
+ max_visible_bases,
+ hard_left_edge ? 1 : -max_visible_bases / 2,
+ sequence_length + max_visible_bases / 2);
+
+ scrollbar.setBlockIncrement(max_visible_bases);
+
+ if(max_visible_bases >= 10 && getScaleFactor() > 0)
+ scrollbar.setUnitIncrement(max_visible_bases / 10);
+ else
+ scrollbar.setUnitIncrement(1);
+ }
+
+ /**
+ * Set the size of the canvas, taking the value of font_height and
+ * show_label into account.
+ **/
+ private void fixCanvasSize()
+ {
+ int new_width = getSize().width;
+ int new_height = getFontHeight() * getLineCount();
+
+ if(scale_changer != null)
+ new_width += scale_changer.getPreferredSize().height;
+ if(scrollbar != null)
+ new_height += scrollbar.getPreferredSize().height;
+
+ if(new_height != getSize().width ||
+ new_width != getSize().height)
+ {
+ final Dimension preferred_size =
+ new Dimension(getSize().width, new_height);
+ setPreferredSize(preferred_size);
+
+ final Dimension minimum_size = new Dimension(1, new_height);
+ setMinimumSize(minimum_size);
+
+ revalidate();
+// repaint();
+ }
+ }
+
+ /**
+ * Returns the base length of the sequence we are displaying in this
+ * component.
+ **/
+ public int getSequenceLength()
+ {
+ return getEntryGroup().getSequenceLength();
+ }
+
+ /**
+ * Set the first visible base in the forward direction (set the base on the
+ * forward strand that is on the left of the canvas). This method will
+ * scroll the display so that this base is is at the very left hand side of
+ * the canvas.
+ **/
+ private void setFirstVisibleForwardBase(int new_position)
+ {
+ if(left_edge_base != new_position)
+ {
+ left_edge_base = new_position;
+ if(scrollbar != null)
+ scrollbar.setValue(new_position);
+
+ needVisibleFeatureVectorUpdate();
+ updateOneLinePerFeatureFlag();
+ repaint();
+ }
+ }
+
+ /**
+ * Return the current scale factor. The scale factor is a number greater
+ * than or equal to zero than controls the number of bases that can appear
+ * on screen. See getScaleValue().
+ **/
+ protected int getScaleFactor()
+ {
+ return scale_factor;
+ }
+
+ /**
+ * Return the ID of the first(ie. top) line of the display.
+ **/
+ private int getFirstLineID()
+ {
+ if(show_forward_lines)
+ return FORWARD_FRAME_1;
+ else
+ return FORWARD_STRAND;
+ }
+
+ /**
+ * Return the ID of the last(ie. bottom) line of the display.
+ **/
+ private int getLastLineID()
+ {
+ if(show_reverse_lines)
+ return REVERSE_FRAME_1;
+ else
+ return REVERSE_STRAND;
+ }
+
+
+ protected int getDisplayWidth()
+ {
+ return getWidth() - scale_changer.getWidth();
+ }
+
+
+ /**
+ * Return the display line that contains the given point, or -1 if
+ * the point is off screen.
+ **/
+ private int getLineFromPoint(final Point point)
+ {
+ if(point.y >= getHeight() - scrollbar.getHeight())
+ return -1;
+
+ final int return_value = point.y / getFontHeight();
+
+ if(return_value < 0)
+ return -1;
+ else
+ return return_value;
+ }
+
+ /**
+ * Return the ID of the frame at the given Point in the canvas or NO_FRAME
+ * if the Point is not within a frame. If labels are on then points on the
+ * label lines get returned as the frame id of the STRAND or FRAME line
+ * above.
+ **/
+ private int getFrameFromPoint(final Point point)
+ {
+ if(getOneLinePerEntryFlag())
+ {
+ // there are no frame lines so just look at the strand lines
+ final int line_point = getLineFromPoint(point);
+ if(line_point == getFrameDisplayLine(FORWARD_STRAND) ^
+ isRevCompDisplay())
+ return FORWARD_STRAND;
+
+ if(line_point == getFrameDisplayLine(REVERSE_STRAND) ^
+ isRevCompDisplay())
+ return REVERSE_STRAND;
+
+ // fall through
+ }
+ else
+ {
+ final int start_frame = getFirstLineID();
+ final int end_frame = getLastLineID();
+
+ for(int i = start_frame ; i <= end_frame ; ++i) {
+ final int top_of_line = getLineOffset(getFrameDisplayLine(i));
+ final int line_height;
+
+ if(show_labels)
+ line_height = getFontHeight() * 2;
+ else
+ line_height = getFontHeight();
+
+ if(point.y >= top_of_line &&
+ point.y < top_of_line + line_height)
+ return i;
+ }
+ }
+ // the point isn't in any or the frames
+ return NO_FRAME;
+ }
+
+ /**
+ * Return the base number of the first visible base displayed in the
+ * forward direction, ie the base on the forward(or top strand) that is on
+ * the left each of the canvas. Note that the strand that is currently
+ * being display in the forward direction will be the reverse strand if
+ * rev_comp_display is true. The number returned will always be > 1 and <
+ * the sequence length. If hard_left_edge is true this method will always
+ * return the same as getForwardBaseAtLeftEdge*(.
+ **/
+ protected int getFirstVisibleForwardBase()
+ {
+ if(left_edge_base < 1)
+ return 1;
+ else
+ return left_edge_base;
+ }
+
+ /**
+ * Return the base number of the first visible base displayed in the
+ * forward direction, ie the base on the forward(or top strand) that is on
+ * the left each of the canvas. Note that the strand that is currently
+ * being display in the forward direction will be the reverse strand if
+ * rev_comp_display is true.
+ **/
+ public int getForwardBaseAtLeftEdge()
+ {
+ return left_edge_base;
+ }
+
+ /**
+ * Return a base marker of the first visible base in the forward
+ * direction, ie the base on the forward strand that is on the left
+ * each of the canvas. See comments on getFirstVisibleForwardBase().
+ **/
+ /*private Marker getFirstVisibleForwardBaseMarker()
+ {
+ try
+ {
+ final Strand strand;
+ if(isRevCompDisplay())
+ strand = getBases().getReverseStrand();
+ else
+ strand = getBases().getForwardStrand();
+
+ return strand.makeMarker(getFirstVisibleForwardBase());
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected OutOfRangeException");
+ }
+ }*/
+
+ /**
+ * Return the base number of the last visible base in the forward
+ * direction, ie the base on the forward strand that is on the right
+ * edge of the canvas. The number returned will always be > 1 and < the
+ * sequence length.
+ **/
+ protected int getLastVisibleForwardBase()
+ {
+ final int possible_last_base =
+ (int)(getForwardBaseAtLeftEdge() + getMaxVisibleBases());
+
+ if(possible_last_base > getSequenceLength())
+ return getSequenceLength();
+ else
+ {
+ if(possible_last_base > 0)
+ return possible_last_base;
+ else
+ return 1;
+ }
+ }
+
+ /**
+ * Return the base number of the first visible base in the reverse
+ * direction , ie the base on the reverse strand that is on the left
+ * each of the canvas.
+ **/
+ private int getFirstVisibleReverseBase()
+ {
+ final int last_forward_base = getLastVisibleForwardBase();
+ return getBases().getComplementPosition(last_forward_base);
+ }
+
+ /**
+ * Return the base number of the last visible base in the reverse
+ * direction, ie the base on the reverse strand that is on the right
+ * edge of the canvas.
+ **/
+ private int getLastVisibleReverseBase()
+ {
+ // XXX
+ final int first_forward_base = getFirstVisibleForwardBase();
+ return getBases().getComplementPosition(first_forward_base);
+ }
+
+ /**
+ * Return the base of the forward Strand of the sequence that is closest to
+ * the centre of the view.
+ **/
+ private int getCentreForwardBase()
+ {
+ int possible_return_position =
+ getForwardBaseAtLeftEdge() + getMaxVisibleBases() / 2;
+
+ int return_position;
+
+ if(possible_return_position < getSequenceLength())
+ return_position = possible_return_position;
+ else
+ return_position = getSequenceLength();
+
+ return return_position;
+ }
+
+ /**
+ * Return the amount display is scaled. The value is 3 to the power of
+ * -(scale_factor - 1), except for a scale_factor of zero which gives a
+ * scale value of font_width.
+ **/
+ protected float getScaleValue()
+ {
+ return scale_value;
+ }
+
+ /**
+ * Return the height in pixels we should use for drawing features.
+ **/
+ private int getFeatureHeight()
+ {
+ // we want a nice space between
+ // frames/lines
+ return getFontAscent() + 2;
+ }
+
+ /**
+ * Return the number of bases we can fit on screen at once, ie the number
+ * that will fit side by side on the canvas.
+ **/
+ public int getMaxVisibleBases()
+ {
+ return (int)(getWidth()/getScaleValue());
+ }
+
+ /**
+ * Return the Bases object of the EntryGroup that this FeatureDisplay is
+ * displaying.
+ **/
+ public Bases getBases()
+ {
+ return getEntryGroup().getBases();
+ }
+
+ /**
+ * Update the value of scale_value to reflect the current value of
+ * scale_factor.
+ **/
+ private void setScaleValue()
+ {
+ final int scale_factor = getScaleFactor();
+
+ if(scale_factor > 0)
+ scale_value =(float)(1 / Math.pow(3, scale_factor - 1));
+ else
+ scale_value = getFontWidth();
+ }
+
+ /**
+ * Scroll and scale the display so that the given first base is at the left
+ * edge of the screen and the given last base is at the right edge.
+ **/
+ protected void setFirstAndLastBase(final int first, final int last)
+ {
+ left_edge_base = first;
+ setScaleValue(1.0F * getWidth() /(last - first + 1));
+ }
+
+ /**
+ * Scroll the display so that the given first base is at the left edge of
+ * the screen.
+ **/
+ protected void setFirstBase(int base_position)
+ {
+ if(base_position > getSequenceLength())
+ base_position = getSequenceLength();
+
+ if(base_position < 1 && hard_left_edge)
+ base_position = 1;
+
+ setFirstVisibleForwardBase(base_position);
+ fireAdjustmentEvent(DisplayAdjustmentEvent.SCROLL_ADJUST_EVENT);
+ }
+
+ /**
+ * Set the scale value to use for this FeatureDisplay. Note that it is
+ * better to call setScaleFactor() which will also set the scale value.
+ **/
+ private void setScaleValue(final float scale_value)
+ {
+ if(scale_value <= 0)
+ throw new Error("internal error in FeatureDisplay.setScaleValue() - " +
+ "scale value must be positive");
+
+ this.scale_value = scale_value;
+
+ if(scale_value > 1)
+ scale_factor = 1;
+ else
+ scale_factor =
+ (int) Math.round(Math.log(1/scale_value) / Math.log(3)) + 1;
+
+ scale_changer.setValue(scale_factor);
+
+ fixScrollbar();
+ needVisibleFeatureVectorUpdate();
+ repaint();
+ fireAdjustmentEvent(DisplayAdjustmentEvent.SCALE_ADJUST_EVENT);
+ }
+
+ /**
+ * Make a new Range and throw a Error is an OutOfRangeException occurs.
+ **/
+ private Range newRange(final int start, final int end)
+ {
+ try
+ {
+ return new Range(start, end);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Arrange for updateVisibleFeatureVector() to be called in the next call
+ * to paint()
+ **/
+ protected void needVisibleFeatureVectorUpdate()
+ {
+ update_visible_features = true;
+ }
+
+
+////////////////////
+// DRAG AND DROP
+////////////////////
+
+ /**
+ *
+ * Read an entry from a remote file node (ssh)
+ *
+ **/
+ private void readAnEntryFromRemoteFileNode(final RemoteFileNode node)
+ {
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ try
+ {
+ EntryInformation new_entry_information =
+ new SimpleEntryInformation(Options.getArtemisEntryInformation());
+
+ final Entry entry = new Entry(getEntryGroup().getBases(),
+ EntryFileDialog.getEntryFromFile(null,
+ new RemoteFileDocument(node),
+ new_entry_information, true));
+ if(entry != null)
+ getEntryGroup().add(entry);
+ }
+ catch(final OutOfRangeException e)
+ {
+ new MessageDialog(null,
+ "read failed: one of the features " +
+ "in the entry has an out of " +
+ "range location: " +
+ e.getMessage());
+ }
+ return null;
+ }
+ };
+ entryWorker.start();
+ }
+
+
+ /**
+ * Read an entry
+ **/
+ private void readAnEntryFromFile(final File file,
+ final EntryGroup entry_group)
+ {
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ try
+ {
+ EntryInformation new_entry_information =
+ new SimpleEntryInformation(Options.getArtemisEntryInformation());
+
+ final Entry new_entry = new Entry(entry_group.getBases(),
+ EntryFileDialog.getEntryFromFile(null,
+ new FileDocument(file),
+ new_entry_information, true));
+
+ if(new_entry != null)
+ entry_group.add(new_entry);
+ }
+ catch(final OutOfRangeException e)
+ {
+ new MessageDialog(null,
+ "read failed: one of the features " +
+ "in the entry has an out of " +
+ "range location: " +
+ e.getMessage());
+ }
+ return null;
+ }
+ };
+ entryWorker.start();
+ }
+
+ protected static Vector<String> getContigKeys()
+ {
+ if(contigKeys == null)
+ {
+ contigKeys = new Vector<String>(3);
+ contigKeys.add("fasta_record");
+ contigKeys.add("contig");
+ contigKeys.add("insertion_gap");
+ }
+
+ return contigKeys;
+ }
+
+ protected static Vector<String> getAllPossibleContigKeys()
+ {
+ if(allPossibleContigKeys == null)
+ {
+ allPossibleContigKeys = new Vector<String>();
+ allPossibleContigKeys.add("fasta_record");
+ allPossibleContigKeys.add("contig");
+ allPossibleContigKeys.add("insertion_gap");
+ allPossibleContigKeys.add("gap");
+ allPossibleContigKeys.add("scaffold");
+ allPossibleContigKeys.add("source");
+ }
+ return allPossibleContigKeys;
+ }
+
+ /**
+ *
+ */
+ protected FeatureVector getContigs()
+ {
+ final FeatureVector tmpContigFeatures = new FeatureVector();
+ // find all fasta_record features
+ final Vector<String> contigKeys = getContigKeys();
+ final FeaturePredicate key_predicate_contig[]
+ = new FeatureKeyQualifierPredicate[contigKeys.size()];
+
+ for(int i=0; i<contigKeys.size(); i++)
+ key_predicate_contig[i] = new FeatureKeyQualifierPredicate(
+ new Key(contigKeys.get(i)),
+ null, // match any qialifier
+ false);
+
+ final FeatureEnumeration test_enumerator = getEntryGroup().features();
+
+ while(test_enumerator.hasMoreFeatures())
+ {
+ final Feature this_feature = test_enumerator.nextFeature();
+
+ for(int i=0; i<contigKeys.size(); i++)
+ {
+ if(key_predicate_contig[i].testPredicate(this_feature))
+ tmpContigFeatures.add(this_feature);
+ }
+ }
+
+
+ final FeaturePredicate subsetPredicate = new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ final Location loc = feature.getLocation();
+ final int start = loc.getFirstBase();
+ final int end = loc.getLastBase();
+
+ for(int i=0; i<tmpContigFeatures.size(); i++)
+ {
+ Feature contig = tmpContigFeatures.elementAt(i);
+ if(contig.getSystematicName().equals(feature.getSystematicName()))
+ continue;
+
+ int this_start = contig.getLocation().getFirstBase();
+ int this_end = contig.getLocation().getLastBase();
+
+ if( (this_start >= start && this_start < end) &&
+ (this_end > start && this_end <= end) )
+ return true;
+ }
+ return false;
+ }
+ };
+
+ FeatureVector contigFeatures = new FeatureVector();
+ for(int i=0; i<tmpContigFeatures.size(); i++)
+ {
+ Feature contig = tmpContigFeatures.elementAt(i);
+ if(!subsetPredicate.testPredicate(contig))
+ contigFeatures.add(contig);
+ }
+
+ tmpContigFeatures.removeAllElements();
+
+ return contigFeatures;
+ }
+
+ /**
+ *
+ * Contig reordering. The current selection is moved to highlight_drop_base
+ * base position in the sequence.
+ *
+ */
+ protected void reorder(int drop_base, Feature selected_feature)
+ {
+ // rearrange contigs
+ try
+ {
+ Sequence sequence = getBases().getSequence();
+ FeatureVector contig_features = null;
+ int old_pos[] = null;
+
+ if(sequence instanceof FastaStreamSequence)
+ {
+ contig_features = getContigs();
+
+ // get fasta_record old positions
+ old_pos = new int[contig_features.size()];
+ for(int i=0; i<old_pos.length; i++)
+ old_pos[i] = contig_features.elementAt(i).getMaxRawRange().getStart()-1;
+ }
+
+ //
+ // Test that there are no features overlapping contig boundaries
+ //
+ final FeaturePredicate overlapPredicate = new FeaturePredicate()
+ {
+ private FeatureVector contig_features = getContigs();
+ public boolean testPredicate(final Feature feature)
+ {
+ //if(getContigKeys().contains(feature.getKey().getKeyString()))
+ // return false;
+
+ final Location loc = feature.getLocation();
+
+ final int start = loc.getFirstBase();
+ final int end = loc.getLastBase();
+
+ for(int i=0; i<contig_features.size(); i++)
+ {
+ Feature contig = contig_features.elementAt(i);
+ int this_start = contig.getLocation().getFirstBase();
+ int this_end = contig.getLocation().getLastBase();
+
+ if( (this_start > start && this_start < end) ||
+ (this_end > start && this_end < end) )
+ return true;
+ }
+ return false;
+ }
+ };
+
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(getEntryGroup(), overlapPredicate, "Overlapping Features");
+
+ if(filtered_entry_group.getAllFeaturesCount() > 0)
+ {
+ JOptionPane.showMessageDialog(null,
+ "There is/are "+filtered_entry_group.getAllFeaturesCount()+
+ " feature(s) overlapping the contig boundaries."+
+ "\nA list of these features will appear."+
+ "\nThis needs fixing before contigs can be reordered.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+
+ FeatureListFrame list = new FeatureListFrame("Overlapping Features (to be fixed before reordering)",
+ getSelection(), getGotoEventSource(), filtered_entry_group, getBasePlotGroup());
+ list.setVisible(true);
+
+ return;
+ }
+
+ // rearrange contig order
+ getEntryGroup().getBases().contigRearrange(selected_feature,
+ drop_base);
+
+ // get fasta_record new positions
+ if(sequence instanceof FastaStreamSequence &&
+ old_pos != null)
+ {
+ int new_pos[] = new int[old_pos.length];
+ for(int i=0; i<new_pos.length; i++)
+ new_pos[i] = contig_features.elementAt(i).getMaxRawRange().getStart()-1;
+
+ // update header record
+ ((RawStreamSequence)sequence).setFastaHeaderPosition(old_pos,new_pos);
+ }
+ }
+ catch(ReadOnlyException roe)
+ {
+ final String message =
+ "one or more of the features is read-only or is in a " +
+ "read-only entry - cannot continue";
+ new MessageDialog(null, message);
+ highlight_drop_base = -1;
+
+ return;
+ }
+
+ }
+// drop
+ protected static Border dropBorder = new BevelBorder(BevelBorder.LOWERED);
+
+ private void getNearestFeatureEnd(Point loc)
+ {
+ final int base_pos = getBasePositionOfPoint(loc, FORWARD_STRAND);
+ FeatureVector features_from_entry = getVisibleFeatures();
+ int first;
+ int last;
+ Vector<String> contig_keys = getContigKeys();
+
+ for(int i = 0; i < features_from_entry.size(); i++)
+ {
+ final Feature this_feature = features_from_entry.elementAt(i);
+
+ if(contig_keys.contains(this_feature.getKey()))
+ {
+ first = this_feature.getRawFirstBase();
+ last = this_feature.getRawLastBase();
+
+ if( Math.abs(first - base_pos) < Math.abs(base_pos - highlight_drop_base) )
+ highlight_drop_base = first;
+ if( Math.abs(last - base_pos) < Math.abs(base_pos - highlight_drop_base) )
+ highlight_drop_base = last+1;
+ }
+ }
+ }
+
+ public void drop(DropTargetDropEvent e)
+ {
+ Transferable t = e.getTransferable();
+ try
+ {
+ if(t.isDataFlavorSupported(FileNode.FILENODE))
+ {
+ FileNode fn = (FileNode)t.getTransferData(FileNode.FILENODE);
+ readAnEntryFromFile(fn.getFile(), getEntryGroup());
+ }
+ else if(t.isDataFlavorSupported(RemoteFileNode.REMOTEFILENODE))
+ {
+ final RemoteFileNode node =
+ (RemoteFileNode)t.getTransferData(RemoteFileNode.REMOTEFILENODE);
+ readAnEntryFromRemoteFileNode(node);
+ }
+ else if(e.isDataFlavorSupported(DataFlavor.stringFlavor))
+ {
+ final FeatureVector selected_features = getSelection().getAllFeatures();
+ if(selected_features.size() > 1)
+ return;
+
+ reorder(highlight_drop_base, selected_features.elementAt(0)); // rearrange contigs
+ }
+ else
+ e.rejectDrop();
+ }
+ catch(UnsupportedFlavorException ufe)
+ {
+ ufe.printStackTrace();
+ }
+ catch(IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ finally
+ {
+ highlight_drop_base = -1;
+ setBorder(null);
+ }
+ }
+
+ public void dragExit(DropTargetEvent e)
+ {
+ highlight_drop_base = -1;
+ setBorder(null);
+ repaint();
+ }
+
+ public void dropActionChanged(DropTargetDragEvent e) {}
+
+ public void dragOver(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(FileNode.FILENODE))
+ {
+ setBorder(dropBorder);
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ return;
+ }
+ else if(e.isDataFlavorSupported(DataFlavor.stringFlavor))
+ {
+ Point ploc = e.getLocation();
+ getNearestFeatureEnd(ploc);
+ repaint();
+ }
+ else
+ e.rejectDrag();
+ }
+
+ public void dragEnter(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(FileNode.FILENODE))
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+
+
+// drag source
+ public void dragGestureRecognized(DragGestureEvent e)
+ {
+ // ignore if mouse popup trigger
+ InputEvent ie = e.getTriggerEvent();
+ if(ie instanceof MouseEvent)
+ if(((MouseEvent)ie).isPopupTrigger())
+ return;
+
+ if(getFeatureStackViewFlag() || getEntryGroup().isReadOnly())
+ return;
+
+ final Vector<String> contig_keys = getContigKeys();
+ final FeatureVector selected_features = getSelection().getAllFeatures();
+ if(selected_features.size() == 1 &&
+ contig_keys.contains(selected_features.elementAt(0).getKey()))
+ {
+ ClassLoader cl = this.getClass().getClassLoader();
+ ImageIcon icon = new ImageIcon(cl.getResource("images/icon.gif"));
+ final Image icon_image = icon.getImage();
+
+ //TransferableContig tcontig = new TransferableContig(selected_features.elementAt(0));
+ StringSelection name = new StringSelection(selected_features.elementAt(0).getGeneName());
+
+ e.startDrag(DragSource.DefaultCopyDrop, // cursor
+ icon_image, new Point(-1, -1),
+ (Transferable)name, // transferable data
+ this); // drag source listener
+ }
+ }
+
+ public void dragDropEnd(DragSourceDropEvent e) {}
+ public void dragEnter(DragSourceDragEvent e)
+ {
+ }
+
+ public void dragExit(DragSourceEvent e)
+ {
+ highlight_drop_base = -1;
+ }
+ public void dragOver(DragSourceDragEvent e) {}
+ public void dropActionChanged(DragSourceDragEvent e) {}
+
+ protected ZoomScrollBar getScaleChanger()
+ {
+ return scale_changer;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/FeatureEdit.java b/uk/ac/sanger/artemis/components/FeatureEdit.java
new file mode 100644
index 0000000..6b459dd
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeatureEdit.java
@@ -0,0 +1,1809 @@
+/* FeatureEdit.java
+ *
+ * created: Tue Dec 1 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureEdit.java,v 1.70 2009-09-24 15:01:27 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.OutOfDateException;
+import uk.ac.sanger.artemis.io.LocationParseException;
+import uk.ac.sanger.artemis.io.PartialSequence;
+import uk.ac.sanger.artemis.io.QualifierLazyLoading;
+import uk.ac.sanger.artemis.io.QualifierParseException;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.KeyVector;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.StreamQualifier;
+import uk.ac.sanger.artemis.io.QualifierInfo;
+import uk.ac.sanger.artemis.io.ValidateFeature;
+
+import uk.ac.sanger.artemis.components.ProgressThread;
+import uk.ac.sanger.artemis.components.genebuilder.BasicGeneBuilderFrame;
+import uk.ac.sanger.artemis.components.genebuilder.GeneBuilderFrame;
+import uk.ac.sanger.artemis.components.genebuilder.GeneEditorPanel;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.genebuilder.ProteinMapPanel;
+import uk.ac.sanger.artemis.components.genebuilder.ReferencesPanel;
+import uk.ac.sanger.artemis.components.genebuilder.cv.CVPanel;
+import uk.ac.sanger.artemis.components.genebuilder.gff.PropertiesPanel;
+import uk.ac.sanger.artemis.components.genebuilder.ortholog.MatchPanel;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+import java.util.Collections;
+import java.util.Comparator;
+
+import javax.swing.*;
+
+
+/**
+ * FeatureEdit class
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureEdit.java,v 1.70 2009-09-24 15:01:27 tjc Exp $
+ **/
+public class FeatureEdit extends JPanel
+ implements EntryChangeListener, FeatureChangeListener
+{
+ private static final long serialVersionUID = 1L;
+
+
+ /** The choice of feature keys - created in createComponents(). */
+ private KeyChoice key_choice;
+
+ /** The choice of qualifiers - created in createComponents(). */
+ private QualifierChoice qualifier_choice = null;
+
+ private final static int LOCATION_TEXT_WIDTH = 80;
+
+ /** The location text - set by updateLocation(). */
+ private JTextField location_text = new JTextField(LOCATION_TEXT_WIDTH);
+
+ /** When pressed - discard changes and dispose of the component. */
+ private JButton cancel_button = new JButton("Cancel");
+
+ /** When pressed - apply changes and keep the component open. */
+ private JButton apply_button = new JButton("Apply");
+
+ /** Edit area for qualifiers - created by createComponents(). */
+ private QualifierTextArea qualifier_text_area;
+
+ /** The Feature this object is displaying. */
+ private Feature edit_feature;
+
+ /**
+ * The GotoEventSource that was passed to the constructor - used for the
+ * "Goto Feature" button.
+ **/
+ private GotoEventSource goto_event_source;
+
+ /** Entry containing the Feature this object is displaying. */
+ private Entry edit_entry;
+
+ /** EntryGroup that contains this Feature (passed to the constructor). */
+ private EntryGroup entry_group;
+
+ /**
+ * The datestamp of the RWCorbaFeature when updateFromFeature() was last
+ * called or null if this is not a RWCorbaFeature.
+ **/
+ private Date datestamp = null;
+
+ /** The Selection that was passed to the constructor. */
+ private Selection selection;
+
+ /**
+ * The contents of the QualifierTextArea before the user edits anything.
+ * This is used to work out if anything has changed since the creation of
+ * the FeatureEdit.
+ **/
+ private final String orig_qualifier_text;
+
+ private JFrame frame;
+
+ /** controlled vocabulary tab */
+ private CVPanel cvForm;
+
+ /** GFF tab */
+ private PropertiesPanel propertiesPanel;
+
+ /** similarity/ortholog/paralog tab */
+ private MatchPanel matchForm;
+
+ private ReferencesPanel refPanel;
+
+ private EntryInformation entry_information;
+
+ private static boolean isTabbedView = false;
+
+ private GeneEditorPanel editorPanel;
+
+ private static UserDefinedQualifiers userDefinedQualifierFrame;
+
+ /**
+ * Create a new FeatureEdit object from the given Feature.
+ * @param entry_group The EntryGroup that contains this Feature.
+ * @param selection The Selection operate on. The operations are "Remove
+ * Range" and "Grab Range"
+ * @param goto_event_source The object the we will call gotoBase() on.
+ **/
+ public FeatureEdit(final Feature edit_feature,
+ final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final JFrame frame)
+ {
+ this.frame = frame;
+ this.edit_feature = edit_feature;
+ this.edit_entry = edit_feature.getEntry();
+ this.entry_group = entry_group;
+ this.selection = selection;
+ this.goto_event_source = goto_event_source;
+ this.entry_information = edit_feature.getEntry().getEntryInformation();
+
+ setLayout(new BorderLayout());
+ createComponents();
+ updateFromFeature();
+
+ orig_qualifier_text = qualifier_text_area.getText();
+
+ if(edit_feature.getEntry() != null)
+ {
+ edit_feature.getEntry().addEntryChangeListener(this);
+ edit_feature.addFeatureChangeListener(this);
+
+ frame.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ stopListening();
+ frame.dispose();
+ if(userDefinedQualifierFrame != null)
+ userDefinedQualifierFrame.setVisible(false);
+ }
+ });
+ }
+
+ qualifier_text_area.requestFocus();
+ }
+
+ private boolean isPartialSequence()
+ {
+ return entry_group.getSequenceEntry().getBases().getSequence() instanceof PartialSequence;
+ }
+
+ /**
+ * Set the feature to edit
+ * @param edit_feature
+ * @param isSet
+ */
+ public void setActiveFeature(final Feature edit_feature,
+ final boolean isSet)
+ {
+ if(!isPartialSequence() && isSet)
+ setFeature();
+ this.edit_feature = edit_feature;
+ this.edit_entry = edit_feature.getEntry();
+
+ if(edit_feature.getEntry() != null)
+ {
+ edit_feature.getEntry().addEntryChangeListener(this);
+ edit_feature.addFeatureChangeListener(this);
+ }
+ updateFromFeature();
+ }
+
+ /**
+ * Remove this object as a feature and entry change listener.
+ **/
+ public void stopListening()
+ {
+ getEntry().removeEntryChangeListener(this);
+ getFeature().removeFeatureChangeListener(this);
+ if(cvForm != null)
+ getFeature().removeFeatureChangeListener(cvForm);
+ if(propertiesPanel != null)
+ getFeature().removeFeatureChangeListener(propertiesPanel);
+ if(matchForm != null)
+ getFeature().removeFeatureChangeListener(matchForm);
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so we can notify the user if of this component if the
+ * feature gets deleted.
+ **/
+ public void entryChanged(EntryChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryChangeEvent.FEATURE_DELETED:
+ if(event.getFeature() == edit_feature)
+ {
+ stopListening();
+ frame.dispose();
+ }
+ break;
+ default:
+ // do nothing;
+ break;
+ }
+ }
+
+ /**
+ * Add an ActionListener to the Cancel JButton of this FeatureEdit.
+ **/
+ protected void addCancelActionListener(final ActionListener l)
+ {
+ cancel_button.addActionListener(l);
+ }
+
+ /**
+ * Remove an ActionListener from the Cancel JButton of this FeatureEdit.
+ **/
+ protected void removeCancelActionListener(final ActionListener l)
+ {
+ cancel_button.removeActionListener(l);
+ }
+
+ /**
+ * Add an ActionListener to the Apply JButton of this FeatureEdit.
+ **/
+ protected void addApplyActionListener(final ActionListener l)
+ {
+ apply_button.addActionListener(l);
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events from the Features in this object so that
+ * we can update the display.
+ * @param event The change event.
+ **/
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ getFeature().resetColour();
+ // re-read the information from the feature
+ switch(event.getType())
+ {
+ case FeatureChangeEvent.LOCATION_CHANGED:
+ updateLocation();
+ break;
+ case FeatureChangeEvent.SEGMENT_CHANGED:
+ updateLocation();
+ break;
+ case FeatureChangeEvent.KEY_CHANGED:
+ updateKey();
+ break;
+ case FeatureChangeEvent.QUALIFIER_CHANGED:
+ if(qualifier_text_area.getText().equals(orig_qualifier_text))
+ updateFromFeature();
+ else
+ {
+ if(!event.getFeature().getIDString().equals(getFeature().getIDString()))
+ break;
+
+ final String message =
+ "warning: the qualifiers have changed outside the editor - " +
+ "view now?";
+
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog(frame, message);
+
+ if(yes_no_dialog.getResult())
+ new FeatureViewer(getFeature());
+ }
+ break;
+ default:
+ updateFromFeature();
+ break;
+ }
+ }
+
+
+ /**
+ * Create all the components for this FeatureEdit component.
+ **/
+ private void createComponents()
+ {
+ qualifier_text_area = new QualifierTextArea();
+ //qualifier_text_area.setWrapStyleWord(true);
+
+ FlowLayout flowLayoutZeroHVgap = new FlowLayout(FlowLayout.LEADING, 0, 0);
+
+ key_choice =
+ new KeyChoice(getEntryInformation(),getFeature().getKey());
+ key_choice.setLayout(flowLayoutZeroHVgap);
+ final JPanel key_and_qualifier_panel = new JPanel();
+ location_text.setBackground(Color.white);
+
+ final JPanel key_panel = new JPanel(flowLayoutZeroHVgap);
+ final JLabel locLabel = new JLabel("Location: ");
+ final JLabel keyLabel = new JLabel("Key: ");
+ keyLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+ keyLabel.setPreferredSize(locLabel.getPreferredSize());
+
+ key_panel.add(keyLabel);
+ key_panel.add(key_choice);
+
+ key_and_qualifier_panel.setLayout(new BorderLayout());
+ key_and_qualifier_panel.add(key_panel, "West");
+
+ boolean isGFF = false;
+ if(edit_feature.getEmblFeature().getEntry() instanceof GFFDocumentEntry)
+ isGFF = true;
+
+ qualifier_choice = new QualifierChoice(getEntryInformation(),
+ key_choice.getSelectedItem(),null,
+ isGFF);
+
+ final JPanel qualifier_panel = new JPanel(new FlowLayout(FlowLayout.TRAILING,0,0));
+ final JButton qualifier_add_button = new JButton("Add Qualifier:");
+
+ qualifier_panel.add(qualifier_add_button);
+ qualifier_panel.add(qualifier_choice);
+
+ key_and_qualifier_panel.add(qualifier_panel, "East");
+
+ key_choice.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent _)
+ {
+ qualifier_choice.setKey(key_choice.getSelectedItem());
+ }
+ });
+
+ qualifier_add_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final String qualifier_name =
+ (String)qualifier_choice.getSelectedItem();
+
+ QualifierInfo qualifier_info =
+ getEntryInformation().getQualifierInfo(qualifier_name);
+
+ if(qualifier_info == null)
+ qualifier_info = new QualifierInfo(qualifier_name,
+ QualifierInfo.OPTIONAL_QUOTED_TEXT,
+ null, null, false);
+
+ qualifier_text_area.append("/" + qualifier_name);
+
+ switch(qualifier_info.getType())
+ {
+ case QualifierInfo.QUOTED_TEXT:
+ if(qualifier_name.equals("GO"))
+ {
+ // special case for /GO
+ final java.util.Calendar calendar =
+ java.util.Calendar.getInstance();
+
+ final Date current_time = calendar.getTime();
+
+ final java.text.SimpleDateFormat date_format =
+ new java.text.SimpleDateFormat("yyyyMMdd");
+
+ final StringBuffer result_buffer = new StringBuffer();
+
+ date_format.format(current_time, result_buffer,
+ new java.text.FieldPosition(java.text.DateFormat.DATE_FIELD));
+
+ final String go_string = "aspect=; term=; GOid=GO:; "+
+ "evidence=ISS; db_xref=GO_REF:0000001; " +
+ "with=UniProtKB:; date=" + result_buffer;
+ qualifier_text_area.append("=\"" + go_string + "\"");
+ }
+ else if(qualifier_name.equals("controlled_curation"))
+ {
+ final java.util.Calendar calendar =
+ java.util.Calendar.getInstance();
+
+ final Date current_time = calendar.getTime();
+
+ final java.text.SimpleDateFormat date_format =
+ new java.text.SimpleDateFormat("yyyyMMdd");
+
+ final StringBuffer result_buffer = new StringBuffer();
+
+ date_format.format(current_time, result_buffer,
+ new java.text.FieldPosition(java.text.DateFormat.DATE_FIELD));
+
+ final String cc_string = "term=; db_xref=; date="+ result_buffer;
+ qualifier_text_area.append("=\"" + cc_string + "\"");
+ }
+ else
+ qualifier_text_area.append ("=\"\"");
+ break;
+
+ case QualifierInfo.NO_VALUE:
+ case QualifierInfo.OPTIONAL_QUOTED_TEXT:
+ break;
+
+ default:
+ qualifier_text_area.append("=");
+ }
+
+ qualifier_text_area.append("\n");
+ }
+ });
+
+ final JPanel middle_panel = new JPanel();
+ middle_panel.setLayout(new BorderLayout(0,0));
+
+ final JPanel lower_panel = new JPanel();
+ lower_panel.setLayout(new BorderLayout(0,0));
+
+ //final JPanel outer_location_button_panel = new JPanel(flowLayoutZeroHgap);
+ //outer_location_button_panel.setLayout(new BorderLayout(0,0));
+
+ final JToolBar location_button_panel = new JToolBar();
+ location_button_panel.setRollover(true);
+ //outer_location_button_panel.add(location_button_panel, "West");
+ lower_panel.add(location_button_panel, "North");
+
+ final JPanel location_panel = new JPanel(new BorderLayout(0,0));
+ location_panel.add(locLabel, "West");
+ location_panel.add(location_text, "Center");
+
+ final JButton complement_button = new JButton("Complement");
+ complement_button.setToolTipText("Complement position");
+ location_button_panel.add(complement_button);
+ complement_button.addActionListener(new ActionListener ()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ complementLocation();
+ }
+ });
+
+ if(GeneUtils.isDatabaseEntry(getFeature().getEmblFeature()))
+ {
+ final JButton refresh_button = new JButton("Refresh");
+ location_button_panel.add(refresh_button);
+ refresh_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ refresh();
+ }
+ });
+ }
+
+ final JButton grab_button = new JButton("Grab Range");
+ grab_button.setToolTipText("Add selected base range from feature coordinates");
+ location_button_panel.add(grab_button);
+ grab_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ grabSelectedRange();
+ }
+ });
+
+ final JButton remove_button = new JButton("Remove Range");
+ remove_button.setToolTipText("Remove selected base range from feature coordinates");
+ location_button_panel.add(remove_button);
+ remove_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ removeSelectedRange();
+ }
+ });
+
+ final JButton goto_button = new JButton("Goto Feature");
+ goto_button.setToolTipText("Goto and select this feature");
+ location_button_panel.add(goto_button);
+ goto_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ goto_event_source.gotoBase(getFeature().getFirstBaseMarker());
+ getSelection().set(getFeature());
+ }
+ });
+
+/* final JButton select_button = new JButton("Select Feature");
+ location_button_panel.add(select_button);
+ select_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ getSelection().set(getFeature());
+ }
+ });*/
+
+ if(Options.getOptions().getPropertyTruthValue("sanger_options"))
+ {
+ // a PSU only hack
+ final JButton tidy_button = new JButton("Tidy");
+ location_button_panel.add(tidy_button);
+ tidy_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ try
+ {
+ tidy();
+ tidyGO();
+ }
+ catch(QualifierParseException exception)
+ {
+ new MessageDialog(frame,
+ "Cannot tidy - qualifier error: " +
+ exception.getMessage());
+ }
+ }
+ });
+ }
+
+ final JButton transferAnnotationBbutton = new JButton("TAT");
+ location_button_panel.add(transferAnnotationBbutton);
+ transferAnnotationBbutton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ new TransferAnnotationTool(getFeature(), entry_group, matchForm);
+ }
+ });
+
+
+ if(Options.isUnixHost())
+ {
+ JButton oo_edit_button = new JButton("ObjectEdit");
+ location_button_panel.add(oo_edit_button);
+ final uk.ac.sanger.artemis.editor.BigPane bp =
+ new uk.ac.sanger.artemis.editor.BigPane(getEntryInformation());
+
+ oo_edit_button.addActionListener(new ActionListener ()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ String qualifier_txt = qualifier_text_area.getText();
+
+ File base_dir = getBaseDirectoryFromEntry(edit_entry);
+ String baseDirStr = "";
+
+ if(base_dir != null)
+ baseDirStr = base_dir.getAbsolutePath() +
+ System.getProperty("file.separator");
+
+ StringReader strRead = new StringReader(qualifier_txt);
+ BufferedReader buff = new BufferedReader(strRead);
+ String line;
+ final Hashtable<String, Vector<String>> dataFile = new Hashtable<String, Vector<String>>();
+ try
+ {
+ int ind;
+ while((line = buff.readLine()) != null)
+ {
+ if(line.startsWith("/fasta_file="))
+ {
+ if((ind = line.indexOf(':'))>-1)
+ line = baseDirStr+line.substring(ind+1);
+ else
+ line = baseDirStr+line.substring(13);
+
+ ind = line.lastIndexOf("\"");
+ if(ind > -1)
+ line = line.substring(0, ind);
+
+ Vector<String> v;
+ if(dataFile.containsKey("fasta"))
+ v = dataFile.get("fasta");
+ else
+ v = new Vector<String>();
+ v.add(line);
+ dataFile.put("fasta",v);
+ }
+ else if(line.startsWith("/blastp_file="))
+ {
+ if((ind = line.indexOf(':'))>-1)
+ line = baseDirStr+line.substring(ind+1);
+ else
+ line = baseDirStr+line.substring(14);
+
+ ind = line.lastIndexOf("\"");
+ if(ind > -1)
+ line = line.substring(0, ind);
+
+ Vector<String> v;
+ if(dataFile.containsKey("blastp"))
+ v = dataFile.get("blastp");
+ else
+ v = new Vector<String>();
+ v.add(line);
+ dataFile.put("blastp",v);
+ }
+ else if(line.startsWith("/blastp+go_file="))
+ {
+ if((ind = line.indexOf(':'))>-1)
+ line = line.substring(ind+1);
+ else
+ line = baseDirStr+line.substring(17);
+ ind = line.lastIndexOf("\"");
+ if(ind > -1)
+ line = line.substring(0, ind);
+
+ Vector<String> v;
+ if(dataFile.containsKey("blastp+go"))
+ v = dataFile.get("blastp+go");
+ else
+ v = new Vector<String>();
+ v.add(line);
+ dataFile.put("blastp+go",v);
+ }
+ }
+ }
+ catch(IOException ioe){}
+
+ FeatureEdit.this.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ final ProgressThread progress = new ProgressThread(null,
+ "Loading Data....");
+ progress.start();
+ SwingWorker ooEd = new SwingWorker()
+ {
+ public Object construct()
+ {
+ // find overlaping features
+ final Location this_loc = edit_feature.getLocation();
+ final int this_start = this_loc.getFirstBase();
+ final int this_end = this_loc.getLastBase();
+
+ FeaturePredicate predicate = new FeaturePredicate()
+ {
+ public boolean testPredicate (final Feature feature)
+ {
+ final Location loc = feature.getLocation();
+
+ final int start = loc.getFirstBase();
+ final int end = loc.getLastBase();
+
+ if((start > this_start &&
+ start < this_end) ||
+ (end > this_start &&
+ end < this_end))
+ {
+ final String note = feature.getNote();
+ if(note != null &&
+ note.indexOf("Pfam")>-1)
+ return true;
+ }
+
+ return false;
+ }
+ };
+
+ final FeatureVector overlapFeatures = new FeatureVector();
+ final FeatureEnumeration featureEnum = entry_group.features();
+ while(featureEnum.hasMoreFeatures())
+ {
+ Feature this_feature = featureEnum.nextFeature();
+ if(predicate.testPredicate(this_feature))
+ overlapFeatures.add(this_feature);
+ }
+
+ // show object editor
+ try
+ {
+ if(dataFile.size() > 0)
+ bp.set(dataFile, qualifier_text_area, overlapFeatures,
+ edit_feature, matchForm, cvForm);
+ else
+ JOptionPane.showMessageDialog(null,"No results files.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ catch(ArrayIndexOutOfBoundsException e)
+ {
+ JOptionPane.showMessageDialog(null,"No results files.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ }
+
+ progress.finished();
+ FeatureEdit.this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ return null;
+ }
+ };
+ ooEd.start();
+ }
+ });
+ }
+
+ final JButton userQualifiers = new JButton("User Qualifiers");
+ userQualifiers.setToolTipText("User defined qualifier selection");
+ location_button_panel.add(userQualifiers);
+ userQualifiers.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(userDefinedQualifierFrame == null)
+ {
+ userDefinedQualifierFrame = new UserDefinedQualifiers();
+ userDefinedQualifierFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ }
+
+ userDefinedQualifierFrame.pack();
+ final JFrame topFrame =
+ (JFrame) SwingUtilities.getWindowAncestor(FeatureEdit.this);
+ Point p = topFrame.getLocationOnScreen();
+ p.x -= userDefinedQualifierFrame.getWidth();
+ if(p.x < 10)
+ p.x = 10;
+ userDefinedQualifierFrame.setLocation(p);
+
+ userDefinedQualifierFrame.setQualifierTextArea(qualifier_text_area);
+ userDefinedQualifierFrame.setSelection(selection);
+ userDefinedQualifierFrame.setVisible(true);
+ }
+ });
+
+ middle_panel.add(location_panel, "North");
+ add(key_and_qualifier_panel, "North");
+
+ cancel_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(edit_feature.getEntry() != null)
+ stopListening();
+ frame.dispose();
+ if(userDefinedQualifierFrame != null)
+ userDefinedQualifierFrame.setVisible(false);
+ }
+ });
+
+ final JButton ok_button = new JButton("OK");
+ if(!getFeature().isReadOnly())
+ {
+ ok_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(setFeature())
+ {
+ stopListening();
+
+ if(propertiesPanel != null)
+ propertiesPanel.updateSettings();
+
+ frame.dispose();
+ if(userDefinedQualifierFrame != null)
+ userDefinedQualifierFrame.setVisible(false);
+ }
+ }
+ });
+
+ apply_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setFeature();
+ }
+ });
+ }
+
+ final FlowLayout flow_layout =
+ new FlowLayout(FlowLayout.CENTER, 18, 1);
+
+ final JPanel ok_cancel_update_panel = new JPanel(flow_layout);
+ Box fillerBox = Box.createHorizontalBox();
+ if(GeneUtils.isDatabaseEntry(getFeature().getEmblFeature()))
+ {
+ cvForm = new CVPanel(getFeature());
+ cvForm.setBackground(Color.WHITE);
+
+ matchForm = new MatchPanel(getFeature(),
+ (DocumentEntry)getFeature().getEmblFeature().getEntry());
+ matchForm.setBackground(Color.WHITE);
+
+ propertiesPanel = new PropertiesPanel(getFeature());
+ propertiesPanel.setBackground(Color.WHITE);
+
+ refPanel = new ReferencesPanel(getFeature());
+ refPanel.setBackground(Color.WHITE);
+
+ addGffAnnotationView(lower_panel);
+
+ final JCheckBox tabbedView = new JCheckBox("Tab View", isTabbedView);
+ tabbedView.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ isTabbedView = tabbedView.isSelected();
+ addGffAnnotationView(lower_panel);
+ lower_panel.revalidate();
+ lower_panel.repaint();
+ }
+ });
+
+
+ final JCheckBox oneView = new JCheckBox("Overview", false);
+ oneView.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(setFeature())
+ {
+ stopListening();
+
+ if(propertiesPanel != null)
+ propertiesPanel.updateSettings();
+
+ frame.dispose();
+ System.setProperty("basic", "true");
+ new BasicGeneBuilderFrame(getFeature(), entry_group,
+ selection, null);
+ }
+ }
+ });
+
+ if(((GFFStreamFeature)getFeature().getEmblFeature()).getChadoGene() != null)
+ ok_cancel_update_panel.add(oneView);
+ ok_cancel_update_panel.add(tabbedView);
+ fillerBox.add(Box.createHorizontalStrut(
+ tabbedView.getPreferredSize().width ));
+ }
+ else
+ lower_panel.add(new JScrollPane(qualifier_text_area), "Center");
+
+ if(!getFeature().isReadOnly())
+ ok_cancel_update_panel.add(ok_button);
+
+ ok_cancel_update_panel.add(cancel_button);
+
+ if(!getFeature().isReadOnly())
+ ok_cancel_update_panel.add(apply_button);
+ ok_cancel_update_panel.add(fillerBox);
+
+ add(ok_cancel_update_panel, "South");
+
+ middle_panel.add(lower_panel, "Center");
+
+ add(middle_panel, "Center");
+ }
+
+ /**
+ * Refresh the annotation for the active feature
+ */
+ private void refresh()
+ {
+ // refresh from the database
+ final DatabaseDocument originalDocument =
+ (DatabaseDocument)((DocumentEntry)edit_feature.getEmblFeature().getEntry()).getDocument();
+
+ final Set<String> uniquenames = ((GFFStreamFeature)edit_feature.getEmblFeature()).getSegmentRangeStore().keySet();
+ final Iterator<String> it = uniquenames.iterator();
+ final String uniquename = it.next();
+ final DatabaseDocument newDocument = new DatabaseDocument(originalDocument,
+ uniquename, null, true, null);
+ newDocument.setLazyFeatureLoad(false);
+ newDocument.setReadChildren(false);
+
+ try
+ {
+ DatabaseDocumentEntry dbentry = new DatabaseDocumentEntry(newDocument, null);
+ uk.ac.sanger.artemis.io.Feature databaseFeature = dbentry.getAllFeatures().featureAt(0);
+
+ // compare timelastmodified
+ Qualifier qualifier = edit_feature.getQualifierByName("timelastmodified");
+ String active_timelastmodified = (String)qualifier.getValues().get(0);
+ qualifier = databaseFeature.getQualifierByName("timelastmodified");
+ String database_timelastmodified = (String)qualifier.getValues().get(0);
+
+ if(active_timelastmodified.equals(database_timelastmodified))
+ {
+ JOptionPane.showMessageDialog(this,
+ "No new changes found for the feature\n"+
+ uniquename+"\n"+
+ "in the database since:\n"+database_timelastmodified,
+ "No Updates", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ else
+ {
+ JOptionPane.showMessageDialog(this,
+ "Changes found for the feature\n"+
+ uniquename+"\n"+
+ "in the database at:\n"+database_timelastmodified,
+ "Changes Found", JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ final QualifierVector db_qv = databaseFeature.getQualifiers();
+ final QualifierVector qv = edit_feature.getQualifiers();
+ final QualifierVector new_qv = new QualifierVector();
+
+ for(int i=0; i<qv.size(); i++)
+ {
+ Qualifier q = (Qualifier)qv.get(i);
+ if(q.getName().equals("Parent") ||
+ q.getName().equals("Derives_from") ||
+ q.getName().equals("ID") )
+ new_qv.addQualifierValues(q);
+ }
+
+
+ for(int i=0; i<db_qv.size(); i++)
+ {
+ Qualifier q = (Qualifier)db_qv.get(i);
+ if(q.getName().equals("Parent") ||
+ q.getName().equals("Derives_from") ||
+ q.getName().equals("ID") )
+ continue;
+ new_qv.add(q);
+ }
+
+ edit_feature.getQualifiers().removeAllElements();
+ edit_feature.getQualifiers().addAll(new_qv);
+ updateQualifiers();
+ }
+ catch(EntryInformationException e) {}
+ catch(IOException e) {}
+ }
+
+ /**
+ * Add the annotation view as tabbed or in a single pane
+ * @param lower_panel
+ */
+ private void addGffAnnotationView(final JPanel lower_panel)
+ {
+ Component c[] = lower_panel.getComponents();
+
+ if(isTabbedView)
+ {
+ for(int i=0; i<c.length; i++)
+ {
+ if(c[i] instanceof JScrollPane)
+ lower_panel.remove(c[i]);
+ }
+ // tabbed pane of core and cv annotaion
+ JTabbedPane tabbedPane = new JTabbedPane();
+
+ JScrollPane jspGff = new JScrollPane(propertiesPanel);
+ propertiesPanel.setVisible(true); // ensure visible
+ //jspGff.setPreferredSize(jspCore.getPreferredSize());
+ tabbedPane.add("Properties", jspGff);
+
+ JScrollPane jspCore = new JScrollPane(qualifier_text_area);
+ tabbedPane.add("Core", jspCore);
+ JScrollPane jspCV = new JScrollPane(cvForm);
+ cvForm.setVisible(true); // ensure visible
+ jspCV.setPreferredSize(jspCore.getPreferredSize());
+ tabbedPane.add("CV", jspCV);
+
+
+ JScrollPane jspRef = new JScrollPane(refPanel);
+ refPanel.setVisible(true); // ensure visible
+ jspRef.setPreferredSize(jspCore.getPreferredSize());
+ tabbedPane.add("References", jspRef);
+
+ JScrollPane jspOrtholog = new JScrollPane(matchForm);
+ matchForm.setVisible(true); // ensure visible
+ jspOrtholog.setPreferredSize(getPreferredSize());
+ tabbedPane.add("Match", jspOrtholog);
+
+ lower_panel.add(tabbedPane, "Center");
+ }
+ else
+ {
+ for(int i=0; i<c.length; i++)
+ {
+ if(c[i] instanceof JTabbedPane)
+ lower_panel.remove(c[i]);
+ }
+
+ editorPanel = new GeneEditorPanel(qualifier_text_area, cvForm,
+ refPanel, matchForm, propertiesPanel);
+ JScrollPane jsp = new JScrollPane(editorPanel);
+
+ jsp.setPreferredSize(
+ new Dimension(qualifier_text_area.getPreferredSize().width,
+ (int)(qualifier_text_area.getPreferredSize().height*1.5)));
+ // single panel containing annotation forms
+ lower_panel.add(jsp, "Center");
+ }
+ }
+
+ /**
+ * Return the dirtectory that the given entry was read from.
+ **/
+ private File getBaseDirectoryFromEntry(final Entry entry)
+ {
+ final uk.ac.sanger.artemis.io.Entry embl_entry = entry.getEMBLEntry();
+
+ if(embl_entry instanceof DocumentEntry)
+ {
+ final DocumentEntry document_entry =(DocumentEntry) embl_entry;
+
+ if(document_entry.getDocument() instanceof FileDocument)
+ {
+ final FileDocument file_document =
+ (FileDocument) document_entry.getDocument();
+
+ if(file_document.getFile().getParent() != null)
+ return new File(file_document.getFile().getParent());
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Read the key, location and qualifier information from the feature and
+ * update the components.
+ **/
+ private void updateFromFeature()
+ {
+ datestamp = getFeature().getDatestamp();
+
+ updateKey();
+ updateLocation();
+ updateQualifiers();
+ }
+
+ /**
+ * Read the location from the feature and update the location field.
+ **/
+ private void updateLocation()
+ {
+ location_text.setText(getFeature().getLocation().toStringShort());
+ }
+
+ /**
+ * Complement the current location_text.
+ **/
+ private void complementLocation()
+ {
+ if(GeneUtils.isDatabaseEntry(getFeature().getEmblFeature()))
+ {
+ final ChadoCanonicalGene chadoGene =
+ ((GFFStreamFeature)getFeature().getEmblFeature()).getChadoGene();
+ GeneUtils.complementGeneModel(chadoGene);
+ ((GeneBuilderFrame)frame).dispose(true);
+ return;
+ }
+
+ if(rationalizeLocation())
+ {
+ if(location_text.getText().startsWith("complement("))
+ {
+ final String new_text = location_text.getText().substring(11);
+ if (new_text.endsWith(")"))
+ {
+ final String new_text2 =
+ new_text.substring(0, new_text.length () - 1);
+ location_text.setText(new_text2);
+ }
+ else
+ location_text.setText(new_text);
+ }
+ else
+ {
+ final String new_text = location_text.getText ();
+ location_text.setText("complement(" + new_text + ")");
+ }
+ }
+ else
+ new MessageDialog(frame, "complement failed - " +
+ "current location cannot be parsed");
+ }
+
+ /**
+ * Tidy the qualifiers by removing any indication that the annotation has
+ * been transferred from another gene. Remove the "transferred_" part of
+ * all qualifier names and the "[[FROM sc_001234 abc1]]" bit of each
+ * corresponding qualifier value.
+ **/
+ private void tidy() throws QualifierParseException
+ {
+ final StringBuffer buffer = new StringBuffer();
+ final QualifierVector qualifiers =
+ qualifier_text_area.getParsedQualifiers(getEntryInformation());
+
+ for(Qualifier this_qualifier: qualifiers)
+ {
+ final QualifierInfo qualifier_info =
+ getEntryInformation().getQualifierInfo(this_qualifier.getName());
+ final StringVector qualifier_strings =
+ StreamQualifier.toStringVector(qualifier_info, this_qualifier);
+
+ for(String qualifier_string: qualifier_strings)
+ buffer.append(tidyHelper(qualifier_string) + "\n");
+ }
+ qualifier_text_area.setText(buffer.toString());
+ }
+
+ private void tidyGO() throws QualifierParseException
+ {
+ String qualifier_txt = qualifier_text_area.getText();
+ BufferedReader buff = new BufferedReader(new StringReader(qualifier_txt));
+ final Vector<String> qual_str = new Vector<String>();
+ try
+ {
+ String line;
+ while((line = buff.readLine()) != null)
+ qual_str.add(line);
+ }
+ catch(IOException ioe){}
+
+ Comparator<String> comparator = new Comparator<String>()
+ {
+ public int compare (String fst, String snd)
+ {
+ if( !fst.startsWith("/GO") ||
+ !snd.startsWith("/GO") )
+ return 0;
+ return fst.compareTo(snd);
+ }
+ };
+ Collections.sort(qual_str, comparator);
+
+ StringBuffer buffer = new StringBuffer();
+ for(String qualifier_string: qual_str)
+ buffer.append(tidyHelper(qualifier_string) + "\n");
+
+ qualifier_text_area.setText(buffer.toString());
+ }
+
+
+ /**
+ * Perform the action of tidy() on the String version of one qualifier.
+ **/
+ private String tidyHelper(final String qualifier_string)
+ {
+ final String temp_string;
+
+ if (qualifier_string.startsWith("/transferred_"))
+ temp_string = "/" + qualifier_string.substring(13);
+ else
+ temp_string = qualifier_string;
+
+ final int start_index = temp_string.indexOf ("<<FROM ");
+ final int end_index = temp_string.indexOf (">>");
+
+ if(start_index != -1 && end_index != -1)
+ {
+ if(temp_string.length() > end_index + 2 &&
+ temp_string.charAt(end_index + 2) == ' ')
+ return temp_string.substring(0, start_index) +
+ temp_string.substring(end_index + 3);
+ else
+ return temp_string.substring(0, start_index) +
+ temp_string.substring(end_index + 2);
+ }
+ else
+ return temp_string;
+ }
+
+ /**
+ * Add the currently selected range to location_text.
+ **/
+ private void grabSelectedRange()
+ {
+ if(!rationalizeLocation ())
+ {
+ new MessageDialog(frame,
+ "grab failed - current location cannot be parsed");
+ return;
+ }
+
+ final Range selected_range = getSelection().getSelectionRange();
+
+ if(selected_range == null)
+ {
+ new MessageDialog(frame, "grab failed - nothing is selected");
+ return;
+ }
+
+ // save it in case it gets mangled
+ final String old_location_text = location_text.getText();
+
+ if (old_location_text.endsWith ("))"))
+ {
+ final String new_text =
+ old_location_text.substring(0, old_location_text.length () - 2);
+
+ location_text.setText(new_text + "," + selected_range.getStart () +
+ ".." + selected_range.getEnd () + "))");
+ }
+ else
+ {
+ if(old_location_text.endsWith (")"))
+ {
+ final String new_text =
+ old_location_text.substring(0, old_location_text.length () - 1);
+
+ location_text.setText(new_text + "," + selected_range.getStart () +
+ ".." + selected_range.getEnd () + ")");
+ }
+ else
+ location_text.setText(old_location_text + "," +
+ selected_range.getStart () +
+ ".." + selected_range.getEnd ());
+ }
+
+ if(!rationalizeLocation())
+ {
+ location_text.setText (old_location_text);
+ new MessageDialog(frame, "grab failed - location cannot be parsed after " +
+ "grabbing");
+ }
+ }
+
+ /**
+ * Remove the currently selected range of bases from location_text.
+ **/
+ private void removeSelectedRange()
+ {
+ if(!rationalizeLocation())
+ {
+ new MessageDialog(frame,
+ "grab failed - current location cannot be parsed");
+ return;
+ }
+
+ final MarkerRange selected_marker_range =
+ getSelection().getMarkerRange();
+
+ if(selected_marker_range == null)
+ {
+ new MessageDialog(frame, "remove range failed - no bases are selected");
+ return;
+ }
+
+ final Range selected_range = selected_marker_range.getRawRange();
+
+ if (selected_marker_range.getStrand() != getFeature().getStrand())
+ {
+ new MessageDialog(frame, "remove range failed - you need to select " +
+ "some bases on the other strand");
+ return;
+ }
+
+ final Location location;
+
+ try
+ {
+ location = new Location(location_text.getText ());
+ }
+ catch (LocationParseException e)
+ {
+ // this shouldn't happen because we called rationalizeLocation ()
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final Range location_total_range = location.getTotalRange();
+
+ if(!selected_range.overlaps(location_total_range))
+ {
+ new MessageDialog(frame, "remove range failed - the range you " +
+ "selected does not overlap the feature");
+ return;
+ }
+
+ if(selected_range.contains(location_total_range))
+ {
+ new MessageDialog(frame, "remove range failed - the range you " +
+ "selected overlaps the whole feature");
+ return;
+ }
+
+ final RangeVector location_ranges = location.getRanges();
+ final boolean location_is_complemented = location.isComplement();
+
+ final RangeVector new_ranges = new RangeVector();
+
+ // if the selected_range completely covers a range remove the
+ // range. otherwise if the selected_range is completely within one of the
+ // ranges two new ranges are created. if the selected_range is not
+ // completely contained then the appropriate end of the range is truncated
+ for(int i = 0; i < location_ranges.size(); ++i)
+ {
+ final Range this_range = (Range)location_ranges.elementAt(i);
+
+ if(selected_range.overlaps(this_range))
+ {
+ try
+ {
+ if(this_range.contains(selected_range) &&
+ this_range.getStart() != selected_range.getStart() &&
+ this_range.getEnd() != selected_range.getEnd())
+ {
+ // chop a piece out of the middle and make two new ranges
+ final Range new_start_range =
+ this_range.change(selected_range.getEnd() + 1,
+ this_range.getEnd());
+ new_ranges.add(new_start_range);
+ final Range new_end_range =
+ this_range.change(this_range.getStart(),
+ selected_range.getStart() - 1);
+ new_ranges.add(new_end_range);
+ }
+ else
+ {
+ if(selected_range.contains(this_range)) {
+ // delete (ie. don't copy) the range
+ }
+ else
+ {
+ if(this_range.getStart() < selected_range.getStart())
+ {
+ // truncate the end of the range
+ final Range new_start_range =
+ this_range.change(this_range.getStart(),
+ selected_range.getStart() - 1);
+ new_ranges.add(new_start_range);
+ }
+ else
+ {
+ if(this_range.getEnd() > selected_range.getEnd())
+ {
+ // truncate the start of the range
+ final Range new_end_range =
+ this_range.change(selected_range.getEnd() + 1,
+ this_range.getEnd());
+ new_ranges.add(new_end_range);
+ }
+ else
+ throw new Error ("internal error - can't remove range");
+ }
+ }
+ }
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ else
+ new_ranges.add(this_range); // copy it unchanged
+ }
+
+ final Location new_location =
+ new Location(new_ranges, location_is_complemented);
+
+ location_text.setText(new_location.toStringShort());
+ }
+
+ /**
+ * Attempt to parse the current location_text as a Location. If it can be
+ * parsed it will be canonicalized (ie. the complement, if any, will be
+ * outermost). Returns true if and only if the location_text could be
+ * parsed.
+ **/
+ private boolean rationalizeLocation()
+ {
+ try
+ {
+ final Location location = new Location(location_text.getText());
+ location_text.setText(location.toStringShort());
+ return true;
+ }
+ catch(LocationParseException e)
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Read the qualifiers from the feature and update the qualifier JTextArea.
+ **/
+ private void updateQualifiers()
+ {
+ if(GeneUtils.isDatabaseEntry(getFeature().getEmblFeature()))
+ GeneUtils.addLazyQualifiers((GFFStreamFeature)getFeature().getEmblFeature());
+
+ qualifier_text_area.setText(getQualifierString());
+
+ if(GeneUtils.isDatabaseEntry(getFeature().getEmblFeature()))
+ {
+ // load synonym
+ if(cvForm != null)
+ cvForm.updateFromFeature(getFeature());
+
+ if(refPanel != null)
+ refPanel.updateFromFeature(getFeature());
+
+ if(propertiesPanel != null)
+ propertiesPanel.updateFromFeature(getFeature());
+
+ if(matchForm != null)
+ matchForm.updateFromFeature(getFeature());
+
+ if(!isTabbedView && editorPanel != null)
+ editorPanel.updatePanelState();
+ }
+ }
+
+ /**
+ * Return a string containing one qualifier per line. These are the
+ * original qualifiers, not the qualifiers from the qualifier_text_area.
+ **/
+ private String getQualifierString()
+ {
+ final StringBuffer buffer = new StringBuffer();
+ final QualifierVector qualifiers = getFeature().getQualifiers();
+
+ for(Qualifier this_qualifier: qualifiers)
+ {
+ //
+ // strip out CV qualifiers
+ //
+ if( (cvForm != null && CVPanel.isCvTag(this_qualifier)) ||
+ (refPanel != null && ReferencesPanel.isReferenceTag(this_qualifier)) ||
+ (propertiesPanel != null && PropertiesPanel.isPropertiesTag(this_qualifier, getFeature())) ||
+ (matchForm != null && MatchPanel.isMatchTag(this_qualifier)) ||
+ (propertiesPanel != null && ProteinMapPanel.isProteinMapElement(this_qualifier)) )
+ continue;
+
+ if(this_qualifier instanceof QualifierLazyLoading)
+ ((QualifierLazyLoading)this_qualifier).setForceLoad(true);
+
+ final QualifierInfo qualifier_info =
+ getEntryInformation().getQualifierInfo(this_qualifier.getName());
+
+ final StringVector qualifier_strings =
+ StreamQualifier.toStringVector(qualifier_info, this_qualifier);
+
+ for(String qualStr: qualifier_strings)
+ buffer.append(qualStr + "\n");
+ }
+
+ return buffer.toString();
+ }
+
+
+
+ /**
+ * Set the key, location and qualifiers of the feature to be the same as
+ * what values currently shown in the components.
+ * @return true if and only if action succeeds. It may fail because of an
+ * illegal location or qualifier, in which case a message will be
+ * displayed before returning.
+ **/
+ private boolean setFeature()
+ {
+ final Key key = key_choice.getSelectedItem();
+ final KeyVector possible_keys = getEntryInformation().getValidKeys();
+
+ if(possible_keys != null && !possible_keys.contains(key))
+ {
+ final YesNoDialog dialog =
+ new YesNoDialog(frame, "Add this new key: " + key + "?");
+
+ if(dialog.getResult()) // yes
+ getEntryInformation ().addKey (key);
+ else
+ return false;
+ }
+
+ final Location location;
+ try
+ {
+ location = new Location(location_text.getText());
+ }
+ catch(LocationParseException exception)
+ {
+ final String error_string = exception.getMessage ();
+ System.out.println(error_string);
+ new MessageDialog(frame, "Cannot apply changes because of location error: " +
+ error_string);
+ return false;
+ }
+
+
+ final QualifierVector qualifiers;
+
+ try
+ {
+ qualifiers =
+ qualifier_text_area.getParsedQualifiers(getEntryInformation ());
+
+ updateGffIds(qualifiers);
+
+ // if using controlled vocab form
+ if(cvForm != null)
+ {
+ QualifierVector cvQualifiers = cvForm.getCvQualifiers();
+ if(cvQualifiers != null && cvQualifiers.size() > 0)
+ qualifiers.addAll(cvQualifiers);
+ }
+
+ if(refPanel != null)
+ {
+ QualifierVector refQualifiers = refPanel.getQualifiers();
+ if(refQualifiers != null && refQualifiers.size() > 0)
+ qualifiers.addAll(refQualifiers);
+ }
+
+ if(propertiesPanel != null)
+ {
+ QualifierVector gffQualifiers = propertiesPanel.getGffQualifiers(getFeature());
+ if(gffQualifiers != null && gffQualifiers.size() > 0)
+ qualifiers.addAll(gffQualifiers);
+
+ QualifierVector mapQualifiers = ProteinMapPanel.getProteinMapQualifiers(getFeature());
+ if(mapQualifiers != null && mapQualifiers.size() > 0)
+ qualifiers.addAll(mapQualifiers);
+ }
+
+ if(matchForm != null)
+ {
+ QualifierVector orthologQualifiers = matchForm.getMatchQualifiers();
+ if(orthologQualifiers != null && orthologQualifiers.size() > 0)
+ qualifiers.addAll(orthologQualifiers);
+ }
+
+ final String goErrs = ValidateFeature.validateGO(qualifiers, getEntryInformation());
+ if(goErrs.length()>0)
+ {
+ Object[] options = { "CANCEL", "CONTINUE" };
+ int opt = JOptionPane.showOptionDialog(null, goErrs, "GO errors",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
+ null, options, options[0]);
+
+ if(opt == 0)
+ return false;
+ }
+ //if(similarityTextArea != null)
+ // similarityTextArea.checkForChanges();
+ }
+ catch(QualifierParseException exception)
+ {
+ final String error_string = exception.getMessage();
+ System.out.println(error_string);
+ new MessageDialog(frame, "Cannot apply changes because of a qualifier " +
+ "error: " + error_string);
+ return false;
+ }
+
+ try
+ {
+ entry_group.getActionController().startAction();
+
+ try
+ {
+ getFeature().set(datestamp, key, location, qualifiers);
+ }
+ catch(OutOfDateException e)
+ {
+ final YesNoDialog dialog =
+ new YesNoDialog(frame, "the feature has changed since the edit " +
+ "window was opened, continue?");
+
+ if(dialog.getResult()) // yes - ignore the datestamp
+ getFeature().set(key, location, qualifiers);
+ else
+ return false;
+ }
+ catch(java.lang.Error err)
+ {
+ err.printStackTrace();
+
+ if(err.getMessage().indexOf("InvalidRelationException")>-1)
+ {
+ JScrollPane jsp = new JScrollPane(new JLabel(err.getMessage()));
+ jsp.setPreferredSize(new Dimension(200,100));
+ JOptionPane.showMessageDialog(null, jsp,
+ "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ return false;
+ }
+ }
+ catch(EntryInformationException e)
+ {
+ final String error_string = e.getMessage();
+ new MessageDialog(frame, "Cannot apply changes: " + error_string);
+
+ return false;
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(frame, "Cannot apply changes - the location is out of " +
+ "range for this sequence");
+ return false;
+ }
+ catch(ReadOnlyException e)
+ {
+ new MessageDialog(frame, "Cannot apply changes - the feature is " +
+ "read only");
+ return false;
+ }
+ finally
+ {
+ entry_group.getActionController ().endAction ();
+ }
+
+ dribble();
+
+ return true;
+ }
+
+ /**
+ * Propagate any changes to GFF ID qualifiers through the
+ * gene model
+ * @param qualifiers
+ */
+ private void updateGffIds(QualifierVector qualifiers)
+ {
+ if( !GeneUtils.isDatabaseEntry(getFeature().getEmblFeature()) &&
+ getFeature().getEmblFeature() instanceof GFFStreamFeature )
+ {
+ final GFFStreamFeature gffFeature = (GFFStreamFeature)getFeature().getEmblFeature();
+ if(gffFeature.getChadoGene() != null)
+ {
+ try
+ {
+ final String newName = ((String) (qualifiers.getQualifierByName("ID").getValues().get(0))).trim();
+ final String oldName = ((String) (gffFeature.getQualifierByName("ID").getValues().get(0))).trim();
+
+ if(!newName.equals(oldName))
+ {
+ int val = JOptionPane.showConfirmDialog(null,
+ "Change name of children based on this ["+newName+"]?",
+ "Name Change", JOptionPane.OK_CANCEL_OPTION);
+ if(val == JOptionPane.CANCEL_OPTION)
+ return;
+
+ final Set<uk.ac.sanger.artemis.io.Feature> children =
+ gffFeature.getChadoGene().getChildren(gffFeature);
+ GeneUtils.propagateId(gffFeature, newName, children);
+ GeneUtils.fixParentQualifier(oldName, newName, children);
+
+ final Iterator<uk.ac.sanger.artemis.io.Feature> it = children.iterator();
+ while(it.hasNext())
+ {
+ final GFFStreamFeature child = (GFFStreamFeature)it.next();
+ if( child.getSegmentRangeStore().size() == 1 &&
+ !child.getKey().getKeyString().equals("CDS") &&
+ child.getKey().getKeyString().indexOf("exon") == -1)
+ child.setSegmentRangeStore(null);
+ }
+ }
+ }
+ catch(Exception e){ }
+ }
+ }
+ }
+
+ /**
+ * Return the Feature we are editing as passed to the constructor.
+ **/
+ public Feature getFeature()
+ {
+ return edit_feature;
+ }
+
+ /**
+ * On Unix machines this method will append the text of the feature to a
+ * file in a current directory called .dribble + <the entry name>
+ **/
+ private void dribble()
+ {
+ if(!Options.isUnixHost())
+ return;
+
+ final String dribble_file_name;
+
+ if(getEntry().getName() != null)
+ dribble_file_name = ".dribble." + getEntry().getName();
+ else
+ dribble_file_name = ".dribble.no_name";
+
+ try
+ {
+ final Writer writer = new FileWriter(dribble_file_name, true);
+ getFeature().writeNative(writer);
+ writer.flush();
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ System.err.println("IO exception while accessing " + dribble_file_name +
+ ": " + e.getMessage());
+ }
+ }
+
+ /**
+ * Return the Entry that contains the Feature this object is displaying.
+ **/
+ private Entry getEntry()
+ {
+ return edit_entry;
+ }
+
+ /**
+ * Read the key from the feature and update the key chooser.
+ **/
+ private void updateKey()
+ {
+ final Key feature_key = getFeature().getKey();
+ key_choice.setKey(feature_key);
+ }
+
+ /**
+ * Return the EntryInformation object of the entry containing the feature.
+ **/
+ public EntryInformation getEntryInformation()
+ {
+ if(entry_information == null)
+ entry_information = getEntry().getEntryInformation();
+ return entry_information;
+ }
+
+ /**
+ * Return the Selection that was passed to the constructor.
+ **/
+ private Selection getSelection()
+ {
+ return selection;
+ }
+
+ public static boolean isTabbedView()
+ {
+ return isTabbedView;
+ }
+
+ public static void setTabbedView(boolean isTabbedView)
+ {
+ FeatureEdit.isTabbedView = isTabbedView;
+ }
+
+ /**
+ * Set whether the feature is obsolete (database mode).
+ * @param obsoleteChanged
+ */
+ public void setObsoleteChanged(boolean obsoleteChanged)
+ {
+ propertiesPanel.setObsoleteChanged(obsoleteChanged);
+ }
+
+ public QualifierTextArea getQualifierTextArea()
+ {
+ return qualifier_text_area;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/FeatureInfo.java b/uk/ac/sanger/artemis/components/FeatureInfo.java
new file mode 100644
index 0000000..3d8e125
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeatureInfo.java
@@ -0,0 +1,658 @@
+/* FeatureInfo.java
+ *
+ * created: Sat Dec 19 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureInfo.java,v 1.1 2004-06-09 09:46:39 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.plot.*;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+/**
+ * This component displays a summary of the statistical information about one
+ * feature (such as correlation score, codon usage and gc content).
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureInfo.java,v 1.1 2004-06-09 09:46:39 tjc Exp $
+ **/
+public class FeatureInfo extends JFrame
+ implements EntryChangeListener, FeatureChangeListener {
+ /**
+ * Create a new FeatureInfo component.
+ * @param feature The Feature that this component is showing information
+ * about.
+ **/
+ public FeatureInfo (final Feature feature,
+ final CodonUsageAlgorithm codon_usage_algorithm) {
+ super ("Feature infomation: " + feature.getIDString ());
+
+ this.feature = feature;
+ this.entry = feature.getEntry ();
+ this.codon_usage_algorithm = codon_usage_algorithm;
+
+ setBackground (new Color (210, 210, 210));
+ getContentPane ().setBackground (new Color (210, 210, 210));
+
+ codon_info_areas = new JTextArea [4][4];
+ base_count_info_areas = new JTextArea [4];
+
+ final JPanel centre_panel = new JPanel ();
+ centre_panel.setLayout (new BorderLayout ());
+
+ makeCountList ();
+ getContentPane ().add (aa_count_panel, "West");
+
+ makeMiscInfogrid ();
+ centre_panel.add (misc_info_panel, "South");
+
+ makeCodonInfogrid ();
+ centre_panel.add (codon_info_panel, "Center");
+
+ getContentPane ().add (centre_panel, "Center");
+
+ button_panel = new JPanel ();
+ final JButton close_button = new JButton ("Close");
+
+ close_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ stopListening ();
+ FeatureInfo.this.dispose ();
+ }
+ });
+
+ button_panel.add (close_button);
+
+ getContentPane ().add (button_panel, "South");
+
+ updateComponents ();
+
+ getFeature ().addFeatureChangeListener (this);
+ getFeature ().getEntry ().addEntryChangeListener (this);
+
+ addWindowListener (new WindowAdapter () {
+ public void windowClosing (WindowEvent event) {
+ stopListening ();
+ FeatureInfo.this.dispose ();
+ }
+ });
+
+ pack ();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+
+ setVisible (true);
+ }
+
+ /**
+ * Remove this object as a feature change and entry change listener.
+ **/
+ private void stopListening () {
+ getEntry ().removeEntryChangeListener (this);
+ getFeature ().removeFeatureChangeListener (this);
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so we can notify the user if of this component if the
+ * feature gets deleted.
+ **/
+ public void entryChanged (EntryChangeEvent event) {
+ switch (event.getType ()) {
+ case EntryChangeEvent.FEATURE_DELETED:
+ if (event.getFeature () == getFeature ()) {
+ stopListening ();
+ dispose ();
+ }
+ break;
+ default:
+ // do nothing;
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events from the Features in this object.
+ * @param event The change event.
+ **/
+ public void featureChanged (FeatureChangeEvent event) {
+ updateComponents ();
+ }
+
+ /**
+ * Create all the labels in the misc_info_panel.
+ **/
+ private void makeMiscInfogrid () {
+ misc_info_panel = new JPanel ();
+
+ GridLayout grid = new GridLayout (0, 1);
+ misc_info_panel.setLayout (grid);
+
+ molecular_weight_label = new JLabel ();
+ misc_info_panel.add (molecular_weight_label);
+
+ correlation_scores_label = new JLabel ();
+ misc_info_panel.add (correlation_scores_label);
+
+ if (codon_usage_algorithm != null) {
+ usage_scores_label = new JLabel ();
+ misc_info_panel.add (usage_scores_label);
+ }
+ }
+
+ /**
+ * Create a List containing a count of the occurrences of each codon in the
+ * feature.
+ **/
+ private void makeCountList () {
+ aa_count_panel = new JPanel ();
+
+ final JPanel aa_count_sub_panel1 = new JPanel ();
+ aa_count_sub_panel1.setLayout (new GridLayout (0, 1));
+ aa_count_panel.add (aa_count_sub_panel1);
+
+ final JPanel aa_count_sub_panel2 = new JPanel ();
+ aa_count_sub_panel2.setLayout (new GridLayout (0, 1));
+ aa_count_panel.add (aa_count_sub_panel2);
+
+ aa_count_list = new JLabel [AminoAcidSequence.symbol_count];
+
+ for (int i = 0 ; i < AminoAcidSequence.symbol_count / 2 ; ++i) {
+ aa_count_list[i] = new JLabel ();
+ aa_count_sub_panel1.add (aa_count_list[i]);
+ }
+
+ // add a blank label so that the columns balance
+ aa_count_sub_panel1.add (new JLabel (""));
+
+ for (int i = AminoAcidSequence.symbol_count / 2 ;
+ i < AminoAcidSequence.symbol_count ;
+ ++i) {
+ aa_count_list[i] = new JLabel ();
+ aa_count_sub_panel2.add (aa_count_list[i]);
+ }
+ }
+
+ /**
+ * Create all the labels in the codon_info_panel.
+ **/
+ private void makeCodonInfogrid () {
+ codon_info_panel = new JPanel ();
+
+ GridBagLayout gridbag = new GridBagLayout();
+
+ codon_info_panel.setLayout (gridbag);
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ final int GRID_WIDTH = 7;
+
+ c.gridwidth = GRID_WIDTH;
+ c.anchor = GridBagConstraints.NORTH;
+ c.insets = new Insets (1, 1, 1, 1);
+ c.fill = GridBagConstraints.NONE;
+
+
+ // create the first row with some dummy labels to pad it out
+
+ // the first row has the bases T,C,A,G as column titles
+ final JLabel dummy_label1 = new JLabel ("");
+ gridbag.setConstraints (dummy_label1, c);
+ codon_info_panel.add (dummy_label1);
+
+ final JLabel dummy_label2 = new JLabel ("");
+ gridbag.setConstraints (dummy_label2, c);
+ codon_info_panel.add (dummy_label2);
+
+ for (int i = 0 ; i < Bases.letter_index.length ; ++i) {
+ final JLabel new_label =
+ new JLabel (String.valueOf (Bases.letter_index[i]).toUpperCase ());
+ gridbag.setConstraints (new_label, c);
+ codon_info_panel.add (new_label);
+ }
+
+ final JLabel dummy_label3 = new JLabel ("");
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints (dummy_label3, c);
+ codon_info_panel.add (dummy_label3);
+
+ c.anchor = GridBagConstraints.WEST;
+ c.gridwidth = GRID_WIDTH;
+
+ final int ROWS = 4;
+
+ // create the main rows
+
+ for (int row = 0 ; row < ROWS ; ++row) {
+ {
+ final JLabel new_label =
+ new JLabel (String.valueOf (Bases.letter_index[row]).toUpperCase ());
+
+ new_label.setBorder (new javax.swing.border.EmptyBorder (1,1,1,1));
+ gridbag.setConstraints (new_label, c);
+ codon_info_panel.add (new_label);
+ }
+
+ {
+ base_count_info_areas[row] = new JTextArea (5, 15);
+ base_count_info_areas[row].setEditable (false);
+
+ final JScrollPane scroll_pane =
+ new JScrollPane (base_count_info_areas[row]);
+ gridbag.setConstraints (scroll_pane, c);
+ codon_info_panel.add (scroll_pane);
+
+ final Color parent_background = getBackground ();
+ base_count_info_areas[row].setBackground (parent_background);
+ }
+
+ for (int column = 2 ; column < GRID_WIDTH - 1 ; ++column) {
+
+ final JTextArea new_text = new JTextArea (5, 12);
+ new_text.setText ("r: " + row + " c: " + column);
+
+ new_text.setEditable (false);
+
+ new_text.setBackground (Color.white);
+
+ final JScrollPane scroll_pane = new JScrollPane (new_text);
+ gridbag.setConstraints (scroll_pane, c);
+ codon_info_panel.add (scroll_pane);
+
+ codon_info_areas[row][column - 2] = new_text;
+ }
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+
+ {
+ final JTextArea new_text = new JTextArea (5, 2);
+ new_text.setText (Bases.letter_index[0] + "\n" +
+ Bases.letter_index[1] + "\n" +
+ Bases.letter_index[2] + "\n" +
+ Bases.letter_index[3]);
+
+ new_text.setEditable (false);
+
+ final JScrollPane scroll_pane = new JScrollPane (new_text);
+ gridbag.setConstraints (scroll_pane, c);
+ codon_info_panel.add (scroll_pane);
+ }
+
+ c.gridwidth = GRID_WIDTH;
+ }
+ }
+
+ /**
+ * Set the List components with the information from the feature.
+ **/
+ private void updateComponents () {
+ updateAACountList ();
+
+ updateCodonInfoAreas ();
+
+ updateMolecularWeightLabel ();
+
+ updateCorrelationScoresLabel ();
+
+ updateUsageScoresLabel ();
+
+ updateBaseCounts ();
+
+ validate ();
+ }
+
+ /**
+ * Update the aa_count_list JTextArea.
+ **/
+ private void updateAACountList () {
+ for (int i = 0 ; i < AminoAcidSequence.symbol_count ; ++i) {
+ if (i == AminoAcidSequence.symbol_count - 1 &&
+ getFeature ().getResidueCount (i) == 0) {
+ // don't include Selenocysteine in the list in the list if there are
+ // none
+ aa_count_list[i].setText ("");
+ continue;
+ }
+
+ final String three_letter_abbreviation =
+ AminoAcidSequence.getThreeLetterAbbreviation (i);
+
+ final char one_letter_abbreviation =
+ Character.toUpperCase (AminoAcidSequence.getSymbolFromIndex (i)) ;
+
+ final String label_string =
+ three_letter_abbreviation + " (" + one_letter_abbreviation +"): " +
+ getFeature ().getResidueCount (i);
+
+ aa_count_list[i].setText (label_string);
+ }
+ }
+
+ /**
+ * Update all the JTextArea objects in codon_info_areas.
+ **/
+ private void updateCodonInfoAreas () {
+ for (int first = 0 ; first < 4 ; ++first) {
+
+ for (int second = 0 ; second < 4 ; ++second) {
+
+ codon_info_areas[first][second].setText ("");
+
+ for (int third = 0 ; third < 4 ; ++third) {
+
+ final char this_codon_symbol =
+ AminoAcidSequence.getCodonTranslation (Bases.letter_index[first],
+ Bases.letter_index[second],
+ Bases.letter_index[third]);
+
+ final String this_codon_string =
+ AminoAcidSequence.getThreeLetterAbbreviation (this_codon_symbol);
+
+ final int this_codon_count =
+ getFeature ().getCodonCount (first,second,third);
+
+ final int this_codon_index =
+ AminoAcidSequence.getSymbolIndex (this_codon_symbol);
+
+ final int this_amino_acid_count =
+ getFeature ().getResidueCount (this_codon_index);
+
+ final String percent_string;
+
+ if (this_amino_acid_count == this_codon_count) {
+ percent_string = "ALL";
+ } else {
+ if (this_amino_acid_count < this_codon_count) {
+ // some of the amino acids aren't coded for by the standard
+ // codon - probably because of the /transl_except qualifier
+ percent_string = "---";
+ } else {
+ final int percentage =
+ 100 * this_codon_count / this_amino_acid_count;
+
+ percent_string = String.valueOf (percentage) + "%";
+ }
+ }
+
+ final String first_part_of_line =
+ this_codon_string + " " + this_codon_count;
+
+ codon_info_areas[first][second].append (first_part_of_line);
+
+ final int line_length_without_spaces =
+ first_part_of_line.length () + percent_string.length ();
+
+ // add some spaces so that everything lines up nicely
+ for (int i = 0 ; i < 11 - line_length_without_spaces ; ++i) {
+ codon_info_areas[first][second].append (" ");
+ }
+
+ codon_info_areas[first][second].append (percent_string);
+
+ if (third != 3) {
+ // insert a newline on all but the last line
+ codon_info_areas[first][second].append ("\n");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update molecular_weight_label.
+ **/
+ private void updateMolecularWeightLabel () {
+ final String new_text =
+ "Mol weight: " +
+ getFeature ().getTranslation ().getMolecularWeight () + " Start: " +
+ getFeature ().getFirstBase () + " End: " +
+ getFeature ().getLastBase () + " Bases: " +
+ getFeature ().getBaseCount () + " AA length: " +
+ getFeature ().getTranslation ().length ();
+ molecular_weight_label.setText (new_text);
+ }
+
+
+ /**
+ * The method updates the components in base_count_info_areas.
+ **/
+ private void updateBaseCounts () {
+ final int translation_base_count =
+ getFeature ().getTranslationBases ().length ();
+
+ final int amino_acid_count = getFeature ().getTranslation ().length ();
+
+ for (int i = 0 ; i < 4 ; ++i) {
+ base_count_info_areas[i].setText ("");
+ {
+ final String string =
+ "ALL:" + updateBaseCountsFormatter (getFeature ().getBaseCount (i),
+ translation_base_count);
+
+ base_count_info_areas[i].append (string);
+ }
+
+ for (int codon_base_index = 0;
+ codon_base_index < 3 ;
+ ++codon_base_index) {
+ String label = null;
+ switch (codon_base_index) {
+ case 0: label = "\n1st:"; break;
+ case 1: label = "\n2nd:"; break;
+ case 2: label = "\n3rd:"; break;
+ }
+
+ final int base_count =
+ getFeature ().getPositionalBaseCount (codon_base_index, i);
+
+ final String string =
+ label + updateBaseCountsFormatter (base_count, amino_acid_count);
+
+ base_count_info_areas[i].append (string);
+ }
+ }
+ }
+
+ /**
+ * A helper method for updateBaseCounts ().
+ **/
+ private String updateBaseCountsFormatter (int base_count,
+ int total) {
+ final String count_string = " " + base_count;
+
+ final String percent_string;
+
+ if (base_count < total) {
+ percent_string = " " + 100 * base_count / total + "%";
+ } else {
+ percent_string = "ALL";
+ }
+
+ final int count_width = 5;
+
+ final int percent_width = 3;
+
+ return
+ count_string.substring (count_string.length () - count_width) + " " +
+ percent_string.substring (percent_string.length () - percent_width);
+ }
+
+ /**
+ * Update correlation_scores_label.
+ **/
+ private void updateCorrelationScoresLabel () {
+ final int c_total =
+ getFeature ().getBaseCount (Bases.getIndexOfBase ('c'));
+ final int g_total =
+ getFeature ().getBaseCount (Bases.getIndexOfBase ('g'));
+
+ final String c3_score;
+ final String g1_score;
+ final String g3_score;
+
+ final int c3_count =
+ getFeature ().getPositionalBaseCount (2, Bases.getIndexOfBase ('c'));
+ final int g1_count =
+ getFeature ().getPositionalBaseCount (0, Bases.getIndexOfBase ('g'));
+ final int g3_count =
+ getFeature ().getPositionalBaseCount (2, Bases.getIndexOfBase ('g'));
+
+ if (c_total == 0) {
+ c3_score = "ALL";
+ } else {
+ c3_score =
+ String.valueOf (1000 * (3 * c3_count - c_total) / c_total / 10.0);
+ }
+
+ if (g_total == 0) {
+ g1_score = "ALL";
+ } else {
+ g1_score =
+ String.valueOf (1000 * (3 * g1_count - g_total) / g_total / 10.0);
+ }
+
+ if (g_total == 0) {
+ g3_score = "ALL";
+ } else {
+ g3_score =
+ String.valueOf (1000 * (3 * g3_count - g_total) / g_total / 10.0);
+ }
+
+ final double cor1_2_score =
+ ((int) getFeature ().get12CorrelationScore () * 10) / 10.0;
+
+ final double gc_percent =
+ ((int) (getFeature ().getPercentGC () * 100.0)) / 100.0;
+
+ final String new_label =
+ "position 1/2 score = " + cor1_2_score + " " +
+ "C3/G1/G3 (o-e)/e = " + c3_score + " " + g1_score + " " + g3_score +
+ " " + gc_percent + "% GC";
+
+ correlation_scores_label.setText (new_label);
+ }
+
+ /**
+ * Update usage_scores_label.
+ **/
+ private void updateUsageScoresLabel () {
+ if (codon_usage_algorithm != null) {
+ final String new_label =
+ "usage score = " + codon_usage_algorithm.getFeatureScore (feature);
+
+ usage_scores_label.setText (new_label);
+ }
+ }
+
+ /**
+ * Return the feature this component is showing information about.
+ **/
+ private Feature getFeature () {
+ return feature;
+ }
+
+ /**
+ * Return the Entry that contains the Feature this object is displaying.
+ **/
+ private Entry getEntry () {
+ return entry;
+ }
+
+ /**
+ * The Feature that this component is showing information about.
+ **/
+ private Feature feature = null;
+
+ /**
+ * The Entry that contains the Feature this object is displaying.
+ **/
+ private Entry entry;
+
+ /**
+ * A panel containing the labels with the amino acid counts for the
+ * feature.
+ **/
+ private JPanel aa_count_panel = null;
+
+ /**
+ * An array Labels of showing the amino acid counts for the feature. There
+ * is one Label for each symbol (24).
+ **/
+ private JLabel [] aa_count_list = null;
+
+ /**
+ * A panel containing general statistics about the whole sequence.
+ **/
+ private JPanel misc_info_panel = null;
+
+ /**
+ * A Label containing molecular weight of the protein, start and end
+ * positions and length.
+ **/
+ private JLabel molecular_weight_label = null;
+
+ /**
+ * A Label containing the correlation scores for this feature.
+ **/
+ private JLabel correlation_scores_label = null;
+
+ /**
+ * A Label containing the usage scores for this feature.
+ **/
+ private JLabel usage_scores_label = null;
+
+ /**
+ * Statistics about the codon frequency.
+ **/
+ private JPanel codon_info_panel = null;
+
+ /**
+ * A panel to hold the close button.
+ **/
+ private JPanel button_panel = null;
+
+ /**
+ * These components each hold information about 4 codons each.
+ **/
+ private JTextArea [] [] codon_info_areas;
+
+ /**
+ * These components display position information about the base counts.
+ * See positional_base_counts and base_counts.
+ **/
+ private JTextArea [] base_count_info_areas;
+
+ /**
+ * The CodonUsageAlgorithm reference that was passed to the constructor.
+ * (Used by updateUsageScoresLabel ()).
+ **/
+ private final CodonUsageAlgorithm codon_usage_algorithm;
+}
diff --git a/uk/ac/sanger/artemis/components/FeatureList.java b/uk/ac/sanger/artemis/components/FeatureList.java
new file mode 100644
index 0000000..c9a27ab
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeatureList.java
@@ -0,0 +1,1080 @@
+/* FeatureList.java
+ *
+ * created: Fri Oct 9 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureList.java,v 1.28 2008-10-23 14:44:28 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.plot.CodonUsageAlgorithm;
+
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.QualifierInfo;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.StreamQualifier;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.Container;
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.Graphics;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.text.NumberFormat;
+
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JViewport;
+import javax.swing.JComponent;
+
+/**
+ * This component gives the user a list containing the details the current
+ * Features.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureList.java,v 1.28 2008-10-23 14:44:28 tjc Exp $
+ *
+ **/
+
+public class FeatureList extends EntryGroupPanel
+ implements EntryGroupChangeListener,
+ EntryChangeListener, FeatureChangeListener,
+ SelectionChangeListener, DisplayComponent
+{
+ private static final long serialVersionUID = 1L;
+
+ /** true if correlation scores should be shown */
+ private boolean show_correlation_scores = false;
+
+ /**
+ * This is set to true by selectionChanged() and used by paintComponent().
+ **/
+ private boolean selection_changed_flag = false;
+
+ /** colour used to draw the background. */
+ private Color background_colour = Color.white;
+
+ /**
+ * If true this component will show Feature.getIDString() (ie /gene or
+ * /label) instead of the key.
+ **/
+ private boolean show_gene_names = false;
+
+ private String user_defined_qualifier = null;
+
+ /** show Feature.getSystematicName() */
+ private boolean show_systematic_names = false;
+
+ /** show the /product qualifier instead of /note field. */
+ private boolean show_products = false;
+
+ /** show all the qualifiers after the note. */
+ private boolean show_qualifiers = false;
+
+ /**
+ * The is the maximum width of the strings containing the feature start and
+ * stop positions. Set in the constructor.
+ **/
+ private int max_base_pos_width;
+
+ /** JScrollPane viewport that this panel is the view of */
+ private JViewport viewport = null;
+
+ /**
+ * Create a new FeatureList with the default number of rows.
+ * @param entry_group The EntryGroup that this component will display.
+ * @param selection The Selection object for this component. Selected
+ * objects will be highlighted.
+ * @param goto_event_source The object to use when we need to call
+ * gotoBase().
+ **/
+ public FeatureList(final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group)
+ {
+ super(entry_group, selection, goto_event_source, base_plot_group);
+
+ addMouseListener(new MouseAdapter()
+ {
+ private FeaturePopup popup = null;
+
+ /**
+ * Listen for mouse press events so that we can do popup menus and
+ * selection.
+ **/
+ public void mousePressed(MouseEvent event)
+ {
+ if(isMenuTrigger(event))
+ {
+ if(popup == null)
+ popup = new FeaturePopup(FeatureList.this,
+ getEntryGroup(),
+ getSelection(),
+ getGotoEventSource(),
+ getBasePlotGroup());
+ final JComponent parent = (JComponent)event.getSource();
+
+ popup.show(parent, event.getX(), event.getY());
+ }
+ else
+ handleCanvasMousePress(event);
+ }
+ });
+
+ getSelection().addSelectionChangeListener(this);
+
+ // changes to the EntryGroup will be noticed by listening for EntryChange
+ // and FeatureChange events.
+
+ getEntryGroup().addEntryGroupChangeListener(this);
+ getEntryGroup().addEntryChangeListener(this);
+ getEntryGroup().addFeatureChangeListener(this);
+
+ // find the maximum posible width for the high and low positions
+ final int sequence_length = getEntryGroup().getSequenceLength();
+ max_base_pos_width = (int)(Math.log(sequence_length)/Math.log(10)) + 1;
+
+ if(max_base_pos_width < 4)
+ max_base_pos_width = 4;
+
+ setBackground(background_colour);
+ }
+
+ /**
+ * Remove this component from all the listener lists it is on.
+ **/
+ void stopListening()
+ {
+ getSelection().removeSelectionChangeListener(this);
+ getEntryGroup().removeEntryGroupChangeListener(this);
+ getEntryGroup().removeEntryChangeListener(this);
+ getEntryGroup().removeFeatureChangeListener(this);
+ }
+
+ /**
+ * Set value of the show correlation scores flag.
+ * @param show_correlation_scores Show correlation scores in the list if
+ * and only if this argument is true.
+ **/
+ protected void setCorrelationScores(final boolean show_correlation_scores)
+ {
+ if(this.show_correlation_scores != show_correlation_scores)
+ {
+ this.show_correlation_scores = show_correlation_scores;
+ repaint();
+ }
+ }
+
+ /**
+ * Get the value of the "show correlation scores" flag.
+ **/
+ protected boolean getCorrelationScores()
+ {
+ return show_correlation_scores;
+ }
+
+ /**
+ * Set value of the show /gene flag.
+ * @param show_gene_names If true this component will show the /gene (really
+ * Feature.getIDString()) instead of the key.
+ **/
+ protected void setShowGenes(final boolean show_gene_names)
+ {
+ if(this.show_gene_names != show_gene_names)
+ {
+ this.show_gene_names = show_gene_names;
+ repaint();
+ }
+ }
+
+
+ /**
+ * Set value of the show /systematic_id flag.
+ * @param show_systematic_names If true this component will show the /gene (really
+ * Feature.getSystematicName()) instead of the key.
+ **/
+ protected void setShowSystematicID(final boolean show_systematic_names)
+ {
+ if(this.show_systematic_names != show_systematic_names)
+ {
+ this.show_systematic_names = show_systematic_names;
+ repaint();
+ }
+ }
+
+
+ protected void setShowUserDefinedQualifier(final String user_defined_qualifier)
+ {
+ this.user_defined_qualifier = user_defined_qualifier;
+ repaint();
+ }
+
+ protected StringVector getShowUserDefinedQualifier()
+ {
+ if(user_defined_qualifier == null)
+ return null;
+ return StringVector.getStrings(user_defined_qualifier);
+ }
+
+ /**
+ * Get the value of the "show genes" flag.
+ **/
+ protected boolean getShowGenes()
+ {
+ return show_gene_names;
+ }
+
+
+ /**
+ * Get the value of the "show systematic id" flag.
+ **/
+ protected boolean getShowSysID()
+ {
+ return show_systematic_names;
+ }
+
+
+ /**
+ * Set value of the show qualifiers flag.
+ * @param show_quailfiers If true this component will show all the
+ * qualifiers after the note.
+ **/
+ protected void setShowQualifiers(final boolean show_qualifiers)
+ {
+ if(this.show_qualifiers != show_qualifiers)
+ {
+ if(show_qualifiers)
+ user_defined_qualifier = null;
+
+ this.show_qualifiers = show_qualifiers;
+ repaint();
+ }
+ }
+
+ /**
+ * Get the value of the "show qualifiers" flag.
+ **/
+ protected boolean getShowQualifiers()
+ {
+ return show_qualifiers;
+ }
+
+ /**
+ * Set value of the show /product flag.
+ * @param show_products If true this component will show the /product
+ * qualifier instead of the /note.
+ **/
+ protected void setShowProducts(final boolean show_products)
+ {
+ if(this.show_products != show_products)
+ {
+ if(show_products)
+ user_defined_qualifier = null;
+
+ this.show_products = show_products;
+ repaint();
+ }
+ }
+
+ /**
+ * Get the value of the "show products" flag.
+ **/
+ protected boolean getShowProducts()
+ {
+ return show_products;
+ }
+
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can update the display if entries
+ * are added or deleted.
+ **/
+ public void entryGroupChanged(EntryGroupChangeEvent event)
+ {
+ final int hgt = getEntryGroup().getAllFeaturesCount() *
+ getLineHeight();
+
+ setPreferredSize(new Dimension(getSize().width*4,hgt));
+ revalidate();
+ repaint();
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface.
+ **/
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ if(!isVisible())
+ return;
+
+ repaint();
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so that we can update the list if features are added
+ * or deleted.
+ **/
+ public void entryChanged(EntryChangeEvent event)
+ {
+ if(!isVisible())
+ return;
+
+ repaint();
+ }
+
+ /**
+ * Implementation of the SelectionChangeListener interface. We listen to
+ * SelectionChange events so that we can update the list to reflect the
+ * current selection.
+ **/
+ public void selectionChanged(SelectionChangeEvent event)
+ {
+ if(!isVisible())
+ return;
+
+ // don't bother with events we sent ourself
+ if(event.getSource() == this)
+ return;
+
+ // if the selected range changes we don't care
+ if(getSelection().getMarkerRange() != null &&
+ event.getType() == SelectionChangeEvent.OBJECT_CHANGED)
+ return;
+
+ selection_changed_flag = true;
+
+ onSelectionChange();
+ repaint();
+ }
+
+ /**
+ * Return a vector containing the text that is shown in the list - one
+ * String per line.
+ **/
+ protected StringVector getListStrings()
+ {
+ final StringVector return_vector = new StringVector();
+ final FeatureEnumeration test_enumerator = getEntryGroup().features();
+
+ while(test_enumerator.hasMoreFeatures())
+ {
+ final Feature this_feature = test_enumerator.nextFeature();
+ return_vector.add(makeFeatureString(this_feature, true));
+ }
+
+ return return_vector;
+ }
+
+
+ /**
+ * Return the JViewport that this component is contained in.
+ */
+ protected JViewport getViewport()
+ {
+ if(viewport != null)
+ return viewport;
+
+ Container container = getParent();
+ while(!(container instanceof JScrollPane))
+ container = container.getParent();
+
+ viewport = ((JScrollPane)container).getViewport();
+ return viewport;
+ }
+
+ /**
+ *
+ * Find the point at the top right hand corner of the
+ * scroll pane.
+ *
+ */
+ private Point getScrollPoint()
+ {
+ return getViewport().getViewPosition();
+ }
+
+ /**
+ * Handle a mouse press event on the drawing canvas - select on click,
+ * select and broadcast it on double click.
+ **/
+ private void handleCanvasMousePress(final MouseEvent event)
+ {
+ if(event.getID() != MouseEvent.MOUSE_PRESSED)
+ return;
+
+ requestFocus();
+
+ if(!event.isShiftDown())
+ getSelection().clear();
+
+ final int clicked_feature_index = event.getY()/getLineHeight();
+
+ if(clicked_feature_index < getEntryGroup().getAllFeaturesCount())
+ {
+ final FeatureVector selected_features =
+ getSelection().getAllFeatures();
+
+ final Feature clicked_feature =
+ getEntryGroup().featureAt(clicked_feature_index);
+
+ if(selected_features.contains(clicked_feature))
+ {
+ getSelection().remove(clicked_feature);
+ getSelection().removeSegmentsOf(clicked_feature);
+ }
+ else
+ getSelection().add(clicked_feature);
+
+ if(event.getClickCount() == 2)
+ {
+ makeSelectionVisible();
+
+ if((event.getModifiers() & InputEvent.BUTTON2_MASK) != 0 ||
+ event.isAltDown())
+ {
+ if(Options.readWritePossible())
+ {
+ final JFrame frame = new JFrame("Artemis Feature Edit: " +
+ clicked_feature.getIDString() +
+ (clicked_feature.isReadOnly() ?
+ " - (read only)" :
+ ""));
+
+ final FeatureEdit fe = new FeatureEdit(clicked_feature, getEntryGroup(),
+ getSelection(), getGotoEventSource(), frame);
+ frame.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ fe.stopListening();
+ frame.dispose();
+ }
+ });
+
+ frame.getContentPane().add(fe);
+ frame.pack();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ frame.setLocation(new Point((screen.width - getSize().width)/2,
+ (screen.height - getSize().height)/2));
+ frame.setVisible(true);
+ }
+ }
+ }
+
+ }
+ }
+
+ private void onSelectionChange()
+ {
+ if(!selection_changed_flag)
+ return;
+
+ selection_changed_flag = false;
+ final FeatureVector selected_features =
+ getSelection().getAllFeatures();
+
+ if(selected_features.size() > 0)
+ {
+ Point viewPoint = getScrollPoint();
+ final EntryGroup entry_group = getEntryGroup();
+ final int feature_count = entry_group.getAllFeaturesCount();
+
+ // set to true if any of the selected features is visible
+ boolean a_selected_feature_is_visible = false;
+
+ int first_line_in_view = viewPoint.y/getLineHeight();
+
+ if(first_line_in_view == -1)
+ first_line_in_view = 0;
+
+ int numberLines = linesInView();
+ for(int i = first_line_in_view;
+ i < feature_count && i <= first_line_in_view + numberLines;
+ ++i)
+ {
+ final Feature this_feature = entry_group.featureAt(i);
+ if(selected_features.contains(this_feature))
+ {
+ a_selected_feature_is_visible = true;
+ break;
+ }
+ }
+
+ if(!a_selected_feature_is_visible)
+ {
+ // make the first selected feature visible
+ final Feature first_selected_feature =
+ selected_features.elementAt(0);
+
+ final int index_of_first_selected_feature =
+ entry_group.indexOf(first_selected_feature);
+
+ if( index_of_first_selected_feature > -1 &&
+ (index_of_first_selected_feature < first_line_in_view ||
+ index_of_first_selected_feature >= first_line_in_view + numberLines))
+ {
+ getViewport().setViewPosition(new Point(0,
+ index_of_first_selected_feature * getLineHeight()));
+ }
+ }
+ }
+ }
+
+ /**
+ * The main paint function for the canvas. An off screen image used for
+ * double buffering when drawing the canvas.
+ * @param g The Graphics object of the canvas.
+ **/
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+
+ if(!isVisible())
+ return;
+
+ Point viewPoint = getScrollPoint();
+ final int feature_count = getEntryGroup().getAllFeaturesCount();
+
+ // must check size in case new features/entries added
+ if(feature_count*getLineHeight() > getPreferredSize().height)
+ {
+ final int hgt = feature_count * getLineHeight();
+ setPreferredSize(new Dimension(getSize().width*4,hgt));
+ revalidate();
+ }
+
+ if(feature_count != 0)
+ {
+ final int lines_in_view = linesInView()+1;
+ int first_index_in_view = (viewPoint.y/getLineHeight());
+
+ if(first_index_in_view == -1)
+ first_index_in_view = 0;
+
+ int last_index_in_view;
+
+ if(lines_in_view < feature_count - first_index_in_view)
+ last_index_in_view = first_index_in_view + lines_in_view;
+ else
+ last_index_in_view = feature_count - 1;
+
+ final FeatureVector features_in_view =
+ getEntryGroup().getFeaturesInIndexRange(first_index_in_view,
+ last_index_in_view);
+
+ g.setFont(getFont());
+
+ final int features_in_view_size = features_in_view.size();
+ for(int i = 0; i < features_in_view_size; i++)
+ {
+ final Feature this_feature = features_in_view.elementAt(i);
+ final String feature_string = makeFeatureString(this_feature, false);
+ drawFeatureLine(g, this_feature, feature_string);
+ }
+ }
+ }
+
+
+ /**
+ * Return the number of visible text lines on canvas.
+ **/
+ private int linesInView()
+ {
+ return getViewport().getExtentSize().height/getLineHeight();
+ }
+
+ /**
+ * Draw the given Feature at the given line of the list, taking the
+ * selection into account.
+ **/
+ private void drawFeatureLine(final Graphics g,
+ final Feature feature,
+ final String feature_string)
+ {
+ // width of coloured blob at the left of the text
+ final int BOX_WIDTH = getLineHeight();
+ final int y_pos = getEntryGroup().indexOf(feature)*BOX_WIDTH;
+
+ final Color feature_colour = feature.getColour();
+
+ // default colour is white
+ if(feature_colour == null)
+ g.setColor(Color.white);
+ else
+ g.setColor(feature_colour);
+
+ g.fillRect(1, y_pos+1,
+ BOX_WIDTH, BOX_WIDTH - 1);
+
+ g.setColor(Color.black);
+
+ final FeatureVector selected_features = getSelection().getAllFeatures();
+ if(selected_features.contains(feature))
+ {
+ // draw in reverse
+ g.fillRect(BOX_WIDTH + 4, y_pos,
+ getSize().width + getScrollPoint().x,
+ getLineHeight());
+ g.setColor(background_colour);
+ }
+
+ if( feature.getEmblFeature() instanceof GFFStreamFeature &&
+ !getSelection().contains(feature) &&
+ !((GFFStreamFeature)feature.getEmblFeature()).isVisible() )
+ {
+ //
+ // use gray for the key if the feature is NOT visible
+ g.setColor(Color.gray);
+ int ind = feature_string.indexOf(' ');
+ final String keyString = feature_string.substring(0, ind);
+ g.drawString(keyString,
+ BOX_WIDTH + 5,
+ y_pos + getFontAscent());
+
+ g.setColor(Color.black);
+ g.drawString(feature_string.substring(ind),
+ BOX_WIDTH + 5 + getFontMetrics(getFont()).stringWidth(keyString),
+ y_pos + getFontAscent());
+ }
+ else
+ g.drawString(feature_string,
+ BOX_WIDTH + 5,
+ y_pos + getFontAscent());
+ }
+
+ /**
+ * Return a String object suitable for displaying in the list of features.
+ * @param dont_truncate if true the gene name / key field won't be
+ * truncated if it is longer than the field width
+ **/
+ private String makeFeatureString(final Feature feature,
+ final boolean dont_truncate)
+ {
+ String key_string;
+ final int KEY_FIELD_WIDTH = 15;
+
+ if(show_gene_names)
+ {
+ key_string = feature.getGeneName();
+
+ if(key_string == null)
+ key_string = feature.getSystematicName();
+
+ if(key_string.length() > KEY_FIELD_WIDTH && !dont_truncate)
+ key_string = key_string.substring(0, KEY_FIELD_WIDTH);
+ }
+ else if(show_systematic_names)
+ {
+ key_string = feature.getSystematicName();
+
+ if(key_string.length() > KEY_FIELD_WIDTH && !dont_truncate)
+ key_string = key_string.substring(0, KEY_FIELD_WIDTH);
+ }
+ else
+ key_string = feature.getKey().toString();
+
+ final Marker low_marker = feature.getFirstBaseMarker();
+ final Marker high_marker = feature.getLastBaseMarker();
+
+ final StringBuffer description_string_buffer = new StringBuffer();
+
+ if(user_defined_qualifier != null && !user_defined_qualifier.equals(""))
+ {
+ try
+ {
+ final StringVector sv = StringVector.getStrings(user_defined_qualifier);
+ for(int i=0; i<sv.size(); i++)
+ {
+ final Qualifier q = feature.getQualifierByName((String)sv.get(i));
+ if(q != null)
+ {
+ final StringVector values = q.getValues();
+ if(values != null)
+ {
+ if(values.size() == 0)
+ description_string_buffer.append("/"+sv.get(i)+" ");
+ else
+ for(int j=0; j<values.size(); j++) // show multiple values
+ description_string_buffer.append("/"+sv.get(i)+
+ (values.get(j) == null ? "" : "="+values.get(j))+" ");
+ }
+ else
+ description_string_buffer.append("/"+sv.get(i)+" ");
+ }
+ }
+ }
+ catch(InvalidRelationException ire){}
+ }
+ else if(show_products)
+ {
+ final String product_string = feature.getProductString();
+
+ if(product_string == null)
+ {
+ // description is not blank
+ if(feature.isCDS())
+ description_string_buffer.append("[no /product]");
+ }
+ else
+ description_string_buffer.append(product_string);
+ }
+ else
+ {
+ String note = null;
+ if(feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ try
+ {
+ if(feature.getValueOfQualifier("isObsolete") != null &&
+ feature.getValueOfQualifier("isObsolete").equals("true"))
+ note = "obsolete";
+ else
+ note = feature.getValueOfQualifier("comment");
+ }
+ catch(InvalidRelationException e){}
+ }
+
+ if(note == null)
+ note = feature.getNote();
+
+ if(note != null && note.length() != 0)
+ {
+ final int QUALIFIER_COLUMN = 10;
+
+ final String note_string =
+ padRightWithSpaces(note, QUALIFIER_COLUMN);
+
+ description_string_buffer.append(note_string);
+ description_string_buffer.append(" ");
+ }
+
+ if(show_qualifiers)
+ description_string_buffer.append(getQualifierString(feature));
+ }
+
+
+ String low_pos;
+ String high_pos;
+ if(low_marker == null || high_marker == null)
+ {
+ low_pos = "unknown";
+ high_pos = "unknown";
+ }
+ else
+ {
+ if(low_marker.getRawPosition() < high_marker.getRawPosition())
+ {
+ low_pos = String.valueOf(low_marker.getRawPosition());
+ high_pos = String.valueOf(high_marker.getRawPosition());
+ }
+ else
+ {
+ low_pos = String.valueOf(high_marker.getRawPosition());
+ high_pos = String.valueOf(low_marker.getRawPosition());
+ }
+ }
+
+ if(feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ try
+ {
+ if(feature.getQualifierByName("Start_range") != null)
+ low_pos = "<"+low_pos;
+ if(feature.getQualifierByName("End_range") != null)
+ high_pos = ">"+high_pos;
+ }
+ catch (InvalidRelationException e){}
+ }
+ else
+ {
+ if(feature.getLocation().isPartial(true)) // 5prime
+ {
+ if(feature.isForwardFeature())
+ low_pos = "<"+low_pos;
+ else
+ high_pos = ">"+high_pos;
+ }
+ if(feature.getLocation().isPartial(false)) // 3prime
+ {
+ if(feature.isForwardFeature())
+ high_pos = ">"+high_pos;
+ else
+ low_pos = "<"+low_pos;
+ }
+ }
+
+ StringBuffer new_list_line = new StringBuffer();
+
+ new_list_line.append(padRightWithSpaces(key_string, KEY_FIELD_WIDTH));
+ new_list_line.append(" ");
+
+ new_list_line.append(padLeftWithSpaces(low_pos, max_base_pos_width));
+ new_list_line.append(" ");
+ new_list_line.append(padLeftWithSpaces(high_pos, max_base_pos_width));
+ new_list_line.append(" ");
+
+ if(feature.isForwardFeature())
+ new_list_line.append(" ");
+ else
+ new_list_line.append("c ");
+
+ if(show_correlation_scores)
+ {
+ if(feature.isCDS() ||
+ feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL))
+ {
+ new_list_line.append(getScoresString(feature));
+ new_list_line.append(" ");
+ }
+ else
+ {
+ new_list_line.append(" ");
+ if(getBasePlotGroup().getCodonUsageAlgorithm() != null)
+ new_list_line.append(" ");
+ }
+ }
+
+ new_list_line.append(description_string_buffer.toString());
+
+ return new_list_line.toString();
+ }
+
+
+ /**
+ * Return a String containing the given Qualifier and it's values (in EMBL
+ * format).
+ * @param start_index ignore the values before this index
+ **/
+ private String formatQualifier(final String qualifier_name,
+ final Feature feature,
+ final int start_index)
+ {
+ final StringBuffer buffer = new StringBuffer();
+
+ try
+ {
+ final Qualifier qualifier = feature.getQualifierByName(qualifier_name);
+
+ if(qualifier != null)
+ {
+ final EntryInformation entry_information =
+ feature.getEntry().getEntryInformation();
+
+ final QualifierInfo qualifier_info =
+ entry_information.getQualifierInfo(qualifier_name);
+
+ final StringVector qualifier_strings =
+ StreamQualifier.toStringVector(qualifier_info,
+ qualifier);
+
+ final int qualifier_strings_size = qualifier_strings.size();
+ for(int i = start_index; i < qualifier_strings_size; ++i)
+ {
+ final String qualifier_string = (String)qualifier_strings.elementAt(i);
+ buffer.append(qualifier_string + " ");
+ }
+ }
+ }
+ catch(InvalidRelationException e) {}
+
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String containing all the qualifiers of the given Feature
+ * (except /note) in EMBL format. Any /similarity qualifier will come
+ * first.
+ **/
+ private String getQualifierString(final Feature feature)
+ {
+ final StringBuffer buffer = new StringBuffer();
+
+ final QualifierVector qualifiers = feature.getQualifiers();
+
+ // if there is a /note and it has more than one value put it next (without
+ // the first value)
+ final Qualifier note_qualifier =
+ qualifiers.getQualifierByName("note");
+
+ if(note_qualifier != null && note_qualifier.getValues().size() > 1)
+ {
+ buffer.append(formatQualifier("note", feature, 1));
+ buffer.append(" ");
+ }
+
+ // put /similarity before all but the /note qualifier
+ final Qualifier similarity_qualifier =
+ qualifiers.getQualifierByName("similarity");
+
+ if(similarity_qualifier != null)
+ {
+ buffer.append(formatQualifier("similarity", feature, 0));
+ buffer.append(" ");
+ }
+
+ final int qualifiers_size = qualifiers.size();
+ for(int i = 0 ; i < qualifiers_size; ++i)
+ {
+ final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(i);
+ final String this_qualifier_name = this_qualifier.getName();
+
+ if(!this_qualifier_name.equals("note") &&
+ !this_qualifier_name.equals("similarity"))
+ {
+ buffer.append(formatQualifier(this_qualifier_name, feature, 0));
+ buffer.append(" ");
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String containing the correlation scores.
+ **/
+ private String getScoresString(final Feature feature)
+ {
+ //final int base_total = feature.getTranslationBases().length();
+ final int c_total = feature.getBaseCount(Bases.getIndexOfBase('c'));
+ final int g_total = feature.getBaseCount(Bases.getIndexOfBase('g'));
+
+ final int g1_count =
+ feature.getPositionalBaseCount(0, Bases.getIndexOfBase('g'));
+
+ final int c3_count =
+ feature.getPositionalBaseCount(2, Bases.getIndexOfBase('c'));
+ final int g3_count =
+ feature.getPositionalBaseCount(2, Bases.getIndexOfBase('g'));
+
+ final double c3_score = 100.0 * (3 * c3_count - c_total) / c_total;
+ final double g1_score = 100.0 * (3 * g1_count - g_total) / g_total;
+ final double g3_score = 100.0 * (3 * g3_count - g_total) / g_total;
+
+ final double cor1_2_score = feature.get12CorrelationScore();
+
+ final NumberFormat number_format = NumberFormat.getNumberInstance();
+
+ number_format.setMaximumFractionDigits(1);
+ number_format.setMinimumFractionDigits(1);
+
+ final String cor1_2_score_string = number_format.format(cor1_2_score);
+ final String c3_score_string;
+ final String g1_score_string;
+ final String g3_score_string;
+
+ if(c_total == 0)
+ c3_score_string = "ALL";
+ else
+ c3_score_string = number_format.format(c3_score);
+
+ if(g_total == 0)
+ g1_score_string = "ALL";
+ else
+ g1_score_string = number_format.format(g1_score);
+
+ if(g_total == 0)
+ g3_score_string = "ALL";
+ else
+ g3_score_string = number_format.format(g3_score);
+
+ String codon_usage_score_string = "";
+
+ final CodonUsageAlgorithm codon_usage_alg =
+ getBasePlotGroup().getCodonUsageAlgorithm();
+
+ if(codon_usage_alg != null)
+ {
+ number_format.setMaximumFractionDigits(3);
+ number_format.setMinimumFractionDigits(3);
+
+ codon_usage_score_string =
+ number_format.format(codon_usage_alg.getFeatureScore(feature)) + " ";
+ }
+
+ return codon_usage_score_string +
+ padRightWithSpaces(cor1_2_score_string, 5) + " " +
+ padRightWithSpaces(c3_score_string, 5) + " " +
+ padRightWithSpaces(g1_score_string, 5) + " " +
+ padRightWithSpaces(g3_score_string, 5);
+ }
+
+ /**
+ * Return the given string padded with spaces to the given width. The
+ * spaces are added on the right of the string.
+ **/
+ private String padRightWithSpaces(final String string, final int width)
+ {
+ final int len = string.length();
+ if(len == width)
+ return string;
+
+ final StringBuffer buffer = new StringBuffer(string);
+ for(int i = 0 ; i < width - len; ++i)
+ buffer.append(' ');
+
+ return buffer.toString();
+ }
+
+ /**
+ * Return the given string padded with spaces to the given width. The
+ * spaces are added on the left of the string.
+ **/
+ private String padLeftWithSpaces(final String string, final int width)
+ {
+ final int len = string.length();
+ if(len == width)
+ return string;
+
+ final StringBuffer buffer = new StringBuffer();
+
+ for(int i = 0; i < width - len; ++i)
+ buffer.append(' ');
+
+ buffer.append(string);
+ return buffer.toString();
+ }
+
+ /**
+ * Return the height each line of the display should be. Each feature will
+ * be drawn into one line.
+ **/
+ protected int getLineHeight()
+ {
+ return getFontAscent() + 2;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/FeatureListFrame.java b/uk/ac/sanger/artemis/components/FeatureListFrame.java
new file mode 100644
index 0000000..f72ba08
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeatureListFrame.java
@@ -0,0 +1,206 @@
+/* FeatureListFrame.java
+ *
+ * created: Fri Sep 3 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999,2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureListFrame.java,v 1.3 2005-12-09 16:17:13 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+ * A JFrame that contains a FeatureList component and an Close button.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureListFrame.java,v 1.3 2005-12-09 16:17:13 tjc Exp $
+ **/
+
+public class FeatureListFrame extends JFrame
+ implements EntryGroupChangeListener
+{
+ /**
+ * Create a new FeatureListFrame component. The constructor does not call
+ * setVisible (true).
+ * @param title The title to use for the new JFrame.
+ * @param feature_list The FeatureList to show.
+ * @param selection The Selection that the commands in the menus will
+ * operate on.
+ * @param entry_group The EntryGroup object where new features/entries will
+ * be added.
+ * @param goto_event_source The object that the menu item will call
+ * makeBaseVisible() on.
+ **/
+ public FeatureListFrame(final String title,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group)
+ {
+ super(title);
+
+ this.entry_group = entry_group;
+
+ feature_list = new FeatureList(entry_group, selection, goto_event_source,
+ base_plot_group);
+
+ final Font default_font = Options.getOptions().getFont();
+ setFont(default_font);
+
+ final JMenuBar menu_bar = new JMenuBar();
+ menu_bar.setFont(default_font);
+ setJMenuBar(menu_bar);
+
+ final JMenu file_menu = new JMenu("File");
+ final JMenuItem close = new JMenuItem("Close");
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ setVisible(false);
+ FeatureListFrame.this.dispose();
+ feature_list.stopListening();
+ }
+ });
+
+ file_menu.add(close);
+ menu_bar.add(file_menu);
+
+ final JMenu select_menu =
+ new SelectMenu(this, selection, goto_event_source, entry_group,
+ base_plot_group);
+ menu_bar.add(select_menu);
+
+ final JMenu view_menu =
+ new ViewMenu(this, selection, goto_event_source, entry_group,
+ base_plot_group);
+ menu_bar.add(view_menu);
+
+ final JMenu goto_menu =
+ new GotoMenu(this, selection, goto_event_source, entry_group);
+ menu_bar.add(goto_menu);
+
+ if(Options.readWritePossible())
+ {
+ final JMenu edit_menu =
+ new EditMenu(this, selection, goto_event_source, entry_group,
+ base_plot_group, null);
+ menu_bar.add(edit_menu);
+
+ final JMenu write_menu = new WriteMenu(this, selection, entry_group);
+ menu_bar.add(write_menu);
+
+ final JMenu run_menu = new RunMenu(this, selection);
+ menu_bar.add(run_menu);
+ }
+
+ final JScrollPane jsp_feature_list = new JScrollPane(feature_list);
+ getContentPane().add(jsp_feature_list, "Center");
+
+ final JPanel panel = new JPanel();
+
+ final JButton close_button = new JButton("Close");
+
+ panel.add(close_button);
+ close_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setVisible(false);
+ FeatureListFrame.this.dispose();
+ feature_list.stopListening();
+ }
+ });
+
+ getContentPane().add(panel, "South");
+ pack();
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ setVisible(false);
+ entry_group.removeEntryGroupChangeListener(FeatureListFrame.this);
+ FeatureListFrame.this.dispose();
+ feature_list.stopListening();
+ }
+ });
+
+ entry_group.addEntryGroupChangeListener(this);
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ int screen_height = screen.height;
+ int screen_width = screen.width;
+
+ if(screen_width <= 700 || screen_height <= 400)
+ setSize(screen_width * 9 / 10, screen_height * 9 / 10);
+ else
+ setSize(700, 400);
+
+ final int hgt = entry_group.getAllFeaturesCount() *
+ feature_list.getLineHeight();
+ feature_list.setPreferredSize(new Dimension(getSize().width*4,hgt));
+ jsp_feature_list.getVerticalScrollBar().setUnitIncrement(feature_list.getLineHeight());
+
+ setLocation(new Point((screen.width - getSize().width) / 2,
+ (screen.height - getSize().height) / 2));
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can get rid of the Navigator when the
+ * EntryGroup is no longer in use (for example when the EntryEdit is
+ * closed).
+ **/
+ public void entryGroupChanged(final EntryGroupChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryGroupChangeEvent.DONE_GONE:
+ entry_group.removeEntryGroupChangeListener(this);
+ dispose();
+ break;
+ }
+ }
+
+ /**
+ * Return the FeatureList that this JFrame is displaying.
+ **/
+ public FeatureList getFeatureList()
+ {
+ return feature_list;
+ }
+
+ /**
+ * The FeatureList that this JFrame is displaying.
+ **/
+ private FeatureList feature_list;
+
+ /**
+ * The EntryGroup that was passed to the constructor.
+ **/
+ private EntryGroup entry_group;
+}
diff --git a/uk/ac/sanger/artemis/components/FeaturePlot.java b/uk/ac/sanger/artemis/components/FeaturePlot.java
new file mode 100644
index 0000000..6d3425b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeaturePlot.java
@@ -0,0 +1,393 @@
+/* FeaturePlot.java
+ *
+ * created: Wed Dec 16 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeaturePlot.java,v 1.11 2009-07-20 15:11:17 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.plot.FeatureAlgorithm;
+import uk.ac.sanger.artemis.plot.LineAttributes;
+import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
+
+
+/**
+ * The components of this class display a plot of a FeatureAlgorithm for a
+ * particular feature.
+ * @author Kim Rutherford
+ **/
+
+public class FeaturePlot extends Plot
+ implements DisplayAdjustmentListener, FeatureChangeListener {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new FeatureDisplay object.
+ * @param algorithm The object that will generate the values we plot in
+ * this component.
+ **/
+ public FeaturePlot (FeatureAlgorithm algorithm) {
+ super (algorithm, true); // true means draw a scale at the bottom of
+ // the graph
+
+ getFeature ().addFeatureChangeListener (this);
+
+ recalculate_flag = true;
+ }
+
+ /**
+ * Used by getPreferredSize () and getMinimumSize ();
+ **/
+ protected static int HEIGHT;
+
+ static {
+ final Integer feature_plot_height =
+ Options.getOptions ().getIntegerProperty ("feature_plot_height");
+
+ if (feature_plot_height == null) {
+ HEIGHT = 160;
+ } else {
+ HEIGHT = feature_plot_height.intValue ();
+ }
+ }
+
+ /**
+ * Overridden to set the component height to 150.
+ **/
+ public Dimension getPreferredSize() {
+ return (new Dimension(getSize ().width, HEIGHT));
+ }
+
+ /**
+ * Overridden to set the component height to 150.
+ **/
+ public Dimension getMinimumSize() {
+ return (new Dimension(getSize ().width, HEIGHT));
+ }
+
+ /**
+ * Remove this object as a feature change listener. (Called by
+ * FeaturePlotGroup)
+ **/
+ void stopListening () {
+ getFeature ().removeFeatureChangeListener (this);
+ }
+
+ /**
+ * Implementation of the DisplayAdjustmentListener interface. Invoked when
+ * a component scrolls or changes the scale.
+ **/
+ public void displayAdjustmentValueChanged (DisplayAdjustmentEvent event) {
+ start_base = event.getStart ();
+ end_base = event.getEnd ();
+ //width_in_bases = event.getWidthInBases ();
+
+ recalculate_flag = true;
+ repaint();
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface.
+ * @param event The change event.
+ **/
+ public void featureChanged (FeatureChangeEvent event) {
+ recalculate_flag = true;
+ }
+
+ /**
+ * Return the algorithm that was passed to the constructor.
+ **/
+ public FeatureAlgorithm getFeatureAlgorithm () {
+ return (FeatureAlgorithm) super.getAlgorithm ();
+ }
+
+ /**
+ * Return the new start base to display, from the last event.
+ **/
+ private int getStart () {
+ return start_base;
+ }
+
+ /**
+ * Return the new end base to display, from the last event.
+ **/
+ private int getEnd () {
+ return end_base;
+ }
+
+ /**
+ * This array is used by drawGraph (). It is reallocated when the scale
+ * changes.
+ **/
+ private float [][] value_array_array = null;
+
+ /**
+ * The number of bases to step before each evaluation of the algorithm.
+ * (Set by recalculateValues ()).
+ **/
+ private int step_size = 0;
+
+ /**
+ * The maximum of the values in value_array_array.
+ **/
+ private float min_value = Float.MAX_VALUE;
+
+ /**
+ * The minimum of the values in value_array_array.
+ **/
+ private float max_value = Float.MIN_VALUE;
+
+ /**
+ * Recalculate the values in value_array_array, step_size, min_value and
+ * max_value.
+ **/
+ protected void recalculateValues () {
+ final Float algorithm_minimum = getAlgorithm ().getMinimum ();
+ final Float algorithm_maximum = getAlgorithm ().getMaximum ();
+
+ // use the Algorithm specified maximum if there is one - otherwise
+ // calculate it
+ if (algorithm_maximum == null) {
+ max_value = Float.MIN_VALUE;
+ } else {
+ max_value = algorithm_maximum.floatValue ();
+ }
+
+ // use the Algorithm specified minimum if there is one - otherwise
+ // calculate it
+ if (algorithm_minimum == null) {
+ min_value = Float.MAX_VALUE;
+ } else {
+ min_value = algorithm_minimum.floatValue ();
+ }
+
+ final int window_size = getWindowSize ();
+
+ final Integer default_step_size =
+ getAlgorithm ().getDefaultStepSize (window_size);
+
+ if (default_step_size == null) {
+ step_size = window_size;
+ } else {
+ if (default_step_size.intValue () < window_size) {
+ step_size = default_step_size.intValue ();
+ } else {
+ step_size = window_size;
+ }
+ }
+
+ final int unit_count = getEnd () - getStart ();
+
+ // the number of plot points in the graph
+ final int number_of_values =
+ (unit_count - (window_size - step_size)) / step_size;
+
+ if (number_of_values < 2) {
+ // there is nothing to plot
+ value_array_array = null;
+ return;
+ }
+
+ // the number of values that getValues () will return
+ final int get_values_return_count =
+ getFeatureAlgorithm ().getValueCount ();
+
+ if (value_array_array == null) {
+ value_array_array = new float [get_values_return_count][];
+ }
+
+ if (value_array_array[0] == null ||
+ value_array_array[0].length != number_of_values) {
+ for (int i = 0 ; i < value_array_array.length ; ++i) {
+ value_array_array[i] = new float [number_of_values];
+ }
+ } else {
+ // reuse the previous arrays
+ }
+
+ if (!isVisible ()) {
+ return;
+ }
+
+ float [] temp_values = new float [get_values_return_count];
+
+ for (int i = 0 ; i < number_of_values ; ++i) {
+ getFeatureAlgorithm ().getValues (getStart () + i * step_size,
+ getStart () + i * step_size +
+ window_size - 1,
+ temp_values);
+
+ for (int value_index = 0 ;
+ value_index < get_values_return_count ;
+ ++value_index) {
+ final float current_value = temp_values[value_index];
+
+ value_array_array[value_index][i] = current_value;
+
+ // use the Algorithm specified maximum if there is one - otherwise
+ // calculate it
+ if (algorithm_maximum == null) {
+ if (current_value > max_value) {
+ max_value = current_value;
+ }
+ }
+
+ // use the Algorithm specified minimum if there is one - otherwise
+ // calculate it
+ if (algorithm_minimum == null) {
+ if (current_value < min_value) {
+ min_value = current_value;
+ }
+ }
+ }
+ }
+
+ recalculate_flag = false;
+ }
+
+ /**
+ * Redraw the graph on the canvas using the algorithm, start_base and
+ * end_base. This method plots BaseWindowAlgorithm objects only.
+ * @param g The object to draw into.
+ **/
+ protected int drawMultiValueGraph (Graphics g, LineAttributes[] lines) {
+ if (recalculate_flag) {
+ recalculateValues ();
+ }
+
+ if (value_array_array == null) {
+ // there is nothing to draw - probably because the sequence is too short
+ drawMinMax (g, 0, 1);
+
+ return 0;
+ }
+
+ final int window_size = getWindowSize ();
+
+ // the number of values to plot at each x position
+ final int get_values_return_count =
+ getFeatureAlgorithm ().getValueCount ();
+
+ // the number of x positions to plot points at
+ final int number_of_values = value_array_array[0].length;
+
+ if (number_of_values > 1) {
+ drawGlobalAverage (g, min_value, max_value);
+ }
+
+ for (int value_index = 0 ;
+ value_index < get_values_return_count ;
+ ++value_index) {
+ if (get_values_return_count == 1) {
+ g.setColor (Color.black);
+ } else {
+ switch (value_index) {
+ case 0:
+ g.setColor (new Color (255, 0, 0));
+ break;
+ case 1:
+ g.setColor (new Color (100, 255, 100));
+ break;
+ case 2:
+ g.setColor (new Color (0, 0, 255));
+ break;
+ default:
+ g.setColor (Color.black);
+ }
+ }
+
+ drawPoints (g, min_value, max_value, step_size, window_size,
+ getSize ().width,
+ 0, // no offset.
+ value_array_array[value_index], value_index,
+ get_values_return_count, false, false);
+ }
+
+ drawMinMax (g, min_value, max_value);
+
+ drawScaleLine (g, getStart (), getEnd ());
+
+ final int cross_hair_position = getCrossHairPosition ();
+
+ if (cross_hair_position >= 0) {
+ if (cross_hair_position >= getTranslation ().length ()) {
+ cancelCrossHairs ();
+ } else {
+ drawCrossHair (g, cross_hair_position,
+ String.valueOf (getPointPosition (cross_hair_position +
+ getStart () - 1)),
+ 0);
+ }
+ }
+
+ return get_values_return_count;
+ }
+
+ /**
+ * Get the position in the Feature of the given canvas x position. This
+ * amino acid position is the label used when the user clicks the mouse in
+ * on the canvas (see drawCrossHair ()).
+ **/
+ protected int getPointPosition (final int canvas_x_position) {
+ return canvas_x_position + 1;
+ }
+
+ /**
+ * Return the feature that this component is plotting.
+ **/
+ private Feature getFeature () {
+ return getFeatureAlgorithm ().getFeature ();
+ }
+
+ /**
+ * Return the translation of the bases of the feature we are plotting.
+ **/
+ private AminoAcidSequence getTranslation () {
+ return getFeature ().getTranslation ();
+ }
+
+ /**
+ * The start base to plot, as obtained from the DisplayAdjustmentEvent.
+ **/
+ private int start_base;
+
+ /**
+ * The end base to plot, as obtained from the DisplayAdjustmentEvent.
+ **/
+ private int end_base;
+
+ protected void calculateFeatures(boolean fromPeak)
+ {
+ // TODO Auto-generated method stub
+ }
+
+ protected void showAveragesForRange() {}
+}
diff --git a/uk/ac/sanger/artemis/components/FeaturePlotGroup.java b/uk/ac/sanger/artemis/components/FeaturePlotGroup.java
new file mode 100644
index 0000000..80dff7e
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeaturePlotGroup.java
@@ -0,0 +1,337 @@
+/* FeaturePlotGroup.java
+ *
+ * created: Wed Dec 16 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeaturePlotGroup.java,v 1.2 2004-12-03 18:11:28 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.plot.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+
+import javax.swing.*;
+
+/**
+ * This is a super-component containing several FeaturePlot components, each
+ * of which can toggled off and on. The component contains a row of toggle
+ * buttons and then the FeaturePlot components below.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeaturePlotGroup.java,v 1.2 2004-12-03 18:11:28 tjc Exp $
+ **/
+
+public class FeaturePlotGroup extends JFrame
+ implements EntryChangeListener, FeatureChangeListener {
+ /**
+ * Create a new FeaturePlotGroup component for the given feature.
+ **/
+ public FeaturePlotGroup (Feature feature) {
+ super ("Graphs for: " + feature.getIDString ());
+ this.feature = feature;
+ this.entry = feature.getEntry ();
+
+ // don't repeat an algorithm in this array.
+ final FeatureAlgorithm [] plot_value_producers = {
+ new HydrophobicityAlgorithm (getFeature ()),
+ new HydrophilicityAlgorithm (getFeature ()),
+ new CoilFeatureAlgorithm (getFeature ())
+ };
+
+ final Font font = Options.getOptions ().getFont ();
+
+ setFont (font);
+
+ GridBagLayout gridbag = new GridBagLayout();
+ getContentPane ().setLayout (gridbag);
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.anchor = GridBagConstraints.NORTH;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.gridheight = 1;
+ c.weightx = 1;
+ c.fill = GridBagConstraints.BOTH;
+ c.weighty = 1;
+// c.insets = new Insets (0,0,5,0);
+
+ for (int i = 0 ; i < plot_value_producers.length ; ++i) {
+ final FeatureAlgorithm this_algorithm = plot_value_producers[i];
+
+ final FeaturePlot new_feature_plot = new FeaturePlot (this_algorithm);
+
+ gridbag.setConstraints (new_feature_plot, c);
+ getContentPane ().add (new_feature_plot);
+ new_feature_plot.setVisible (true);
+ }
+
+ getFeature ().getEntry ().addEntryChangeListener (this);
+ getFeature ().addFeatureChangeListener (this);
+
+ addWindowListener (new WindowAdapter () {
+ public void windowClosing (WindowEvent event) {
+ stopListening ();
+ FeaturePlotGroup.this.dispose ();
+ }
+ });
+
+ addComponentListener (new ComponentAdapter () {
+ public void componentResized (ComponentEvent event) {
+ fixScrollbar ();
+ fireAdjustmentEvent (scrollbar.getValue ());
+ }
+ });
+
+ int new_x_size = feature.getTranslation ().length () + SCROLL_BAR_SIZE;
+
+ if (new_x_size > 1000) {
+ new_x_size = 1000;
+ }
+
+ if (new_x_size < 300) {
+ new_x_size = 300;
+ }
+
+ scrollbar = new JScrollBar (Scrollbar.HORIZONTAL);
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weighty = 0;
+ gridbag.setConstraints (scrollbar, c);
+ scrollbar.addAdjustmentListener (new AdjustmentListener () {
+ public void adjustmentValueChanged(AdjustmentEvent e) {
+ fireAdjustmentEvent (e.getValue ());
+ }
+ });
+ getContentPane ().add (scrollbar);
+
+ final Component [] children = getContentPane ().getComponents ();
+
+ for (int i = 0 ; i < children.length ; ++i) {
+ if (children[i] instanceof FeaturePlot) {
+ addDisplayAdjustmentListener ((FeaturePlot)children[i]);
+ }
+ }
+
+
+ bottom_button_panel = new JPanel ();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weighty = 0;
+ gridbag.setConstraints (bottom_button_panel, c);
+ getContentPane ().add (bottom_button_panel);
+
+ close_button = new JButton ("Close");
+ close_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ stopListening ();
+ FeaturePlotGroup.this.dispose ();
+ }
+ });
+
+ bottom_button_panel.add (close_button);
+
+ pack ();
+
+ // give each FeaturePlot component a height of 200
+ setSize (new_x_size, getSize ().height);
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+
+ setVisible(true);
+
+ fixScrollbar ();
+
+ fireAdjustmentEvent (1);
+ }
+
+ /**
+ * Fudge factor to take the scroll bar width into account when creating
+ * windows and scrollbars.
+ **/
+ private final int SCROLL_BAR_SIZE = 100;
+
+ /**
+ * Remove this object as a entry change listener and then call
+ * stopListening () on each FeaturePlot component.
+ **/
+ private void stopListening () {
+ getEntry ().removeEntryChangeListener (this);
+
+ final Component [] children = getComponents ();
+
+ for (int i = 0 ; i < children.length ; ++i) {
+ if (children[i] instanceof FeaturePlot) {
+ ((FeaturePlot)children[i]).stopListening ();
+ }
+ }
+
+ getFeature ().getEntry ().removeEntryChangeListener (this);
+ getFeature ().removeFeatureChangeListener (this);
+ }
+
+ /**
+ * Set the scrollbar maximum, minimum and value.
+ **/
+ private void fixScrollbar () {
+ final int feature_length = getFeature ().getTranslation ().length ();
+
+ int scroll_max = feature_length;
+
+ if (scroll_max < 1) {
+ scroll_max = 1;
+ }
+
+ scrollbar.setValues (1, getSize ().width,
+ 1, scroll_max + SCROLL_BAR_SIZE);
+
+ scrollbar.setBlockIncrement (getSize ().width);
+ }
+
+ /**
+ * Adds the specified event adjustemnt listener to receive adjustment
+ * change events from this object.
+ * @param l the event change listener.
+ **/
+ private void addDisplayAdjustmentListener (DisplayAdjustmentListener l) {
+ adjustment_listener_list.addElement (l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * adjustment change events from this object.
+ * @param l the event change listener.
+ **/
+ private void removeDisplayAdjustmentListener (DisplayAdjustmentListener l) {
+ adjustment_listener_list.removeElement (l);
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface.
+ * @param event The change event.
+ **/
+ public void featureChanged (FeatureChangeEvent event) {
+ fixScrollbar ();
+ fireAdjustmentEvent (scrollbar.getValue ());
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so we can delete this component if the feature gets
+ * deleted.
+ **/
+ public void entryChanged (EntryChangeEvent event) {
+ switch (event.getType ()) {
+ case EntryChangeEvent.FEATURE_DELETED:
+ if (event.getFeature () == getFeature ()) {
+ stopListening ();
+ dispose ();
+ }
+ break;
+ default:
+ // do nothing;
+ break;
+ }
+ }
+
+ /**
+ * Send a DisplayAdjustmentEvent to the objects that are listening for it.
+ **/
+ private void fireAdjustmentEvent (final int scroll_value) {
+ final int feature_length = getFeature ().getTranslation ().length ();
+
+ int end_value = scroll_value + getSize ().width;
+
+ if (end_value > feature_length) {
+ end_value = feature_length;
+ }
+
+ final DisplayAdjustmentEvent event =
+ new DisplayAdjustmentEvent (this,
+ scroll_value,
+ end_value,
+ getSize ().width,
+ 1,
+ 0,// this arg will be ignored
+ false,
+ DisplayAdjustmentEvent.SCALE_ADJUST_EVENT);
+
+ final Vector targets;
+
+ // copied from a book - synchronising the whole method might cause a
+ // deadlock
+ synchronized (this) {
+ targets = (Vector) adjustment_listener_list.clone ();
+ }
+
+ for ( int i = 0 ; i < targets.size () ; ++i ) {
+ DisplayAdjustmentListener target =
+ (DisplayAdjustmentListener) targets.elementAt (i);
+
+ target.displayAdjustmentValueChanged (event);
+ }
+ }
+
+ /**
+ * Return the feature that this component is plotting.
+ **/
+ private Feature getFeature () {
+ return feature;
+ }
+
+ /**
+ * Return the Entry that contains the Feature this object is displaying.
+ **/
+ private Entry getEntry () {
+ return entry;
+ }
+
+ /**
+ * The feature we are plotting.
+ **/
+ private Feature feature;
+
+ /**
+ * The Entry that contains the Feature this object is displaying.
+ **/
+ private Entry entry;
+
+ /**
+ * Pressing this button will distroy the JFrame.
+ **/
+ private JButton close_button;
+
+ /**
+ * A JPanel to hold the buttons.
+ **/
+ private JPanel bottom_button_panel;
+
+ private JScrollBar scrollbar;
+
+ /**
+ * A vector of those objects listening for adjustment events.
+ **/
+ final private Vector adjustment_listener_list = new Vector ();
+}
diff --git a/uk/ac/sanger/artemis/components/FeaturePopup.java b/uk/ac/sanger/artemis/components/FeaturePopup.java
new file mode 100644
index 0000000..0ce2a17
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeaturePopup.java
@@ -0,0 +1,970 @@
+/* FeaturePopup.java
+ *
+ * created: Wed Oct 21 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeaturePopup.java,v 1.22 2008-01-17 09:57:06 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.util.StringVector;
+
+import java.io.*;
+import java.awt.Component;
+import java.awt.BorderLayout;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+/**
+ * FeaturePopup class
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeaturePopup.java,v 1.22 2008-01-17 09:57:06 tjc Exp $
+ *
+ **/
+
+public class FeaturePopup extends JPopupMenu
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The reference of the EntryGroup object that was passed to the
+ * constructor.
+ **/
+ private EntryGroup entry_group;
+
+ /**
+ * This is the Selection object that was passed to the constructor.
+ **/
+ final private Selection selection;
+
+ /**
+ * This is a reference to the GotoEventSource object that was passed to the
+ * constructor.
+ **/
+ private GotoEventSource goto_event_source;
+
+ /**
+ * The reference of the object that created this popup.
+ **/
+ private DisplayComponent owner;
+
+ /**
+ * If the parent component of this popup is a FeatureDisplay then this will
+ * contain it's reference.
+ **/
+ private FeatureDisplay feature_display = null;
+
+ /**
+ * If the parent component of this popup is a FeatureList then this will
+ * contain it's reference.
+ **/
+ private FeatureList feature_list = null;
+
+ /**
+ * Set by the constructor to be the(possibly) empty vector of selected
+ * features.
+ **/
+ private FeatureVector selection_features;
+
+ /**
+ * Set by the constructor to be the(possibly) empty vector of selected
+ * features.
+ **/
+ private FeatureSegmentVector selection_segments;
+ private BasePlotGroup base_plot_group = null;
+ private JMenuItem feature_display_menus[] = null;
+ private int SCORE_MINIMUM = Integer.MAX_VALUE;
+ private int SCORE_MAXIMUM = Integer.MIN_VALUE;
+
+ /**
+ * Create a new FeaturePopup object.
+ * @param owner The component where this popup was popped up from.
+ * @param selection The selection to use for this popup.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ public FeaturePopup(final DisplayComponent owner,
+ final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group)
+ {
+ super(getMenuName(owner));
+
+ this.owner = owner;
+ this.entry_group = entry_group;
+ this.selection = selection;
+ this.goto_event_source = goto_event_source;
+ this.base_plot_group = base_plot_group;
+
+ selection_features = selection.getSelectedFeatures();
+ selection_segments = selection.getSelectedSegments();
+
+ final JMenuItem action_menus[] = makeSubMenus();
+ JMenuItem feature_list_menus[] = null;
+
+ if(owner instanceof FeatureDisplay)
+ {
+ feature_display = (FeatureDisplay)owner;
+ feature_display_menus = addFeatureDisplayItems();
+ for(int i = 0; i<feature_display_menus.length; i++)
+ if(!(feature_display_menus[i] instanceof JCheckBoxMenuItem))
+ maybeAdd(feature_display_menus[i]);
+ }
+ else // must be a FeatureList
+ {
+ feature_list = (FeatureList)owner;
+ feature_list_menus = addFeatureListItems();
+ for(int i=0; i<feature_list_menus.length; i++)
+ if(!(feature_list_menus[i] instanceof JCheckBoxMenuItem))
+ maybeAdd(feature_list_menus[i]);
+ }
+ addSeparator();
+
+
+ final JMenuItem miValidate = new JMenuItem("Validation report...");
+ miValidate.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ FeatureVector features = null;
+ if(selection.getAllFeatures().size() < 1)
+ {
+ int status = JOptionPane.showConfirmDialog(owner.getParentFrame(),
+ "No features selected. Validate all features.", "Select Features",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+ }
+ else
+ features = selection.getAllFeatures();
+ new ValidateViewer(getEntryGroup(), features);
+ }
+ });
+ maybeAdd(miValidate);
+ addSeparator();
+
+
+ for(int i = 0; i<action_menus.length; i++)
+ maybeAdd(action_menus[i]);
+
+ addSeparator();
+ if(owner instanceof FeatureDisplay)
+ {
+ for(int i = 0; i<feature_display_menus.length; i++)
+ if((feature_display_menus[i] instanceof JCheckBoxMenuItem))
+ maybeAdd(feature_display_menus[i]);
+ }
+ else
+ {
+ for(int i=0; i<feature_list_menus.length; i++)
+ if((feature_list_menus[i] instanceof JCheckBoxMenuItem))
+ maybeAdd(feature_list_menus[i]);
+ }
+
+ }
+
+ /**
+ * Rename the name String to use for this JMenu.
+ **/
+ private static String getMenuName(final DisplayComponent owner)
+ {
+ if(owner instanceof FeatureDisplay)
+ return "Feature Viewer JMenu";
+ else
+ return "Feature List JMenu";
+ }
+
+ /**
+ * Add an item only if it isn't null.
+ **/
+ private void maybeAdd(JMenuItem item)
+ {
+ if(item != null)
+ add(item);
+ }
+
+ /**
+ * Create the Edit, Add and View sub menus.
+ **/
+ private JMenuItem[] makeSubMenus()
+ {
+ final JMenuItem[] action_menus = new JMenuItem[8];
+
+ final JFrame frame = owner.getParentFrame();
+ action_menus[0] = new EntryGroupMenu(frame, getEntryGroup());
+
+ AlignmentViewer alignQueryViewer = null;
+ AlignmentViewer alignSubjectViewer = null;
+
+ if(frame instanceof MultiComparator)
+ {
+ MultiComparator mc = (MultiComparator)frame;
+
+ // determine which FeatureDisplay
+ int ifeature_display;
+ for(ifeature_display = 0; ifeature_display < mc.getEntryGroupArray().length;
+ ++ifeature_display)
+ {
+ final EntryGroup this_entry_group = mc.getEntryGroupArray()[ifeature_display];
+ if(this_entry_group == getEntryGroup())
+ break;
+ }
+
+ if(ifeature_display==0)
+ alignQueryViewer = null;
+ else
+ alignQueryViewer = mc.getAlignmentViewerArray()[ifeature_display-1];
+
+ if(ifeature_display == mc.getEntryGroupArray().length-1)
+ alignSubjectViewer = null;
+ else
+ alignSubjectViewer = mc.getAlignmentViewerArray()[ifeature_display];
+
+ action_menus[1] =
+ new SelectMenu(frame, selection,
+ getGotoEventSource(),
+ getEntryGroup(),
+ base_plot_group,
+ alignQueryViewer, alignSubjectViewer,
+ "Select");
+ }
+ else
+ action_menus[1] = new SelectMenu(frame, selection,
+ getGotoEventSource(),
+ getEntryGroup(),
+ base_plot_group);
+
+ action_menus[2] = new ViewMenu(frame, selection,
+ getGotoEventSource(),
+ getEntryGroup(),
+ base_plot_group);
+
+ action_menus[3] = new GotoMenu(frame, selection,
+ getGotoEventSource(),
+ getEntryGroup());
+
+ if(Options.readWritePossible())
+ {
+ action_menus[4] = new EditMenu(frame, selection,
+ getGotoEventSource(),
+ getEntryGroup(),
+ base_plot_group, owner);
+ if(entry_group instanceof SimpleEntryGroup)
+ {
+ if(frame instanceof MultiComparator)
+ action_menus[5] = new AddMenu(frame, selection,
+ getEntryGroup(),
+ getGotoEventSource(),
+ base_plot_group,
+ alignQueryViewer, alignSubjectViewer,
+ "Create");
+ else
+ action_menus[5] = new AddMenu(frame, selection,
+ getEntryGroup(),
+ getGotoEventSource(),
+ base_plot_group);
+ }
+
+ action_menus[6] = new WriteMenu(frame, selection,
+ getEntryGroup());
+ if(Options.isUnixHost())
+ action_menus[7] = new RunMenu(frame, selection);
+ }
+ return action_menus;
+ }
+
+ /**
+ * Create those menu items that are relevant only to FeatureDisplay objects.
+ **/
+ private JMenuItem[] addFeatureDisplayItems()
+ {
+ final JMenuItem[] feature_display_menus;
+
+ final boolean isDatabaseGroup = GeneUtils.isDatabaseEntry( getEntryGroup() );
+ final boolean isGFFGroup = GeneUtils.isGFFEntry( getEntryGroup() );
+
+ if(isDatabaseGroup || isGFFGroup)
+ feature_display_menus = new JMenuItem[22];
+ else
+ feature_display_menus = new JMenuItem[21];
+
+ feature_display_menus[0] = new JCheckBoxMenuItem("Start Codons");
+ ((JCheckBoxMenuItem)feature_display_menus[0]).setState(
+ feature_display.getShowStartCodons());
+ feature_display_menus[0].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ feature_display.setShowStartCodons(
+ ((JCheckBoxMenuItem)feature_display_menus[0]).getState());
+ }
+ });
+
+ feature_display_menus[1] = new JCheckBoxMenuItem("Stop Codons");
+ ((JCheckBoxMenuItem)feature_display_menus[1]).setState(
+ feature_display.getShowStopCodons());
+ feature_display_menus[1].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ feature_display.setShowStopCodons(
+ ((JCheckBoxMenuItem)feature_display_menus[1]).getState());
+ }
+ });
+
+ feature_display_menus[2] = new JCheckBoxMenuItem("Feature Arrows");
+ ((JCheckBoxMenuItem)feature_display_menus[2]).setState(
+ feature_display.getShowFeatureArrows());
+ feature_display_menus[2].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ feature_display.setShowFeatureArrows(
+ ((JCheckBoxMenuItem)feature_display_menus[2]).getState());
+ }
+ });
+
+ feature_display_menus[3] = new JCheckBoxMenuItem("Feature Borders");
+ ((JCheckBoxMenuItem)feature_display_menus[3]).setState(
+ feature_display.getShowFeatureBorders());
+ feature_display_menus[3].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ feature_display.setShowFeatureBorders(
+ ((JCheckBoxMenuItem)feature_display_menus[3]).getState());
+ }
+ });
+
+ feature_display_menus[4] = new JCheckBoxMenuItem("Feature Labels");
+ ((JCheckBoxMenuItem)feature_display_menus[4]).setState(feature_display.getShowLabels());
+ feature_display_menus[4].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ feature_display.setShowLabels(
+ ((JCheckBoxMenuItem)feature_display_menus[4]).getState());
+ }
+ });
+
+ feature_display_menus[5] = new JCheckBoxMenuItem("One Line Per Entry");
+ ((JCheckBoxMenuItem)feature_display_menus[5]).setState(
+ feature_display.getOneLinePerEntryFlag());
+ feature_display_menus[5].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ final boolean new_state =
+ ((JCheckBoxMenuItem)feature_display_menus[5]).getState();
+ if(new_state && getEntryGroup().size() > 8)
+ feature_display.setShowLabels(false);
+ feature_display.setOneLinePerEntry(new_state);
+ if(new_state)
+ ((JCheckBoxMenuItem)feature_display_menus[6]).setState(false);
+ }
+ });
+
+ feature_display_menus[6] = new JCheckBoxMenuItem("Feature Stack View");
+ ((JCheckBoxMenuItem)feature_display_menus[6]).setState(
+ feature_display.getFeatureStackViewFlag());
+ feature_display_menus[6].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ final boolean new_state =
+ ((JCheckBoxMenuItem)feature_display_menus[6]).getState();
+ if(new_state && getEntryGroup().size() > 8)
+ feature_display.setShowLabels(false);
+ feature_display.setFeatureStackViewFlag(new_state);
+ if(new_state)
+ ((JCheckBoxMenuItem)feature_display_menus[5]).setState(false);
+ }
+ });
+
+ feature_display_menus[7] = new JCheckBoxMenuItem("Forward Frame Lines");
+ ((JCheckBoxMenuItem)feature_display_menus[7]).setState(
+ feature_display.getShowForwardFrameLines());
+ feature_display_menus[7].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ feature_display.setShowForwardFrameLines(
+ ((JCheckBoxMenuItem)feature_display_menus[7]).getState());
+ }
+ });
+
+ feature_display_menus[8] = new JCheckBoxMenuItem("Reverse Frame Lines");
+ ((JCheckBoxMenuItem)feature_display_menus[8]).setState(
+ feature_display.getShowReverseFrameLines());
+ feature_display_menus[8].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ feature_display.setShowReverseFrameLines(
+ ((JCheckBoxMenuItem)feature_display_menus[8]).getState());
+ }
+ });
+
+ feature_display_menus[9] = new JCheckBoxMenuItem("All Features On Frame Lines");
+ ((JCheckBoxMenuItem)feature_display_menus[9]).setState(
+ feature_display.getFrameFeaturesFlag());
+ feature_display_menus[9].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ feature_display.setFrameFeaturesFlag(
+ ((JCheckBoxMenuItem)feature_display_menus[9]).getState());
+ }
+ });
+
+ feature_display_menus[10] = new JCheckBoxMenuItem("Show Source Features");
+ ((JCheckBoxMenuItem)feature_display_menus[10]).setState(
+ feature_display.getShowSourceFeatures());
+ feature_display_menus[10].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ feature_display.setShowSourceFeatures(
+ ((JCheckBoxMenuItem)feature_display_menus[10]).getState());
+ }
+ });
+
+ feature_display_menus[11] = new JCheckBoxMenuItem("Flip Display");
+ ((JCheckBoxMenuItem)feature_display_menus[11]).setState(
+ feature_display.isRevCompDisplay());
+ feature_display_menus[11].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ feature_display.setRevCompDisplay(
+ ((JCheckBoxMenuItem)feature_display_menus[11]).getState());
+ }
+ });
+
+ feature_display_menus[12] = new JCheckBoxMenuItem("Colourise Bases");
+ ((JCheckBoxMenuItem)feature_display_menus[12]).setState(
+ feature_display.getShowBaseColours());
+ feature_display_menus[12].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ feature_display.setShowBaseColours(
+ ((JCheckBoxMenuItem)feature_display_menus[12]).getState());
+ }
+ });
+
+ feature_display_menus[13] = new JMenuItem("Smallest Features In Front");
+ feature_display_menus[13].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ // clear the selection because selected features will always be on
+ // top - which is not usually what is wanted
+ selection.clear();
+ feature_display.smallestToFront();
+ }
+ });
+
+ feature_display_menus[14] = new JMenuItem("Set Score Cutoffs ...");
+ feature_display_menus[14].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final ScoreChangeListener minimum_listener =
+ new ScoreChangeListener()
+ {
+ public void scoreChanged(final ScoreChangeEvent event)
+ {
+ feature_display.setMinimumScore(event.getValue());
+ }
+ };
+
+ final ScoreChangeListener maximum_listener =
+ new ScoreChangeListener()
+ {
+ public void scoreChanged(final ScoreChangeEvent event)
+ {
+ feature_display.setMaximumScore(event.getValue());
+ }
+ };
+
+ FeatureVector features = getEntryGroup().getAllFeatures();
+ int score;
+ for(int i=0; i<features.size(); i++)
+ {
+ score = features.elementAt(i).getScore();
+ if(score > -1)
+ {
+ if(score < SCORE_MINIMUM)
+ SCORE_MINIMUM = score;
+ else if(score > SCORE_MAXIMUM)
+ SCORE_MAXIMUM = score;
+ }
+ }
+
+ if(SCORE_MINIMUM == Integer.MAX_VALUE)
+ SCORE_MINIMUM = 0;
+ if(SCORE_MAXIMUM == Integer.MIN_VALUE)
+ SCORE_MAXIMUM = 100;
+
+ final ScoreChanger score_changer =
+ new ScoreChanger("Score Cutoffs",
+ minimum_listener, maximum_listener,
+ SCORE_MINIMUM, SCORE_MAXIMUM);
+
+ score_changer.setVisible(true);
+ }
+ });
+
+ feature_display_menus[15] = new JMenuItem("Raise Selected Features");
+ feature_display_menus[15].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ raiseSelection();
+ }
+ });
+
+ feature_display_menus[16] = new JMenuItem("Lower Selected Features");
+ feature_display_menus[16].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ lowerSelection();
+ }
+ });
+
+ feature_display_menus[17] = new JMenuItem("Zoom to Selection");
+ feature_display_menus[17].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ zoomToSelection((FeatureDisplay) owner);
+ }
+ });
+
+ feature_display_menus[18] = new JMenuItem("Select Visible Range");
+ feature_display_menus[18].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ selection.setMarkerRange(feature_display.getVisibleMarkerRange());
+ }
+ });
+
+ feature_display_menus[19] = new JMenuItem("Select Visible Features");
+ feature_display_menus[19].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ selection.set(feature_display.getCurrentVisibleFeatures());
+ }
+ });
+
+ feature_display_menus[20] = new JMenuItem("Frame Line Features ...");
+ feature_display_menus[20].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final DefaultListModel listModel = new DefaultListModel();
+ final Object protein_keys[] = ((FeatureDisplay)owner).getProteinKeys();
+
+ for(int i=0; i<protein_keys.length; i++)
+ listModel.addElement(protein_keys[i]);
+
+ JPanel frame_keys = new JPanel(new BorderLayout());
+
+ final JLabel label = new JLabel("Features Displayed on the Frame Lines:");
+ final JList protein_list = new JList(listModel);
+ final JScrollPane jsp = new JScrollPane(protein_list);
+
+ JButton remove_butt = new JButton("REMOVE");
+ remove_butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ while(!protein_list.isSelectionEmpty())
+ listModel.remove(protein_list.getSelectedIndex());
+ }
+ });
+
+ Box bdown = Box.createVerticalBox();
+ bdown.add(label);
+ bdown.add(jsp);
+ bdown.add(remove_butt);
+ frame_keys.add(bdown, BorderLayout.CENTER);
+
+
+ final KeyChoice key_choice =
+ new KeyChoice(entry_group.elementAt(0).getEntryInformation(),
+ new uk.ac.sanger.artemis.io.Key("CDS"));
+
+ JButton add_butt = new JButton("ADD");
+ add_butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ listModel.addElement(key_choice.getSelectedItem().toString());
+ }
+ });
+
+ bdown = Box.createVerticalBox();
+ bdown.add(Box.createVerticalGlue());
+ bdown.add(key_choice);
+ bdown.add(add_butt);
+ frame_keys.add(bdown, BorderLayout.EAST);
+
+ int select = JOptionPane.showConfirmDialog(null, frame_keys,
+ "Frame Line Features ...",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+
+ ((FeatureDisplay)owner).setProteinKeys(listModel.toArray());
+ }
+ });
+
+ if(isDatabaseGroup || isGFFGroup)
+ {
+ feature_display_menus[21] = new JMenuItem("Show/Hide Features ...");
+ feature_display_menus[21].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ GeneUtils.defineShowHideGeneFeatures(feature_display.getEntryGroup()
+ .getAllFeatures());
+ }
+ });
+ }
+
+ return feature_display_menus;
+ }
+
+
+ /**
+ *
+ * Override show()
+ *
+ */
+ public void show(Component invoker, int x, int y)
+ {
+ if(owner instanceof FeatureDisplay)
+ {
+ if(selection_features.size() > 0 || selection_segments.size() > 0)
+ {
+ feature_display_menus[14].setEnabled(true);
+ feature_display_menus[15].setEnabled(true);
+// feature_display_menus[16].setEnabled(true);
+ }
+ else
+ {
+ feature_display_menus[14].setEnabled(false);
+ feature_display_menus[15].setEnabled(false);
+// feature_display_menus[16].setEnabled(false);
+ }
+ }
+
+ super.show(invoker, x, y);
+ }
+
+ /**
+ * Create those menu items that are relevant only to FeatureList objects.
+ **/
+ private JMenuItem[] addFeatureListItems()
+ {
+ final JMenuItem feature_list_menus[] = new JMenuItem[7];
+ if(Options.readWritePossible())
+ {
+ feature_list_menus[0] = new JMenuItem("Save List To File ...");
+ feature_list_menus[0].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ saveFeatureList();
+ }
+ });
+ }
+
+ feature_list_menus[1] = new JCheckBoxMenuItem("Show Correlation Scores");
+ ((JCheckBoxMenuItem)feature_list_menus[1]).setState(
+ feature_list.getCorrelationScores());
+ feature_list_menus[1].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ feature_list.setCorrelationScores(
+ ((JCheckBoxMenuItem)feature_list_menus[1]).getState());
+ }
+ });
+
+ feature_list_menus[2] = new JCheckBoxMenuItem("Show Gene Names");
+ ((JCheckBoxMenuItem)feature_list_menus[2]).setState(feature_list.getShowGenes());
+ feature_list_menus[2].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ boolean show_sysid = ((JCheckBoxMenuItem)feature_list_menus[3]).getState();
+ boolean show_genes = ((JCheckBoxMenuItem)feature_list_menus[2]).getState();
+ if(show_sysid && show_genes)
+ {
+ ((JCheckBoxMenuItem)feature_list_menus[3]).setState(false);
+ feature_list.setShowSystematicID(false);
+ }
+
+ feature_list.setShowGenes(show_genes);
+ }
+ });
+
+ feature_list_menus[3] = new JCheckBoxMenuItem("Show Systematic ID");
+ ((JCheckBoxMenuItem)feature_list_menus[3]).setState(feature_list.getShowSysID());
+ feature_list_menus[3].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ boolean show_sysid = ((JCheckBoxMenuItem)feature_list_menus[3]).getState();
+ boolean show_genes = ((JCheckBoxMenuItem)feature_list_menus[2]).getState();
+ if(show_genes && show_sysid)
+ {
+ ((JCheckBoxMenuItem)feature_list_menus[2]).setState(false);
+ feature_list.setShowGenes(false);
+ }
+
+ feature_list.setShowSystematicID(show_sysid);
+ }
+ });
+
+ feature_list_menus[4] = new JCheckBoxMenuItem("Show Products");
+ ((JCheckBoxMenuItem)feature_list_menus[4]).setState(feature_list.getShowProducts());
+ feature_list_menus[4].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ boolean show_products = ((JCheckBoxMenuItem)feature_list_menus[4]).getState();
+ if(show_products)
+ {
+ ((JCheckBoxMenuItem)feature_list_menus[5]).setState(false);
+ feature_list.setShowQualifiers(false);
+ }
+ feature_list.setShowProducts(show_products);
+ }
+ });
+
+ feature_list_menus[5] = new JCheckBoxMenuItem("Show Qualifiers");
+ ((JCheckBoxMenuItem)feature_list_menus[5]).setState(feature_list.getShowQualifiers());
+ feature_list_menus[5].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ boolean show_qualifiers = ((JCheckBoxMenuItem)feature_list_menus[5]).getState();
+ feature_list.setShowQualifiers(show_qualifiers);
+ if(show_qualifiers)
+ {
+ ((JCheckBoxMenuItem)feature_list_menus[4]).setState(false);
+ feature_list.setShowProducts(false);
+ }
+ }
+ });
+
+ feature_list_menus[6] = new JMenuItem("Show Selected Qualifiers ...");
+ feature_list_menus[6].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final StringVector sv = feature_list.getShowUserDefinedQualifier();
+ final Object display_names[];
+
+ if(sv != null && sv.size() > 0)
+ display_names = sv.toArray();
+ else
+ {
+ display_names = new Object[1];
+ display_names[0] = "note";
+ }
+
+ final String[] description = {
+ "Qualifiers to display in feature list displays."};
+ ListSelectionPanel displayListSelectionPanel =
+ new ListSelectionPanel(entry_group, display_names,
+ description, false, new Key("gene"), null);
+ int select = JOptionPane.showConfirmDialog(null,
+ displayListSelectionPanel,
+ "Select Qualifier to Display",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+
+ feature_list.setShowUserDefinedQualifier(
+ displayListSelectionPanel.getResultString());
+ }
+ });
+
+ return feature_list_menus;
+ }
+
+ /**
+ * Save the text of the feature list to a file.
+ **/
+ private void saveFeatureList()
+ {
+ final JFrame frame = owner.getParentFrame();
+ final StickyFileChooser file_dialog = new StickyFileChooser();
+
+ file_dialog.setDialogTitle("Choose save file ...");
+ file_dialog.setDialogType(JFileChooser.SAVE_DIALOG);
+ final int status = file_dialog.showSaveDialog(frame);
+
+ if(status != JFileChooser.APPROVE_OPTION ||
+ file_dialog.getSelectedFile() == null)
+ return;
+
+ final File write_file =
+ new File(file_dialog.getCurrentDirectory(),
+ file_dialog.getSelectedFile().getName());
+
+ if(write_file.exists())
+ {
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog(frame,
+ "this file exists: " + write_file +
+ " overwrite it?");
+ if(yes_no_dialog.getResult())
+ {
+ // yes - continue
+ }
+ else
+ return;
+ }
+
+ try
+ {
+ final PrintWriter writer =
+ new PrintWriter(new FileWriter(write_file));
+
+ final StringVector list_strings = feature_list.getListStrings();
+
+ for(int i = 0 ; i < list_strings.size() ; ++i)
+ writer.println(list_strings.elementAt(i));
+
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(frame, "error while writing: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Raise the selected features. (FeatureDisplay only.)
+ **/
+ private void raiseSelection()
+ {
+ final FeatureVector features_to_raise = selection.getAllFeatures();
+
+ for(int i = 0 ; i < features_to_raise.size() ; ++i)
+ {
+ final Feature selection_feature = features_to_raise.elementAt(i);
+ feature_display.raiseFeature(selection_feature);
+ }
+ }
+
+ /**
+ * Lower the selected features. (FeatureDisplay only.)
+ **/
+ private void lowerSelection()
+ {
+ final FeatureVector features_to_lower = selection.getAllFeatures();
+
+ for(int i = 0 ; i < features_to_lower.size() ; ++i)
+ {
+ final Feature selection_feature = features_to_lower.elementAt(i);
+ feature_display.lowerFeature(selection_feature);
+ }
+ }
+
+ /**
+ * Zoom the FeatureDisplay to the selection.
+ **/
+ static void zoomToSelection(final FeatureDisplay feature_display)
+ {
+ final Selection selection = feature_display.getSelection();
+
+ if(selection.isEmpty())
+ return;
+
+ // why bother in this case?
+ if(feature_display.getEntryGroup().getSequenceLength() < 1000)
+ return;
+
+ int first_base;
+ int last_base;
+
+ final FeatureSegmentVector segments = selection.getSelectedSegments();
+
+ if(segments.size() == 1)
+ {
+ // special case - zoom to the feature instead
+ first_base = segments.elementAt(0).getFeature().getRawFirstBase();
+ last_base = segments.elementAt(0).getFeature().getRawLastBase();
+ }
+ else
+ {
+ first_base = selection.getLowestBaseOfSelection().getRawPosition();
+ last_base = selection.getHighestBaseOfSelection().getRawPosition();
+ }
+
+ if(first_base < 250)
+ first_base = 250;
+ else
+ first_base -= 250;
+
+ last_base += 250;
+
+ feature_display.setFirstAndLastBase(first_base, last_base);
+ }
+
+ /**
+ * Return the EntryGroup object that this FeatureDisplay is displaying.
+ **/
+ private EntryGroup getEntryGroup()
+ {
+ return entry_group;
+ }
+
+ /**
+ * Return an object that implements the GotoEventSource interface and is
+ * for the sequence that this DisplayComponent is displaying.
+ **/
+ public GotoEventSource getGotoEventSource()
+ {
+ return goto_event_source;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/FeatureViewer.java b/uk/ac/sanger/artemis/components/FeatureViewer.java
new file mode 100644
index 0000000..85e084b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FeatureViewer.java
@@ -0,0 +1,169 @@
+/* FeatureViewer.java
+ *
+ * created: Thu Nov 19 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureViewer.java,v 1.2 2005-01-11 16:07:35 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+import java.io.*;
+import java.awt.event.*;
+
+/**
+ * A viewer for Feature objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureViewer.java,v 1.2 2005-01-11 16:07:35 tjc Exp $
+ *
+ **/
+
+public class FeatureViewer
+ implements EntryChangeListener, FeatureChangeListener
+{
+
+ /**
+ * The Feature this object is displaying.
+ **/
+ private Feature view_feature;
+
+ /**
+ * The Entry that contains the Feature this object is displaying.
+ **/
+//private Entry entry;
+
+ /**
+ * The FileViewer object that is displaying the feature.
+ **/
+ private FileViewer file_viewer;
+
+ /**
+ * Create a new FeatureViewer object from the given Feature.
+ **/
+ public FeatureViewer(Feature view_feature)
+ {
+ this.view_feature = view_feature;
+// this.entry = view_feature.getEntry();
+
+ file_viewer = new FileViewer("Artemis Feature View: " +
+ view_feature.getIDString());
+ readFeature(view_feature);
+
+ view_feature.getEntry().addEntryChangeListener(this);
+ view_feature.addFeatureChangeListener(this);
+
+ file_viewer.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosed(WindowEvent event)
+ {
+ stopListening();
+ }
+ });
+ }
+
+ /**
+ * Remove this object as a feature and entry change listener.
+ **/
+ public void stopListening()
+ {
+ view_feature.getEntry().removeEntryChangeListener(this);
+ getFeature().removeFeatureChangeListener(this);
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so we can delete this component if the feature gets
+ * deleted.
+ **/
+ public void entryChanged(EntryChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryChangeEvent.FEATURE_DELETED:
+ if(event.getFeature() == view_feature)
+ {
+ stopListening();
+ file_viewer.dispose();
+ }
+ break;
+ default:
+ // do nothing;
+ break;
+ }
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events from the Features in this object so that
+ * we can keep the display up to date.
+ * @param event The change event.
+ **/
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ // re-read the information from the feature
+ readFeature(view_feature);
+ }
+
+ /**
+ * Read the given Feature into this FeatureViewer object.
+ **/
+ public void readFeature(Feature feature)
+ {
+ try
+ {
+ file_viewer.clear();
+ file_viewer.appendFile(view_feature.toReader());
+ }
+ catch(uk.ac.sanger.artemis.io.ReadFormatException e)
+ {
+ throw new Error("internal error - unexpected exception: " +
+ e.getMessage() +
+ (e.getLineNumber() > 1 ?
+ " at line " + e.getLineNumber() :
+ ""));
+ }
+ catch(IOException e)
+ {
+ throw new Error("internal error - unexpected exception: " +
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Return the Feature we are viewing.
+ **/
+ public Feature getFeature()
+ {
+ return view_feature;
+ }
+
+ /**
+ * Return the Entry that contains the Feature this object is displaying.
+ **/
+//private Entry getEntry()
+//{
+// return entry;
+//}
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/FileDialogEntrySource.java b/uk/ac/sanger/artemis/components/FileDialogEntrySource.java
new file mode 100644
index 0000000..b7807c0
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FileDialogEntrySource.java
@@ -0,0 +1,242 @@
+/* FileDialogEntrySource.java
+ *
+ * created: Thu Jun 8 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FileDialogEntrySource.java,v 1.2 2004-12-03 17:47:04 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.SimpleEntryInformation;
+
+import javax.swing.JFrame;
+
+/**
+ * This is an EntrySource that reads Entry objects from the local filesystem.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: FileDialogEntrySource.java,v 1.2 2004-12-03 17:47:04 tjc Exp $
+ **/
+
+public class FileDialogEntrySource
+ implements EntrySource
+{
+
+ /**
+ * The component that created this FileEntrySource. (Used for requesters.)
+ **/
+ final JFrame frame;
+
+ /** InputStreamProgressEvents are sent to this object. */
+ private final InputStreamProgressListener stream_progress_listener;
+
+ /**
+ * Create a new FileDialogEntrySource.
+ * @param frame The component that created this FileDialogEntrySource.
+ * (Used for requesters.)
+ * @param listener InputStreamProgressEvent objects will be sent to this
+ * listener as progress on reading is made.
+ **/
+ public FileDialogEntrySource (final JFrame frame,
+ final InputStreamProgressListener listener)
+ {
+ this.frame = frame;
+ this.stream_progress_listener = listener;
+ }
+
+ /**
+ * Get an Entry object from this source (by reading from a file).
+ * @param bases The Bases object to pass to the Entry constructor.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading.
+ * @return null if and only if the user cancels the read.
+ **/
+ public Entry getEntry(final Bases bases,
+ final boolean show_progress)
+ throws OutOfRangeException
+ {
+ try
+ {
+ return getEntryInternal(bases, show_progress);
+ }
+ catch (NoSequenceException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Get an Entry object from this source (by reading from a file).
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if the entry that we read has no
+ * sequence.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading.
+ * @return null if and only if the user cancels the read.
+ **/
+ public Entry getEntry (final boolean show_progress)
+ throws OutOfRangeException, NoSequenceException
+ {
+ return getEntryInternal (null, show_progress);
+ }
+
+ /**
+ * Returns true if and only if this EntrySource always returns "full"
+ * entries. ie. entries that contain features and sequence. Entries that
+ * are read from a file may contain just features so in this class this
+ * method returns false.
+ **/
+ public boolean isFullEntrySource ()
+ {
+ return false;
+ }
+
+
+//// change ArtemisMain.readArgsAndOptions () to use this?:
+
+// /**
+// * Get an Entry object from this source by name (by reading from a file).
+// * @exception OutOfRangeException Thrown if one of the features in
+// * embl_entry is out of range of the Bases object.
+// * @exception NoSequenceException Thrown if the entry that we read has no
+// * sequence.
+// * @return null if and only if there is no Entry with that name.
+// **/
+// public Entry getEntryByName (final String entry_file_name)
+// throws OutOfRangeException, NoSequenceException, IOException {
+// final Document new_document =
+// new FileProgressDocument (new File (entry_file_name),
+// getInputStreamProgressListener ());
+
+// final EntryInformation new_entry_information =
+// new SimpleEntryInformation (Options.getArtemisEntryInformation ());
+
+// boolean seen_error = false;
+
+// while (true) {
+// try {
+// final uk.ac.sanger.artemis.io.Entry new_entry =
+// DocumentEntryFactory.makeDocumentEntry (new_entry_information,
+// new_document);
+
+// return new Entry (new_entry);
+// } catch (EntryInformationException e) {
+
+// if (!seen_error) {
+// final String message =
+// "warning while reading " + entry_file_name + " - " +
+// e.getMessage ();
+
+// System.err.println (message);
+
+// new MessageDialog (frame, message);
+
+// seen_error = true;
+// }
+
+// EntryFileDialog.handleOpenException (new_entry_information, e);
+
+// // go around the loop again
+// }
+// }
+// }
+
+ /**
+ * Make a new Entry.
+ * @param bases The Bases object to pass to the Entry constructor. If null
+ * a new Bases object will be created.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if bases is null and the Entry that
+ * we read has no sequence.
+ **/
+ private Entry makeEntry (final Bases bases,
+ final uk.ac.sanger.artemis.io.Entry embl_entry)
+ throws OutOfRangeException, NoSequenceException
+ {
+ if (bases == null)
+ return new Entry (embl_entry);
+ else
+ return new Entry (bases, embl_entry);
+ }
+
+ /**
+ * Return the InputStreamProgressListener that was passed to the
+ * constructor.
+ **/
+ public InputStreamProgressListener getInputStreamProgressListener ()
+ {
+ return stream_progress_listener;
+ }
+
+ /**
+ * Return the name of this source (for display to the user in menus).
+ **/
+ public String getSourceName ()
+ {
+ return "Filesystem";
+ }
+
+ /**
+ * Implementation of getEntry ().
+ * @param bases The Bases object to pass to the Entry constructor.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * Entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if bases is null and the entry that
+ * we read has no sequence.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading.
+ * @return null if and only if the user cancels the read.
+ **/
+ private Entry getEntryInternal(final Bases bases,
+ final boolean show_progress)
+ throws OutOfRangeException, NoSequenceException
+ {
+ final EntryInformation new_entry_information =
+ new SimpleEntryInformation(Options.getArtemisEntryInformation());
+
+ EntryFileDialog dialog;
+
+ if(bases == null)
+ dialog = new EntryFileDialog(frame, true);
+ else
+ dialog = new EntryFileDialog(frame, false);
+
+
+ uk.ac.sanger.artemis.io.Entry new_embl_entry =
+ dialog.getEntry(new_entry_information, stream_progress_listener,
+ show_progress);
+
+ if(new_embl_entry == null)
+ return null;
+
+ return makeEntry(bases, new_embl_entry);
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/FileViewer.java b/uk/ac/sanger/artemis/components/FileViewer.java
new file mode 100644
index 0000000..15ba1ef
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FileViewer.java
@@ -0,0 +1,488 @@
+/* FileViewer.java
+ *
+ * created: Thu Nov 19 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FileViewer.java,v 1.19 2008-11-12 16:50:37 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.*;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.Reader;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Hashtable;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextPane;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+
+import org.apache.log4j.Level;
+
+import uk.ac.sanger.artemis.Options;
+
+
+/**
+ * This class implements a simple file viewer. In fact any Reader object can
+ * be viewed.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FileViewer.java,v 1.19 2008-11-12 16:50:37 tjc Exp $
+ *
+ **/
+
+public class FileViewer extends JFrame
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /** A JPanel to hold the close button. */
+ protected JPanel button_panel;
+
+ /** The main component we use for displaying the file. */
+ private JTextPane textPane = null;
+
+ private Hashtable<Level, MutableAttributeSet> fontAttributes;
+
+ private final static Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ /**
+ * The size of the last FileViewer JFrame to be resized. When a new
+ * FileViewer component is created it is given the current value of this
+ * variable. This is updated when any FileViewer frame is resized.
+ **/
+ private static Dimension saved_size = null;
+
+ /**
+ * The position of the last FileViewer JFrame to be moved. When a new
+ * FileViewer component is created it is given the current value of this
+ * variable. This is updated when any FileViewer frame is moved.
+ **/
+ private static Point saved_position = null;
+
+ private boolean isHideOnClose = false;
+
+ /**
+ * Create a new FileViewer component and make it visible.
+ * @param label The name to attach to the new JFrame.
+ **/
+ public FileViewer(final String label)
+ {
+ this(label, true);
+ }
+
+ public FileViewer(final String label, final boolean visible)
+ {
+ this(label, visible, true, false);
+ }
+
+ public FileViewer(final String label, final boolean visible,
+ final boolean showClearButton,
+ final boolean showSaveButton)
+ {
+ this(label, visible, showClearButton, showSaveButton, null);
+ }
+
+ /**
+ * Create a new FileViewer component.
+ * @param label The name to attach to the new JFrame.
+ * @param visible The new FileViewer will be made visible if and only if
+ * this argument is true.
+ **/
+ public FileViewer(final String label, final boolean visible,
+ final boolean showClearButton,
+ final boolean showSaveButton,
+ final ActionListener saveListener)
+ {
+ super(label);
+
+ final JMenuBar mBar = new JMenuBar();
+ setJMenuBar(mBar);
+ final JMenu fileMenu = new JMenu("File");
+ final JMenuItem close = new JMenuItem("Close");
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ if(isHideOnClose())
+ setVisible(false);
+ else
+ dispose();
+ }
+ });
+ fileMenu.add(close);
+ mBar.add(fileMenu);
+
+ getContentPane().setLayout(new BorderLayout());
+ final Font font = Options.getOptions().getFont();
+ setFont(font);
+
+ // ensure wrapping is turned off
+ textPane = new JTextPane()
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public boolean getScrollableTracksViewportWidth()
+ {
+ return false;
+ }
+ };
+
+ final JScrollPane scroller = new JScrollPane(textPane);
+ scroller.getViewport().setBackground(Color.white);
+
+ textPane.setEditable(false);
+ textPane.setFont(font);
+ textPane.setBackground(Color.white);
+
+ getContentPane().add(scroller, "Center");
+
+ button_panel = new JPanel(new FlowLayout());
+ getContentPane().add(button_panel, "South");
+
+ final JButton close_button = new JButton("Close");
+ close_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(isHideOnClose())
+ setVisible(false);
+ else
+ dispose();
+ }
+ });
+ button_panel.add(close_button);
+
+ if(showClearButton)
+ {
+ final JButton clearbutton = new JButton("Clear");
+ clearbutton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ clear();
+ }
+ });
+ button_panel.add(clearbutton);
+ }
+
+ if(showSaveButton)
+ {
+ final JButton saveToFile = new JButton("Save");
+ if(saveListener != null)
+ saveToFile.addActionListener(saveListener);
+ else
+ saveToFile.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ writeToFile(getText());
+ }
+ });
+ button_panel.add(saveToFile);
+ }
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ if(isHideOnClose())
+ setVisible(false);
+ else
+ dispose();
+ }
+ });
+
+ if(saved_position == null)
+ {
+ Dimension d = new Dimension((int)screen.getWidth()/2,
+ (int)screen.getHeight()/2);
+ scroller.setPreferredSize(d);
+ }
+ else
+ {
+ if(saved_position.x < 0 || saved_position.x + 20 > screen.width)
+ saved_position.x = 20;
+
+ if(saved_position.y < 0 || saved_position.y + 20 > screen.height)
+ saved_position.y = 20;
+
+ if(saved_size.width < 50)
+ saved_size.width = 50;
+
+ if(saved_size.height < 50)
+ saved_size.height = 50;
+
+ setLocation(saved_position);
+
+ if(this instanceof ValidateViewer)
+ scroller.setPreferredSize(new Dimension((int)screen.getWidth()/3,
+ (int)screen.getHeight()/3));
+ else
+ scroller.setPreferredSize(saved_size);
+ }
+
+ pack();
+ if(saved_position == null)
+ Utilities.centreFrame(this);
+
+ if(visible)
+ setVisible(true);
+ createDefaultFontAttributes();
+
+ addComponentListener(new ComponentAdapter()
+ {
+ public void componentResized(ComponentEvent e)
+ {
+ saved_size = scroller.getSize();
+ saved_position = FileViewer.this.getLocation();
+ }
+ public void componentMoved(ComponentEvent e)
+ {
+ saved_size = scroller.getSize();
+ saved_position = FileViewer.this.getLocation();
+ }
+ });
+ }
+
+
+ /**
+ * Clear the viewer window.
+ **/
+ protected void clear()
+ {
+ textPane.setText("");
+ }
+
+ /**
+ * Read from the given Reader and append the text to this FileViewer.
+ * @param read_stream The stream to read the contents of the viewer from.
+ **/
+ protected void appendFile(Reader read_stream)
+ throws IOException
+ {
+ final BufferedReader buffered_reader = new BufferedReader(read_stream);
+ String line;
+
+ while((line = buffered_reader.readLine()) != null)
+ {
+ appendString(line + "\n");
+ Thread.yield();
+ }
+
+ buffered_reader.close();
+ }
+
+ /**
+ * Clear the viewer window and display the lines from the given Reader.
+ * @param read_stream The stream to read the contents of the viewer from.
+ **/
+ protected void readFile(Reader read_stream)
+ throws IOException
+ {
+ final BufferedReader buffered_reader = new BufferedReader(read_stream);
+
+ String line;
+ final StringBuffer line_buffer = new StringBuffer();
+ while((line = buffered_reader.readLine()) != null)
+ line_buffer.append(line).append('\n');
+
+ textPane.setText(line_buffer.toString());
+ textPane.setCaretPosition(0);
+ buffered_reader.close();
+ }
+
+ /**
+ * Clear the viewer window and display the lines from the given String.
+ * @param read_string The string to read the contents of the viewer from.
+ **/
+ protected void setText(String read_string)
+ {
+ textPane.setText(read_string);
+ }
+
+
+ /**
+ * Clear the viewer window and display the lines from the given String.
+ * @param read_string The string to read the contents of the viewer from.
+ **/
+ protected void appendString(String read_string)
+ {
+ final Level level;
+ if(read_string.indexOf("FATAL") > -1)
+ level = Level.FATAL;
+ else if(read_string.indexOf("ERROR") > -1)
+ level = Level.ERROR;
+ else if(read_string.indexOf("WARN") > -1)
+ level = Level.WARN;
+ else if(read_string.indexOf("INFO") > -1)
+ level = Level.INFO;
+ else
+ level = Level.DEBUG;
+ appendString(read_string, level);
+ }
+
+ public void appendString(final String read_string, final Level level)
+ {
+ final Document doc = textPane.getStyledDocument();
+ try
+ {
+ doc.insertString(doc.getLength(), read_string,
+ (MutableAttributeSet)fontAttributes.get(level));
+ textPane.setCaretPosition(doc.getLength());
+ }
+ catch(BadLocationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void createDefaultFontAttributes()
+ {
+ Level[] prio = { Level.FATAL, Level.ERROR,
+ Level.WARN, Level.INFO, Level.DEBUG };
+
+ fontAttributes = new Hashtable<Level, MutableAttributeSet>();
+ for (int i=0; i<prio.length;i++)
+ {
+ MutableAttributeSet att = new SimpleAttributeSet();
+ fontAttributes.put(prio[i], att);
+ }
+
+ setTextColor(Level.FATAL, Color.red);
+ setTextColor(Level.ERROR, Color.magenta);
+ setTextColor(Level.WARN, Color.orange);
+ setTextColor(Level.INFO, Color.black);
+ setTextColor(Level.DEBUG, Color.blue);
+ }
+
+ void setTextColor(Level p, Color c)
+ {
+ StyleConstants.setForeground(
+ (MutableAttributeSet)fontAttributes.get(p),c);
+ }
+
+ /**
+ * Return a String containing the text that this component is displaying.
+ **/
+ protected String getText()
+ {
+ return textPane.getText();
+ }
+
+
+ /**
+ * Return the JPanel containing the close button of this FileViewer.
+ **/
+ protected JPanel getButtonPanel()
+ {
+ return button_panel;
+ }
+
+ /**
+ *
+ * @return
+ */
+ protected int getLineCount()
+ {
+ int caretPosition = textPane.getCaretPosition();
+ Element root = textPane.getDocument().getDefaultRootElement();
+
+ return root.getElementIndex( caretPosition ) + 1;
+
+ /*try
+ {
+ int offset = textPane.getDocument().getLength();
+ Rectangle r = textPane.modelToView( offset );
+
+ return (int) r.y/lineHeight;
+ }
+ catch(Exception e)
+ {
+ return 0;
+ }*/
+
+ }
+
+ public JTextPane getTextPane()
+ {
+ return textPane;
+ }
+
+ public boolean isHideOnClose()
+ {
+ return isHideOnClose;
+ }
+
+ public void setHideOnClose(boolean isHideOnClose)
+ {
+ this.isHideOnClose = isHideOnClose;
+ }
+
+ public static void writeToFile(final String txt)
+ {
+ StickyFileChooser fc = new StickyFileChooser();
+ fc.showSaveDialog(null);
+
+ File f = fc.getSelectedFile();
+ if(f.exists() && f.canWrite())
+ {
+ int status = JOptionPane.showConfirmDialog(null,
+ f.getName()+" exists overwrite?",
+ "Overwrite", JOptionPane.OK_CANCEL_OPTION);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+ }
+ try
+ {
+ FileWriter writer = new FileWriter(f);
+ writer.write(txt);
+ writer.close();
+ }
+ catch (IOException e1)
+ {
+ JOptionPane.showMessageDialog(null,
+ e1.getMessage(), "Problem Writing", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/FindAndReplace.java b/uk/ac/sanger/artemis/components/FindAndReplace.java
new file mode 100644
index 0000000..1f8aba7
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/FindAndReplace.java
@@ -0,0 +1,707 @@
+/*
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.EntryGroupChangeEvent;
+import uk.ac.sanger.artemis.EntryGroupChangeListener;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureKeyQualifierPredicate;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.FeaturePredicateConjunction;
+import uk.ac.sanger.artemis.FeaturePredicateVector;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.FilteredEntryGroup;
+import uk.ac.sanger.artemis.GotoEventSource;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+public class FindAndReplace extends JFrame
+ implements EntryGroupChangeListener
+{
+ private static final long serialVersionUID = 1L;
+ private EntryGroup entry_group;
+
+ /**
+ * Find, replace and delete qualifiers
+ * @param selection
+ * @param goto_event_source
+ * @param entry_group
+ * @param base_plot_group
+ */
+ public FindAndReplace(final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group)
+ {
+ super("Search");
+
+ this.entry_group = entry_group;
+
+ final JTabbedPane tabPane = new JTabbedPane();
+ getContentPane().add(tabPane, BorderLayout.CENTER);
+ addFindReplaceTab(selection, goto_event_source,
+ base_plot_group, tabPane);
+ addFindDeleteDuplicateTab(selection, goto_event_source,
+ entry_group, base_plot_group, tabPane);
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ entry_group.removeEntryGroupChangeListener(FindAndReplace.this);
+ FindAndReplace.this.dispose();
+ }
+ });
+
+ final Box xbox = Box.createHorizontalBox();
+ final JButton closeButton = new JButton("Close");
+
+ closeButton.setHorizontalAlignment(SwingConstants.LEFT);
+ closeButton.addActionListener(new ActionListener()
+ {
+
+ public void actionPerformed(ActionEvent e)
+ {
+ entry_group.removeEntryGroupChangeListener(FindAndReplace.this);
+ FindAndReplace.this.dispose();
+ }
+
+ });
+ xbox.add(closeButton);
+ xbox.add(Box.createHorizontalGlue());
+ getContentPane().add(xbox, BorderLayout.SOUTH);
+
+ pack();
+
+ Utilities.centreFrame(this);
+ setVisible(true);
+ }
+
+ /**
+ * Find and replace option for qualifier text
+ * @param selection
+ * @param goto_event_source
+ * @param base_plot_group
+ * @param tabPane
+ */
+ private void addFindReplaceTab(final Selection selection,
+ final GotoEventSource goto_event_source,
+ final BasePlotGroup base_plot_group,
+ final JTabbedPane tabPane)
+ {
+ GridBagLayout gridbag = new GridBagLayout();
+ final JPanel panel = new JPanel(gridbag);
+ tabPane.addTab("Qualifier Text", panel);
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.WEST;
+ c.ipadx = 5;
+ c.ipady = 5;
+
+
+ /*final FeatureVector features = entry_group.getAllFeatures ();
+
+ for(int i=0; i<features.size(); i++)
+ findOrDeleteDuplicate(features.elementAt(i), true);*/
+
+ Entry default_entry = entry_group.getDefaultEntry();
+ if(default_entry == null)
+ default_entry = entry_group.elementAt(0);
+
+ boolean isGFF = false;
+ if(default_entry.getEMBLEntry() instanceof GFFDocumentEntry)
+ isGFF = true;
+
+ final EntryInformation default_entry_information =
+ default_entry.getEntryInformation();
+
+ final KeyChoice key_selector = new KeyChoice(default_entry_information);
+ key_selector.setEnabled(false);
+ final QualifierChoice qualifier_selector = new QualifierChoice(
+ default_entry_information, null, null, isGFF);
+ qualifier_selector.setEnabled(false);
+
+ // column 1
+ int ypos = 0;
+ final JTextField findTextField = new JTextField(15);
+ c.gridx = 0;
+ c.gridy = ypos;
+ c.anchor = GridBagConstraints.EAST;
+ c.fill = GridBagConstraints.NONE;
+ panel.add(new JLabel("Find:"),c);
+ final JTextField replaceTextField = new JTextField(15);
+ c.gridy = ++ypos;
+ panel.add(new JLabel("Replace with:"), c);
+ c.gridy = ++ypos;
+ panel.add(key_selector,c);
+ c.gridy = ++ypos;
+ panel.add(qualifier_selector,c);
+ final JCheckBox caseSensitive = new JCheckBox("Case sensitive", true);
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridy = ++ypos;
+ panel.add(caseSensitive, c);
+
+ final JCheckBox qualifierValueSubString = new JCheckBox("Match substring", true);
+ c.gridy = ++ypos;
+ panel.add(qualifierValueSubString, c);
+
+ final JCheckBox deleteQualifier = new JCheckBox("Delete qualifier(s)", false);
+ deleteQualifier.setToolTipText("Find & Delete");
+ c.gridy = ++ypos;
+ panel.add(deleteQualifier, c);
+
+ // boolean searches
+ c.gridy = ++ypos;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.NONE;
+ final JButton booleanSearch = new JButton("Show Boolean Search Options");
+ panel.add(booleanSearch, c);
+
+ final JPanel booleanSearchPanel = new JPanel(gridbag);
+ ButtonGroup buttonGroup = new ButtonGroup();
+ final JRadioButton qualifierValueUseBoolean = new JRadioButton(
+ "Use boolean operators (and, or, &, |)", false);
+ c.gridy = ++ypos;
+ booleanSearchPanel.add(qualifierValueUseBoolean, c);
+
+ final JRadioButton qualifierValueMatchAny = new JRadioButton(
+ "Match any string (i.e. x OR y)", false);
+ c.gridy = ++ypos;
+ booleanSearchPanel.add(qualifierValueMatchAny, c);
+
+ final JRadioButton qualifierValueMatchAll = new JRadioButton(
+ "Match all strings (i.e. x AND y)", false);
+ c.gridy = ++ypos;
+ booleanSearchPanel.add(qualifierValueMatchAll, c);
+
+ final JRadioButton noBooleanSearch = new JRadioButton(
+ "No boolean search", false);
+ c.gridy = ++ypos;
+ booleanSearchPanel.add(noBooleanSearch, c);
+
+ c.gridwidth = 2;
+ panel.add(booleanSearchPanel, c);
+ c.gridwidth = 1;
+
+ buttonGroup.add(qualifierValueUseBoolean);
+ buttonGroup.add(qualifierValueMatchAny);
+ buttonGroup.add(qualifierValueMatchAll);
+ buttonGroup.add(noBooleanSearch);
+ booleanSearchPanel.setVisible(false);
+ noBooleanSearch.setSelected(true);
+
+ booleanSearch.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(booleanSearch.getText().startsWith("Show "))
+ {
+ booleanSearch.setText("Hide Boolean Search Options");
+ booleanSearchPanel.setVisible(true);
+ }
+ else
+ {
+ booleanSearch.setText("Show Boolean Search Options");
+ booleanSearchPanel.setVisible(false);
+ }
+ panel.repaint();
+ FindAndReplace.this.pack();
+ FindAndReplace.this.setVisible(true);
+ }
+ });
+
+ // column 2
+ ypos = 0;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 1;
+ c.gridy = ypos;
+ panel.add(findTextField, c);
+
+ c.gridy = ++ypos;
+ panel.add(replaceTextField, c);
+
+ final JCheckBox selectedKeyButton = new JCheckBox("Restrict to a Key", false);
+ selectedKeyButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(selectedKeyButton.isSelected())
+ key_selector.setEnabled(true);
+ else
+ key_selector.setEnabled(false);
+ }
+ });
+ c.gridy = ++ypos;
+ panel.add(selectedKeyButton,c);
+
+ final JCheckBox selectedQualifierButton = new JCheckBox("Restrict to a Qualifier", false);
+ selectedQualifierButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(selectedQualifierButton.isSelected())
+ qualifier_selector.setEnabled(true);
+ else
+ qualifier_selector.setEnabled(false);
+ }
+ });
+ c.gridy = ++ypos;
+ panel.add(selectedQualifierButton,c);
+
+
+ final JButton findButton = new JButton("Find");
+ findButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(findTextField.getText().equals(""))
+ JOptionPane.showMessageDialog(FindAndReplace.this,
+ "No text entered.", "No Text", JOptionPane.WARNING_MESSAGE);
+
+ if(deleteQualifier.isSelected())
+ {
+ int status = JOptionPane.showConfirmDialog(FindAndReplace.this,
+ "Delete the matching qualifiers?",
+ "Delete", JOptionPane.OK_CANCEL_OPTION);
+
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+ }
+
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ Key key = null;
+ if(selectedKeyButton.isSelected())
+ key = key_selector.getSelectedItem();
+
+ String qualifierName = null;
+ if(selectedQualifierButton.isSelected())
+ qualifierName = (String) qualifier_selector.getSelectedItem();
+
+ final FeaturePredicate predicate;
+ if(qualifierValueUseBoolean.isSelected() ||
+ qualifierValueMatchAny.isSelected() ||
+ qualifierValueMatchAll.isSelected())
+ {
+ String findText = findTextField.getText();
+
+ if(qualifierValueMatchAny.isSelected())
+ findText = findText.trim().replaceAll("\\s+", " | ");
+ else if(qualifierValueMatchAll.isSelected())
+ findText = findText.trim().replaceAll("\\s+", " & ");
+ predicate = constructFeaturePredicateFromBooleanList(
+ findText, key, qualifierName,
+ qualifierValueSubString.isSelected(), !caseSensitive.isSelected(),
+ deleteQualifier.isSelected());
+ }
+ else
+ predicate = new FeatureKeyQualifierPredicate(key, qualifierName,
+ findTextField.getText(),
+ qualifierValueSubString.isSelected(),
+ !caseSensitive.isSelected(),
+ deleteQualifier.isSelected());
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(entry_group, predicate, findTextField.getText());
+
+ if(filtered_entry_group.getAllFeaturesCount() < 1)
+ JOptionPane.showMessageDialog(FindAndReplace.this, "No matches found.");
+ else
+ {
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame("Features Found",
+ selection,
+ goto_event_source, filtered_entry_group,
+ base_plot_group);
+ feature_list_frame.setVisible(true);
+ }
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ });
+ ypos+=8;
+ c.gridx = 0;
+ c.gridy = ++ypos;
+ panel.add(findButton, c);
+
+ final JButton replaceButton = new JButton("Replace");
+ replaceButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(qualifierValueUseBoolean.isSelected())
+ {
+ int val = JOptionPane.showConfirmDialog(
+ FindAndReplace.this,
+ "Boolean operators can ONLY be used with the Find function.",
+ "Replace", JOptionPane.OK_CANCEL_OPTION);
+ if(val == JOptionPane.CANCEL_OPTION)
+ return;
+ }
+
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ Key key = null;
+ if(selectedKeyButton.isSelected())
+ key = key_selector.getSelectedItem();
+
+ String qualifierName = null;
+ StringVector qualifierStrings = null;
+ if(selectedQualifierButton.isSelected())
+ {
+ qualifierName = (String) qualifier_selector.getSelectedItem();
+ qualifierStrings = new StringVector();
+ qualifierStrings.add(qualifierName);
+ }
+
+ final FeaturePredicate predicate =
+ new FeatureKeyQualifierPredicate(key, qualifierName,
+ findTextField.getText(),
+ qualifierValueSubString.isSelected(),
+ !caseSensitive.isSelected(),
+ false);
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(entry_group, predicate, findTextField.getText());
+
+ entry_group.getActionController().startAction();
+
+ FeatureVector features = filtered_entry_group.getAllFeatures();
+
+ for(int i=0; i<features.size(); i++)
+ {
+ final Feature feature = features.elementAt(i);
+ feature.findOrReplaceText(findTextField.getText(),
+ !caseSensitive.isSelected(), qualifierValueSubString.isSelected(),
+ false,
+ qualifierStrings, replaceTextField.getText());
+ }
+
+ entry_group.getActionController().endAction();
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+/* final FeatureListFrame feature_list_frame =
+ new FeatureListFrame("Features Found and Text Replaced",
+ selection,
+ goto_event_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible(true);*/
+ }
+ });
+ c.gridx = 1;
+ panel.add(replaceButton, c);
+ }
+
+ /**
+ * Search and delete options for duplicate qualifiers on features
+ * @param selection
+ * @param goto_event_source
+ * @param entry_group
+ * @param base_plot_group
+ * @param tabPane
+ */
+ public void addFindDeleteDuplicateTab(final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group,
+ final JTabbedPane tabPane)
+ {
+ GridBagLayout gridbag = new GridBagLayout();
+ final JPanel panel = new JPanel(gridbag);
+ tabPane.addTab("Duplicate Qualifiers", panel);
+ panel.setLayout(gridbag);
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.WEST;
+ c.ipadx = 5;
+ c.ipady = 5;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 3;
+ panel.add(new JLabel("Search features for duplicate qualifiers:"), c);
+
+ c.gridwidth = 1;
+
+ final JButton findButton = new JButton("Find");
+ findButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final FeaturePredicate predicate = new FeaturePredicate ()
+ {
+ public boolean testPredicate (final Feature feature)
+ {
+ return findOrDeleteDuplicate(feature, false);
+ }
+ };
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(entry_group, predicate, "Features with duplicate qualifiers");
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame("Features Found",
+ selection,
+ goto_event_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible(true);
+ }
+ });
+ c.gridx = 0;
+ c.gridy = 1;
+ panel.add(findButton, c);
+
+ final JButton deleteButton = new JButton("Delete");
+ deleteButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+
+ final FeaturePredicate predicate = new FeaturePredicate ()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ return findOrDeleteDuplicate(feature, true);
+ }
+ };
+
+ entry_group.getActionController().startAction();
+ int ncount = 0;
+ final FeatureVector features = entry_group.getAllFeatures();
+ for(int i=0; i<features.size(); i++)
+ {
+ if(predicate.testPredicate(features.elementAt(i)))
+ ncount++;
+ }
+ entry_group.getActionController().endAction();
+
+ JOptionPane.showMessageDialog(FindAndReplace.this,
+ ( (ncount>0) ? "Duplicate qualifiers in "+ncount+" feature(s) deleted." :
+ "No duplicate qualifiers found."),
+ "Duplicate Qualifiers",
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ });
+ c.gridx = 1;
+ panel.add(deleteButton, c);
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ entry_group.removeEntryGroupChangeListener(FindAndReplace.this);
+ FindAndReplace.this.dispose();
+ }
+ });
+
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can get rid of the FinAndReplace when the
+ * EntryGroup is no longer in use(for example when the EntryEdit is
+ * closed).
+ **/
+ public void entryGroupChanged(EntryGroupChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryGroupChangeEvent.DONE_GONE:
+ entry_group.removeEntryGroupChangeListener(this);
+ dispose();
+ break;
+ }
+ }
+
+ /**
+ * Construct a FeaturePredicate from a string with conditional (& / |).
+ * @param text
+ * @param key
+ * @param qualifierName
+ * @param isSubString
+ * @param isCaseInsensitive
+ * @return
+ */
+ private FeaturePredicate constructFeaturePredicateFromBooleanList(
+ String text,
+ final Key key,
+ final String qualifierName,
+ final boolean isSubString,
+ final boolean isCaseInsensitive,
+ final boolean deleteQualifier)
+ {
+ text = text.replaceAll(" && ", " & ");
+ text = text.replaceAll(" (a|A)(n|N)(d|D) ", " & ");
+ text = text.replaceAll(" \\|\\| ", " \\| ");
+ text = text.replaceAll(" (o|O)(r|R) ", " \\| ");
+
+ final String valuesAnd[] = text.split("&");
+ final FeaturePredicateVector andPredicates = new FeaturePredicateVector();
+ final FeaturePredicateVector orPredicates = new FeaturePredicateVector();
+
+ // process string
+ for(int i=0; i<valuesAnd.length; i++)
+ {
+ if(valuesAnd[i].indexOf('|')>-1)
+ {
+ String valuesOr[] = valuesAnd[i].trim().split("\\|");
+ for(int j=0; j<valuesOr.length; j++)
+ {
+ orPredicates.add(new FeatureKeyQualifierPredicate(key, qualifierName,
+ valuesOr[j].trim(),
+ isSubString,
+ isCaseInsensitive,
+ deleteQualifier));
+ }
+ }
+ else
+ {
+ andPredicates.add(new FeatureKeyQualifierPredicate(key, qualifierName,
+ valuesAnd[i].trim(),
+ isSubString,
+ isCaseInsensitive,
+ deleteQualifier));
+ }
+ }
+
+ if(andPredicates.size() == 0 && orPredicates.size() == 0)
+ return new FeatureKeyQualifierPredicate(key, qualifierName,
+ text, isSubString, isCaseInsensitive, deleteQualifier);
+ else if(andPredicates.size() == 0)
+ return new FeaturePredicateConjunction(orPredicates, FeaturePredicateConjunction.OR);
+ else if(orPredicates.size() == 0)
+ return new FeaturePredicateConjunction(andPredicates, FeaturePredicateConjunction.AND);
+
+ // combine & / | results
+ FeaturePredicateConjunction andPredicateConj =
+ new FeaturePredicateConjunction(andPredicates, FeaturePredicateConjunction.AND);
+ FeaturePredicateConjunction orPredicateConj =
+ new FeaturePredicateConjunction(orPredicates, FeaturePredicateConjunction.OR);
+ return new FeaturePredicateConjunction(andPredicateConj,
+ orPredicateConj,
+ FeaturePredicateConjunction.AND);
+ }
+
+ /**
+ * Return true if duplicate qualifier values found
+ * @param feature The text to search for.
+ * @param delete
+ **/
+ private boolean findOrDeleteDuplicate(final Feature feature, final boolean delete)
+ {
+ final QualifierVector qualifiers = feature.getQualifiers();
+ final QualifierVector newQualifiers = new QualifierVector();
+
+ for(int i = 0; i < qualifiers.size(); ++i)
+ {
+ final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(i);
+ final StringVector values = this_qualifier.getValues();
+
+ if(values != null)
+ {
+ StringVector newValues = new StringVector();
+ int val_size = values.size();
+
+ for(int values_index = 0; values_index < val_size;
+ ++values_index)
+ {
+ String this_value_string = (String)values.elementAt(values_index);
+ if(this_value_string == null)
+ continue;
+
+ if(!newValues.contains(this_value_string) ||
+ this_value_string.startsWith("LAZY LOADING")) // ignore lazy data not loaded yet
+ newValues.add(this_value_string);
+ else if(!delete)
+ return true;
+ }
+
+ if(values.size() != newValues.size())
+ {
+ if(newValues.size() != 0)
+ newQualifiers.setQualifier(
+ new Qualifier(this_qualifier.getName(),newValues));
+ else
+ newQualifiers.setQualifier(
+ new Qualifier(this_qualifier.getName()));
+ }
+ }
+ }
+
+ if(!delete || newQualifiers.size() == 0)
+ return false;
+
+ for(int i = 0; i < newQualifiers.size(); i++)
+ {
+ try
+ {
+ feature.setQualifier((Qualifier) newQualifiers.elementAt(i));
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/GotoMenu.java b/uk/ac/sanger/artemis/components/GotoMenu.java
new file mode 100644
index 0000000..da890b3
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/GotoMenu.java
@@ -0,0 +1,430 @@
+/* GotoMenu.java
+ *
+ * created: Thu Jan 7 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/GotoMenu.java,v 1.3 2006-01-17 16:05:05 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.Marker;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import javax.swing.JFrame;
+import javax.swing.JMenuItem;
+import javax.swing.KeyStroke;
+
+/**
+ * A JMenu with commands for moving around the entries.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GotoMenu.java,v 1.3 2006-01-17 16:05:05 tjc Exp $
+ **/
+
+public class GotoMenu extends SelectionMenu {
+
+ private static final long serialVersionUID = 1L;
+
+ /** GotoEventSource object that was passed to the constructor */
+ private GotoEventSource goto_event_source;
+
+ /**
+ * Set by the constructor to the reference of the first feature in
+ * selection_features or null if selection_features is empty.
+ **/
+ private Feature selection_feature;
+
+ /**
+ * Set by the constructor to the reference of the first segment in
+ * selection_segments or null if selection_segments is empty.
+ **/
+ private FeatureSegment selection_segment;
+
+ /** shortcut for the Navigator */
+ private final static KeyStroke NAVIGATOR_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_G,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+
+ /** shortcut to go to the start of the selection */
+ private final static KeyStroke START_OF_SELECTION_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_LEFT,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+
+ /** shortcut to go to the end of the selection */
+ private final static KeyStroke END_OF_SELECTION_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_RIGHT,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+
+ /** shortcut to go to the start of the sequence */
+ private final static KeyStroke START_OF_SEQUENCE_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_UP,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+
+ /** shortcut to go to the end of the sequence */
+ private final static KeyStroke END_OF_SEQUENCE_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_DOWN,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+
+ /** shortcut to go to the start of the feature */
+ private final static KeyStroke START_OF_FEATURE_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_COMMA, KeyEvent.SHIFT_MASK);
+
+ /** shortcut to go to the start of the feature */
+ private final static KeyStroke END_OF_FEATURE_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_PERIOD, KeyEvent.SHIFT_MASK);
+
+ /**
+ * Create a new GotoMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param goto_event_source The object the we will call makeBaseVisible ()
+ * on.
+ * @param entry_group The EntryGroup object used to create the Navigator
+ * component.
+ * @param menu_name The name of the new menu.
+ **/
+ public GotoMenu (final JFrame frame,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final String menu_name) {
+ super (frame, menu_name, selection);
+ this.goto_event_source = goto_event_source;
+
+ final JMenuItem navigator_item = new JMenuItem ("Navigator ...");
+ navigator_item.setAccelerator (NAVIGATOR_KEY);
+ navigator_item.addActionListener(new ActionListener () {
+ private Navigator navigator = null;
+ public void actionPerformed (ActionEvent event) {
+ if(navigator == null || navigator.isVisible())
+ {
+ if(navigator != null)
+ navigator.setDisposeOnClose(true);
+ navigator = new Navigator (getSelection (),
+ GotoMenu.this.goto_event_source,
+ entry_group);
+ }
+ else
+ navigator.setVisible(true);
+ }
+ });
+
+ final JMenuItem goto_selection_start_item = new JMenuItem ("Start of Selection");
+ goto_selection_start_item.setAccelerator (START_OF_SELECTION_KEY);
+ goto_selection_start_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ makeSelectionStartVisible ();
+ }
+ });
+
+ final JMenuItem goto_selection_end_item = new JMenuItem ("End of Selection");
+ goto_selection_end_item.setAccelerator (END_OF_SELECTION_KEY);
+ goto_selection_end_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ makeSelectionEndVisible ();
+ }
+ });
+
+ final JMenuItem goto_feature_start_item = new JMenuItem ("Feature Start");
+ goto_feature_start_item.setAccelerator(START_OF_FEATURE_KEY);
+ goto_feature_start_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ gotoFeatureStart ();
+ }
+ });
+
+ final JMenuItem goto_feature_end_item = new JMenuItem ("Feature End");
+ goto_feature_end_item.setAccelerator(END_OF_FEATURE_KEY);
+ goto_feature_end_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ gotoFeatureEnd ();
+ }
+ });
+
+ final JMenuItem goto_feature_base = new JMenuItem ("Feature Base Position ...");
+ goto_feature_base.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ gotoFeaturePosition (false);
+ }
+ });
+
+ final JMenuItem goto_feature_aa_position = new JMenuItem ("Feature Amino Acid ...");
+ goto_feature_aa_position.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ gotoFeaturePosition (true);
+ }
+ });
+
+
+ final JMenuItem goto_first_item = new JMenuItem ("Start of Sequence");
+ goto_first_item.setAccelerator (START_OF_SEQUENCE_KEY);
+ goto_first_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ goto_event_source.gotoFirstBase ();
+ }
+ });
+
+ final JMenuItem goto_last_item = new JMenuItem ("End of Sequence");
+ goto_last_item.setAccelerator (END_OF_SEQUENCE_KEY);
+ goto_last_item.addActionListener(new ActionListener () {
+ public void actionPerformed (ActionEvent event) {
+ goto_event_source.gotoLastBase ();
+ }
+ });
+
+ add (navigator_item);
+ addSeparator ();
+ add (goto_selection_start_item);
+ add (goto_selection_end_item);
+ add (goto_feature_start_item);
+ add (goto_feature_end_item);
+ add (goto_first_item);
+ add (goto_last_item);
+ add (goto_feature_base);
+ add (goto_feature_aa_position);
+ }
+
+ /**
+ * Create a new GotoMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param goto_event_source The object the we will call makeBaseVisible ()
+ * on.
+ * @param entry_group The EntryGroup object used to create the Navigator
+ * component.
+ **/
+ public GotoMenu (final JFrame frame,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group) {
+ this (frame, selection, goto_event_source, entry_group, "Goto");
+ }
+
+ /**
+ * This method sends an GotoEvent to all the GotoEvent listeners that will
+ * make the first base of the selection visible.
+ **/
+ private void makeSelectionStartVisible () {
+ final GotoEvent new_event =
+ new GotoEvent (this, getSelection ().getStartBaseOfSelection ());
+
+ goto_event_source.sendGotoEvent (new_event);
+ }
+
+ /**
+ * This method sends an GotoEvent to all the GotoEvent listeners that will
+ * make the last base of the selection visible.
+ **/
+ private void makeSelectionEndVisible () {
+ final GotoEvent new_event =
+ new GotoEvent (this, getSelection ().getEndBaseOfSelection ());
+
+ goto_event_source.sendGotoEvent (new_event);
+ }
+
+ /**
+ * Goto the start of the (first) selected feature. (FeatureDisplay only.)
+ **/
+ private void gotoFeatureStart () {
+ setInternalVariables ();
+ if (selection_feature == null) {
+ if (selection_segment == null) {
+ // do nothing
+ } else {
+ // go to the start of the parent feature of the first selected segment
+ final Feature segment_feature = selection_segment.getFeature ();
+ makeFeatureStartVisible (segment_feature);
+ }
+ } else {
+ makeFeatureStartVisible (selection_feature);
+ }
+ }
+
+ /**
+ * Goto the end of the (first) selected feature. (FeatureDisplay only.)
+ **/
+ private void gotoFeatureEnd () {
+ setInternalVariables ();
+ if (selection_feature == null) {
+ if (selection_segment == null) {
+ // do nothing
+ } else {
+ // go to the end of the parent feature of the first selected segment
+ final Feature segment_feature = selection_segment.getFeature ();
+ makeFeatureEndVisible (segment_feature);
+ }
+ } else {
+ makeFeatureEndVisible (selection_feature);
+ }
+ }
+
+ /**
+ * This method will ask the user for a base or amino acid position within
+ * the first selected feature (using a TextRequester component) and will
+ * then goto that position.
+ * @param goto_aa_position If true goto an amino acid position, otherwise
+ * goto a base position
+ **/
+ private void gotoFeaturePosition (final boolean goto_aa_position) {
+ if (!checkForSelectionFeatures ()) {
+ return;
+ }
+
+ final FeatureVector selected_features = getSelection ().getAllFeatures ();
+
+ if (selected_features.size () > 1) {
+ new MessageDialog (getParentFrame (), "select only one feature");
+ return;
+ }
+
+ final Feature first_feature = selected_features.elementAt (0);
+
+ final TextRequester text_requester =
+ goto_aa_position ?
+ new TextRequester ("amino acid position within selected feature:",
+ 18, "") :
+ new TextRequester ("base position within selected feature:",
+ 18, "");
+
+ text_requester.addTextRequesterListener (new TextRequesterListener () {
+ public void actionPerformed (final TextRequesterEvent event) {
+ final String position_string = event.getRequesterText ().trim ();
+
+ if (position_string.length () == 0) {
+ return;
+ }
+
+ try {
+ final int feature_position =
+ Integer.valueOf (position_string).intValue ();
+
+ final Marker sequence_marker;
+
+ if (goto_aa_position) {
+ // base and aa positions are numbered from 1
+ final int aa_position = (feature_position - 1) * 3 + 1;
+
+ sequence_marker =
+ first_feature.getPositionInSequence (aa_position);
+ } else {
+ sequence_marker =
+ first_feature.getPositionInSequence (feature_position);
+ }
+
+ final MarkerRange range = new MarkerRange (sequence_marker);
+
+ goto_event_source.gotoBase (sequence_marker);
+
+ /*final*/ MarkerRange selection_range;
+
+ if (goto_aa_position) {
+ // we want to select the whole codon if we are going to a amino
+ // acid position
+ try {
+ final MarkerRange codon_end_marker_range =
+ new MarkerRange (sequence_marker.moveBy (2));
+ selection_range =
+ range.combineRanges (codon_end_marker_range, false);
+ } catch (OutOfRangeException e) {
+ // just select one base
+ selection_range = range;
+ }
+ } else {
+ selection_range = range;
+ }
+
+ // select that base (or range)
+ getSelection ().setMarkerRange (selection_range);
+
+ } catch (NumberFormatException e) {
+ new MessageDialog (getParentFrame (),
+ "this is not a number: " + position_string);
+ } catch (OutOfRangeException e) {
+ new MessageDialog (getParentFrame (), "the base position is not " +
+ "within the selection feature: " +
+ position_string);
+ }
+ }
+ });
+
+ text_requester.setVisible(true);
+ }
+
+ /**
+ * This method sends an GotoEvent to all the GotoEvent listeners that will
+ * make the given base visible.
+ **/
+ private void makeBaseVisible (final Marker base_marker) {
+ goto_event_source.gotoBase (base_marker);
+ }
+
+ /**
+ * Scroll the display so that the start of the given feature is visible
+ * (it's start will be in the middle of the screen after the call).
+ * @param feature The feature to make visible.
+ **/
+ private void makeFeatureStartVisible (Feature feature) {
+ if (feature == null)
+ return;
+ makeBaseVisible (feature.getFirstBaseMarker ());
+ }
+
+ /**
+ * Scroll the display so that the start of the given feature is visible
+ * (it's start will be in the middle of the screen after the call).
+ * @param feature The feature to make visible.
+ **/
+ private void makeFeatureEndVisible (Feature feature) {
+ if (feature == null)
+ return;
+ makeBaseVisible (feature.getLastBaseMarker ());
+ }
+
+ /**
+ * Initialise selection_feature and selection_segment.
+ **/
+ private void setInternalVariables () {
+ final FeatureVector selection_features =
+ getSelection ().getSelectedFeatures ();
+ final FeatureSegmentVector selection_segments =
+ getSelection ().getSelectedSegments ();
+
+ if (selection_features.size () > 0) {
+ selection_feature = selection_features.elementAt (0);
+ } else {
+ selection_feature = null;
+ }
+
+ if (selection_segments.size () > 0) {
+ selection_segment = selection_segments.elementAt (0);
+ } else {
+ selection_segment = null;
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/GraphMenu.java b/uk/ac/sanger/artemis/components/GraphMenu.java
new file mode 100644
index 0000000..d421c9b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/GraphMenu.java
@@ -0,0 +1,652 @@
+/* GraphMenu.java
+ *
+ * created: Tue Sep 18 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/GraphMenu.java,v 1.11 2009-08-03 10:29:32 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.chado.Graph;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.plot.Algorithm;
+import uk.ac.sanger.artemis.plot.BaseAlgorithm;
+import uk.ac.sanger.artemis.plot.CodonUsageAlgorithm;
+import uk.ac.sanger.artemis.plot.CodonUsageWeight;
+import uk.ac.sanger.artemis.plot.UserDataAlgorithm;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.Document;
+
+import java.awt.Cursor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.IOException;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.*;
+
+/**
+ * This menu controls one particular BasePlotGroup.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: GraphMenu.java,v 1.11 2009-08-03 10:29:32 tjc Exp $
+ **/
+
+public class GraphMenu extends JMenu
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The JFrame reference that was passed to the constructor.
+ **/
+ private JFrame frame;
+
+ /**
+ * The BasePlotGroup that was passed to the constructor.
+ **/
+ private BasePlotGroup base_plot_group;
+
+ /**
+ * The EntryGroup object that was passed to the constructor.
+ **/
+ private EntryGroup entry_group = null;
+
+ /**
+ * The FeatureDisplay that was passed to the constructor.
+ **/
+ private FeatureDisplay feature_display;
+
+ private JMenu menuSubMenu = new JMenu("Other Graphs");
+
+ /**
+ * This list of the CheckboxMenuItems for the graphs is stored so that we
+ * can turn them all off with "Hide All Graphs".
+ **/
+ private Vector<JCheckBoxMenuItem> algorithm_menu_items = new Vector <JCheckBoxMenuItem>();
+
+ private JSplitPane splitPane;
+
+ /**
+ * Create a new GraphMenu object and all it's menu items.
+ * @param frame The JFrame that owns this JMenu.
+ * @param entry_group The EntryGroup containing the sequence to plot.
+ * @param base_plot_group The BasePlotGroup that this menu controls.
+ * @param view_menu This ViewMenu is updated when a usage plot is added.
+ * @param add_menu This AddMenu is updated when a usage plot is added.
+ * @param menu_name The name of the new menu.
+ **/
+ public GraphMenu (final JFrame frame,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group,
+ final FeatureDisplay feature_display,
+ final String menu_name,
+ final JSplitPane splitPane,
+ final int index)
+ {
+ super (menu_name);
+ this.frame = frame;
+ this.entry_group = entry_group;
+ this.base_plot_group = base_plot_group;
+ this.feature_display = feature_display;
+ this.splitPane = splitPane;
+
+ final BaseAlgorithm [] orig_algorithms =
+ base_plot_group.getPlotAlgorithms ();
+
+ final JMenuItem hide_all_graphs_item = new JMenuItem ("Hide All Graphs");
+ hide_all_graphs_item.addActionListener (new ActionListener ()
+ {
+ public void actionPerformed (ActionEvent event)
+ {
+ final BaseAlgorithm [] current_algorithms =
+ base_plot_group.getPlotAlgorithms ();
+
+ for (int i = 0 ; i < current_algorithms.length ; ++i)
+ {
+ final BaseAlgorithm this_algorithm = current_algorithms[i];
+
+ base_plot_group.setVisibleByAlgorithm (this_algorithm, false);
+
+ final JCheckBoxMenuItem this_menu_item =
+ algorithm_menu_items.elementAt (i);
+
+ this_menu_item.setState (false);
+ }
+ if (getParent () != null)
+ {
+ // XXX change to revalidate().
+ frame.validate ();
+ }
+ }
+ });
+ add (hide_all_graphs_item);
+
+ addSeparator ();
+
+ if (Options.readWritePossible ())
+ {
+ final JMenuItem usage_plot_item = new JMenuItem ("Add Usage Plots ...");
+ usage_plot_item.addActionListener (new ActionListener ()
+ {
+ public void actionPerformed (ActionEvent event)
+ {
+ addUsagePlot ();
+ adjustSplitPane(true);
+ }
+ });
+ add (usage_plot_item);
+
+ final JMenuItem user_plot_item = new JMenuItem ("Add User Plot ...");
+ user_plot_item.addActionListener (new ActionListener ()
+ {
+ public void actionPerformed (ActionEvent event)
+ {
+ try
+ {
+ addUserPlot ((uk.ac.sanger.artemis.util.Document)null, false);
+ adjustSplitPane(true);
+ }
+ catch(java.lang.OutOfMemoryError emem)
+ {
+ JOptionPane.showMessageDialog(frame,
+ "Out of memory. Increase the maximum memory"+
+ "\navailable for this application and try again.",
+ "Out Of Memory",
+ JOptionPane.WARNING_MESSAGE);
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+
+ add (user_plot_item);
+
+ if(getEntryGroup().getSequenceEntry().getEMBLEntry() instanceof
+ DatabaseDocumentEntry)
+ {
+ final JMenu database_user_plot = new JMenu("Add Database Plot ...");
+ final DatabaseDocument dbDoc =
+ (DatabaseDocument) ((DocumentEntry) entry_group
+ .getSequenceEntry().getEMBLEntry()).getDocument();
+
+ try
+ {
+ boolean hasGraphs = addDatabaseUserPlot(dbDoc, database_user_plot);
+ database_user_plot.setEnabled(hasGraphs);
+ add(database_user_plot);
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ addSeparator ();
+ }
+
+ boolean useSubMenu = false;
+ for (int i = 0 ; i < orig_algorithms.length ; ++i)
+ {
+ final BaseAlgorithm this_algorithm = orig_algorithms[i];
+
+ if(this_algorithm.getAlgorithmName().startsWith("Cumulative AT"))
+ useSubMenu = true;
+ addAlgorithm (this_algorithm, false, useSubMenu);
+ }
+ if(useSubMenu)
+ add(menuSubMenu);
+
+ if (Options.getOptions ().getProperty ("codon_usage_file") != null)
+ {
+ final String codon_usage_file_name =
+ Options.getOptions ().getProperty ("codon_usage_file");
+
+ try
+ {
+ addUsagePlot (new File (codon_usage_file_name), true, false);
+ addUsagePlot (new File (codon_usage_file_name), false, false);
+
+ if (getParent () != null)
+ {
+ // XXX change to revalidate().
+ frame.validate ();
+ }
+ }
+ catch (IOException e)
+ {
+ new MessageDialog (frame, "error while reading usage data: " + e);
+ }
+ }
+
+ // add user plots from the command line JVM option
+ if(System.getProperty("userplot"+ (index > 0 ? index : "")) != null ||
+ System.getProperty("loguserplot"+ (index > 0 ? index : "")) != null)
+ {
+ String plots[] = new String[]{};
+ if(System.getProperty("userplot"+ (index > 0 ? index : "")) != null)
+ plots = System.getProperty("userplot"+ (index > 0 ? index : "")).split("[\\s,]");
+
+ String logplots[] = new String[]{};
+ if(System.getProperty("loguserplot"+ (index > 0 ? index : "")) != null)
+ logplots = System.getProperty("loguserplot"+ (index > 0 ? index : "")).split("[\\s,]");
+ try
+ {
+ for(int i=0;i<plots.length; i++)
+ addUserPlot (plots[i], false);
+
+ for(int i=0;i<logplots.length; i++)
+ addUserPlot (logplots[i], true);
+
+ splitPane.setDividerSize(3);
+ splitPane.setDividerLocation(150);
+ }
+ catch(Exception e){}
+ }
+ }
+
+ /**
+ * Create a new GraphMenu object and all it's menu items.
+ * @param frame The JFrame that owns this JMenu.
+ * @param entry_group The EntryGroup containing the sequence to plot.
+ * @param base_plot_group The BasePlotGroup that this menu controls.
+ * @param view_menu This ViewMenu is updated when a usage plot is added.
+ * @param add_menu This AddMenu is updated when a usage plot is added.
+ * @param menu_name The name of the new menu.
+ **/
+ public GraphMenu (final JFrame frame,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group,
+ final FeatureDisplay feature_display,
+ final JSplitPane splitPane)
+ {
+ this (frame, entry_group, base_plot_group, feature_display, "Graph",splitPane, -1);
+ }
+
+ /**
+ * Add a menu item for the given algorithm to the Display menu.
+ * @param is_visible The JCheckBoxMenuItem starts in the true state if and
+ * only if this is true.
+ * @return The new JCheckBoxMenuItem
+ **/
+ private JCheckBoxMenuItem addAlgorithm (final BaseAlgorithm algorithm,
+ final boolean is_visible,
+ final boolean useSubMenu)
+ {
+ final JCheckBoxMenuItem new_item =
+ new JCheckBoxMenuItem (algorithm.getAlgorithmName (), is_visible);
+
+ new_item.addItemListener (new ItemListener ()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ base_plot_group.setVisibleByAlgorithm (algorithm,
+ new_item.isSelected());
+ adjustSplitPane(new_item.isSelected());
+ }
+ });
+
+ if(useSubMenu)
+ menuSubMenu.add(new_item);
+ else
+ add (new_item);
+
+ algorithm_menu_items.addElement (new_item);
+
+ return new_item;
+ }
+
+ /**
+ * Adjust the JSplitPane divider position if all plots are
+ * hidden or if a single graph has been made visible.
+ * @param thisGraphOn
+ */
+ private void adjustSplitPane(final boolean thisGraphOn)
+ {
+ if(splitPane == null)
+ return;
+
+ final BaseAlgorithm [] current_algorithms =
+ base_plot_group.getPlotAlgorithms ();
+
+ boolean usageDisplayed = false;
+ int nvisible = 0;
+ for (int i = 0 ; i < current_algorithms.length ; ++i)
+ {
+ final JCheckBoxMenuItem this_menu_item =
+ algorithm_menu_items.elementAt (i);
+
+ if(this_menu_item.isSelected())
+ {
+ nvisible++;
+ if(this_menu_item.getText().indexOf("Codon Usage") > -1)
+ usageDisplayed = true;
+ else
+ usageDisplayed = false;
+ }
+ }
+
+ if(nvisible == 0)
+ {
+ splitPane.setDividerSize(0);
+ splitPane.setDividerLocation(0);
+ }
+ else if( ( thisGraphOn && nvisible == 1 ) ||
+ (usageDisplayed && nvisible == 2) ||
+ (splitPane.getDividerLocation() == 0))
+ {
+ splitPane.setDividerSize(3);
+ splitPane.setDividerLocation(0.2d);
+ }
+ }
+
+ /**
+ * Ask the user for a file name, then read the codon usage data from that
+ * file, then make and add forward and a reverse BasePlot component using
+ * the data.
+ **/
+ private void addUsagePlot ()
+ {
+ final JFrame frame = Utilities.getComponentFrame (base_plot_group);
+
+ final StickyFileChooser dialog = new StickyFileChooser ();
+
+ dialog.setDialogTitle ("Select a codon usage data file name ...");
+ dialog.setDialogType (JFileChooser.OPEN_DIALOG);
+
+ final int status = dialog.showOpenDialog (frame);
+
+ if (status != JFileChooser.APPROVE_OPTION ||
+ dialog.getSelectedFile () == null)
+ {
+ return;
+ }
+
+ final File file =
+ new File (dialog.getCurrentDirectory (),
+ dialog.getSelectedFile ().getName ());
+
+ if (file.length () != 0)
+ {
+ try
+ {
+ final BasePlot new_forward_plot =
+ addUsagePlot (file, true, true);
+ final BasePlot new_reverse_plot =
+ addUsagePlot (file, false, true);
+
+ final Algorithm forward_algorithm = new_forward_plot.getAlgorithm ();
+ final Algorithm reverse_algorithm = new_reverse_plot.getAlgorithm ();
+
+ base_plot_group.setVisibleByAlgorithm (forward_algorithm, true);
+ base_plot_group.setVisibleByAlgorithm (reverse_algorithm, true);
+ }
+ catch (IOException e)
+ {
+ new MessageDialog (Utilities.getComponentFrame (base_plot_group),
+ "error while reading usage data: " + e);
+ }
+ }
+ }
+
+ /**
+ * Read the codon usage data from the given File, then make and add a
+ * BasePlot component using the data.
+ * @param use_forward_strand The plot will be a forward plot if and only if
+ * this is true.
+ * @param is_visible The plot will start off visible if and only if this is
+ * true.
+ * @return The BasePlot that was added.
+ **/
+ private BasePlot addUsagePlot (final File codon_usage_file,
+ final boolean use_forward_strand,
+ final boolean is_visible)
+ throws IOException
+ {
+ final CodonUsageAlgorithm codon_usage_algorithm;
+
+ if (use_forward_strand)
+ {
+ final Strand forward_strand =
+ entry_group.getBases ().getForwardStrand ();
+
+ final CodonUsageWeight usage_weights =
+ new CodonUsageWeight (codon_usage_file, forward_strand);
+
+ codon_usage_algorithm =
+ new CodonUsageAlgorithm (forward_strand, usage_weights);
+ }
+ else
+ {
+ final Strand backward_strand =
+ entry_group.getBases ().getReverseStrand ();
+
+ final CodonUsageWeight usage_weights =
+ new CodonUsageWeight (codon_usage_file, backward_strand);
+
+ codon_usage_algorithm =
+ new CodonUsageAlgorithm (backward_strand, usage_weights);
+ }
+
+ addAlgorithm (codon_usage_algorithm, is_visible, false);
+
+ final BasePlot new_plot =
+ base_plot_group.addAlgorithm (codon_usage_algorithm);
+
+ base_plot_group.setVisibleByAlgorithm (codon_usage_algorithm, is_visible);
+
+ // XXX hack to force the BasePlot to initialise
+ final DisplayAdjustmentEvent event =
+ new DisplayAdjustmentEvent (this,
+ feature_display.getFirstVisibleForwardBase (),
+ feature_display.getLastVisibleForwardBase (),
+ feature_display.getMaxVisibleBases (),
+ feature_display.getScaleValue (),
+ feature_display.getScaleFactor (),
+ feature_display.isRevCompDisplay (),
+ DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT);
+
+ base_plot_group.displayAdjustmentValueChanged (event);
+
+ return new_plot;
+ }
+
+ /**
+ * Add a UserDataAlgorithm from the database to the display.
+ * This returns true only if graph data is found.
+ * @param dbDoc
+ * @param database_user_plot
+ * @return
+ */
+ private boolean addDatabaseUserPlot(final DatabaseDocument dbDoc,
+ final JMenu database_user_plot)
+ {
+ List graphs = dbDoc.getGraphs();
+ if(graphs == null || graphs.size() == 0)
+ return false;
+
+ for (int i = 0; i < graphs.size(); i++)
+ {
+ final Graph graph = (Graph) graphs.get(i);
+ JMenuItem menuGraph = new JMenuItem(graph.getName());
+ database_user_plot.add(menuGraph);
+
+ menuGraph.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ Document doc = dbDoc.getGraphData(graph.getGraphId(), graph.getName());
+ final JCheckBox logTransform = new JCheckBox("Use log(data+1)", false);
+ JOptionPane.showMessageDialog(frame, logTransform,
+ "Transform", JOptionPane.QUESTION_MESSAGE);
+ frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ try
+ {
+ UserDataAlgorithm new_algorithm = new UserDataAlgorithm(
+ getEntryGroup().getBases().getForwardStrand(), doc,
+ logTransform.isSelected());
+ final BasePlot new_base_plot = base_plot_group.addAlgorithm(new_algorithm);
+
+ base_plot_group.setVisibleByAlgorithm(new_algorithm, true);
+ addAlgorithm(new_algorithm, true, false);
+
+ // XXX hack to force the BasePlot to initialise
+ final DisplayAdjustmentEvent e = new DisplayAdjustmentEvent(
+ this, feature_display.getFirstVisibleForwardBase(),
+ feature_display.getLastVisibleForwardBase(),
+ feature_display.getMaxVisibleBases(),
+ feature_display.getScaleValue(),
+ feature_display.getScaleFactor(),
+ feature_display.isRevCompDisplay(),
+ DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT);
+
+ base_plot_group.displayAdjustmentValueChanged(e);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ adjustSplitPane(true);
+ }
+ catch (java.lang.OutOfMemoryError emem)
+ {
+ JOptionPane.showMessageDialog(frame,
+ "Out of memory. Increase the maximum memory"
+ + "\navailable for this application and try again.",
+ "Out Of Memory", JOptionPane.WARNING_MESSAGE);
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+ }
+ return true;
+ }
+
+ private void addUserPlot (final String plot, final boolean isLog) throws MalformedURLException
+ {
+ if (plot.startsWith("http:") || plot.startsWith("file:") || plot.startsWith("ftp:"))
+ addUserPlot(new uk.ac.sanger.artemis.util.URLDocument(new URL(plot)), isLog);
+ else
+ {
+ File f = new File(plot);
+ if (f.exists())
+ addUserPlot(new uk.ac.sanger.artemis.util.FileDocument(f), isLog);
+ else
+ System.err.println(plot + " not found.");
+ }
+ }
+
+ /**
+ * Add a UserDataAlgorithm to the display.
+ **/
+ private void addUserPlot (uk.ac.sanger.artemis.util.Document document,
+ boolean isLog)
+ {
+ final JCheckBox logTransform = new JCheckBox("Use log(data+1)", isLog);
+ if (document == null)
+ {
+ final JFrame frame = Utilities.getComponentFrame(base_plot_group);
+ final StickyFileChooser dialog = new StickyFileChooser();
+
+ dialog.setDialogTitle("Select a data file name ...");
+ dialog.setDialogType(JFileChooser.OPEN_DIALOG);
+
+ dialog.setAccessory(logTransform);
+
+ final int status = dialog.showOpenDialog(frame);
+ if(status != JFileChooser.APPROVE_OPTION ||
+ dialog.getSelectedFile() == null)
+ {
+ return;
+ }
+
+ File file = new File(dialog.getCurrentDirectory(),
+ dialog.getSelectedFile().getName());
+
+ document =
+ new uk.ac.sanger.artemis.util.FileDocument (file);
+ }
+
+ frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+
+ final Strand forward_strand =
+ getEntryGroup ().getBases ().getForwardStrand ();
+
+ try
+ {
+ final UserDataAlgorithm new_algorithm =
+ new UserDataAlgorithm (forward_strand, document, logTransform.isSelected());
+
+ final BasePlot new_base_plot =
+ base_plot_group.addAlgorithm (new_algorithm);
+
+ base_plot_group.setVisibleByAlgorithm (new_algorithm, true);
+
+ addAlgorithm (new_algorithm, true, false);
+
+ // XXX hack to force the BasePlot to initialise
+ final DisplayAdjustmentEvent event =
+ new DisplayAdjustmentEvent (this,
+ feature_display.getFirstVisibleForwardBase (),
+ feature_display.getLastVisibleForwardBase (),
+ feature_display.getMaxVisibleBases (),
+ feature_display.getScaleValue (),
+ feature_display.getScaleFactor (),
+ feature_display.isRevCompDisplay (),
+ DisplayAdjustmentEvent.ALL_CHANGE_ADJUST_EVENT);
+
+ base_plot_group.displayAdjustmentValueChanged (event);
+
+ if (getParent () != null)
+ {
+ // XXX change to revalidate().
+ frame.validate ();
+ }
+ }
+ catch (IOException e)
+ {
+ new MessageDialog (Utilities.getComponentFrame (base_plot_group),
+ "error while reading user data: " + e);
+ }
+
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+
+ /**
+ * Return the EntryGroup that was passed to the constructor.
+ **/
+ private EntryGroup getEntryGroup ()
+ {
+ return entry_group;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/IndexReferenceEvent.java b/uk/ac/sanger/artemis/components/IndexReferenceEvent.java
new file mode 100644
index 0000000..5c6151a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/IndexReferenceEvent.java
@@ -0,0 +1,14 @@
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.ChangeEvent;
+
+public class IndexReferenceEvent extends ChangeEvent
+{
+ private static final long serialVersionUID = 1L;
+
+ public IndexReferenceEvent(Object source)
+ {
+ super(source);
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/IndexReferenceListener.java b/uk/ac/sanger/artemis/components/IndexReferenceListener.java
new file mode 100644
index 0000000..33b0d60
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/IndexReferenceListener.java
@@ -0,0 +1,9 @@
+package uk.ac.sanger.artemis.components;
+
+public interface IndexReferenceListener extends uk.ac.sanger.artemis.ChangeListener
+{
+ /**
+ * Invoked when a component scrolls or changes the scale.
+ **/
+ void indexReferenceChanged (IndexReferenceEvent event);
+}
diff --git a/uk/ac/sanger/artemis/components/InputStreamProgressDialog.java b/uk/ac/sanger/artemis/components/InputStreamProgressDialog.java
new file mode 100644
index 0000000..7a9b5e4
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/InputStreamProgressDialog.java
@@ -0,0 +1,144 @@
+/* InputStreamProgressDialog.java
+ *
+ * created: Thu Aug 8 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/InputStreamProgressDialog.java,v 1.3 2005-04-01 16:08:23 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.util.InputStreamProgressEvent;
+
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JButton;
+
+
+/**
+ * A JDialog the show the byte count of an InputStream (via
+ * InputStreamProgressEvent objects)
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: InputStreamProgressDialog.java,v 1.3 2005-04-01 16:08:23 tjc Exp $
+ **/
+
+public class InputStreamProgressDialog extends JDialog
+{
+
+ /**
+ * An InputStreamProgressListener used to update the label with the
+ * current number of chars read.
+ **/
+ private InputStreamProgressListener stream_progress_listener = null;
+
+ /**
+ * Create a blocking InputStreamProgressDialog JFrame.
+ * @param parent The parent window.
+ * @param message The message to display in the JDialog and to use as the
+ * frame title.
+ **/
+ public InputStreamProgressDialog (final JFrame parent,
+ final String message)
+ {
+ this (parent, message, message, true);
+ }
+
+ /**
+ * Create a new InputStreamProgressDialog JFrame.
+ * @param parent The parent window.
+ * @param title The title of the new JDialog.
+ * @param message The message to display in the JDialog.
+ * @param modal If true, dialog blocks input to the parent window when
+ * shown.
+ **/
+ public InputStreamProgressDialog(final JFrame parent_frame,
+ final String title,
+ final String message,
+ final boolean modal)
+ {
+ super(parent_frame, title, modal);
+ getContentPane().add(new JLabel(message), "North");
+
+ final JLabel bytes_label = new JLabel(" ");
+ getContentPane().add(bytes_label, "Center");
+
+ final JButton ok_button = new JButton ("OK");
+
+ ok_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ InputStreamProgressDialog.this.dispose();
+ }
+ });
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ InputStreamProgressDialog.this.dispose();
+ }
+ });
+
+ stream_progress_listener = new InputStreamProgressListener()
+ {
+ public void progressMade(final InputStreamProgressEvent event)
+ {
+ final int char_count = event.getCharCount();
+ if(char_count == -1)
+ bytes_label.setText ("");
+ else
+ {
+ bytes_label.setText("Characters read so far: " + char_count);
+ if(!isVisible())
+ setVisible(true);
+ }
+ }
+
+ public void progressMade(String progress)
+ {
+ bytes_label.setText(progress);
+ }
+
+ };
+ getContentPane().add(ok_button, "South");
+ pack();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation(new Point((screen.width - getSize().width) / 2,
+ (screen.height - getSize().height) / 2));
+
+ setVisible(false);
+ }
+
+ /**
+ * Return an InputStreamProgressListener to pass to FileProgressDocument.
+ **/
+ public InputStreamProgressListener getInputStreamProgressListener()
+ {
+ return stream_progress_listener;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/KeyChoice.java b/uk/ac/sanger/artemis/components/KeyChoice.java
new file mode 100644
index 0000000..7992702
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/KeyChoice.java
@@ -0,0 +1,171 @@
+/* KeyChoice.java
+ *
+ * created: Mon Sep 6 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/KeyChoice.java,v 1.5 2008-07-21 15:43:16 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.KeyVector;
+import uk.ac.sanger.artemis.io.EntryInformation;
+
+import java.awt.Font;
+import java.awt.event.ItemListener;
+
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+
+/**
+ * This component is a Choice component that shows the possible feature keys.
+ *
+ * @author Kim Rutherford
+ * @version $Id: KeyChoice.java,v 1.5 2008-07-21 15:43:16 tjc Exp $
+ **/
+public class KeyChoice extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+
+ /** The JComboBox component that will show the feature keys. */
+ private JComboBox key_chooser = null;
+
+ /**
+ * Create a new KeyChoice component with CDS as the default key.
+ * @param entry_information The object to get the list of possible
+ * keys from.
+ **/
+ public KeyChoice (final EntryInformation entry_information)
+ {
+ this (entry_information, Key.CDS);
+ }
+
+ /**
+ * Create a new KeyChoice component with the given key as the default.
+ * @param entry_information The object to get the list of possible
+ * keys from.
+ **/
+ public KeyChoice (final EntryInformation entry_information,
+ final Key default_key)
+ {
+ final Font font = Options.getOptions ().getFont ();
+ setFont (font);
+
+ key_chooser = new JComboBox ();
+ key_chooser.setEditable(true);
+
+ final int MAX_VISIBLE_ROWS = 30;
+ key_chooser.setMaximumRowCount (MAX_VISIBLE_ROWS);
+
+ addChoice (entry_information, default_key);
+ }
+
+ /**
+ * Return the currently selected key.
+ **/
+ public Key getSelectedItem ()
+ {
+ return new Key ((String) key_chooser.getSelectedItem ());
+ }
+
+ /**
+ * Set the selected Key.
+ **/
+ public void setKey (final Key new_key)
+ {
+ final int key_index = keyIndex (new_key);
+
+ if (key_index == -1)
+ {
+ // add the key
+ key_chooser.addItem (new_key.toString ());
+ key_chooser.setSelectedItem (new_key.toString ());
+ }
+ else
+ key_chooser.setSelectedIndex (key_index);
+ }
+
+ /**
+ * Adds the specified item listener to receive item events from the Choice
+ * component of this KeyChoice.
+ * @param l The item listener.
+ **/
+ public void addItemListener(ItemListener l)
+ {
+ key_chooser.addItemListener (l);
+ }
+
+
+ /**
+ * Removes the specified item listener so that it no longer receives item
+ * events from the Choice component of this KeyChoice.
+ * @param l The item listener.
+ **/
+ public void removeItemListener(ItemListener l)
+ {
+ key_chooser.removeItemListener (l);
+ }
+
+ /**
+ * Add the key_chooser.
+ **/
+ private void addChoice (final EntryInformation entry_information,
+ final Key default_key)
+ {
+ final Font font = Options.getOptions ().getFont ();
+ key_chooser.setFont (font);
+
+ KeyVector keys = entry_information.getSortedValidKeys();
+
+ if (keys == null)
+ {
+ keys = new KeyVector ();
+ keys.add (Key.CDS);
+ }
+
+ for (int i = 0 ; i < keys.size () ; ++i)
+ key_chooser.addItem ( ((Key)keys.get(i)).toString ());
+
+ if (keyIndex (default_key) != -1)
+ setKey (default_key);
+ add (key_chooser);
+ }
+
+ /**
+ * Return the index in the key_chooser component of the given Key.
+ **/
+ private int keyIndex (final Key key)
+ {
+ for (int i = 0 ; i < key_chooser.getItemCount () ; ++i)
+ {
+ if (key.toString ().equals ((String)key_chooser.getItemAt (i)))
+ return i;
+ }
+ return -1;
+ }
+
+ public void setEnabled(final boolean isEnabled)
+ {
+ key_chooser.setEnabled(isEnabled);
+ repaint();
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/KeyChooser.java b/uk/ac/sanger/artemis/components/KeyChooser.java
new file mode 100644
index 0000000..23569c6
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/KeyChooser.java
@@ -0,0 +1,130 @@
+/* KeyChooser.java
+ *
+ * created: Mon Sep 6 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/KeyChooser.java,v 1.2 2008-07-21 16:03:18 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.EntryInformation;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+
+/**
+ * This component is a KeyChoice component in a JFrame (see addItemListener ()
+ * and removeItemListener () for details).
+ *
+ * @author Kim Rutherford
+ * @version $Id: KeyChooser.java,v 1.2 2008-07-21 16:03:18 tjc Exp $
+ **/
+public class KeyChooser extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+
+ private KeyChoice key_choice;
+
+ final private JButton ok_button = new JButton ("OK");
+
+ /**
+ * Create a new KeyChooser component with CDS as the default key.
+ * @param entry_information The object to get the list of possible
+ * keys from.
+ **/
+ public KeyChooser (final EntryInformation entry_information)
+ {
+ this (entry_information, Key.CDS);
+ }
+
+ /**
+ * Create a new KeyChooser component with the given key as the default.
+ * @param entry_information The object to get the list of possible
+ * keys from.
+ **/
+ public KeyChooser (final EntryInformation entry_information,
+ final Key default_key)
+ {
+ key_choice = new KeyChoice (entry_information, default_key);
+
+ getContentPane ().add (key_choice, "Center");
+
+ final JPanel panel = new JPanel ();
+
+ panel.add (ok_button);
+ ok_button.addActionListener (new ActionListener ()
+ {
+ public void actionPerformed (ActionEvent e)
+ {
+ KeyChooser.this.dispose ();
+ }
+ });
+
+ final JButton close_button = new JButton ("Cancel");
+ panel.add (close_button);
+ close_button.addActionListener (new ActionListener ()
+ {
+ public void actionPerformed (ActionEvent e)
+ {
+ KeyChooser.this.dispose ();
+ }
+ });
+
+ getContentPane ().add (panel, "South");
+
+ addWindowListener (new WindowAdapter ()
+ {
+ public void windowClosing (WindowEvent event)
+ {
+ KeyChooser.this.dispose ();
+ }
+ });
+
+ pack();
+
+ Utilities.centreFrame(this);
+ }
+
+ /**
+ * Return the KeyChoice component that is displayed in this JFrame.
+ **/
+ protected KeyChoice getKeyChoice ()
+ {
+ return key_choice;
+ }
+
+ /**
+ * Return the reference of the OK button of this KeyChooser.
+ **/
+ protected JButton getOKButton ()
+ {
+ return ok_button;
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/components/ListDialog.java b/uk/ac/sanger/artemis/components/ListDialog.java
new file mode 100644
index 0000000..61c3482
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ListDialog.java
@@ -0,0 +1,128 @@
+/* ListDialog.java
+ *
+ * created: Fri Sep 1 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ListDialog.java,v 1.2 2004-12-03 18:11:28 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+/**
+ * This component is a JDialog that contains a List.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ListDialog.java,v 1.2 2004-12-03 18:11:28 tjc Exp $
+ **/
+
+public class ListDialog extends JDialog {
+ /**
+ * Create a new ListDialog component.
+ **/
+ public ListDialog (final JFrame parent, final String title) {
+ super (parent, title, true);
+
+ JScrollPane scroll_pane = new JScrollPane (getList ());
+
+ getContentPane ().add (scroll_pane, "Center");
+
+ getList ().setBackground (Color.white);
+
+ button_panel = new JPanel ();
+
+ button_panel.add (ok_button);
+
+ getContentPane ().add (button_panel, "South");
+
+ ok_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ selected_item = getList ().getSelectedValue ();
+ ListDialog.this.dispose ();
+ }
+ });
+
+ button_panel.add (cancel_button);
+ cancel_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ selected_item = null;
+ ListDialog.this.dispose ();
+ }
+ });
+ packme ();
+ }
+
+ /**
+ * This method will call pack () and then move the JDialog to the centre of
+ * the screen.
+ **/
+ private void packme () {
+ pack ();
+
+ setSize (750, 400);
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ final int x_position = (screen.width - getSize ().width) / 2;
+ int y_position = (screen.height - getSize ().height) / 2;
+
+ if (y_position < 10) {
+ y_position = 10;
+ }
+
+ setLocation (new Point (x_position, y_position));
+ }
+
+ /**
+ * Show the dialog and then return the selected item, or null if the user
+ * hits cancel.
+ **/
+ public Object getSelectedValue () {
+ setVisible(true);
+ return selected_item;
+ }
+
+ /**
+ * Return the reference of the List.
+ **/
+ public JList getList () {
+ return list;
+ }
+
+ /**
+ * The List.
+ **/
+ final private JList list = new JList ();
+
+ private final JButton ok_button = new JButton ("OK");
+ private final JButton cancel_button = new JButton ("Cancel");
+
+ private JPanel button_panel = null;
+
+ /**
+ * Set to null if and only if the user cancels the dialog, otherwise
+ * contains the selected item.
+ **/
+ private Object selected_item = null;
+}
diff --git a/uk/ac/sanger/artemis/components/ListSelectionPanel.java b/uk/ac/sanger/artemis/components/ListSelectionPanel.java
new file mode 100644
index 0000000..475f25b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ListSelectionPanel.java
@@ -0,0 +1,188 @@
+/* ListSelectionPanel.java
+ *
+ * created: Fri Oct 9 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ListSelectionPanel.java,v 1.6 2007-09-12 10:14:01 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.Box;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.Key;
+
+public class ListSelectionPanel extends JPanel
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+ private DefaultListModel listModel;
+ private JCheckBox save;
+
+ public ListSelectionPanel(final EntryGroup entry_group,
+ final Object names[],
+ final String[] description,
+ final boolean saveOption)
+ {
+ this(entry_group, names, description, saveOption, null, null);
+ }
+
+ /**
+ * Panel used to select from and order a list.
+ * @param entry_group
+ * @param names
+ * @param description
+ */
+ public ListSelectionPanel(final EntryGroup entry_group,
+ final Object names[],
+ final String[] description,
+ final boolean saveOption,
+ final Key key,
+ final String default_qualifier)
+ {
+ listModel = new DefaultListModel();
+
+ for(int i = 0; i < names.length; i++)
+ listModel.addElement(names[i]);
+
+ final JLabel label = new JLabel("Qualifier order :");
+ final JList name_list = new JList(listModel);
+ final JScrollPane jsp = new JScrollPane(name_list);
+
+ JButton remove_butt = new JButton("REMOVE");
+ remove_butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ while(!name_list.isSelectionEmpty())
+ listModel.remove(name_list.getSelectedIndex());
+ }
+ });
+
+ Box bdown = Box.createVerticalBox();
+ bdown = Box.createVerticalBox();
+ JButton upButt = new JButton("UP");
+ upButt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Object obj = name_list.getSelectedValue();
+ int index = name_list.getSelectedIndex();
+
+ if(index <= 0)
+ return;
+ listModel.removeElementAt(index);
+ listModel.insertElementAt(obj, index - 1);
+ name_list.setSelectedIndex(index - 1);
+ }
+ });
+ bdown.add(upButt);
+
+ JButton downButt = new JButton("DOWN");
+ downButt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Object obj = name_list.getSelectedValue();
+ int index = name_list.getSelectedIndex();
+
+ if(index >= listModel.getSize() - 1)
+ return;
+ listModel.removeElementAt(index);
+ listModel.insertElementAt(obj, index + 1);
+ name_list.setSelectedIndex(index + 1);
+ }
+ });
+ bdown.add(downButt);
+ add(bdown, BorderLayout.WEST);
+
+ bdown = Box.createVerticalBox();
+
+ bdown.add(Box.createVerticalStrut(15));
+ for(int i=0; i<description.length; i++)
+ bdown.add(new JLabel(description[i]));
+ bdown.add(label);
+ bdown.add(jsp);
+
+ add(bdown, BorderLayout.CENTER);
+
+ boolean isGFF = false;
+ if(entry_group.getDefaultEntry().getEMBLEntry() instanceof GFFDocumentEntry)
+ isGFF = true;
+
+ final QualifierChoice qualifier_choice = new QualifierChoice(
+ entry_group.elementAt(0).getEntryInformation(), key, default_qualifier, isGFF);
+
+ JButton add_butt = new JButton("ADD");
+ add_butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ listModel.addElement(qualifier_choice.getSelectedItem().toString());
+ }
+ });
+
+ if(saveOption)
+ save = new JCheckBox("Save between sessions", false);
+
+ bdown = Box.createVerticalBox();
+ bdown.add(Box.createVerticalGlue());
+ bdown.add(qualifier_choice);
+ bdown.add(add_butt);
+ bdown.add(remove_butt);
+ if(saveOption)
+ bdown.add(save);
+
+ add(bdown, BorderLayout.EAST);
+ }
+
+ public Object[] getResultArray()
+ {
+ return listModel.toArray();
+ }
+
+ public String getResultString()
+ {
+ Object listNames[] = listModel.toArray();
+ String listNamesString = "";
+ for(int i=0; i<listNames.length; i++)
+ listNamesString = listNamesString + listNames[i] + " ";
+ return listNamesString;
+ }
+
+ public boolean isSaveOption()
+ {
+ return save.isSelected();
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/LogReadListener.java b/uk/ac/sanger/artemis/components/LogReadListener.java
new file mode 100644
index 0000000..ec0d857
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/LogReadListener.java
@@ -0,0 +1,77 @@
+/* LogReadListener.java
+ *
+ * created: Fri Nov 28 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/LogReadListener.java,v 1.2 2004-12-03 17:47:04 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.io.ReadListener;
+import uk.ac.sanger.artemis.io.ReadEvent;
+
+import javax.swing.*;
+
+/**
+ * A class that implements ReadListener by logging all ReadEvents.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: LogReadListener.java,v 1.2 2004-12-03 17:47:04 tjc Exp $
+ **/
+
+public class LogReadListener implements ReadListener {
+ /**
+ * Create a new DialogReadListener.
+ * @param source The source we are reading from - generally a file name or
+ * URL. This file name is prepended to the log entries.
+ **/
+ public LogReadListener (final String source) {
+ this.source = source;
+ }
+
+ /**
+ * Implementation of ReadListener.
+ **/
+ public void notify (final ReadEvent event) {
+
+ if (source == null) {
+ Splash.getLogger ().log (event.getMessage () + "\n");
+ } else {
+ Splash.getLogger ().log ("while reading from " + source + ": " +
+ event.getMessage () + "\n");
+ }
+ seen_message = true;
+ }
+
+ /**
+ * Return true if and only if notify() has been called at least once.
+ **/
+ public boolean seenMessage () {
+ return seen_message;
+ }
+
+ /**
+ * The name that was passed to the constructor.
+ **/
+ private String source;
+
+ private boolean seen_message = false;
+}
diff --git a/uk/ac/sanger/artemis/components/LogViewer.java b/uk/ac/sanger/artemis/components/LogViewer.java
new file mode 100644
index 0000000..8c3d883
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/LogViewer.java
@@ -0,0 +1,149 @@
+/* LogViewer.java
+ *
+ * created: Wed Aug 30 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/LogViewer.java,v 1.3 2007-03-22 13:41:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.*;
+
+/**
+ * A class for viewing log messages in a FileViewer component.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: LogViewer.java,v 1.3 2007-03-22 13:41:31 tjc Exp $
+ **/
+
+public class LogViewer extends AppenderSkeleton implements Logger
+{
+
+ public int maxLogLines;
+ /** The FileViewer that is used to show the messages. */
+ private FileViewer file_viewer = null;
+
+ /**
+ * Create a new, empty LogViewer component.
+ **/
+ public LogViewer()
+ {
+
+ }
+
+ /**
+ * Send the given String to the log.
+ **/
+ public void log(final String message)
+ {
+ maybeMakeViewer();
+ file_viewer.appendString(message);
+ }
+
+ /**
+ * Read from the given Reader and send it to the log.
+ **/
+ public void log(final Reader reader)
+ throws IOException
+ {
+ maybeMakeViewer();
+ file_viewer.appendFile(reader);
+ }
+
+ /**
+ * Call setVisible() on the FileViewer that this object contains.
+ **/
+ public void setVisible(final boolean flag)
+ {
+ maybeMakeViewer();
+ file_viewer.setVisible(flag);
+ }
+
+ /**
+ * If file_viewer is null make a FileViewer and assign it to file_viewer.
+ **/
+ private void maybeMakeViewer()
+ {
+ if (file_viewer == null)
+ {
+ file_viewer = new FileViewer("Log Viewer", false)
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public void dispose()
+ {
+ // if the FileViewer is deleted we want to know
+ file_viewer = null;
+ super.dispose();
+ }
+ };
+
+ file_viewer.pack();
+ }
+ }
+
+ protected void append(LoggingEvent e)
+ {
+ String message = this.layout.format(e);
+ FileViewer fv = ((LogViewer)Splash.getLogger()).getFileViewer();
+ if(fv != null &&
+ maxLogLines < fv.getLineCount())
+ fv.setText("");
+
+ Splash.getLogger().log(message);
+ }
+
+ public void close()
+ {
+
+ }
+
+ public boolean requiresLayout()
+ {
+ return true;
+ }
+
+ public int getMaxLogLines()
+ {
+ return maxLogLines;
+ }
+
+ /**
+ * Set in log4j.properties by log4j.appender.R.MaxLogLines
+ * @param maxLogLines
+ */
+ public void setMaxLogLines(int maxLogLines)
+ {
+ this.maxLogLines = maxLogLines;
+ }
+
+ public FileViewer getFileViewer()
+ {
+ return this.file_viewer;
+ }
+
+
+}
diff --git a/uk/ac/sanger/artemis/components/MarkerRangeRequester.java b/uk/ac/sanger/artemis/components/MarkerRangeRequester.java
new file mode 100644
index 0000000..26b0333
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/MarkerRangeRequester.java
@@ -0,0 +1,106 @@
+/* MarkerRangeRequester.java
+ *
+ * created: Mon Jul 10 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MarkerRangeRequester.java,v 1.1 2004-06-09 09:47:04 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.util.Vector;
+
+/**
+ * A requester that gets a MarkerRange or a Range from the user.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: MarkerRangeRequester.java,v 1.1 2004-06-09 09:47:04 tjc Exp $
+ **/
+
+public class MarkerRangeRequester extends TextRequester {
+ /**
+ * Create a new MarkerRangeRequester component with the given prompt. Other
+ * components can listen for MarkerRangeRequesterEvent object.
+ * @param prompt A message that is displayed in the component beside the
+ * TextArea that the user types into. This String is also used as the
+ * JFrame title.
+ * @param width The width of the TextRequester in the new requester.
+ * @param initial_text The initial text to put in the TextRequester.
+ **/
+ public MarkerRangeRequester (final String prompt,
+ final int width,
+ final String initial_text) {
+ super (prompt, width, initial_text);
+ }
+
+ /**
+ * Add the given object as a listen for MarkerRangeRequester events from
+ * this MarkerRangeRequester.
+ **/
+ public void
+ addMarkerRangeRequesterListener (final MarkerRangeRequesterListener l) {
+ listeners.addElement (l);
+ }
+
+ /**
+ * Send a MarkerRangeRequesterEvent of type OK to all the listeners.
+ **/
+ protected void performOK () {
+ final MarkerRangeRequesterEvent new_event =
+ new MarkerRangeRequesterEvent (this, getText (),
+ MarkerRangeRequesterEvent.OK);
+
+ sendEvent (new_event);
+
+ super.performOK ();
+ }
+
+ /**
+ * Send a MarkerRangeRequesterEvent of type CANCEL to all the listeners.
+ **/
+ protected void performCancel () {
+ final MarkerRangeRequesterEvent new_event =
+ new MarkerRangeRequesterEvent (this, getText (),
+ MarkerRangeRequesterEvent.CANCEL);
+
+ sendEvent (new_event);
+
+ super.performCancel ();
+ }
+
+ /**
+ * Send the given MarkerRangeRequesterEvent to all the object that are
+ * listening for the event.
+ **/
+ private void sendEvent (final MarkerRangeRequesterEvent event) {
+ for (int i = 0 ; i < listeners.size () ; ++i) {
+ final MarkerRangeRequesterListener listener =
+ ((MarkerRangeRequesterListener) listeners.elementAt (i));
+
+ listener.actionPerformed (event);
+ }
+ }
+
+ /**
+ * This contains the objects that are listening for MarkerRangeRequester
+ * events from this MarkerRangeRequester.
+ **/
+ private Vector listeners = new Vector ();
+}
diff --git a/uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent.java b/uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent.java
new file mode 100644
index 0000000..b05921e
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent.java
@@ -0,0 +1,213 @@
+/* MarkerRangeRequesterEvent.java
+ *
+ * created: Mon Jul 10 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MarkerRangeRequesterEvent.java,v 1.2 2004-10-29 09:36:24 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.LocationLexer;
+import uk.ac.sanger.artemis.io.LocationLexer.*;
+import uk.ac.sanger.artemis.util.*;
+
+import javax.swing.*;
+
+/**
+ * This event is sent when the user presses OK or Cancel in a
+ * MarkerRangeRequester component.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: MarkerRangeRequesterEvent.java,v 1.2 2004-10-29 09:36:24 tjc Exp $
+ **/
+
+public class MarkerRangeRequesterEvent extends TextRequesterEvent {
+ /**
+ * Create a new MarkerRangeRequesterEvent object.
+ * @param source The MarkerRangeRequester that generated the event.
+ * @param requester_text The contents of the TextField in the
+ * MarkerRangeRequester.
+ * @param type The type of event.
+ **/
+ public MarkerRangeRequesterEvent(final MarkerRangeRequester source,
+ final String requester_text,
+ final int type)
+ {
+ super(source, requester_text, type);
+ }
+
+
+ /**
+ * Parse a Range and return the start and end positions in an array that is
+ * two elements long.
+ * @return null if and only if there was a parse error. If the range is on
+ * the forward strand the first element will be less than or equal to the
+ * second, otherwise the first will be greater than the second.
+ **/
+ private int[] getRangeInternal()
+ {
+ if(getRequesterText().length() == 0)
+ return null;
+
+ final LocationLexer lexer = new LocationLexer(getRequesterText());
+
+ final TokenEnumeration enumTk = lexer.getTokens();
+
+ boolean complement_flag = false;
+
+ if(enumTk.peekElement() instanceof String &&
+ ((String)enumTk.peekElement()).equals("complement"))
+ {
+ complement_flag = true;
+ enumTk.nextElement();
+ }
+
+ enumTk.eatToken('(');
+
+ if(enumTk.peekElement() instanceof Integer)
+ {
+ int first_base = ((Integer)enumTk.nextElement()).intValue();
+
+ if(enumTk.peekElement() instanceof Integer ||
+ (enumTk.eatToken("..") || enumTk.eatToken('.') ||
+ enumTk.eatToken("-")) &&
+ enumTk.peekElement() instanceof Integer)
+ {
+ int last_base = ((Integer)enumTk.nextElement()).intValue();
+
+ enumTk.eatToken(')');
+
+ if(enumTk.peekElement() == null)
+ {
+ if(complement_flag)
+ {
+ final int temp = first_base;
+ first_base = last_base;
+ last_base = temp;
+ }
+
+ return new int[]
+ {
+ first_base, last_base
+ };
+ }
+ else
+ {
+ new MessageDialog((JFrame) getSource(),
+ "garbage at the end of the range: " + enumTk);
+ return null;
+ }
+ }
+ }
+
+ // if we get to here then there was a parse error
+ new MessageDialog((JFrame) getSource(),
+ "error in range: the range should have this " +
+ "form: 100..200 - error at: " + enumTk);
+
+ return null;
+ }
+
+ /**
+ * Parse the contents of the String that was passed to the constructor as a
+ * Range and return it.
+ * @param bases The Bases object to use to find the Strand that is passed
+ * to the MarkerRange constructor.
+ * @return The Range or null if the Range can't be parsed.
+ **/
+ public MarkerRange getMarkerRange(final Bases bases)
+ {
+ try
+ {
+ final MarkerRange marker_range;
+
+ final int [] return_values = getRangeInternal();
+
+ if(return_values == null)
+ return null;
+
+ final int first_base = return_values[0];
+ final int last_base = return_values[1];
+
+ if(first_base <= last_base)
+ {
+ // forward strand
+ final Strand strand;
+ strand = bases.getForwardStrand();
+
+ marker_range =
+ strand.makeMarkerRangeFromPositions(first_base,
+ last_base);
+ }
+ else
+ {
+ // reverse strand
+ final Strand strand = bases.getReverseStrand();
+ final int raw_first = bases.getComplementPosition(first_base);
+ final int raw_last = bases.getComplementPosition(last_base);
+
+ marker_range =
+ strand.makeMarkerRangeFromPositions(raw_last,
+ raw_first);
+ }
+
+ return marker_range;
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog((JFrame)getSource(),
+ "the bases are out of range for this " +
+ "sequence");
+ return null;
+ }
+ }
+
+ /**
+ * Parse and return a raw Range object from the text.
+ * @return The Range or null if the range could not be parsed.
+ **/
+ public Range getRawRange()
+ {
+ final int [] return_values = getRangeInternal();
+
+ if(return_values == null)
+ return null;
+
+ final int first_base = return_values[0];
+ final int last_base = return_values[1];
+
+ try
+ {
+ if(first_base < last_base)
+ return new Range(first_base, last_base);
+ else
+ return new Range(last_base, first_base);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/components/MarkerRangeRequesterListener.java b/uk/ac/sanger/artemis/components/MarkerRangeRequesterListener.java
new file mode 100644
index 0000000..7324215
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/MarkerRangeRequesterListener.java
@@ -0,0 +1,42 @@
+/* MarkerRangeRequesterListener.java
+ *
+ * created: Mon Jul 10 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MarkerRangeRequesterListener.java,v 1.1 2004-06-09 09:47:06 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * This interface is implemented by those classes that need to listen for
+ * MarkerRangeRequesterEvents.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: MarkerRangeRequesterListener.java,v 1.1 2004-06-09 09:47:06 tjc Exp $
+ **/
+
+public interface MarkerRangeRequesterListener {
+ /**
+ * Invoked when the user presses the OK or Cancel button on a
+ * MarkerRangeRequester component.
+ **/
+ void actionPerformed (final MarkerRangeRequesterEvent event);
+}
diff --git a/uk/ac/sanger/artemis/components/MessageDialog.java b/uk/ac/sanger/artemis/components/MessageDialog.java
new file mode 100644
index 0000000..15d883a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/MessageDialog.java
@@ -0,0 +1,167 @@
+/* MessageDialog.java
+ *
+ * created: Mon Dec 14 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MessageDialog.java,v 1.2 2008-10-15 13:56:42 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import uk.ac.sanger.artemis.Options;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+/**
+ * A popup dialog box that displays a message and has an OK JButton.
+ *
+ * @author Kim Rutherford
+ * @version $Id: MessageDialog.java,v 1.2 2008-10-15 13:56:42 tjc Exp $
+ **/
+
+public class MessageDialog extends JDialog
+{
+ private static final long serialVersionUID = 1L;
+
+ /** Messages longer than this will be put in a TextArea rather than a Label. */
+ final private int MESSAGE_SPLIT_SIZE = 100;
+
+ final private JButton ok_button = new JButton ("OK");
+
+ /**
+ * Create a blocking MessageDialog component.
+ * @param parent The parent window.
+ * @param message The message to display in the JDialog and to use as the
+ * frame title.
+ **/
+ public MessageDialog (final JFrame parent, final String message)
+ {
+ this (parent, message, message, true);
+ }
+
+ /**
+ * Create a new MessageDialog component.
+ * @param parent The parent window.
+ * @param message The message to display in the JDialog and to use as the
+ * frame title.
+ * @param modal If true, dialog blocks input to the parent window when
+ * shown.
+ **/
+ public MessageDialog (final JFrame parent, final String message,
+ final boolean modal)
+ {
+ this (parent, message, message, modal);
+ }
+
+ /**
+ * Create a blocking MessageDialog component.
+ * @param parent_frame The parent window.
+ * @param title The title of the new dialog JFrame.
+ * @param message The message to display in the JDialog.
+ **/
+ public MessageDialog (final JFrame parent_frame,
+ final String title,
+ final String message)
+ {
+ this (parent_frame, title, message, true);
+ }
+
+ /**
+ * Create a new MessageDialog component.
+ * @param parent_frame The parent window.
+ * @param title The title of the new dialog JFrame.
+ * @param message The message to display in the JDialog.
+ * @param modal If true, dialog blocks input to the parent window when
+ * shown.
+ **/
+ public MessageDialog (final JFrame parent_frame,
+ final String title,
+ final String message,
+ final boolean modal)
+ {
+ super (parent_frame, title, modal);
+
+ final Font font = Options.getOptions ().getFont ();
+ setFont (font);
+
+ if (message.length () < MESSAGE_SPLIT_SIZE)
+ getContentPane ().add (new JLabel (message), "North");
+ else
+ {
+ final JTextArea text_area = new JTextArea (18, 90);
+ text_area.setText (message);
+
+ getContentPane().add (new JScrollPane (text_area), "North");
+ text_area.setEditable (false);
+ }
+
+ final JPanel panel = new JPanel ();
+ panel.add (ok_button);
+ ok_button.addActionListener (new ActionListener ()
+ {
+ public void actionPerformed (ActionEvent e)
+ {
+ MessageDialog.this.dispose ();
+ }
+ });
+
+ addWindowListener (new WindowAdapter ()
+ {
+ public void windowClosing (WindowEvent event)
+ {
+ MessageDialog.this.dispose ();
+ }
+ });
+
+ addKeyListener (new KeyAdapter ()
+ {
+ public void keyTyped(final KeyEvent e)
+ {
+ MessageDialog.this.dispose ();
+ }
+ });
+
+ getContentPane ().add (panel, "South");
+ pack ();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+ setVisible (true);
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/components/MessageFrame.java b/uk/ac/sanger/artemis/components/MessageFrame.java
new file mode 100644
index 0000000..d6eace3
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/MessageFrame.java
@@ -0,0 +1,87 @@
+/* MessageFrame.java
+ *
+ * created: Mon Jan 18 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+
+/**
+ * A popup JFrame box that displays a message and has an OK JButton.
+ * @author Kim Rutherford
+ **/
+public class MessageFrame extends JFrame {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new MessageFrame component.
+ * @param message The message to display in the JDialog and it's title.
+ **/
+ public MessageFrame (final String message) {
+ this (message, message);
+ }
+
+ /**
+ * Create a new MessageFrame component.
+ * @param title The title of the new dialog JFrame.
+ * @param message The message to display in the JDialog.
+ **/
+ protected MessageFrame (final String title,
+ final String message) {
+ super (title);
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+ getContentPane ().add (new JLabel (message), "North");
+
+ final JButton ok_button = new JButton ("OK");
+ ok_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ MessageFrame.this.dispose ();
+ }
+ });
+
+ addKeyListener (new KeyAdapter () {
+ public void keyTyped(final KeyEvent e) {
+ MessageFrame.this.dispose ();
+ }
+ });
+
+ getContentPane ().add (ok_button, "South");
+ pack ();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+ setVisible (true);
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/MultiComparator.java b/uk/ac/sanger/artemis/components/MultiComparator.java
new file mode 100644
index 0000000..833807e
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/MultiComparator.java
@@ -0,0 +1,1184 @@
+/* MultiComparator.java
+ *
+ * created: Tue Sep 11 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/MultiComparator.java,v 1.22 2008-09-03 10:58:33 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.alignment.BamView;
+import uk.ac.sanger.artemis.components.alignment.FileSelectionDialog;
+import uk.ac.sanger.artemis.components.filetree.FileManager;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.variant.VCFview;
+import uk.ac.sanger.artemis.util.RemoteFileDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.DocumentEntryFactory;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.IOException;
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+
+/**
+ * This JFrame component contains an arbitrary number of AlignmentViewer
+ * components and FeatureDisplay components along with ComparatorGlue objects
+ * to keep them synchronized.
+ * @author Kim Rutherford
+ **/
+
+public class MultiComparator extends JFrame
+{
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * An array of EntryGroup objects to display(set by the constructor). The
+ * array will contain at least one EntryGroup and will be exactly one
+ * element longer than comparison_data_array.
+ **/
+ private EntryGroup[] entry_group_array = null;
+
+ /**
+ * An array of ComparisonData objects to display(set by the constructor).
+ * The array will be exactly one element shorter than entry_group_array.
+ **/
+ private ComparisonData[] comparison_data_array = null;
+
+ /**
+ * An array of Selection objects - one per EntryGroup. Created by
+ * makeSelectionArray()
+ **/
+ private Selection[] selection_array = null;
+
+ /**
+ * An array of GotoEventSource objects - one per EntryGroup. Created by
+ * makeGotoEventSourceArray()
+ **/
+ private GotoEventSource[] goto_event_source_array = null;
+
+ /**
+ * An array of FeatureDisplay objects - one per EntryGroup. Created in the
+ * constructor.
+ **/
+ private FeatureDisplay[] feature_display_array = null;
+
+ private JPanel bamPanel[] = null;
+ private JPanel vcfPanel[] = null;
+ private Dimension dimensionAlignViewer = null;
+
+ /**
+ * An array of AlignmentViewer objects - one per ComparisonData. Created
+ * in the constructor.
+ **/
+ private AlignmentViewer[] alignment_viewer_array = null;
+
+ /**
+ * An array of AlignmentViewer objects - one per ComparisonData. Created
+ * in the constructor.
+ **/
+ private ComparatorGlue[] comparator_glue_array = null;
+
+ /**
+ * An array of BasePlotGroup objects - one per FeatureDisplay/EntryGroup.
+ * Created in the constructor.
+ **/
+ private BasePlotGroup[] base_plot_group_array = null;
+
+ /** This menu is populated by makeFileMenu(). */
+ private final JMenu file_menu = new JMenu("File");
+
+ /**
+ * The EntrySourceVector reference that is created in the constructor.
+ **/
+ private EntrySourceVector entry_sources;
+
+ /** Used to show the progress of loading file. */
+ private InputStreamProgressListener progress_listener;
+
+ private GridBagLayout layout = new GridBagLayout();
+
+ /**
+ * Initialise entry_group_array and comparison_data_array and create all
+ * the FeatureDisplay and AlignmentViewer components.
+ * @param entry_group_array The EntryGroup components to display.
+ * @param comparison_data_array The ComparisonData to display. This array
+ * must have one element less than entry_group_array.
+ * @param progress_listener The object to which InputStreamProgressEvents
+ * will be send while reading. Currently unused.
+ **/
+ public MultiComparator(final EntryGroup[] entry_group_array,
+ final ComparisonData[] comparison_data_array,
+ final InputStreamProgressListener
+ progress_listener)
+ {
+ super();
+
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+
+ if(entry_group_array.length != comparison_data_array.length + 1)
+ throw new Error("internal error - " +
+ "MultiComparator got illegal arguments");
+
+ this.entry_group_array = entry_group_array;
+ this.comparison_data_array = comparison_data_array;
+ this.progress_listener = progress_listener;
+ this.entry_sources = Utilities.getEntrySources(this, null);
+
+ final StringBuffer title_buffer = new StringBuffer("ACT: ");
+ for(int i = 0; i < entry_group_array.length - 1; ++i)
+ title_buffer.append(
+ entry_group_array[i].getDefaultEntry().getName()).append(" vs ");
+
+ final EntryGroup last_entry_group =
+ entry_group_array[entry_group_array.length - 1];
+
+ title_buffer.append(last_entry_group.getDefaultEntry().getName());
+
+ setTitle(title_buffer.toString());
+
+ makeSelectionArray();
+ makeGotoEventSourceArray();
+
+ feature_display_array =
+ new FeatureDisplay[entry_group_array.length];
+ base_plot_group_array =
+ new BasePlotGroup[entry_group_array.length];
+ bamPanel =
+ new JPanel[entry_group_array.length];
+ vcfPanel =
+ new JPanel[entry_group_array.length];
+ alignment_viewer_array =
+ new AlignmentViewer[comparison_data_array.length];
+ comparator_glue_array =
+ new ComparatorGlue[comparison_data_array.length];
+
+ for(int i = 0; i < getEntryGroupArray().length; ++i)
+ {
+ final EntryGroup this_entry_group = getEntryGroupArray()[i];
+
+ final BasePlotGroup base_plot_group =
+ new BasePlotGroup(this_entry_group, this,
+ getSelectionArray()[i],
+ getGotoEventSourceArray()[i]);
+
+ final int scroll_bar_position;
+ if(i == getEntryGroupArray().length - 1 &&
+ getEntryGroupArray().length == 2)
+ {
+ // put scrollbar at the top normally, but at the bottom of the lower
+ // sequence if there's two entries on display
+ scroll_bar_position = FeatureDisplay.SCROLLBAR_AT_BOTTOM;
+ }
+ else
+ scroll_bar_position = FeatureDisplay.SCROLLBAR_AT_TOP;
+
+ feature_display_array[i] =
+ new FeatureDisplay(this_entry_group,
+ getSelectionArray()[i],
+ getGotoEventSourceArray()[i],
+ base_plot_group,
+ scroll_bar_position);
+
+ feature_display_array[i].setShowLabels(false);
+ feature_display_array[i].setHardLeftEdge(false);
+
+ if(getEntryGroupArray().length > 2)
+ {
+ feature_display_array[i].setShowForwardFrameLines(false);
+ feature_display_array[i].setShowReverseFrameLines(false);
+ }
+
+ feature_display_array[i].addDisplayAdjustmentListener(base_plot_group);
+
+ base_plot_group_array[i] = base_plot_group;
+ bamPanel[i] = new JPanel();
+ vcfPanel[i] = new JPanel();
+ this_entry_group.ref();
+ }
+
+ for(int i = 0 ; i < getComparisonDataArray().length ; ++i)
+ {
+ alignment_viewer_array[i] =
+ new AlignmentViewer(getFeatureDisplayArray()[i] ,
+ getFeatureDisplayArray()[i + 1],
+ getComparisonDataArray()[i]);
+
+ comparator_glue_array[i] =
+ new ComparatorGlue(this,
+ getFeatureDisplayArray()[i],
+ getFeatureDisplayArray()[i + 1],
+ getAlignmentViewerArray()[i]);
+ }
+
+ setFont(getDefaultFont());
+ makeMenus();
+ getContentPane().setLayout(layout);
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.NORTH;
+ c.gridheight = 1;
+ c.weightx = 1;
+ c.weighty = 0;
+
+ for(int i = 0; i < getFeatureDisplayArray().length; ++i)
+ {
+ if(!(i == getEntryGroupArray().length - 1 &&
+ getEntryGroupArray().length == 2))
+ {
+ // put graph above the sequence in this case
+ c.weighty = 0;
+ getContentPane().add(base_plot_group_array[i], c);
+ c.weighty = 1;
+ bamPanel[i].setVisible(false);
+ vcfPanel[i].setVisible(false);
+ getContentPane().add(bamPanel[i], c);
+ getContentPane().add(vcfPanel[i], c);
+ }
+
+ c.weighty = 0;
+ getContentPane().add(feature_display_array[i], c);
+
+ if(i == getEntryGroupArray().length - 1 &&
+ getEntryGroupArray().length == 2)
+ {
+ // put graph below the sequence in this case
+ c.weighty = 0;
+ getContentPane().add(base_plot_group_array[i], c);
+ c.weighty = 1;
+ bamPanel[i].setVisible(false);
+ vcfPanel[i].setVisible(false);
+ getContentPane().add(bamPanel[i], c);
+ getContentPane().add(vcfPanel[i], c);
+ }
+
+ if(i < getAlignmentViewerArray().length)
+ {
+ c.fill = GridBagConstraints.BOTH;
+ c.weighty = 0.5;
+ getContentPane().add(getAlignmentViewerArray()[i], c);
+ }
+ }
+
+ for(int i = 0 ; i < getEntryGroupArray().length ; ++i)
+ {
+ final EntryGroupChangeListener change_listener =
+ new EntryGroupChangeListener()
+ {
+ public void entryGroupChanged(final EntryGroupChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryGroupChangeEvent.ENTRY_ADDED:
+ case EntryGroupChangeEvent.ENTRY_DELETED:
+ makeFileMenu();
+ break;
+ }
+ }
+ };
+
+ getEntryGroupArray()[i].addEntryGroupChangeListener(change_listener);
+
+ final EntryChangeListener entry_change_listener =
+ new EntryChangeListener()
+ {
+ public void entryChanged(final EntryChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryChangeEvent.NAME_CHANGED:
+ makeFileMenu();
+ break;
+ }
+ }
+ };
+
+ getEntryGroupArray()[i].addEntryChangeListener(entry_change_listener);
+ }
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ closeComparator();
+ }
+ });
+
+ final URL icon_url = Splash.class.getResource("/images/act.gif");
+ if(icon_url != null)
+ {
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ final Image icon_image = toolkit.getImage(icon_url);
+ MediaTracker tracker = new MediaTracker(this);
+ tracker.addImage(icon_image, 0);
+
+ try
+ {
+ tracker.waitForAll();
+ setIconImage(icon_image);
+ }
+ catch(InterruptedException e)
+ {
+ // ignore and continue
+ }
+ }
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ int screen_height = screen.height;
+ int screen_width = screen.width;
+
+ if(getAlignmentViewerArray().length > 0)
+ {
+ if(screen_width <= 900 || screen_height <= 700)
+ setSize(screen_width * 9 / 10, screen_height * 9 / 10);
+ else
+ setSize(900, 700);
+ }
+
+ setLocation(new Point((screen.width - getSize().width) / 2,
+ (screen.height - getSize().height) / 2));
+
+
+ for(int i=0; i<getEntryGroupArray().length; i++)
+ loadFromProperty(i);
+ }
+
+ /**
+ * Load BAM/VCF files using the system properties flag -Dbam1, -Dbam2
+ * for sequences 1, 2...
+ * @param index the feature display to associate the files with
+ */
+ private void loadFromProperty(final int index)
+ {
+ final String bamProperty = "bam"+(index+1);
+ if(System.getProperty(bamProperty) != null)
+ {
+ SwingWorker worker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ MultiComparator.this.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ String ngs[] = System.getProperty(bamProperty).split("[\\s,]");
+ FileSelectionDialog fileChooser = new FileSelectionDialog(ngs);
+ List<String> listBams = fileChooser.getFiles(".*\\.bam$");
+ List<String> vcfFiles = fileChooser.getFiles(VCFview.VCFFILE_SUFFIX);
+ loadBamAndVcf(listBams, vcfFiles, index);
+ MultiComparator.this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ return null;
+ }
+ };
+ worker.start();
+ }
+ }
+
+ /**
+ * Move all the sequences(and comparisons) up one place. The first and
+ * last EntryGroups should be the same(reference).
+ **/
+ private void rotateSequences()
+ {
+ final EntryGroup[] new_entry_groups =
+ new EntryGroup[getEntryGroupArray().length];
+
+ final ComparisonData[] new_comparison_data =
+ new ComparisonData[getComparisonDataArray().length];
+
+ for(int i = 1 ; i < new_entry_groups.length ; ++i)
+ new_entry_groups[i - 1] = getEntryGroupArray()[i];
+
+ new_entry_groups[new_entry_groups.length - 1] = new_entry_groups[0];
+
+ for(int i = 1; i < new_comparison_data.length; ++i)
+ new_comparison_data[i - 1] = getComparisonDataArray()[i];
+
+ new_comparison_data[new_comparison_data.length - 1] =
+ getComparisonDataArray()[0];
+
+ final MultiComparator new_comparator =
+ new MultiComparator(new_entry_groups, new_comparison_data,
+ progress_listener);
+
+ setVisible(false);
+ new_comparator.setVisible(true);
+ closeComparator();
+ }
+
+ /**
+ * If there are no unsaved changes, close this EntryEdit. Otherwise ask
+ * the user first.
+ **/
+ private void closeComparator()
+ {
+ for(int i = 0 ; i < getEntryGroupArray().length ; ++i)
+ {
+ final EntryGroup entry_group = getEntryGroupArray()[i];
+
+ if(entry_group.hasUnsavedChanges() &&
+ entry_group.refCount() == 1)
+ {
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog(this,
+ "there are unsaved changes - really close?");
+
+ if(!yes_no_dialog.getResult())
+ return;
+ }
+ }
+
+ setVisible(false);
+
+ for(int i = 0 ; i < getEntryGroupArray().length ; ++i)
+ {
+ final EntryGroup entry_group = getEntryGroupArray()[i];
+ if(!GeneUtils.isDatabaseEntry(entry_group))
+ entry_group.unref();
+ }
+
+ dispose();
+ }
+
+ /**
+ * Make one Selection object each EntryGroup.
+ **/
+ private void makeSelectionArray()
+ {
+ final int length = getEntryGroupArray().length;
+ selection_array = new Selection[length];
+ for(int i = 0 ; i < length ; ++i)
+ selection_array[i] = new Selection(null);
+ }
+
+ /**
+ * Make subject_goto_event_source and query_goto_event_source and wire them
+ * togeather so that goto events do the right thing.
+ **/
+ private void makeGotoEventSourceArray()
+ {
+ final int length = getEntryGroupArray().length;
+ goto_event_source_array = new GotoEventSource[length];
+
+ for(int i = 0 ; i < length ; ++i)
+ {
+ goto_event_source_array[i] =
+ new SimpleGotoEventSource(getEntryGroupArray()[i])
+ {
+ /**
+ * Send the given event to all the GotoListeners.
+ **/
+ public void sendGotoEvent(final GotoEvent goto_event)
+ {
+ // temporarily disable moving so that the other FeatureDisplay
+ // doesn't start moving(because of the listeners set up in
+ // addDisplayListeners()
+ super.sendGotoEvent(goto_event);
+ }
+ };
+ }
+ }
+
+ /**
+ * Save the given entry, prompting for a file name if necessary.
+ **/
+ private void saveEntry(final Entry entry)
+ {
+ if(entry.getName() == null)
+ {
+ final EntryFileDialog file_dialog = new EntryFileDialog(this, true);
+
+ file_dialog.saveEntry(entry, true, true,
+ true, DocumentEntryFactory.ANY_FORMAT);
+ }
+ else
+ {
+ try
+ {
+ entry.save(DocumentEntryFactory.ANY_FORMAT);
+
+ // save it back to ssh server
+ if(((DocumentEntry)entry.getEMBLEntry()).getDocument()
+ instanceof RemoteFileDocument)
+ {
+ RemoteFileDocument node =
+ (RemoteFileDocument)(((DocumentEntry)entry.getEMBLEntry()).getDocument());
+
+ File file = new File( ((DocumentEntry)entry.getEMBLEntry()).getDocument().getLocation().toString() );
+ node.saveEntry(file);
+ }
+
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(this, "error while saving: " + e);
+ return;
+ }
+ catch(EntryInformationException e)
+ {
+ new MessageDialog(this, "error while saving: " + e);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Return the name to use for the sub JMenu for the given EntryGroup.
+ **/
+ private String makeNewSubMenuName(final EntryGroup entry_group)
+ {
+ final Entry sequence_entry = entry_group.getSequenceEntry();
+
+ if(sequence_entry == null)
+ return "(no name)";
+ else
+ {
+ final String sequence_name = sequence_entry.getName();
+ if(sequence_name == null)
+ return "(no name)";
+ else
+ return sequence_name;
+ }
+
+ }
+
+ /**
+ * Make and add the menus for this component.
+ **/
+ private void makeMenus()
+ {
+ //final Font default_font = getDefaultFont();
+ final JMenuBar menu_bar = new JMenuBar();
+
+ setJMenuBar(menu_bar);
+
+ makeFileMenu();
+ menu_bar.add(file_menu);
+
+ final JMenu entries_menu = new JMenu("Entries");
+ entries_menu.setMnemonic(KeyEvent.VK_N);
+ menu_bar.add(entries_menu);
+ final JMenu select_menu = new JMenu("Select");
+ select_menu.setMnemonic(KeyEvent.VK_S);
+ menu_bar.add(select_menu);
+ final JMenu view_menu = new JMenu("View");
+ view_menu.setMnemonic(KeyEvent.VK_V);
+ menu_bar.add(view_menu);
+ final JMenu goto_menu = new JMenu("Goto");
+ goto_menu.setMnemonic(KeyEvent.VK_O);
+ menu_bar.add(goto_menu);
+ final JMenu edit_menu = new JMenu("Edit");
+ edit_menu.setMnemonic(KeyEvent.VK_E);
+ menu_bar.add(edit_menu);
+ final JMenu create_menu = new JMenu("Create");
+ create_menu.setMnemonic(KeyEvent.VK_C);
+ menu_bar.add(create_menu);
+ /*final JMenu write_menu = new JMenu("Write");
+ write_menu.setMnemonic(KeyEvent.VK_W);
+ menu_bar.add(write_menu);*/
+ JMenu run_menu = new JMenu("Run");
+ run_menu.setMnemonic(KeyEvent.VK_R);
+ menu_bar.add(run_menu);
+
+ final JMenu graph_menu = new JMenu("Graph");
+ graph_menu.setMnemonic(KeyEvent.VK_G);
+ menu_bar.add(graph_menu);
+
+ for(int i = 0 ; i < getEntryGroupArray().length ; ++i)
+ {
+ final EntryGroup entry_group = getEntryGroupArray()[i];
+ final String sub_menu_name = makeNewSubMenuName(entry_group);
+
+ AlignmentViewer alignQueryViewer;
+ if(i==0)
+ alignQueryViewer = null;
+ else
+ alignQueryViewer = getAlignmentViewerArray()[i-1];
+
+ AlignmentViewer alignSubjectViewer;
+ if(i == getEntryGroupArray().length-1)
+ alignSubjectViewer = null;
+ else
+ alignSubjectViewer = getAlignmentViewerArray()[i];
+
+ final EntryGroupMenu this_entries_menu =
+ new EntryGroupMenu(this,
+ getEntryGroupArray()[i],
+ sub_menu_name);
+ entries_menu.add(this_entries_menu);
+
+ final SelectMenu this_select_menu =
+ new SelectMenu(this,
+ getSelectionArray()[i],
+ getGotoEventSourceArray()[i],
+ getEntryGroupArray()[i],
+ getBasePlotGroupArray()[i],
+ alignQueryViewer, alignSubjectViewer,
+ sub_menu_name);
+ select_menu.add(this_select_menu);
+
+ final ViewMenu this_view_menu =
+ new ViewMenu(this,
+ getSelectionArray()[i],
+ getGotoEventSourceArray()[i],
+ getEntryGroupArray()[i],
+ getBasePlotGroupArray()[i],
+ sub_menu_name);
+ view_menu.add(this_view_menu);
+
+ final GotoMenu this_goto_menu =
+ new GotoMenu(this,
+ getSelectionArray()[i],
+ getGotoEventSourceArray()[i],
+ getEntryGroupArray()[i],
+ sub_menu_name);
+ goto_menu.add(this_goto_menu);
+
+ AddMenu this_create_menu = null;
+
+ if(Options.readWritePossible())
+ {
+ final EditMenu this_edit_menu =
+ new EditMenu(this,
+ getSelectionArray()[i],
+ getGotoEventSourceArray()[i],
+ getEntryGroupArray()[i],
+ getBasePlotGroupArray()[i],
+ sub_menu_name,
+ getFeatureDisplayArray()[i]);
+ edit_menu.add(this_edit_menu);
+
+ this_create_menu =
+ new AddMenu(this, getSelectionArray()[i],
+ getEntryGroupArray()[i],
+ getGotoEventSourceArray()[i],
+ getBasePlotGroupArray()[i],
+ alignQueryViewer, alignSubjectViewer,
+ sub_menu_name);
+ create_menu.add(this_create_menu);
+
+ final RunMenu this_run_menu =
+ new RunMenu(this, getSelectionArray()[i],
+ sub_menu_name);
+ run_menu.add(this_run_menu);
+ }
+
+ final GraphMenu this_graph_menu =
+ new GraphMenu(this,
+ getEntryGroupArray()[i],
+ getBasePlotGroupArray()[i],
+ getFeatureDisplayArray()[i],
+ sub_menu_name, null, i+1);
+ graph_menu.add(this_graph_menu);
+ }
+
+ final JMenuItem resize = new JMenuItem("Adjust panel heights ...");
+ resize.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ new ActPanelResizer(MultiComparator.this, layout);
+ }
+ });
+ view_menu.add(resize);
+
+ final JMenu display_menu = new JMenu("Display");
+ display_menu.setMnemonic(KeyEvent.VK_D);
+
+ final JMenuItem hide_on_frame_lines_item =
+ new JMenuItem("Hide Frame Lines");
+ hide_on_frame_lines_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ for(int i = 0 ; i < getFeatureDisplayArray().length ; ++i)
+ {
+ getFeatureDisplayArray()[i].setShowForwardFrameLines(false);
+ getFeatureDisplayArray()[i].setShowReverseFrameLines(false);
+ }
+ }
+ });
+
+ display_menu.add(hide_on_frame_lines_item);
+
+ final JMenuItem show_on_frame_lines_item =
+ new JMenuItem("Show Frame Lines");
+ show_on_frame_lines_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ for(int i = 0 ; i < getFeatureDisplayArray().length ; ++i)
+ {
+ getFeatureDisplayArray()[i].setShowForwardFrameLines(true);
+ getFeatureDisplayArray()[i].setShowReverseFrameLines(true);
+ }
+ }
+ });
+
+ display_menu.add(show_on_frame_lines_item);
+
+ menu_bar.add(display_menu);
+ }
+
+
+ /**
+ * Print menu
+ **/
+ private void printMenu()
+ {
+ JMenuItem printImage = new JMenuItem("Save As Image Files (png/svg)...");
+ printImage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintACT pact = new PrintACT(MultiComparator.this);
+ pact.print();
+ }
+ });
+ file_menu.add(printImage);
+
+ JMenuItem printPS = new JMenuItem("Print...");
+ printPS.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintACT pact = new PrintACT(MultiComparator.this);
+ pact.doPrintActions();
+ }
+ });
+ file_menu.add(printPS);
+
+// print preview
+ JMenuItem printPreview = new JMenuItem("Print Preview");
+ file_menu.add(printPreview);
+ printPreview.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintACT pact = new PrintACT(MultiComparator.this);
+ pact.printPreview();
+ }
+ });
+ }
+
+
+ /**
+ * Make a new File menu replacing the current one(if any).
+ **/
+ private void makeFileMenu()
+ {
+ file_menu.removeAll();
+ file_menu.setMnemonic(KeyEvent.VK_F);
+ final EntryGroup[] entry_group_array = getEntryGroupArray();
+
+ JMenuItem popFileManager = new JMenuItem("Show File Manager ...");
+ popFileManager.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(ActMain.filemanager == null)
+ ActMain.filemanager = new FileManager(MultiComparator.this,null);
+ else
+ ActMain.filemanager.setVisible(true);
+ }
+ });
+ file_menu.add(popFileManager);
+
+ if(entry_group_array[0] ==
+ entry_group_array[entry_group_array.length - 1])
+ {
+ final JMenuItem rotate_button = new JMenuItem("Rotate Sequences");
+ rotate_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ rotateSequences();
+ }
+ });
+
+ file_menu.add(rotate_button);
+ file_menu.addSeparator();
+ }
+
+ for(int i = 0; i < getEntryGroupArray().length; ++i)
+ {
+ final EntryGroup entry_group = getEntryGroupArray()[i];
+ final String new_menu_name = makeNewSubMenuName(entry_group);
+ final JMenu entry_group_menu = new JMenu(new_menu_name);
+ file_menu.add(entry_group_menu);
+
+ for(int source_index = 0; source_index < entry_sources.size();
+ ++source_index)
+ {
+ final EntrySource this_source =
+ entry_sources.elementAt(source_index);
+
+ if(this_source.isFullEntrySource())
+ continue;
+
+ String entry_source_name = this_source.getSourceName();
+ String menu_item_name = null;
+
+ if(entry_source_name.equals("Filesystem"))
+ menu_item_name = "Read An Entry ...";
+ else
+ menu_item_name = "Read An Entry From " + entry_source_name + " ...";
+
+ final JMenuItem read_entry = new JMenuItem(menu_item_name);
+
+ read_entry.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ readAnEntry(this_source,entry_group);
+ }
+ });
+
+ entry_group_menu.add(read_entry);
+ }
+
+ entry_group_menu.addSeparator();
+
+ if(entry_group == null || entry_group.size() == 0)
+ {
+ // don't create a menu
+ }
+ else
+ {
+ final JMenu save_entry_menu = new JMenu("Save Entry");
+
+ for(int entry_index = 0; entry_index < entry_group.size();
+ ++entry_index)
+ {
+ final Entry this_entry = entry_group.elementAt(entry_index);
+ String entry_name = this_entry.getName();
+
+ if(entry_name == null)
+ entry_name = "no name";
+
+ final ActionListener save_entry_listener =
+ new ActionListener()
+ {
+ public void actionPerformed(final ActionEvent event)
+ {
+ MultiComparator.this.saveEntry(this_entry);
+ }
+ };
+
+ final JMenuItem save_entry_item = new JMenuItem(entry_name);
+ save_entry_item.addActionListener(save_entry_listener);
+ save_entry_menu.add(save_entry_item);
+ }
+
+ entry_group_menu.add(save_entry_menu);
+
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ {
+ final ChadoTransactionManager ctm = new ChadoTransactionManager();
+ entry_group.addFeatureChangeListener(ctm);
+ entry_group.addEntryChangeListener(ctm);
+ entry_group.getBases().addSequenceChangeListener(ctm, 0);
+ ctm.setEntryGroup(entry_group);
+
+ final Selection selection = getSelectionArray()[i];
+ final GotoEventSource goto_event_source = getGotoEventSourceArray()[i];
+ final BasePlotGroup base_plot_group = getBasePlotGroupArray()[i];
+
+ final JMenuItem commit_entry_item = new JMenuItem("Commit ...");
+ commit_entry_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(final ActionEvent event)
+ {
+ EntryEdit.commitToDatabase(entry_group, ctm, MultiComparator.this,
+ selection, goto_event_source,base_plot_group);
+ }
+ });
+ entry_group_menu.add(commit_entry_item);
+ }
+
+ final JMenuItem save_all_menu = new JMenuItem("Save All");
+
+ save_all_menu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ for(int entry_index = 0; entry_index < entry_group.size();
+ ++entry_index)
+ {
+ final Entry this_entry = entry_group.elementAt(entry_index);
+ MultiComparator.this.saveEntry(this_entry);
+ }
+ }
+ });
+
+ entry_group_menu.add(save_all_menu);
+ entry_group_menu.addSeparator();
+
+
+ final WriteMenu this_write_menu =
+ new WriteMenu(this,
+ getSelectionArray()[i],
+ getEntryGroupArray()[i]);
+ entry_group_menu.add(this_write_menu);
+ entry_group_menu.addSeparator();
+ }
+
+ JMenuItem read_bam_file = new JMenuItem("Read BAM / VCF ...");
+ final int index = i;
+ read_bam_file.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ FileSelectionDialog fileChooser = new FileSelectionDialog(
+ null, false, "BAM / VCF View", "BAM / VCF");
+ List<String> listBams = fileChooser.getFiles(".*\\.bam$");
+ List<String> vcfFiles = fileChooser.getFiles(VCFview.VCFFILE_SUFFIX);
+
+ loadBamAndVcf(listBams, vcfFiles, index);
+ }
+ });
+ entry_group_menu.add(read_bam_file);
+
+ final JMenuItem edit_subject = new JMenuItem("Edit In Artemis");
+ edit_subject.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ final EntryEdit entry_edit = new EntryEdit(entry_group);
+ entry_edit.setVisible(true);
+ }
+ });
+
+ entry_group_menu.add(edit_subject);
+ }
+
+ file_menu.addSeparator();
+ printMenu();
+ file_menu.addSeparator();
+
+ final JMenuItem close_button = new JMenuItem("Close");
+ close_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ closeComparator();
+ }
+ });
+
+ file_menu.add(close_button);
+ }
+
+ private void loadBamAndVcf(List<String> listBams,
+ List<String> vcfFiles,
+ int index)
+ {
+ final JPanel thisBamPanel = bamPanel[index];
+ final JPanel thisVCFPanel = vcfPanel[index];
+ final FeatureDisplay feature_display = feature_display_array[index];
+ final EntryGroup entry_group = getEntryGroupArray()[index];
+
+ if(listBams.size() > 0)
+ {
+ thisBamPanel.removeAll();
+ thisBamPanel.setVisible(true);
+
+ BamView bamView;
+ try
+ {
+ bamView = new BamView(listBams, null, 2000, feature_display,
+ entry_group.getBases(), thisBamPanel, null);
+ }
+ catch(Exception ex)
+ {
+ JOptionPane.showMessageDialog(null,
+ ex.getMessage(),
+ "Error",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ if(dimensionAlignViewer == null)
+ dimensionAlignViewer = alignment_viewer_array[0].getSize();
+ thisBamPanel.setPreferredSize(new Dimension(500, dimensionAlignViewer.height/2));
+
+ bamView.setDisplay(feature_display.getFirstVisibleForwardBase(),
+ feature_display.getLastVisibleForwardBase(), null);
+
+ bamView.getJspView().getVerticalScrollBar().setValue(
+ bamView.getJspView().getVerticalScrollBar().getMaximum());
+
+ feature_display.addDisplayAdjustmentListener(bamView);
+ feature_display.getSelection().addSelectionChangeListener(bamView);
+ MultiComparator.this.validate();
+ }
+
+ if (vcfFiles.size() > 0)
+ {
+ thisVCFPanel.removeAll();
+ thisVCFPanel.setVisible(true);
+
+ VCFview vcfView = new VCFview(null, thisVCFPanel, vcfFiles,
+ feature_display.getMaxVisibleBases(), 1, null, null,
+ null, feature_display);
+
+ feature_display.addDisplayAdjustmentListener(vcfView);
+ feature_display.getSelection().addSelectionChangeListener(vcfView);
+ MultiComparator.this.validate();
+ }
+ }
+
+ /**
+ * Read an entry
+ **/
+ private void readAnEntry(final EntrySource this_source,
+ final EntryGroup entry_group)
+ {
+ try
+ {
+ final Entry new_entry = this_source.getEntry(entry_group.getBases(),
+ true);
+ if(new_entry != null)
+ entry_group.add(new_entry);
+ }
+ catch(final OutOfRangeException e)
+ {
+ new MessageDialog(MultiComparator.this,
+ "read failed: one of the features " +
+ "in the entry has an out of " +
+ "range location: " +
+ e.getMessage());
+ }
+ catch(final IOException e)
+ {
+ new MessageDialog(MultiComparator.this,
+ "read failed due to an IO error: " +
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Return the current default font(from Diana.options).
+ **/
+ private Font getDefaultFont()
+ {
+ return Options.getOptions().getFont();
+ }
+
+ /**
+ * Return the EntryGroup objects that were passed to the constructor.
+ **/
+ protected EntryGroup[] getEntryGroupArray()
+ {
+ return entry_group_array;
+ }
+
+ /**
+ * Return the ComparisonData objects that were passed to the constructor.
+ **/
+ private ComparisonData[] getComparisonDataArray()
+ {
+ return comparison_data_array;
+ }
+
+ /**
+ * Return the AlignmentViewer objects that were created in the constructor.
+ **/
+ protected AlignmentViewer[] getAlignmentViewerArray()
+ {
+ return alignment_viewer_array;
+ }
+
+ /**
+ * Return the FeatureDisplay objects that were created in the constructor.
+ **/
+ protected FeatureDisplay[] getFeatureDisplayArray()
+ {
+ return feature_display_array;
+ }
+
+ /**
+ * Return the Selection objects that were created in the constructor.
+ **/
+ private Selection[] getSelectionArray()
+ {
+ return selection_array;
+ }
+
+ /**
+ * Return the GotoEventSource objects that were created in the constructor.
+ **/
+ private GotoEventSource[] getGotoEventSourceArray()
+ {
+ return goto_event_source_array;
+ }
+
+ /**
+ * Return the BasePlotGroup objects that were created in the constructor.
+ **/
+ protected BasePlotGroup[] getBasePlotGroupArray()
+ {
+ return base_plot_group_array;
+ }
+
+ /**
+ * Return the Bam JPanel objects that were created in the constructor.
+ **/
+ protected JPanel[] getBamPanelArray()
+ {
+ return bamPanel;
+ }
+
+ /**
+ * Return the VCF JPanel objects that were created in the constructor.
+ **/
+ protected JPanel[] getVcfPanelArray()
+ {
+ return vcfPanel;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/Navigator.java b/uk/ac/sanger/artemis/components/Navigator.java
new file mode 100644
index 0000000..df5bf94
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/Navigator.java
@@ -0,0 +1,969 @@
+/* Navigator.java
+ *
+ * created: Sun Jan 10 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/Navigator.java,v 1.4 2008-11-13 12:02:36 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.*;
+
+import uk.ac.sanger.artemis.util.StringVector;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+/**
+ * This component allows the user to navigate around the Entry.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Navigator.java,v 1.4 2008-11-13 12:02:36 tjc Exp $
+ **/
+
+public class Navigator extends JFrame
+ implements EntryGroupChangeListener
+{
+ private static final long serialVersionUID = 1L;
+
+ /** selects the goto base function. */
+ private final JRadioButton goto_base_button;
+
+ /** selects the goto base pattern function. */
+ private final JRadioButton goto_base_pattern_button =
+ new JRadioButton ("Find Base Pattern:", true);;
+
+ /** selects the find amino acid sequence function. */
+ private final JRadioButton goto_aa_pattern_button =
+ new JRadioButton ("Find Amino Acid String:", true);;
+
+ /** selects the goto feature qualifier value function. */
+ private final JRadioButton goto_qualifier_button;
+
+ /** selects the goto gene name function. */
+ private final JRadioButton goto_gene_name_button;
+
+ /** selects the goto feature key function. */
+ private final JRadioButton goto_key_button;
+
+ /** pattern to search for if the user has selected the
+ goto base function. */
+ private final JTextField goto_base_text;
+
+ /** pattern to search for if the user has selected the
+ goto base pattern function. */
+ private final JTextField goto_base_pattern_text;
+
+ /** pattern to search for if the user has selected the
+ goto amino acid function. */
+ private final JTextField goto_aa_pattern_text;
+
+ /** pattern to search for if the user has selected the
+ goto qualifier value function. */
+ private final JTextField goto_qualifier_textfield;
+
+ /** pattern to search for if the user has selected the
+ goto gene name function. */
+ private final JTextField goto_gene_name_textfield;
+
+ /** key to search for if the user has selected the
+ goto key function. */
+ private final JTextField goto_feature_key_textfield;
+
+ /** selects if the search should start at first/last
+ base or first/last feature (depending on the search type). */
+ private JRadioButton start_at_an_end_button;
+
+ /** selects if the search should start at the
+ position of the current selection. */
+ private final JRadioButton start_at_selection_button;
+
+ /** If checked the search will go backwards. */
+ private final JCheckBox search_backward_button;
+
+ /** If checked the search will ignore the case of the query and subject. */
+ private final JCheckBox ignore_case_button;
+
+ /** If checked the search text is allowed to match a substring of a
+ qualifier value. */
+ private final JCheckBox partial_match_button;
+
+ /** Check whether the match overlaps selected range */
+ private final JCheckBox overlaps_with_selection;
+
+ /** Search fwd strand */
+ private final JCheckBox fwd_strand = new JCheckBox("Forward Strand ", true);;
+
+ /** Search bwd strand */
+ private final JCheckBox rev_strand = new JCheckBox("Reverse Strand", true);;
+
+ /**
+ * The GotoEventSource object that was passed to the constructor.
+ **/
+ private final GotoEventSource goto_event_source;
+
+ /**
+ * The EntryGroup object that was passed to the constructor.
+ **/
+ private final EntryGroup entry_group;
+
+ /**
+ * This is the Selection that was passed to the constructor.
+ **/
+ private final Selection selection;
+
+ private boolean disposeOnClose = false;
+
+ /**
+ * Create a new Navigator component.
+ * @param selection The Selection that the commands will operate on.
+ * @param goto_event_source The object the we will call gotoBase () on.
+ * @param entry_group The EntryGroup object used when searching for
+ * qualifier text in features.
+ **/
+ public Navigator (final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group)
+ {
+ super ("Artemis Navigator");
+
+ this.selection = selection;
+ this.entry_group = entry_group;
+ this.goto_event_source = goto_event_source;
+
+ final Font default_font = Options.getOptions ().getFont ();
+
+ GridBagLayout gridbag = new GridBagLayout();
+ getContentPane ().setLayout (gridbag);
+
+ setFont (default_font);
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.NORTH;
+ c.weighty = 0;
+
+ final int TEXT_FIELD_WIDTH = 25;
+
+ final ButtonGroup button_group = new ButtonGroup ();
+
+ goto_base_button = new JRadioButton ("Goto Base:", true);
+
+ button_group.add (goto_base_button);
+
+ final JPanel goto_base_panel = new JPanel (new FlowLayout (FlowLayout.LEFT));
+ goto_base_panel.add (goto_base_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints (goto_base_panel, c);
+ getContentPane ().add (goto_base_panel);
+
+ goto_base_text = new JTextField ("", TEXT_FIELD_WIDTH);
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints (goto_base_text, c);
+ getContentPane ().add (goto_base_text);
+
+ goto_base_text.addKeyListener (new KeyAdapter () {
+ public void keyTyped(final KeyEvent e) {
+ goto_base_button.setSelected (true);
+ if (e.getKeyChar () == '\n') {
+ doGoto ();
+ }
+ }
+ });
+
+
+ goto_gene_name_button =
+ new JRadioButton ("Goto Feature With Gene Name:", true);
+
+ button_group.add (goto_gene_name_button);
+
+ final JPanel goto_gene_name_panel = new JPanel (new FlowLayout (FlowLayout.LEFT));
+ goto_gene_name_panel.add (goto_gene_name_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints (goto_gene_name_panel, c);
+ getContentPane ().add (goto_gene_name_panel);
+
+ goto_gene_name_textfield = new JTextField ("", TEXT_FIELD_WIDTH);
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints (goto_gene_name_textfield, c);
+ getContentPane ().add (goto_gene_name_textfield);
+
+ goto_gene_name_textfield.addKeyListener (new KeyAdapter () {
+ public void keyTyped(final KeyEvent e) {
+ goto_gene_name_button.setSelected (true);
+ if (e.getKeyChar () == '\n') {
+ doGoto ();
+ }
+ }
+ });
+
+
+ goto_qualifier_button =
+ new JRadioButton ("Goto Feature With This Qualifier Value:", true);
+ button_group.add (goto_qualifier_button);
+
+ final JPanel goto_qualifier_panel = new JPanel (new FlowLayout (FlowLayout.LEFT));
+ goto_qualifier_panel.add (goto_qualifier_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints (goto_qualifier_panel, c);
+ getContentPane ().add (goto_qualifier_panel);
+
+ goto_qualifier_textfield = new JTextField ("", TEXT_FIELD_WIDTH);
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints (goto_qualifier_textfield, c);
+ getContentPane ().add (goto_qualifier_textfield);
+
+ goto_qualifier_textfield.addKeyListener (new KeyAdapter () {
+ public void keyTyped(final KeyEvent e) {
+ goto_qualifier_button.setSelected (true);
+ if (e.getKeyChar () == '\n') {
+ doGoto ();
+ }
+ }
+ });
+
+
+ goto_key_button =
+ new JRadioButton ("Goto Feature With This Key:", true);
+ button_group.add (goto_key_button);
+
+ final JPanel goto_key_panel = new JPanel (new FlowLayout (FlowLayout.LEFT));
+ goto_key_panel.add (goto_key_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints (goto_key_panel, c);
+ getContentPane ().add (goto_key_panel);
+
+ goto_feature_key_textfield = new JTextField ("", TEXT_FIELD_WIDTH);
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints (goto_feature_key_textfield, c);
+ getContentPane ().add (goto_feature_key_textfield);
+
+ goto_feature_key_textfield.addKeyListener (new KeyAdapter () {
+ public void keyTyped(final KeyEvent e) {
+ goto_key_button.setSelected (true);
+ if (e.getKeyChar () == '\n') {
+ doGoto ();
+ }
+ }
+ });
+
+
+ goto_base_pattern_button.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent arg0) {
+ rev_strand.setEnabled(goto_aa_pattern_button.isSelected() ||
+ goto_base_pattern_button.isSelected());
+ fwd_strand.setEnabled(goto_aa_pattern_button.isSelected() ||
+ goto_base_pattern_button.isSelected());
+ }
+ });
+ button_group.add (goto_base_pattern_button);
+
+ final JPanel goto_base_pattern_panel = new JPanel (new FlowLayout (FlowLayout.LEFT));
+ goto_base_pattern_panel.add (goto_base_pattern_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints (goto_base_pattern_panel, c);
+ getContentPane ().add (goto_base_pattern_panel);
+
+ goto_base_pattern_text = new JTextField ("", TEXT_FIELD_WIDTH);
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints (goto_base_pattern_text, c);
+ getContentPane ().add (goto_base_pattern_text);
+
+ goto_base_pattern_text.addKeyListener (new KeyAdapter () {
+ public void keyTyped(final KeyEvent e) {
+ goto_base_pattern_button.setSelected (true);
+ if (e.getKeyChar () == '\n') {
+ doGoto ();
+ }
+ }
+ });
+
+
+ goto_aa_pattern_button.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent arg0) {
+ rev_strand.setEnabled(goto_aa_pattern_button.isSelected() ||
+ goto_base_pattern_button.isSelected());
+ fwd_strand.setEnabled(goto_aa_pattern_button.isSelected() ||
+ goto_base_pattern_button.isSelected());
+ }
+ });
+ button_group.add (goto_aa_pattern_button);
+
+ final JPanel goto_aa_pattern_panel = new JPanel (new FlowLayout (FlowLayout.LEFT));
+ goto_aa_pattern_panel.add (goto_aa_pattern_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints (goto_aa_pattern_panel, c);
+ getContentPane ().add (goto_aa_pattern_panel);
+
+ goto_aa_pattern_text = new JTextField ("", TEXT_FIELD_WIDTH);
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints (goto_aa_pattern_text, c);
+ getContentPane ().add (goto_aa_pattern_text);
+
+ goto_aa_pattern_text.addKeyListener (new KeyAdapter () {
+ public void keyTyped(final KeyEvent e) {
+ goto_aa_pattern_button.setSelected (true);
+ if (e.getKeyChar () == '\n') {
+ doGoto ();
+ }
+ }
+ });
+
+ goto_base_button.setSelected (true);
+
+ final ButtonGroup start_position_button_group = new ButtonGroup ();
+ final JPanel start_at_an_end_panel = new JPanel (new FlowLayout(FlowLayout.LEFT));
+
+ start_at_an_end_button =
+ new JRadioButton ("beginning (or end)", true);
+
+ start_position_button_group.add (start_at_an_end_button);
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+
+ start_at_an_end_panel.add (new JLabel(" Start search at:"));
+ start_at_an_end_panel.add (start_at_an_end_button);
+
+ start_at_selection_button =
+ new JRadioButton ("selection", false);
+ start_at_selection_button.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ overlaps_with_selection.setEnabled(start_at_selection_button.isSelected());
+ }
+ });
+
+ start_position_button_group.add (start_at_selection_button);
+
+ start_at_an_end_panel.add (start_at_selection_button);
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ getContentPane ().add (Box.createVerticalStrut(5), c);
+ getContentPane ().add (start_at_an_end_panel, c);
+
+
+ final JPanel select_within_button_panel = new JPanel (new FlowLayout (FlowLayout.LEFT));
+ overlaps_with_selection = new JCheckBox("Overlaps With Selection", false);
+ overlaps_with_selection.setEnabled(start_at_selection_button.isSelected());
+ select_within_button_panel.add(overlaps_with_selection);
+ getContentPane ().add (select_within_button_panel, c);
+
+ //
+ // STRAND SELECTION
+ final JPanel strand_panel = new JPanel (new FlowLayout (FlowLayout.LEFT));
+ fwd_strand.setEnabled(goto_aa_pattern_button.isSelected() ||
+ goto_base_pattern_button.isSelected());
+ strand_panel.add(fwd_strand);
+
+ rev_strand.setEnabled(goto_aa_pattern_button.isSelected() ||
+ goto_base_pattern_button.isSelected());
+ strand_panel.add(rev_strand);
+ getContentPane ().add (strand_panel, c);
+
+
+ //
+ // OTHER OPTIONS
+ final JPanel option_button_panel = new JPanel (new FlowLayout (FlowLayout.LEFT));
+ search_backward_button = new JCheckBox ("Search Backward", false);
+ ignore_case_button = new JCheckBox ("Ignore Case", true);
+ partial_match_button = new JCheckBox ("Allow Substring Matches", true);
+
+ option_button_panel.add (search_backward_button);
+ option_button_panel.add (ignore_case_button);
+ option_button_panel.add (partial_match_button);
+
+ getContentPane ().add (option_button_panel, c);
+
+
+ final JButton goto_button = new JButton ("Goto");
+ goto_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ doGoto ();
+ }
+ });
+
+
+ final JButton clear_button = new JButton ("Clear");
+ clear_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ clear ();
+ }
+ });
+
+
+ final JButton close_button = new JButton ("Close");
+ close_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ onClose();
+ }
+ });
+
+
+ final JPanel close_and_goto_panel = new JPanel (
+ new FlowLayout (FlowLayout.CENTER, 15, 5));
+
+ close_and_goto_panel.add (goto_button);
+ close_and_goto_panel.add (clear_button);
+ close_and_goto_panel.add (close_button);
+
+ gridbag.setConstraints (close_and_goto_panel, c);
+ getContentPane ().add (close_and_goto_panel);
+
+
+ addWindowListener (new WindowAdapter () {
+ public void windowClosing (WindowEvent event) {
+ onClose();
+ }
+ });
+
+ getEntryGroup ().addEntryGroupChangeListener (this);
+
+ pack ();
+ Utilities.centreFrame(this);
+ setVisible (true);
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can get rid of the Navigator when the
+ * EntryGroup is no longer in use (for example when the EntryEdit is
+ * closed).
+ **/
+ public void entryGroupChanged (final EntryGroupChangeEvent event) {
+ switch (event.getType ()) {
+ case EntryGroupChangeEvent.DONE_GONE:
+ getEntryGroup ().removeEntryGroupChangeListener (this);
+ dispose ();
+ break;
+ }
+ }
+
+ private void onClose()
+ {
+ if(disposeOnClose)
+ {
+ getEntryGroup().removeEntryGroupChangeListener(Navigator.this);
+ dispose();
+ }
+ else
+ setVisible(false);
+ }
+
+ protected void setDisposeOnClose(boolean disposeOnClose)
+ {
+ this.disposeOnClose = disposeOnClose;
+ }
+
+ /**
+ * This method finds the given pattern in the Bases of the given EntryGroup
+ * and returns a MarkerRange for it.
+ * @param pattern This is the pattern to search for.
+ * @param entry_group The EntryGroup to search.
+ * @param selection The current selection.
+ * @param start_at_end If true or if there is nothing in the Selection then
+ * the search will start at the first or last base (depending on value of
+ * the next parameter), otherwise the search will start at the Selection.
+ * @param search_backwards If true the search will move from last base to
+ * first base, otherwise first to last.
+ * @return The range that matches the pattern or null if there is no match.
+ **/
+ private static MarkerRange findBasePattern (final BasePattern pattern,
+ final EntryGroup entry_group,
+ final Selection selection,
+ final boolean start_at_end,
+ final boolean search_backwards,
+ final boolean search_fwd_strand,
+ final boolean search_bwd_strand) {
+ // if start_at_end is false we want to start the search at the selection
+ final Marker selection_base = selection.getLowestBaseOfSelection ();
+
+ final Marker start_position;
+
+ if (start_at_end || selection_base == null) {
+ // null means start the search from the beginning
+ start_position = null;
+ } else {
+ start_position = selection_base;
+ }
+
+ final MarkerRange match_range =
+ pattern.findMatch (entry_group.getBases (),
+ start_position,
+ entry_group.getSequenceLength (),
+ search_backwards,
+ search_fwd_strand, search_bwd_strand);
+
+ if (match_range == null) {
+ return null;
+ } else {
+ return match_range;
+ }
+ }
+
+ /**
+ * This method finds the given amino acid seqeunce, sets the selection to
+ * the matching bases and then sends a GotoEvent to all the GotoEvent
+ * listeners that will make the first base of the match visible.
+ * @param sequence This is the pattern to search for.
+ * @param entry_group The EntryGroup to search.
+ * @param selection The current selection.
+ * @param start_at_end If true or if there is nothing in the Selection then
+ * the search will start at first or last base (depending on value of the
+ * next parameter), otherwise the search will start at the Selection.
+ * @param search_backwards If true the search will move from last base to
+ * first base, otherwise first to last.
+ * @return The range that matches the pattern or null if there is no match.
+ **/
+ private static MarkerRange findAminoAcidSequence (final AminoAcidSequence sequence,
+ final EntryGroup entry_group,
+ final Selection selection,
+ final boolean start_at_end,
+ final boolean search_backwards,
+ final boolean search_fwd_strand,
+ final boolean search_bwd_strand) {
+ // if start_at_end is false we want to start the search at the selection
+ final Marker selection_base = selection.getLowestBaseOfSelection ();
+
+ final Marker start_position;
+
+ if (start_at_end || selection_base == null) {
+ // null means start the search from the beginning
+ start_position = null;
+ } else {
+ start_position = selection_base;
+ }
+
+ return sequence.findMatch (entry_group.getBases (),
+ start_position, search_backwards,
+ search_fwd_strand, search_bwd_strand);
+ }
+
+ /**
+ * This method will perform the selected goto function.
+ **/
+ private void doGoto () {
+ if (goto_base_button.isSelected ()) {
+ doGotoBase ();
+ return;
+ }
+
+ if (goto_base_pattern_button.isSelected ()) {
+ if(!rev_strand.isSelected() && !fwd_strand.isSelected())
+ new MessageDialog (this, "Select a strand to search.");
+ else
+ doGotoBasePattern ();
+ return;
+ }
+
+ if (goto_aa_pattern_button.isSelected ()) {
+ if(!rev_strand.isSelected() && !fwd_strand.isSelected())
+ new MessageDialog (this, "Select a strand to search.");
+ else
+ doGotoAAPattern ();
+ return;
+ }
+
+ if (goto_gene_name_button.isSelected ()) {
+ final StringVector qualifiers_to_search = new StringVector ();
+ qualifiers_to_search.add (Options.getOptions ().getAllGeneNames ());
+
+ doGotoQualifierValue (qualifiers_to_search,
+ goto_gene_name_textfield.getText ().trim ());
+ return;
+ }
+
+ if (goto_qualifier_button.isSelected ()) {
+ doGotoQualifierValue (null, goto_qualifier_textfield.getText ().trim ());
+ return;
+ }
+
+ if (goto_key_button.isSelected ()) {
+ doGotoKey ();
+ return;
+ }
+ }
+
+ /**
+ * Clear all the JTextField components.
+ **/
+ private void clear () {
+ goto_base_text.setText ("");
+ goto_base_pattern_text.setText ("");
+ goto_aa_pattern_text.setText ("");
+ goto_qualifier_textfield.setText ("");
+ goto_gene_name_textfield.setText ("");
+ goto_feature_key_textfield.setText ("");
+ }
+
+ /**
+ * Show a message dialog saying that the search text field is empty.
+ **/
+ private void searchTextEmptyError () {
+ new MessageDialog (this, "You have not entered a value to search for");
+ }
+
+ /**
+ * Go to the base that the user typed in to the goto_base_text TextArea.
+ **/
+ private void doGotoBase () {
+ final String number_string = goto_base_text.getText ().trim ();
+
+ if (number_string.length () == 0) {
+ new MessageDialog (this, "you have not entered a number to go to");
+ return;
+ }
+
+ try {
+ final int destination_base =
+ Integer.valueOf (number_string).intValue ();
+
+ final MarkerRange destination_range =
+ goto_event_source.gotoBase (destination_base);
+
+ if (destination_range == null) {
+ new MessageDialog (this,
+ "the base is out of range for this sequence");
+ } else {
+ // success select that base
+ getSelection ().setMarkerRange (destination_range);
+ }
+ } catch (NumberFormatException e) {
+ new MessageDialog (this, "Cannot understand this number: " +
+ goto_base_text.getText ());
+ }
+ }
+
+ /**
+ * Go to the base pattern that the user typed in to the
+ * goto_base_pattern_text TextArea.
+ **/
+ private void doGotoBasePattern () {
+ try {
+ final String pattern_string =
+ goto_base_pattern_text.getText ().trim ();
+
+ if (pattern_string.length () == 0) {
+ new MessageDialog (this, "you have not entered a pattern to go to");
+ return;
+ }
+
+ final BasePattern pattern = new BasePattern (pattern_string);
+
+ final boolean start_at_an_end = start_at_an_end_button.isSelected ();
+
+ final MarkerRange match_range =
+ findBasePattern (pattern, getEntryGroup (),
+ getSelection (), start_at_an_end,
+ search_backward_button.isSelected (),
+ fwd_strand.isSelected(), rev_strand.isSelected());
+
+ if (match_range == null) {
+ new MessageDialog (this, "reached the end of sequence");
+ } else {
+
+ if(start_at_selection_button.isSelected() && overlaps_with_selection.isSelected()) {
+ if(!overlapsRange(getSelection().getSelectionRanges(), match_range)) {
+ new MessageDialog (this, "Next match ("+
+ match_range.getRawRange().toString()+") outside selection.");
+ return;
+ }
+ }
+
+ getSelection ().setMarkerRange (match_range);
+
+ final Marker first_selected_base =
+ getSelection ().getLowestBaseOfSelection ();
+
+ goto_event_source.gotoBase (first_selected_base);
+ start_at_selection_button.setSelected (true);
+ }
+ } catch (BasePatternFormatException e) {
+ new MessageDialog (this,
+ "Illegal base pattern: " +
+ goto_base_pattern_text.getText ());
+ }
+ }
+
+ /**
+ * Find the amino acid pattern that the user typed in to the
+ * goto_aa_pattern_text TextArea.
+ **/
+ private void doGotoAAPattern () {
+ final String pattern_string =
+ goto_aa_pattern_text.getText ().trim ().toUpperCase();
+
+ if (pattern_string.length () == 0) {
+ new MessageDialog (this, "you have not entered a pattern to go to");
+ return;
+ }
+
+ final AminoAcidSequence pattern = new AminoAcidSequence (pattern_string);
+
+ final boolean start_at_an_end = start_at_an_end_button.isSelected ();
+ final boolean search_backwards = search_backward_button.isSelected ();
+
+ final MarkerRange match_range =
+ findAminoAcidSequence (pattern, getEntryGroup (),
+ getSelection (), start_at_an_end,
+ search_backwards,
+ fwd_strand.isSelected(), rev_strand.isSelected());
+
+ if (match_range == null)
+ new MessageDialog (this, "reached the end of sequence");
+ else {
+
+ if(start_at_selection_button.isSelected() && overlaps_with_selection.isSelected()) {
+ if(!overlapsRange(getSelection().getSelectionRanges(), match_range)) {
+ new MessageDialog (this, "Next match ("+
+ match_range.getRawRange().toString()+") outside selection.");
+ return;
+ }
+ }
+
+ start_at_selection_button.setSelected (true);
+ getSelection ().setMarkerRange (match_range);
+ goto_event_source.gotoBase (getSelection ().getLowestBaseOfSelection ());
+ }
+ }
+
+ /**
+ * Check if a MarkerRange is contained in a RangeVector
+ * @param ranges
+ * @param match_range
+ * @return
+ */
+ private boolean overlapsRange(final RangeVector ranges,
+ final MarkerRange match_range)
+ {
+ for(int i=0; i<ranges.size(); i++) {
+ Range r = (Range)ranges.get(i);
+
+ if( ( match_range.getRawStart().getRawPosition() >= r.getStart() &&
+ match_range.getRawStart().getRawPosition() <= r.getEnd() ) ||
+ ( match_range.getRawEnd().getRawPosition() >= r.getStart() &&
+ match_range.getRawEnd().getRawPosition() <= r.getEnd() ))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Select the next feature that contains the text given by the user in the
+ * goto_qualifier_textfield TextArea.
+ **/
+ private void doGotoQualifierValue (final StringVector qualifiers_to_search,
+ final String search_text) {
+ if (search_text.equals ("")) {
+ searchTextEmptyError ();
+ return;
+ }
+
+ final FeatureVector selected_features =
+ getSelection ().getAllFeatures ();
+
+ final int index;
+
+ if (selected_features.size () == 0 ||
+ start_at_an_end_button.isSelected ()) {
+ index = -1;
+ } else {
+ index = getEntryGroup ().indexOf (selected_features.elementAt (0));
+ }
+
+ final int first_search_feature_index;
+
+ Feature found_feature = null;
+
+ if (search_backward_button.isSelected ()) {
+ if (index == -1) {
+ // nothing was selected so start the search at the first feature
+ first_search_feature_index =
+ getEntryGroup ().getAllFeaturesCount () - 1;
+ } else {
+ first_search_feature_index = index - 1;
+ }
+
+ for (int i = first_search_feature_index ; i >= 0 ; --i) {
+ final Feature this_feature = getEntryGroup ().featureAt (i);
+
+ if (this_feature.containsText (search_text,
+ ignore_case_button.isSelected (),
+ partial_match_button.isSelected (),
+ qualifiers_to_search)) {
+ found_feature = this_feature;
+ break;
+ }
+ }
+ } else {
+ if (index == -1) {
+ // nothing was selected so start the search at the first feature
+ first_search_feature_index = 0;
+ } else {
+ first_search_feature_index = index + 1;
+ }
+
+ for (int i = first_search_feature_index ;
+ i < getEntryGroup ().getAllFeaturesCount () ;
+ ++i) {
+ final Feature this_feature = getEntryGroup ().featureAt (i);
+
+ if (this_feature.containsText (search_text,
+ ignore_case_button.isSelected (),
+ partial_match_button.isSelected (),
+ qualifiers_to_search)) {
+
+ found_feature = this_feature;
+ break;
+ }
+ }
+ }
+
+ if (found_feature == null) {
+ getSelection ().clear ();
+ start_at_an_end_button.setSelected (true);
+
+ new MessageDialog (this, "text not found");
+ } else {
+ getSelection ().set (found_feature);
+ goto_event_source.gotoBase (getSelection ().getLowestBaseOfSelection ());
+ start_at_selection_button.setSelected (true);
+ }
+ }
+
+ /**
+ * Select the next feature that has the key given by the user in the
+ * goto_feature_key_textfield TextArea.
+ **/
+ private void doGotoKey () {
+ final FeatureVector selected_features =
+ getSelection ().getAllFeatures ();
+
+ final int index;
+
+ if (selected_features.size () == 0 ||
+ start_at_an_end_button.isSelected ()) {
+ index = -1;
+ } else {
+ index = getEntryGroup ().indexOf (selected_features.elementAt (0));
+ }
+
+ final int first_search_feature_index;
+
+ final String search_key_string =
+ goto_feature_key_textfield.getText ().trim ();
+
+ if (search_key_string.equals ("")) {
+ searchTextEmptyError ();
+ return;
+ }
+
+ Feature found_feature = null;
+
+ if (search_backward_button.isSelected ()) {
+ if (index == -1) {
+ // nothing was selected so start the search at the first feature
+ first_search_feature_index =
+ getEntryGroup ().getAllFeaturesCount () - 1;
+ } else {
+ first_search_feature_index = index - 1;
+ }
+
+ for (int i = first_search_feature_index ; i >= 0 ; --i) {
+ final Feature this_feature = getEntryGroup ().featureAt (i);
+
+ if (keyMatches (this_feature, search_key_string)) {
+ found_feature = this_feature;
+ break;
+ }
+ }
+ } else {
+ if (index == -1) {
+ // nothing was selected so start the search at the first feature
+ first_search_feature_index = 0;
+ } else {
+ first_search_feature_index = index + 1;
+ }
+
+ for (int i = first_search_feature_index ;
+ i < getEntryGroup ().getAllFeaturesCount () ;
+ ++i) {
+ final Feature this_feature = getEntryGroup ().featureAt (i);
+
+ if (keyMatches (this_feature, search_key_string)) {
+ found_feature = this_feature;
+ break;
+ }
+ }
+ }
+
+ if (found_feature == null) {
+ getSelection ().clear ();
+ start_at_an_end_button.setSelected (true);
+
+ new MessageDialog (this, "key not found");
+ } else {
+ getSelection ().set (found_feature);
+ goto_event_source.gotoBase (getSelection ().getLowestBaseOfSelection ());
+ start_at_selection_button.setSelected (true);
+ }
+ }
+
+ /**
+ * Returns true if and only if the given feature has search_key_string as
+ * it's Key.
+ **/
+ private boolean keyMatches (final Feature test_feature,
+ final String search_key_string) {
+ final String feature_key_string;
+
+ if (ignore_case_button.isSelected ()) {
+ feature_key_string = test_feature.getKey ().toString ().toLowerCase ();
+ } else {
+ feature_key_string = test_feature.getKey ().toString ();
+ }
+
+ if (feature_key_string.equals (search_key_string.toLowerCase ())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return the Selection object that was passed to the constructor.
+ **/
+ private Selection getSelection () {
+ return selection;
+ }
+
+ /**
+ * Return the EntryGroup object that was passed to the constructor.
+ **/
+ private EntryGroup getEntryGroup () {
+ return entry_group;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/NonModalDialog.java b/uk/ac/sanger/artemis/components/NonModalDialog.java
new file mode 100644
index 0000000..bb34495
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/NonModalDialog.java
@@ -0,0 +1,95 @@
+/* NonModalDialog
+ *
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.FontMetrics;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+
+public class NonModalDialog extends JDialog
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a non-modal dialog with an array of strings to
+ * display on separate lines.
+ * @param f
+ * @param labelStr
+ */
+ public NonModalDialog(JFrame f, String labelStr[])
+ {
+ super(f, "Check the reference is correctly selected", false);
+
+ GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ GraphicsDevice gd = ge.getDefaultScreenDevice();
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+ Insets ins = Toolkit.getDefaultToolkit().getScreenInsets(gc);
+ int sw = gc.getBounds().width - ins.left - ins.right;
+ int sh = gc.getBounds().height - ins.top - ins.bottom;
+
+ setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ Container cp = getContentPane();
+ Box yBox = Box.createVerticalBox();
+ cp.setLayout(new BorderLayout());
+
+ int width = 10;
+ for(int i=0; i<labelStr.length; i++)
+ {
+ JLabel label = new JLabel(labelStr[i]);
+ yBox.add(label);
+
+ FontMetrics fm = label.getFontMetrics(label.getFont());
+ int thisWidth = fm.stringWidth(label.getText());
+ if(thisWidth > width)
+ width = thisWidth;
+ }
+ cp.add(yBox, BorderLayout.NORTH);
+
+ JButton okButton = new JButton("OK");
+ cp.add(okButton, BorderLayout.SOUTH);
+ okButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ dispose();
+ }
+ });
+
+ setBounds((sw - width)/2, sh/2, width, 100);
+ setVisible(true);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/OSXAdapter.java b/uk/ac/sanger/artemis/components/OSXAdapter.java
new file mode 100644
index 0000000..ed8f8ec
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/OSXAdapter.java
@@ -0,0 +1,209 @@
+package uk.ac.sanger.artemis.components;
+
+import java.lang.reflect.*;
+
+public class OSXAdapter implements InvocationHandler
+
+{
+ protected Object targetObject;
+ protected Method targetMethod;
+ protected String proxySignature;
+ static Object macOSXApplication;
+
+ // Each OSXAdapter has the name of the EAWT method it intends to listen for
+ // (handleAbout, for example),
+ // the Object that will ultimately perform the task, and the Method to be
+ // called on that Object
+ protected OSXAdapter(String proxySignature, Object target, Method handler)
+ {
+ this.proxySignature = proxySignature;
+ this.targetObject = target;
+ this.targetMethod = handler;
+ }
+
+ // Pass this method an Object and Method equipped to perform application
+ // shutdown logic
+ // The method passed should return a boolean stating whether or not the quit
+ // should occur
+ public static void setQuitHandler(Object target, Method quitHandler)
+ {
+ setHandler(new OSXAdapter("handleQuit", target, quitHandler));
+ }
+
+ // Pass this method an Object and Method equipped to display application info
+ // They will be called when the About menu item is selected from the
+ // application menu
+ public static void setAboutHandler(Object target, Method aboutHandler)
+ {
+ boolean enableAboutMenu = (target != null && aboutHandler != null);
+ if(enableAboutMenu)
+ setHandler(new OSXAdapter("handleAbout", target, aboutHandler));
+
+ // If we're setting a handler, enable the About menu item by calling
+ // com.apple.eawt.Application reflectively
+ try
+ {
+ Method enableAboutMethod = macOSXApplication.getClass()
+ .getDeclaredMethod("setEnabledAboutMenu",
+ new Class[] { boolean.class });
+ enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean
+ .valueOf(enableAboutMenu) });
+ }
+ catch(Exception ex)
+ {
+ System.err.println("OSXAdapter could not access the About Menu");
+ ex.printStackTrace();
+ }
+ }
+
+ // Pass this method an Object and a Method equipped to display application
+ // options
+ // They will be called when the Preferences menu item is selected from the
+ // application menu
+ public static void setPreferencesHandler(Object target, Method prefsHandler)
+ {
+ boolean enablePrefsMenu = (target != null && prefsHandler != null);
+ if(enablePrefsMenu)
+ setHandler(new OSXAdapter("handlePreferences", target, prefsHandler));
+
+ // If we're setting a handler, enable the Preferences menu item by calling
+ // com.apple.eawt.Application reflectively
+ try
+ {
+ Method enablePrefsMethod = macOSXApplication.getClass()
+ .getDeclaredMethod("setEnabledPreferencesMenu",
+ new Class[] { boolean.class });
+ enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean
+ .valueOf(enablePrefsMenu) });
+ }
+ catch(Exception ex)
+ {
+ System.err.println("OSXAdapter could not access the About Menu");
+ ex.printStackTrace();
+ }
+ }
+
+ // Pass this method an Object and a Method equipped to handle document
+ // events from the Finder. Documents are registered with the Finder via
+ // the CFBundleDocumentTypes dictionary in the application Info.plist
+ public static void setFileHandler(Object target, Method fileHandler)
+ {
+ setHandler(new OSXAdapter("handleOpenFile", target, fileHandler)
+ {
+ // Override OSXAdapter.callTarget to send information on the
+ // file to be opened
+ public boolean callTarget(Object appleEvent)
+ {
+ if(appleEvent != null)
+ {
+ try
+ {
+ Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod(
+ "getFilename", (Class[]) null);
+ String filename = (String) getFilenameMethod.invoke(appleEvent,
+ (Object[]) null);
+ this.targetMethod.invoke(this.targetObject,
+ new Object[] { filename });
+ }
+ catch(Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ return true;
+ }
+ });
+ }
+
+ // setHandler creates a Proxy object from the passed OSXAdapter and adds it as
+ // an ApplicationListener
+ public static void setHandler(OSXAdapter adapter)
+ {
+ try
+ {
+ Class applicationClass = Class.forName("com.apple.eawt.Application");
+ if(macOSXApplication == null)
+ macOSXApplication = applicationClass.getConstructor((Class[]) null)
+ .newInstance((Object[]) null);
+
+ Class applicationListenerClass = Class
+ .forName("com.apple.eawt.ApplicationListener");
+ Method addListenerMethod = applicationClass.getDeclaredMethod(
+ "addApplicationListener", new Class[] { applicationListenerClass });
+ // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener
+ Object osxAdapterProxy = Proxy.newProxyInstance(OSXAdapter.class
+ .getClassLoader(), new Class[] { applicationListenerClass }, adapter);
+ addListenerMethod.invoke(macOSXApplication,
+ new Object[] { osxAdapterProxy });
+ }
+ catch(ClassNotFoundException cnfe)
+ {
+ System.err.println(
+ "This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled ("
+ + cnfe + ")");
+ }
+ catch(Exception ex)
+ { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods
+ System.err.println("Mac OS X Adapter could not talk to EAWT:");
+ ex.printStackTrace();
+ }
+ }
+
+ // Override this method to perform any operations on the event
+ // that comes with the various callbacks
+ // See setFileHandler above for an example
+ public boolean callTarget(Object appleEvent)
+ throws InvocationTargetException, IllegalAccessException
+ {
+ Object result = targetMethod.invoke(targetObject, (Object[]) null);
+ if(result == null)
+ return true;
+
+ return Boolean.valueOf(result.toString()).booleanValue();
+ }
+
+ // InvocationHandler implementation
+ // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable
+ {
+ if(isCorrectMethod(method, args))
+ {
+ boolean handled = callTarget(args[0]);
+ setApplicationEventHandled(args[0], handled);
+ }
+ // All of the ApplicationListener methods are void; return null regardless of what happens
+ return null;
+ }
+
+ // Compare the method that was called to the intended method when the OSXAdapter instance was created
+ // (e.g. handleAbout, handleQuit, handleOpenFile, etc.)
+ protected boolean isCorrectMethod(Method method, Object[] args)
+ {
+ return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1);
+ }
+
+ // It is important to mark the ApplicationEvent as handled and cancel the default behavior
+ // This method checks for a boolean result from the proxy method and sets the event accordingly
+ protected void setApplicationEventHandled(Object event, boolean handled)
+ {
+ if(event != null)
+ {
+ try
+ {
+ Method setHandledMethod = event.getClass().getDeclaredMethod(
+ "setHandled", new Class[] { boolean.class });
+ // If the target method returns a boolean, use that as a hint
+ setHandledMethod.invoke(event,
+ new Object[] { Boolean.valueOf(handled) });
+ }
+ catch(Exception ex)
+ {
+ System.err
+ .println("OSXAdapter was unable to handle an ApplicationEvent: "
+ + event);
+ ex.printStackTrace();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/Plot.java b/uk/ac/sanger/artemis/components/Plot.java
new file mode 100644
index 0000000..36068fc
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/Plot.java
@@ -0,0 +1,1151 @@
+/* Plot.java
+ *
+ * created: Thu Dec 17 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/Plot.java,v 1.26 2009-08-18 09:01:44 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.circular.TextFieldFloat;
+import uk.ac.sanger.artemis.plot.*;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.GridLayout;
+import java.awt.Image;
+import java.awt.Scrollbar;
+import java.awt.event.*;
+import java.util.Vector;
+
+import javax.swing.JMenu;
+import javax.swing.JPanel;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JSplitPane;
+import javax.swing.JTextField;
+import javax.swing.JOptionPane;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JMenuItem;
+import javax.swing.JScrollBar;
+import javax.swing.JPopupMenu;
+
+import org.apache.batik.svggen.SVGGraphics2D;
+
+/**
+ * This class implements a simple plot component.
+ * @author Kim Rutherford
+ **/
+
+public abstract class Plot extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+
+ /** scroll bar for changing the window size. */
+ private JScrollBar window_changer = null;
+
+ /** height of the font used in this component. */
+ private int font_height;
+
+ /** off screen image used for double buffering when drawing */
+ private Image offscreen;
+
+ /**
+ * The object that will generate the value we plot in this component.
+ **/
+ private Algorithm algorithm;
+
+ /**
+ * If true then a scale line will be drawn at the bottom of the graph when
+ * drawScaleLine() is called.
+ **/
+ private boolean draw_scale;
+
+ private final int SCROLL_NOB_SIZE = 10;
+
+ /**
+ * Set to true if drawMultiValueGraph() should call recalculateValues().
+ * It is reset to false by recalculateValues().
+ **/
+ protected boolean recalculate_flag = true;
+
+ private boolean showAverage = true;
+
+ /**
+ * The x position of the last click or -1 if the user hasn't clicked
+ * anywhere yet or the user clicked outside the graph.
+ **/
+ private int cross_hair_position = -1;
+
+ /**
+ * The x position of the start of the last mouse drag or -1 if the user
+ * hasn't clicked anywhere yet or the user clicked outside the graph.
+ **/
+ private int drag_start_position = -1;
+
+ /**
+ * A vector of those objects listening for PlotMouse events.
+ **/
+ final private Vector<PlotMouseListener> listener_list = new Vector<PlotMouseListener>();
+
+ /**
+ * Recalculate the values all the state that is used for drawing the plot
+ **/
+ protected abstract void recalculateValues();
+
+ /**
+ * Get the position in the Feature or Sequence of the given x canvas
+ * position. This is the label used when the user clicks the mouse in on
+ * the canvas (see drawCrossHair()).
+ **/
+ protected abstract int getPointPosition(final int canvas_x_position);
+
+ protected abstract void calculateFeatures(boolean fromPeak);
+
+ protected abstract void showAveragesForRange();
+
+ /** number of graph lines to be drawn */
+ private int numPlots;
+
+ protected LineAttributes lines[];
+
+ private int lastPaintHeight = getHeight();
+
+ /** the minimum distance in pixels between the labels */
+ private final static int MINIMUM_LABEL_SPACING = 50;
+
+ /**
+ * Create a new plot component.
+ * @param algorithm The object that will generate the values we plot in
+ * this component.
+ * @param draw_scale If true then a scale line will be drawn at the bottom
+ * of the graph.
+ **/
+ public Plot(Algorithm algorithm, boolean draw_scale)
+ {
+ super(new BorderLayout());
+ this.algorithm = algorithm;
+ this.draw_scale = draw_scale;
+
+ setFont(Options.getOptions().getFont());
+ FontMetrics fm = getFontMetrics(getFont());
+ font_height = fm.getHeight();
+
+ final int MAX_WINDOW;
+ if(getAlgorithm().getDefaultMaxWindowSize() != null)
+ MAX_WINDOW = getAlgorithm().getDefaultMaxWindowSize().intValue();
+ else
+ MAX_WINDOW = 500;
+
+ final int MIN_WINDOW;
+ if(getAlgorithm().getDefaultMinWindowSize() != null)
+ MIN_WINDOW = getAlgorithm().getDefaultMinWindowSize().intValue();
+ else
+ MIN_WINDOW = 5;
+
+ final int START_WINDOW;
+ if(getAlgorithm().getDefaultWindowSize() == null)
+ START_WINDOW = 10;
+ else
+ START_WINDOW = getAlgorithm().getDefaultWindowSize().intValue();
+
+ window_changer = new JScrollBar(Scrollbar.VERTICAL);
+ window_changer.setValues(START_WINDOW, SCROLL_NOB_SIZE,
+ MIN_WINDOW, MAX_WINDOW + SCROLL_NOB_SIZE);
+ if(MAX_WINDOW >= 50)
+ window_changer.setBlockIncrement(MAX_WINDOW/50);
+ else
+ window_changer.setBlockIncrement(1);
+
+ window_changer.addAdjustmentListener(new AdjustmentListener()
+ {
+ public void adjustmentValueChanged(AdjustmentEvent e)
+ {
+ recalculate_flag = true;
+ repaint();
+ }
+ });
+
+ addComponentListener(new ComponentAdapter()
+ {
+ public void componentShown(ComponentEvent e)
+ {
+ recalculate_flag = true;
+ repaint();
+ }
+ });
+
+ add(window_changer, "East");
+ addMouseListener(mouse_listener);
+ addMouseMotionListener(mouse_motion_listener);
+ }
+
+ /**
+ * Return the algorithm that was passed to the constructor.
+ **/
+ public Algorithm getAlgorithm()
+ {
+ return algorithm;
+ }
+
+ /**
+ * Return the current value of the window size, as set by the
+ * window_changer scrollbar.
+ **/
+ public int getWindowSize()
+ {
+ return window_changer.getValue();
+ }
+
+ final MouseListener mouse_listener = new MouseAdapter()
+ {
+ /**
+ * Listen for mouse press popup menu and crosshair events.
+ **/
+ public void mousePressed(MouseEvent event)
+ {
+ if(event.isPopupTrigger() || event.isMetaDown())
+ {
+ final JComponent parent = (JComponent)event.getSource();
+ final JPopupMenu popup = new JPopupMenu("Plot Options");
+
+ // configure colours for multiple graph plots
+ final JMenuItem config = new JMenuItem("Configure...");
+ config.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ lines = LineAttributes.configurePlots(numPlots, lines, Plot.this);
+ }
+ });
+ popup.add(config);
+
+
+ final JMenuItem setScale = new JMenuItem("Set the Window Size...");
+ setScale.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ final JTextField newWinSize = new JTextField(Integer.toString(getWindowSize()));
+ String window_options[] = { "Set Window Size", "Cancel" };
+ int select = JOptionPane.showOptionDialog(null,
+ newWinSize,
+ "Set Window Size",
+ JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null, window_options, window_options[0]);
+ final int value;
+ try
+ {
+ value = Integer.parseInt(newWinSize.getText().trim());
+ }
+ catch(NumberFormatException nfe)
+ {
+ return;
+ }
+ if(value > window_changer.getMaximum() ||
+ value < window_changer.getMinimum())
+ {
+ window_options[0] = "Continue";
+ select = JOptionPane.showOptionDialog(null,
+ "Value selected: " + value +
+ " is outside the range\n"+
+ " Min: "+window_changer.getMinimum() +
+ " Max: "+window_changer.getMaximum(),
+ "Set Window Size",
+ JOptionPane.DEFAULT_OPTION,
+ JOptionPane.WARNING_MESSAGE,
+ null, window_options, window_options[1]);
+ if(select == 1)
+ return;
+
+ if(value > window_changer.getMaximum())
+ window_changer.setMaximum(value+10);
+ else
+ window_changer.setMinimum(value);
+ }
+
+ if(select == 0)
+ {
+ recalculate_flag = true;
+ window_changer.setValue(value);
+ repaint();
+ }
+ }
+ });
+ popup.add(setScale);
+
+
+ final JCheckBoxMenuItem scaling_toggle =
+ new JCheckBoxMenuItem("Scaling",getAlgorithm().scalingFlag());
+ scaling_toggle.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent _)
+ {
+ getAlgorithm().setScalingFlag(scaling_toggle.getState());
+ recalculate_flag = true;
+ repaint();
+ }
+ });
+ popup.add(scaling_toggle);
+
+ final JCheckBoxMenuItem showAverageLn = new JCheckBoxMenuItem("Show average", showAverage);
+ showAverageLn.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent _)
+ {
+ showAverage = showAverageLn.isSelected();
+ repaint();
+ }
+ });
+ popup.add(showAverageLn);
+
+ if(Plot.this instanceof BasePlot)
+ {
+ final JMenuItem showMinMaxValues =
+ new JMenuItem("Set Min/Max Values...");
+ popup.add(showMinMaxValues);
+ showMinMaxValues.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ JPanel gridPane = new JPanel(new GridLayout(2,2));
+ TextFieldFloat minValue = new TextFieldFloat();
+ minValue.setValue( ((BasePlot)Plot.this).getMin_value() );
+ gridPane.add(new JLabel("Min:"));
+ gridPane.add(minValue);
+
+ TextFieldFloat maxValue = new TextFieldFloat();
+ maxValue.setValue( ((BasePlot)Plot.this).getMax_value() );
+ gridPane.add(new JLabel("Max:"));
+ gridPane.add(maxValue);
+
+ String window_options[] = { "Set", "Cancel" };
+ int select = JOptionPane.showOptionDialog(null, gridPane,
+ "Set Min/Max Plot Values", JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE, null, window_options,
+ window_options[0]);
+ if(select == 1)
+ return;
+
+ getAlgorithm().setUserMaxMin(true);
+ getAlgorithm().setUserMin((float) minValue.getValue());
+ getAlgorithm().setUserMax((float) maxValue.getValue());
+ ((BasePlot)Plot.this).setMin_value((float) minValue.getValue());
+ ((BasePlot)Plot.this).setMax_value((float) maxValue.getValue());
+ getAlgorithm().setScalingFlag(false);
+ repaint();
+ }
+ });
+ }
+
+ popup.addSeparator();
+
+ final JMenu max_window_size =
+ new JMenu("Maximum Window Size");
+ popup.add(max_window_size);
+
+ final int[] window_sizes =
+ {
+ 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000,
+ 200000, 500000, 1000000
+ };
+
+ JMenuItem window_size_item;
+ for(int i = 0 ; i < window_sizes.length ; ++i)
+ {
+ final int size = i;
+ window_size_item = new JMenuItem(" " + window_sizes[i]);
+
+ window_size_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ final int new_maximum = window_sizes[size];
+ if(new_maximum > window_changer.getMinimum())
+ {
+ window_changer.setMaximum(new_maximum + SCROLL_NOB_SIZE);
+ recalculate_flag = true;
+ repaint();
+ }
+ }
+ });
+
+ max_window_size.add(window_size_item);
+ }
+
+ if(numPlots == 1 && getAlgorithm() instanceof BaseAlgorithm)
+ {
+ popup.addSeparator();
+
+ final JMenu createFeaturesFrom = new JMenu("Create features from graph");
+ popup.add(createFeaturesFrom);
+
+ final JMenuItem createFeaturesFromPeak =
+ new JMenuItem("peaks...");
+ createFeaturesFrom.add(createFeaturesFromPeak);
+
+ createFeaturesFromPeak.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ calculateFeatures(true);
+ }
+ });
+
+ final JMenuItem createFeaturesFromDip =
+ new JMenuItem("trough...");
+ createFeaturesFrom.add(createFeaturesFromDip);
+
+ createFeaturesFromDip.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ calculateFeatures(false);
+ }
+ });
+ }
+
+ ///
+ if(Plot.this instanceof BasePlot)
+ {
+ final JMenuItem showAverages =
+ new JMenuItem("Values and average(s) for selected range...");
+ popup.add(showAverages);
+
+ showAverages.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ showAveragesForRange();
+ }
+ });
+ }
+
+ final JSplitPane splitPane = getJSplitPane();
+ if(splitPane == null)
+ {
+ popup.addSeparator();
+ final JMenu graphHeight = new JMenu("Graph Height");
+ popup.add(graphHeight);
+ final JMenuItem smaller = new JMenuItem("smaller");
+ final JMenuItem larger = new JMenuItem("larger");
+ final JMenuItem setHeight = new JMenuItem("set...");
+
+ graphHeight.add(smaller);
+ graphHeight.add(larger);
+ graphHeight.add(setHeight);
+
+ smaller.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ rescale((int) (getSize().height * 0.9f));
+ }
+ });
+
+ larger.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ rescale((int) (getSize().height * 1.1f));
+ }
+ });
+
+ setHeight.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final JTextField newGraphHgt = new JTextField(Integer
+ .toString(getSize().height));
+ String window_options[] = { "Set Window Size", "Cancel" };
+ int select = JOptionPane.showOptionDialog(null, newGraphHgt,
+ "Set Window Size", JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE, null, window_options,
+ window_options[0]);
+
+ if(select == 1)
+ return;
+ try
+ {
+ rescale(Integer.parseInt(
+ newGraphHgt.getText().trim()));
+ }
+ catch(NumberFormatException nfe)
+ {
+ }
+ }
+ });
+ }
+
+ parent.add(popup);
+ popup.show(parent, event.getX(), event.getY());
+ }
+ else
+ {
+ final int point_x = event.getPoint().x;
+ final int point_y = event.getPoint().y;
+
+ if(point_y > getLabelHeight())
+ {
+ cross_hair_position = point_x;
+ drag_start_position = point_x;
+ }
+ else
+ cancelCrossHairs();
+
+ if(event.getClickCount() == 2)
+ fireDoubleClickEvent();
+ else
+ fireClickEvent();
+
+ repaint();
+ }
+ }
+ };
+
+ final MouseMotionListener mouse_motion_listener =
+ new MouseMotionAdapter()
+ {
+ public void mouseDragged(MouseEvent event)
+ {
+ if(isMenuTrigger(event))
+ return;
+
+ final int point_x = event.getPoint().x;
+ final int point_y = event.getPoint().y;
+
+ if(point_y > getLabelHeight())
+ cross_hair_position = point_x;
+ else
+ cancelCrossHairs();
+
+ fireDragEvent();
+ repaint();
+ }
+ };
+
+ /**
+ * Find JSplitPane parent container or null if it does not
+ * belong to one.
+ * @return
+ */
+ private JSplitPane getJSplitPane()
+ {
+ JComponent child = Plot.this;
+ JSplitPane splitPane = null;
+ int count = 0;
+ try
+ {
+ while(splitPane == null && count < 10)
+ {
+ JComponent plotParent = (JComponent) child.getParent();
+ if(plotParent instanceof JSplitPane)
+ splitPane = (JSplitPane) plotParent;
+ child = plotParent;
+ count++;
+ }
+ }
+ catch(Exception e){}
+ return splitPane;
+ }
+
+ /**
+ * Reset graph height
+ * @param hgt
+ */
+ private void rescale(int hgt)
+ {
+ setSize(getSize().width, hgt);
+
+ if(Plot.this instanceof BasePlot)
+ BasePlot.HEIGHT = getSize().height;
+ else if(Plot.this instanceof FeaturePlot)
+ FeaturePlot.HEIGHT = getSize().height;
+
+ offscreen = null;
+ revalidate();
+ }
+
+ /**
+ * Return true if and only if the given MouseEvent (a mouse press) should
+ * pop up a JPopupMenu.
+ **/
+ private boolean isMenuTrigger(final MouseEvent event)
+ {
+ if(event.isPopupTrigger() ||
+ (event.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Call mouseClick() on each of the PlotMouseListener objects in the
+ * listener list.
+ **/
+ private void fireClickEvent()
+ {
+ PlotMouseListener listener;
+ for(int i = 0; i < listener_list.size(); ++i)
+ {
+ listener = listener_list.elementAt(i);
+ listener.mouseClick(getPointPosition(cross_hair_position));
+ }
+ }
+
+ /**
+ * Call mouseDragged() on each of the PlotMouseListener objects in the
+ * listener list.
+ **/
+ private void fireDragEvent()
+ {
+ PlotMouseListener listener;
+ for(int i = 0; i < listener_list.size(); ++i)
+ {
+ listener = listener_list.elementAt(i);
+ listener.mouseDrag(getPointPosition(drag_start_position),
+ getPointPosition(cross_hair_position));
+ }
+ }
+
+ /**
+ * Call mouseDoubleClick() on each of the PlotMouseListener objects in the
+ * listener list.
+ **/
+ private void fireDoubleClickEvent()
+ {
+ PlotMouseListener listener;
+ for(int i = 0; i < listener_list.size(); ++i)
+ {
+ listener = listener_list.elementAt(i);
+ listener.mouseDoubleClick(getPointPosition(cross_hair_position));
+ }
+ }
+
+ /**
+ * Adds the given listener to receive mouse events from this object.
+ * @param l the listener.
+ **/
+ public void addPlotMouseListener(final PlotMouseListener listener)
+ {
+ listener_list.addElement(listener);
+ }
+
+ /**
+ * Removes the given listener from the list of those objects that receive
+ * mouse events from this object.
+ * @param l the listener.
+ **/
+ public void removePlotMouseListener(final PlotMouseListener listener)
+ {
+ listener_list.removeElement(listener);
+ }
+
+ /**
+ * The main paint function for the canvas. An off screen image used for
+ * double buffering when drawing the canvas.
+ * @param g The Graphics object of the canvas.
+ **/
+ protected void paintComponent(final Graphics g)
+ {
+ super.paintComponent(g);
+ if(!isVisible())
+ return;
+
+ final int width = getWidth() - window_changer.getWidth();
+ final int height = getHeight();
+
+ // there is no point painting a zero width canvas
+ if(height <= 0 || width <= 0)
+ return;
+
+ if(offscreen == null || lastPaintHeight != height)
+ offscreen = createImage(width, height);
+
+ final Graphics og;
+ if(g instanceof SVGGraphics2D)
+ og = g;
+ else
+ {
+ og = offscreen.getGraphics();
+ og.setClip(0, 0, width, height);
+ og.setColor(Color.WHITE);
+ og.fillRect(0, 0, width, height);
+ }
+
+ // Redraw the graph on the canvas using the algorithm from the
+ // constructor.
+
+ if(lines == null && getAlgorithm() instanceof BaseAlgorithm)
+ {
+ final int get_values_return_count =
+ ((BaseAlgorithm)getAlgorithm()).getValueCount();
+
+ if(getAlgorithm() instanceof UserDataAlgorithm)
+ lines = ((UserDataAlgorithm)getAlgorithm()).getLineAttributes();
+ if(lines == null)
+ lines = LineAttributes.init(get_values_return_count);
+ }
+
+ numPlots = drawMultiValueGraph(og,lines);
+ drawLabels(og,numPlots);
+
+ if( !(g instanceof SVGGraphics2D) )
+ {
+ g.drawImage(offscreen, 0, 0, null);
+ og.dispose();
+ }
+ lastPaintHeight = height;
+ }
+
+ protected void resetOffscreenImage()
+ {
+ offscreen = null;
+ }
+
+ /**
+ * Return the canvas x position of the last click or -1 if the user hasn't
+ * clicked anywhere yet.
+ **/
+ protected int getCrossHairPosition()
+ {
+ if(cross_hair_position >= getSize().width)
+ return -1;
+ else
+ return cross_hair_position;
+ }
+
+ /**
+ * Force this component to stop drawing crosshairs.
+ **/
+ protected void cancelCrossHairs()
+ {
+ cross_hair_position = -1;
+ drag_start_position = -1;
+ }
+
+ /**
+ * Draw the scale line at the bottom of the graph (used by FeaturePlot).
+ * @param start The base on the left
+ * @param end The base on the right
+ **/
+ protected void drawScaleLine(final Graphics g,
+ final int start, final int end)
+ {
+ final int hgt = getHeight();
+ final int scale_number_y_pos = hgt - 1;
+ final float bases_per_pixel = 1.0F;
+
+ final int possible_index_of_first_label = start / MINIMUM_LABEL_SPACING;
+ final int index_of_first_label;
+
+ if(possible_index_of_first_label == 0)
+ index_of_first_label = 1;
+ else
+ index_of_first_label = possible_index_of_first_label;
+
+ final int index_of_last_label = end / MINIMUM_LABEL_SPACING;
+ for(int i = index_of_first_label; i <= index_of_last_label; i++)
+ {
+ final int scale_number_x_pos =
+ (int)((i * MINIMUM_LABEL_SPACING - start) / bases_per_pixel);
+
+ g.drawString(String.valueOf((int)(i * MINIMUM_LABEL_SPACING)),
+ scale_number_x_pos + 2,
+ scale_number_y_pos);
+
+ g.drawLine(scale_number_x_pos, hgt - getScaleHeight() / 2,
+ scale_number_x_pos, hgt - getScaleHeight());
+ }
+ }
+
+ /**
+ * Plot the given points onto a Graphics object.
+ * @param min_value The minimum of the plot_values.
+ * @param max_value The maximum of the plot_values.
+ * @param step_size The current step size for this algorithm. This is
+ * never greater than window_size.
+ * @param window_size The window size used in calculating plot_values.
+ * @param total_unit_count The maximum number of residues/bases we can
+ * show. This is used to draw the scale line and to calculate the
+ * distance (in pixels) between plot points.
+ * @param start_position The distance from the edge of the canvas (measured
+ * in residues/bases) to start drawing the plot.
+ * @param plot_values The values to plot.
+ **/
+ protected void drawPoints(final Graphics g,
+ final float min_value, final float max_value,
+ final int step_size, final int window_size,
+ final int total_unit_count,
+ final int start_position,
+ final float [] plot_values,
+ final int value_index,
+ final int numberPlots,
+ final boolean isWiggle,
+ final boolean isBlast)
+ {
+ final float residues_per_pixel =
+ (float) total_unit_count / getSize().width;
+
+ // this is the height of the graph (slightly smaller than the canvas for
+ // ease of viewing).
+ final int graph_height = getSize().height -
+ getLabelHeight() - // leave room for the algorithm name
+ getScaleHeight() - // leave room for the scale
+ 2;
+
+ // too small to draw
+ if(graph_height < 5)
+ return;
+
+ Color definedColours[] = null;
+ String plotType = null;
+ if(lines != null)
+ {
+ plotType = lines[value_index].getPlotType();
+
+ int NUMBER_OF_SHADES = 254;
+ if(plotType.equals(LineAttributes.PLOT_TYPES[2]))
+ {
+ definedColours = makeColours(lines[value_index].getLineColour(),
+ NUMBER_OF_SHADES);
+ }
+ }
+
+ final int number_of_values = plot_values.length;
+ int start_residue;
+ int end_residue;
+ int start_x;
+ int end_x;
+
+ for(int i = 0; i<number_of_values - 1; ++i)
+ {
+ if( (isBlast || isWiggle) && plot_values[i] == 0)
+ {
+ if( !(isBlast && plotType.equals(LineAttributes.PLOT_TYPES[0])) )
+ continue;
+ }
+ start_residue = window_size / 2 + i * step_size + start_position;
+ start_x = (int)(start_residue / residues_per_pixel);
+
+ if(isWiggle)
+ {
+ int span = ((UserDataAlgorithm)getAlgorithm()).getWiggleSpan(value_index);
+ end_residue = start_residue + span;
+ }
+ else
+ end_residue = start_residue + step_size;
+ end_x = (int)(end_residue / residues_per_pixel);
+
+ // this is a number between 0.0 and 1.0
+ final float scaled_start_value =
+ (plot_values[i] - min_value) / (max_value - min_value);
+ final int start_y =
+ graph_height - (int)(scaled_start_value * graph_height) +
+ getLabelHeight() + 1;
+
+ final float scaled_end_value =
+ (plot_values[i+1] - min_value) / (max_value - min_value);
+ final int end_y =
+ graph_height - (int)(scaled_end_value * graph_height) +
+ getLabelHeight() + 1;
+
+ if(plotType == null ||
+ plotType.equals(LineAttributes.PLOT_TYPES[0]))
+ {
+ if(isWiggle)
+ {
+ g.drawLine(start_x, graph_height+getLabelHeight() + 1, start_x, start_y);
+ g.drawLine(start_x, start_y, end_x, start_y);
+ g.drawLine(end_x, graph_height+getLabelHeight() + 1, end_x, start_y);
+ }
+ else
+ g.drawLine(start_x, start_y, end_x, end_y);
+ }
+ else if(plotType.equals(LineAttributes.PLOT_TYPES[1]))
+ {
+ if(isWiggle)
+ g.fillRect(start_x, start_y, end_x-start_x, graph_height+getLabelHeight() + 1);
+ {
+ int xPoints[] = { start_x, end_x, end_x, start_x };
+ int yPoints[] = { start_y, end_y,
+ graph_height+getLabelHeight() + 1,
+ graph_height+getLabelHeight() + 1};
+ g.fillPolygon(xPoints, yPoints, 4);
+ }
+ }
+ else
+ {
+ int ytop = getLabelHeight() + 1 +
+ (value_index*(graph_height/numberPlots));
+ int ybtm = (graph_height/numberPlots);
+
+ // set color based on value
+ int colourIndex =
+ (int)(definedColours.length * 0.999 * scaled_start_value);
+
+ if(colourIndex > definedColours.length - 1)
+ colourIndex = definedColours.length - 1;
+ else if (colourIndex < 0)
+ colourIndex = 0;
+
+ g.setColor(definedColours[ colourIndex ]);
+ g.fillRect(start_x, ytop, end_x-start_x, ybtm);
+ }
+ }
+ }
+
+ /**
+ * Generate the colours for heat map plots.
+ * @param col
+ * @param NUMBER_OF_SHADES
+ * @return
+ */
+ public static Color[] makeColours(Color col, int NUMBER_OF_SHADES)
+ {
+ Color definedColour[] = new Color[NUMBER_OF_SHADES];
+ for(int i = 0; i < NUMBER_OF_SHADES; ++i)
+ {
+ int R = col.getRed();
+ int G = col.getGreen();
+ int B = col.getBlue();
+ int scale = NUMBER_OF_SHADES-i;
+
+ if((R+scale) <= 255)
+ R += scale;
+ else
+ R = 254;
+ if((G+scale) <= 255)
+ G += scale;
+ else
+ G = 254;
+ if((B+scale) <= 255)
+ B += scale;
+ else
+ B = 254;
+
+ definedColour[i] = new Color(R,G,B);
+ }
+ return definedColour;
+ }
+
+ /**
+ * Redraw the graph on the canvas using the algorithm.
+ * @param g The object to draw into.
+ **/
+ protected abstract int drawMultiValueGraph(final Graphics g, LineAttributes[] lines);
+
+ /**
+ * Draw a line representing the average of the algorithm over the feature.
+ * @param g The object to draw into.
+ * @param min_value The minimum value of the function for the range we are
+ * viewing
+ * @param max_value The maximum value of the function for the range we are
+ * viewing
+ **/
+ protected void drawGlobalAverage(final Graphics g,
+ final float min_value,
+ final float max_value)
+ {
+ // if a heatmap do not show the average
+ if(!showAverage || (
+ lines != null && lines[0].getPlotType().equals(LineAttributes.PLOT_TYPES[2])))
+ return;
+
+ final Float average = getAlgorithm().getAverage();
+
+ if(average != null)
+ {
+ g.setColor(Color.gray);
+
+ // this is the height of the graph (slightly smaller than the canvas for
+ // ease of viewing).
+ final int graph_height =
+ getSize().height - getFontHeight();
+
+ // this is a number between 0.0 and 1.0
+ final float scaled_average =
+ (average.floatValue() - min_value) / (max_value - min_value);
+
+ final int position =
+ graph_height -
+ (int)(scaled_average * graph_height) +
+ getFontHeight() + 1;
+
+ g.drawLine(0, position,
+ getSize().width, position);
+
+ final FontMetrics fm = g.getFontMetrics();
+
+ final int width = getSize().width;
+
+ final String average_string =
+ String.valueOf(Math.round(average.floatValue() * 100.0) / 100.0);
+
+ g.drawString(average_string,
+ width - fm.stringWidth(average_string) -
+ window_changer.getWidth() - 1,
+ position);
+ }
+ }
+
+ /**
+ * Put the algorithm name in the top left corner of the canvas and the
+ * window size in the bottom left.
+ * @param g The object to draw into.
+ **/
+ private void drawLabels(final Graphics g, final int numPlots)
+ {
+ g.setColor(Color.black);
+
+ String desc = getAlgorithm().getAlgorithmName() + " Window size: " +
+ String.valueOf(window_changer.getValue());
+
+ g.drawString(desc, 2, font_height);
+
+ if(numPlots < 2 || numPlots > 10)
+ return;
+
+ final FontMetrics fm = g.getFontMetrics();
+ int font_width = fm.stringWidth("2");
+
+ int width = 0;
+ for(LineAttributes ln : lines)
+ width += ln.getLabelWidth(fm);
+ width = getWidth() - window_changer.getWidth() - width;
+
+ g.translate(width,0);
+ ((BaseAlgorithm)getAlgorithm()).drawLegend(g,font_height,
+ font_width,lines, numPlots);
+ g.translate(-width,0);
+ }
+
+ /**
+ * The method converts the min_value and max_value to String objects and
+ * then draws them onto the canvas. The min_value is drawn at the bottom
+ * right, max_value at the top right.
+ **/
+ protected void drawMinMax(final Graphics g,
+ final float min_value, final float max_value)
+ {
+ g.setColor(Color.black);
+
+ final int width = getWidth() - window_changer.getWidth();
+ final int height = getHeight();
+
+ g.drawLine(0, height - getScaleHeight(),
+ width, height - getScaleHeight());
+
+ g.drawLine(0, getLabelHeight(),
+ width, getLabelHeight());
+
+ final FontMetrics fm = g.getFontMetrics();
+
+ final String min_string =
+ String.valueOf(((int)(min_value * 100)) / 100.0);
+
+ g.drawString(min_string,
+ width - fm.stringWidth(min_string) - 1,
+ height - 1 - getScaleHeight());
+
+ final String max_string =
+ String.valueOf(((int)(max_value * 100)) / 100.0);
+
+ g.drawString(max_string,
+ width - fm.stringWidth(max_string) - 1,
+ 1 + getFontHeight() * 2);
+ }
+
+ /**
+ * Draw a vertical line at the given position.
+ * @param label The label to use on the crosshair
+ * @param label_pos The position on the line at which the label should be
+ * drawn (0 is nearest the top).
+ **/
+ protected void drawCrossHair(final Graphics g, final int x_position,
+ final String label, final int label_pos)
+ {
+ if(x_position >= 0)
+ {
+ g.drawLine(x_position, getLabelHeight(),
+ x_position, getSize().height);
+
+ g.drawString(label, x_position + 2,
+ getFontHeight() * (2 + label_pos) + 2);
+ }
+ }
+
+ /**
+ * Return the amount of vertical space (in pixels) to use for the scale.
+ **/
+ protected int getScaleHeight()
+ {
+ if(draw_scale)
+ return getFontHeight() + 2;
+ else
+ return 0;
+ }
+
+ /**
+ * Return the height in algorithm name and label line (returns the font
+ * height plus a small amount).
+ **/
+ protected int getLabelHeight()
+ {
+ return getFontHeight() + 2;
+ }
+
+ /**
+ * Return the height in pixels of the current font.
+ **/
+ private int getFontHeight()
+ {
+ return font_height;
+ }
+
+ /**
+ * Used to get the Y coordinate for the tooltip text.
+ * @param step_size The current step size for this algorithm. This is
+ * never greater than window_size.
+ * @param window_size The window size used in calculating plot_values.
+ * @param start_position The distance from the edge of the canvas (measured
+ * in residues/bases) to start drawing the plot.
+ * @param plot_values The values to plot.
+ * @param base_pos The base (from getXCoordinate) position.
+ **/
+ protected float getYCoordinate(
+ final int step_size, final int window_size,
+ final int start_position,
+ final float plot_values[], int base_pos)
+ {
+ int ypos = (int)((base_pos - start_position - (window_size/2))/step_size);
+ if(ypos < 0)
+ ypos = 0;
+ else if(ypos > plot_values.length-1)
+ ypos = plot_values.length-1;
+
+ return plot_values[ypos];
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/PlotMouseListener.java b/uk/ac/sanger/artemis/components/PlotMouseListener.java
new file mode 100644
index 0000000..50abff7
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/PlotMouseListener.java
@@ -0,0 +1,64 @@
+/* PlotMouseListener.java
+ *
+ * created: Wed Sep 13 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/PlotMouseListener.java,v 1.1 2004-06-09 09:47:12 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * Implemented by those objects that need to mouse events from a Plot object.
+ * The coordinates of the mouse click are translated to base/aa positions for
+ * ease of use.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: PlotMouseListener.java,v 1.1 2004-06-09 09:47:12 tjc Exp $
+ **/
+
+public interface PlotMouseListener {
+ /**
+ * Called when the user clicks somewhere on the plot canvas.
+ * @param position the base/amino acid position of the click. This is
+ * -1 if and only if the click was outside the graph (eg. in the label at
+ * the top)
+ **/
+ void mouseClick (final int position);
+
+ /**
+ * Called when the user drags the mouse over the plot.
+ * @param drag_start_position The base/amnino acid position where the drag
+ * started or -1 if the drag was started outside the graph.
+ * @param current_position the base/amino acid position of the click.
+ * This is -1 if and only if the user has dragged the mouse out of
+ * the graph (eg. in the label at the top)
+ **/
+ void mouseDrag (final int drag_start_position,
+ final int current_position);
+
+ /**
+ * Called when the user double-clicks somewhere on the plot.
+ * @param position the base/amino acid position of the click. This is
+ * -1 if and only if the click was outside the graph (eg. in the label at
+ * the top)
+ **/
+ void mouseDoubleClick (final int position);
+}
diff --git a/uk/ac/sanger/artemis/components/PrintACT.java b/uk/ac/sanger/artemis/components/PrintACT.java
new file mode 100644
index 0000000..5928f77
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/PrintACT.java
@@ -0,0 +1,562 @@
+/* PrintACT.java
+ *
+ *
+ * Copyright(C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.KeyStroke;
+
+import org.apache.batik.dom.GenericDOMImplementation;
+import org.apache.batik.svggen.SVGGeneratorContext;
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.batik.svggen.SVGGraphics2DIOException;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.editor.ScrollPanel;
+
+/**
+* Use to print images from ACT
+*/
+public class PrintACT extends ScrollPanel implements Printable
+{
+ private static final long serialVersionUID = 1L;
+ /** act display to create image from */
+ private MultiComparator mc;
+ private JCheckBox drawLabels = new JCheckBox("Show labels on alignment");
+
+ public PrintACT(MultiComparator mc)
+ {
+ this.mc = mc;
+ }
+
+
+ public void paintComponent(Graphics g)
+ {
+// let UI delegate paint first (incl. background filling)
+ super.paintComponent(g);
+ Graphics2D g2d = (Graphics2D)g.create();
+
+ for(int i = 0; i < mc.getFeatureDisplayArray().length; ++i)
+ {
+ if(!(i == mc.getEntryGroupArray().length - 1 &&
+ mc.getEntryGroupArray().length == 2))
+ {
+ Component c[] = mc.getBasePlotGroupArray()[i].getComponents();
+ for(int j=0; j<c.length; j++)
+ {
+ if(c[j] instanceof BasePlot && c[j].isVisible())
+ {
+ ((BasePlot)c[j]).paintComponent(g2d);
+ g2d.translate(0,((BasePlot)c[j]).getHeight());
+ }
+ }
+
+ if(mc.getBamPanelArray()[i].isVisible())
+ {
+ mc.getBamPanelArray()[i].paintComponents(g2d);
+ g2d.translate(0,mc.getBamPanelArray()[i].getHeight());
+ }
+
+ if(mc.getVcfPanelArray()[i].isVisible())
+ {
+ mc.getVcfPanelArray()[i].paintComponents(g2d);
+ g2d.translate(0,mc.getVcfPanelArray()[i].getHeight());
+ }
+ }
+
+ mc.getFeatureDisplayArray()[i].paintComponent(g2d);
+ g2d.translate(0,mc.getFeatureDisplayArray()[i].getHeight());
+
+ if(i == mc.getEntryGroupArray().length - 1 &&
+ mc.getEntryGroupArray().length == 2)
+ {
+ Component c[] = mc.getBasePlotGroupArray()[i].getComponents();
+ for(int j=0; j<c.length; j++)
+ {
+ if(c[j] instanceof BasePlot && c[j].isVisible())
+ {
+ ((BasePlot)c[j]).paintComponent(g2d);
+ g2d.translate(0,((BasePlot)c[j]).getHeight());
+ }
+ }
+
+ if(mc.getBamPanelArray()[i].isVisible())
+ {
+ mc.getBamPanelArray()[i].paintComponents(g2d);
+ g2d.translate(0,mc.getBamPanelArray()[i].getHeight());
+ }
+
+ if(mc.getVcfPanelArray()[i].isVisible())
+ {
+ mc.getVcfPanelArray()[i].paintComponents(g2d);
+ g2d.translate(0,mc.getVcfPanelArray()[i].getHeight());
+ }
+ }
+
+ if(i < mc.getAlignmentViewerArray().length)
+ {
+ mc.getAlignmentViewerArray()[i].paintComponentForPrint(g2d,drawLabels.isSelected());
+ g2d.translate(0,mc.getAlignmentViewerArray()[i].getHeight());
+ }
+ }
+
+ }
+
+ /**
+ *
+ * Display a print preview page
+ *
+ */
+ protected void printPreview()
+ {
+ int width = 999999;
+ int height = 0;
+ for(int i = 0; i < mc.getFeatureDisplayArray().length; ++i)
+ {
+ if(!(i == mc.getEntryGroupArray().length - 1 &&
+ mc.getEntryGroupArray().length == 2))
+ {
+ Component c[] = mc.getBasePlotGroupArray()[i].getComponents();
+ for(int j=0; j<c.length; j++)
+ if(c[j] instanceof BasePlot && c[j].isVisible())
+ {
+ height += ((BasePlot)c[j]).getHeight();
+ if(((BasePlot)c[j]).getSize().width < width &&
+ ((BasePlot)c[j]).getSize().width > 0)
+ width = ((BasePlot)c[j]).getSize().width;
+ }
+ }
+
+ if(mc.getBamPanelArray()[i].isVisible())
+ height += mc.getBamPanelArray()[i].getHeight();
+
+ if(mc.getVcfPanelArray()[i].isVisible())
+ height += mc.getVcfPanelArray()[i].getHeight();
+
+ height += mc.getFeatureDisplayArray()[i].getHeight();
+ if(mc.getFeatureDisplayArray()[i].getWidth() < width)
+ width = mc.getFeatureDisplayArray()[i].getWidth();
+
+ if(i == mc.getEntryGroupArray().length - 1 &&
+ mc.getEntryGroupArray().length == 2)
+ {
+ Component c[] = mc.getBasePlotGroupArray()[i].getComponents();
+ for(int j=0; j<c.length; j++)
+ if(c[j] instanceof BasePlot && c[j].isVisible())
+ {
+ height += ((BasePlot)c[j]).getHeight();
+ if(((BasePlot)c[j]).getSize().width < width &&
+ ((BasePlot)c[j]).getSize().width > 0)
+ width = ((BasePlot)c[j]).getSize().width;
+ }
+ }
+
+ if(i < mc.getAlignmentViewerArray().length)
+ {
+ height += mc.getAlignmentViewerArray()[i].getHeight();
+ if(mc.getAlignmentViewerArray()[i].getWidth() < width)
+ width = mc.getAlignmentViewerArray()[i].getWidth();
+ }
+ }
+
+ final JFrame f = new JFrame("Print Preview");
+ JPanel jpane = (JPanel)f.getContentPane();
+ JScrollPane scrollPane = new JScrollPane(this);
+ jpane.setLayout(new BorderLayout());
+ jpane.add(scrollPane,BorderLayout.CENTER);
+
+ final Dimension dScreen = f.getToolkit().getScreenSize();
+ Dimension d = new Dimension((int)(dScreen.getWidth()*3/4),
+ (int)(dScreen.getHeight()/2));
+ f.setSize(d);
+
+ setPreferredSize(new Dimension(width,height));
+
+ JMenuBar menuBar = new JMenuBar();
+ JMenu filemenu = new JMenu("File");
+ menuBar.add(filemenu);
+
+// print png/jpeg
+ JMenuItem printImage = new JMenuItem("Save As Image Files (png/jpeg)...");
+ printImage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ print();
+ }
+ });
+ filemenu.add(printImage);
+
+// print PostScript
+ JMenuItem printPS = new JMenuItem("Print...");
+ printPS.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ doPrintActions();
+ }
+ });
+ filemenu.add(printPS);
+
+// close
+ filemenu.add(new JSeparator());
+ JMenuItem menuClose = new JMenuItem("Close");
+ menuClose.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+
+ filemenu.add(menuClose);
+ menuClose.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+
+ JMenu optionsmenu = new JMenu("Options");
+ menuBar.add(optionsmenu);
+
+// draw labels
+ JCheckBoxMenuItem showLabels = new JCheckBoxMenuItem("Display Labels",
+ drawLabels.isSelected());
+ showLabels.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ drawLabels.setSelected(!drawLabels.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showLabels);
+
+ f.setJMenuBar(menuBar);
+ f.setVisible(true);
+ }
+
+ /**
+ * Print to a jpeg or png file
+ */
+ public void print()
+ {
+ // file chooser
+ final StickyFileChooser fc = new StickyFileChooser();
+ File fselect = new File(fc.getCurrentDirectory()+
+ System.getProperty("file.separator")+
+ "act.png");
+ fc.setSelectedFile(fselect);
+
+ // file name prefix
+ Box YBox = Box.createVerticalBox();
+ JLabel labFormat = new JLabel("Select Format:");
+ Font font = labFormat.getFont();
+ labFormat.setFont(font.deriveFont(Font.BOLD));
+ YBox.add(labFormat);
+
+ Box bacross = Box.createHorizontalBox();
+ final JComboBox formatSelect = new JComboBox(PrintArtemis.getImageFormats());
+ formatSelect.setSelectedItem("png");
+ formatSelect.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ String selected;
+ if(fc.getSelectedFile() != null)
+ {
+ selected = fc.getSelectedFile().getAbsolutePath();
+ String fmts[] = PrintArtemis.getImageFormats();
+ for(int i=0; i<fmts.length; i++)
+ selected = selected.replaceAll("."+fmts[i]+"$", "");
+ }
+ else
+ selected = "act";
+
+ fc.setSelectedFile(new File(selected+"."+
+ formatSelect.getSelectedItem()));
+ }
+ });
+ formatSelect.setSelectedItem("png");
+
+ Dimension d = formatSelect.getPreferredSize();
+ formatSelect.setMaximumSize(d);
+ bacross.add(Box.createHorizontalGlue());
+ bacross.add(formatSelect);
+ YBox.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ bacross.add(Box.createHorizontalGlue());
+ bacross.add(drawLabels);
+ YBox.add(bacross);
+
+ // file prefix & format options
+ fc.setAccessory(YBox);
+ int n = fc.showSaveDialog(null);
+ if(n == JFileChooser.CANCEL_OPTION)
+ return;
+
+ // remove file extension
+ String fsave = fc.getSelectedFile().getAbsolutePath().toLowerCase();
+ if(fsave.endsWith(".svg"))
+ {
+ createSVG(fc.getSelectedFile());
+ return;
+ }
+
+ if(fsave.endsWith(".png") ||
+ fsave.endsWith(".jpg") ||
+ fsave.endsWith(".jpeg") )
+ {
+ int ind = fsave.lastIndexOf(".");
+ fsave = fc.getSelectedFile().getAbsolutePath();
+ fsave = fsave.substring(0,ind);
+ }
+ else
+ fsave = fc.getSelectedFile().getAbsolutePath();
+
+ // image type
+ String ftype = (String)formatSelect.getSelectedItem();
+ try
+ {
+ RenderedImage rendImage = createImage();
+ writeImageToFile(rendImage, new File(fsave+"."+ftype),
+ ftype);
+ }
+ catch(NoClassDefFoundError ex)
+ {
+ JOptionPane.showMessageDialog(this,
+ "This option requires Java 1.4 or higher.");
+ }
+ }
+
+ private void createSVG(final File fout)
+ {
+ final DOMImplementation domImpl =
+ GenericDOMImplementation.getDOMImplementation();
+ final Document doc = domImpl.createDocument(
+ "http://www.w3.org/2000/svg", "svg", null);
+
+ SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(doc);
+ ctx.setComment("Generated by ACT with Batik SVG Generator");
+ final SVGGraphics2D svgG = new SVGGraphics2D(ctx, true);
+ svgG.setFont(Options.getOptions().getFont());
+ svgG.setSVGCanvasSize( getImageSize() );
+ paintComponent(svgG);
+
+ try
+ {
+ final Writer out = new OutputStreamWriter(
+ new FileOutputStream(fout), "UTF-8");
+ svgG.stream(out, true);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ e.printStackTrace();
+ }
+ catch (SVGGraphics2DIOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+
+ return;
+ }
+
+ /**
+ * Returns a generated image
+ * @param pageIndex page number
+ * @return image
+ */
+ private RenderedImage createImage()
+ {
+ Dimension d = getImageSize();
+
+ // Create a buffered image in which to draw
+ BufferedImage bufferedImage = new BufferedImage(
+ d.width,d.height,
+ BufferedImage.TYPE_INT_RGB);
+ // Create a graphics contents on the buffered image
+ Graphics2D g2d = bufferedImage.createGraphics();
+ paintComponent(g2d);
+
+ return bufferedImage;
+ }
+
+ /**
+ * Get the size of the image
+ * @return
+ */
+ private Dimension getImageSize()
+ {
+ int width = 999999;
+ int height = 0;
+ for(int i = 0; i < mc.getFeatureDisplayArray().length; ++i)
+ {
+ if(!(i == mc.getEntryGroupArray().length - 1 &&
+ mc.getEntryGroupArray().length == 2))
+ {
+ Component c[] = mc.getBasePlotGroupArray()[i].getComponents();
+ for(int j=0; j<c.length; j++)
+ if(c[j] instanceof BasePlot)
+ {
+ height += ((BasePlot)c[j]).getHeight();
+ if(((BasePlot)c[j]).getSize().width < width &&
+ ((BasePlot)c[j]).getSize().width > 0)
+ width = ((BasePlot)c[j]).getSize().width;
+ }
+ }
+
+ if(mc.getBamPanelArray()[i].isVisible())
+ height += mc.getBamPanelArray()[i].getHeight();
+
+ if(mc.getVcfPanelArray()[i].isVisible())
+ height += mc.getVcfPanelArray()[i].getHeight();
+
+ height += mc.getFeatureDisplayArray()[i].getHeight();
+ if(mc.getFeatureDisplayArray()[i].getWidth() < width)
+ width = mc.getFeatureDisplayArray()[i].getWidth();
+
+ if(i == mc.getEntryGroupArray().length - 1 &&
+ mc.getEntryGroupArray().length == 2)
+ {
+ Component c[] = mc.getBasePlotGroupArray()[i].getComponents();
+ for(int j=0; j<c.length; j++)
+ if(c[j] instanceof BasePlot)
+ {
+ height += ((BasePlot)c[j]).getHeight();
+ if(((BasePlot)c[j]).getSize().width < width &&
+ ((BasePlot)c[j]).getSize().width > 0)
+ width = ((BasePlot)c[j]).getSize().width;
+ }
+ }
+
+ if(i < mc.getAlignmentViewerArray().length)
+ {
+ height += mc.getAlignmentViewerArray()[i].getHeight();
+ if(mc.getAlignmentViewerArray()[i].getWidth() < width)
+ width = mc.getAlignmentViewerArray()[i].getWidth();
+ }
+ }
+ return new Dimension(width, height);
+ }
+
+ /**
+ *
+ * Write out the image
+ * @param image image
+ * @param file file to write image to
+ * @param type type of image
+ *
+ */
+ private void writeImageToFile(RenderedImage image,
+ File file, String type)
+ {
+ try
+ {
+ javax.imageio.ImageIO.write(image,type,file);
+ }
+ catch ( IOException e )
+ {
+ System.out.println("Java 1.4+ is required");
+ e.printStackTrace();
+ }
+ }
+
+ protected void doPrintActions()
+ {
+ final PrinterJob pj=PrinterJob.getPrinterJob();
+ pj.setPrintable(PrintACT.this);
+ pj.printDialog();
+ try
+ {
+ pj.print();
+ }
+ catch (Exception PrintException) {}
+ }
+
+ /**
+ *
+ * The method @print@ must be implemented for @Printable@ interface.
+ * Parameters are supplied by system.
+ *
+ */
+ public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException
+ {
+ Graphics2D g2 = (Graphics2D) g;
+
+// RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);
+ Dimension d = getImageSize(); //get size of document
+ double panelWidth = d.width; //width in pixels
+ double panelHeight = d.height; //height in pixels
+
+ double pageHeight = pf.getImageableHeight(); //height of printer page
+ double pageWidth = pf.getImageableWidth(); //width of printer page
+ double scale = pageWidth/panelWidth;
+ int totalNumPages = (int)Math.ceil(scale * panelHeight / pageHeight);
+ // Make sure not print empty pages
+ if(pageIndex >= totalNumPages)
+ return Printable.NO_SUCH_PAGE;
+
+ // Shift Graphic to line up with beginning of print-imageable region
+ g2.translate(pf.getImageableX(), pf.getImageableY());
+ // Shift Graphic to line up with beginning of next page to print
+ g2.translate(0f, -pageIndex*pageHeight);
+ // Scale the page so the width fits...
+ g2.scale(scale, scale);
+ paintComponent(g2);
+ return Printable.PAGE_EXISTS;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/PrintArtemis.java b/uk/ac/sanger/artemis/components/PrintArtemis.java
new file mode 100644
index 0000000..5c37fd8
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/PrintArtemis.java
@@ -0,0 +1,724 @@
+/* PrintArtemis.java
+ *
+ *
+ * Copyright(C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.awt.event.*;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.HashSet;
+
+import javax.swing.*;
+
+import org.apache.batik.dom.GenericDOMImplementation;
+import org.apache.batik.svggen.SVGGeneratorContext;
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.batik.svggen.SVGGraphics2DIOException;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.editor.ScrollPanel;
+
+/**
+*
+* Use to print images from Artemis
+*
+*/
+public class PrintArtemis extends ScrollPanel implements Printable
+{
+ private static final long serialVersionUID = 1L;
+
+ /** entry to create image from */
+ private EntryEdit entry;
+
+ private JCheckBox selectDisplay = new JCheckBox("Show Selection Header",true);
+ private JCheckBox featDisplay = new JCheckBox("Show Feature Display",true);
+ private JCheckBox groupsDisplay = new JCheckBox("Show Entries Loaded",true);
+ private JCheckBox plotsDisplay = new JCheckBox("Show Graphs",true);
+ private JCheckBox jamDisplay = new JCheckBox("Show Read Alignment",true);
+ private JCheckBox vcfDisplay = new JCheckBox("Show VCF",true);
+ private JCheckBox onelineDisplay = new JCheckBox("Show One Line Display",true);
+ private JCheckBox baseDisplay = new JCheckBox("Show Bases Display",true);
+ private JCheckBox featListDisplay = new JCheckBox("Show Feature List",true);
+ private int width;
+ private int height;
+
+ public PrintArtemis(EntryEdit entry)
+ {
+ super();
+ this.entry = entry;
+ setBackground(Color.white);
+ }
+
+ /**
+ * Override paintComponent to draw entry
+ */
+ public void paintComponent(Graphics g2d)
+ {
+// let UI delegate paint first (incl. background filling)
+ super.paintComponent(g2d);
+
+ // feature list
+ if(featListDisplay.isSelected())
+ {
+ FeatureList flist = entry.getFeatureList();
+ Point ploc = flist.getViewport().getViewPosition();
+ try
+ {
+ int translateX = 0;
+ if(selectDisplay.isSelected())
+ translateX += entry.getSelectionInfoDisplay().getHeight();
+ if(groupsDisplay.isSelected())
+ translateX += entry.getEntryGroupDisplay().getHeight();
+ if(plotsDisplay.isSelected())
+ translateX += entry.getBasePlotGroup().getHeight();
+ if(jamDisplay.isSelected() && entry.getBamPanel() != null && entry.getBamPanel().isVisible())
+ translateX += entry.getBamPanel().getHeight()-1;
+ if(vcfDisplay.isSelected() && entry.getVcfView() != null && entry.getVcfView().isVisible())
+ translateX += entry.getVcfPanel().getHeight();
+ if(onelineDisplay.isSelected())
+ translateX += entry.getOneLinePerEntryDisplay().getHeight();
+ if(featDisplay.isSelected())
+ translateX += entry.getFeatureDisplay().getHeight();
+ if(baseDisplay.isSelected())
+ translateX += entry.getBaseDisplay().getHeight();
+
+ translateX-=2+ploc.y;
+ g2d.translate(0,translateX);
+ flist.paintComponent(g2d);
+ g2d.translate(0,-translateX);
+ }
+ catch(IllegalArgumentException e){} // thrown if the list is not visible
+ }
+
+ // selection info
+ if(selectDisplay.isSelected())
+ {
+ entry.getSelectionInfoDisplay().paintComponent(g2d);
+ g2d.translate(0,entry.getSelectionInfoDisplay().getHeight());
+ }
+
+ // entry groups
+ if(groupsDisplay.isSelected())
+ {
+ entry.getEntryGroupDisplay().printComponent(g2d);
+ g2d.translate(0,entry.getEntryGroupDisplay().getHeight());
+ }
+
+ // plots
+ if(plotsDisplay.isSelected())
+ entry.getBasePlotGroup().printComponent(g2d);
+// g2d.translate(0,entry.getBasePlotGroup().getHeight());
+
+ if(jamDisplay.isSelected() && entry.getBamPanel() != null && entry.getBamPanel().isVisible())
+ {
+ entry.getBamPanel().paintComponents(g2d);
+ g2d.translate(0,entry.getBamPanel().getHeight()-1);
+ }
+
+ if(vcfDisplay.isSelected() && entry.getVcfView() != null && entry.getVcfView().isVisible())
+ {
+ entry.getVcfPanel().paintComponents(g2d);
+ g2d.translate(0,entry.getVcfPanel().getHeight());
+ }
+
+ // one line per entry
+ if(onelineDisplay.isSelected())
+ {
+ entry.getOneLinePerEntryDisplay().paintComponent(g2d);
+ g2d.translate(0,entry.getOneLinePerEntryDisplay().getHeight());
+ }
+
+ // feature display
+ if(featDisplay.isSelected())
+ {
+ FeatureDisplay fd = entry.getFeatureDisplay();
+ fd.paintComponent(g2d);
+ g2d.translate(0,entry.getFeatureDisplay().getHeight());
+ }
+
+ // base display
+ if(baseDisplay.isSelected())
+ {
+ entry.getBaseDisplay().paintComponent(g2d);
+ g2d.translate(0,entry.getBaseDisplay().getHeight());
+ }
+ }
+
+
+
+
+ /**
+ *
+ * Set the size of the image
+ *
+ */
+ private Dimension getImageSize()
+ {
+ height = 0;
+ width = entry.getFeatureDisplay().getDisplayWidth();
+ if(selectDisplay.isSelected())
+ height += entry.getSelectionInfoDisplay().getHeight();
+
+ if(groupsDisplay.isSelected())
+ height += entry.getEntryGroupDisplay().getHeight();
+
+ if(jamDisplay.isSelected() &&
+ entry.getJamView() != null && entry.getJamView().isVisible())
+ height += entry.getBamPanel().getHeight();
+
+ if(vcfDisplay.isSelected() &&
+ entry.getVcfView() != null && entry.getVcfView().isVisible())
+ height += entry.getVcfPanel().getHeight();
+
+ if(plotsDisplay.isSelected())
+ height += entry.getBasePlotGroup().getHeight();
+
+ if(onelineDisplay.isSelected())
+ height += entry.getOneLinePerEntryDisplay().getHeight();
+
+ if(baseDisplay.isSelected())
+ height += entry.getBaseDisplay().getHeight();
+
+ if(featDisplay.isSelected())
+ height += entry.getFeatureDisplay().getHeight();
+
+ if(featListDisplay.isSelected())
+ height += entry.getFeatureList().getViewport().getExtentSize().height;
+ return new Dimension(width,height);
+ }
+
+ private void setImageSize()
+ {
+ setPreferredSize(getImageSize());
+ }
+
+ /**
+ *
+ * Display a print preview page
+ *
+ */
+ protected void printPreview()
+ {
+ final JFrame f = new JFrame("Print Preview");
+ JPanel jpane = (JPanel)f.getContentPane();
+
+ JScrollPane scrollPane = new JScrollPane(this);
+
+ jpane.setLayout(new BorderLayout());
+ jpane.add(scrollPane,BorderLayout.CENTER);
+
+ final Dimension dScreen = f.getToolkit().getScreenSize();
+ Dimension d = new Dimension((int)(3*dScreen.getWidth()/4),
+ (int)(dScreen.getHeight()/2));
+ f.setSize(d);
+ setImageSize();
+
+ JMenuBar menuBar = new JMenuBar();
+ JMenu filemenu = new JMenu("File");
+ menuBar.add(filemenu);
+
+// print png/jpeg
+ JMenuItem printImage = new JMenuItem("Save As Image Files (png/jpeg)...");
+ printImage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ print();
+ }
+ });
+ filemenu.add(printImage);
+
+// print PostScript
+ JMenuItem printPS = new JMenuItem("Print...");
+ printPS.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ doPrintActions();
+ }
+ });
+ filemenu.add(printPS);
+
+// close
+ filemenu.add(new JSeparator());
+ JMenuItem menuClose = new JMenuItem("Close");
+ menuClose.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+
+ filemenu.add(menuClose);
+ menuClose.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+
+ JMenu optionsmenu = new JMenu("Options");
+ menuBar.add(optionsmenu);
+
+// draw selection info
+ JCheckBoxMenuItem showSelection = new JCheckBoxMenuItem("Show Selection Header",
+ selectDisplay.isSelected());
+ showSelection.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ selectDisplay.setSelected(!selectDisplay.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showSelection);
+
+// draw entry groups
+ JCheckBoxMenuItem showEntryGroups = new JCheckBoxMenuItem("Show Entries Loaded",
+ groupsDisplay.isSelected());
+ showEntryGroups.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ groupsDisplay.setSelected(!groupsDisplay.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showEntryGroups);
+
+// draw graphs
+ JCheckBoxMenuItem showPlots = new JCheckBoxMenuItem("Show Graphs",
+ plotsDisplay.isSelected());
+
+// only enable if graphs displayed
+ if(entry.getBasePlotGroup().getNumberBasePlots() == 0)
+ showPlots.setEnabled(false);
+
+ showPlots.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ plotsDisplay.setSelected(!plotsDisplay.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showPlots);
+
+
+// draw read alignment viewer
+ JCheckBoxMenuItem showJam = new JCheckBoxMenuItem("Show Read Alignment View",
+ jamDisplay.isSelected());
+
+ if(entry.getJamView() == null || !entry.getJamView().isVisible())
+ showJam.setEnabled(false);
+
+ showJam.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ jamDisplay.setSelected(!jamDisplay.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showJam);
+
+ // draw vcf viewer
+ JCheckBoxMenuItem showVcf = new JCheckBoxMenuItem("Show VCF View",
+ vcfDisplay.isSelected());
+
+ if(entry.getVcfPanel() == null || !entry.getVcfPanel().isVisible())
+ showJam.setEnabled(false);
+
+ showVcf.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ vcfDisplay.setSelected(!vcfDisplay.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showVcf);
+
+// draw one line
+ JCheckBoxMenuItem showOneLine = new JCheckBoxMenuItem("Show One Line Display",
+ onelineDisplay.isSelected());
+ if(!entry.getOneLinePerEntryDisplay().isVisible())
+ showOneLine.setEnabled(false);
+
+ showOneLine.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ onelineDisplay.setSelected(!onelineDisplay.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showOneLine);
+
+// draw features
+ JCheckBoxMenuItem showFeatures = new JCheckBoxMenuItem("Show Feature Display",
+ featDisplay.isSelected());
+ showFeatures.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ featDisplay.setSelected(!featDisplay.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showFeatures);
+
+// draw base display
+ JCheckBoxMenuItem showBases = new JCheckBoxMenuItem("Show Bases Display",
+ baseDisplay.isSelected());
+ showBases.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ baseDisplay.setSelected(!baseDisplay.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showBases);
+
+// draw feature list
+ JCheckBoxMenuItem showFeatureList = new JCheckBoxMenuItem("Show Feature List",
+ featListDisplay.isSelected());
+ showFeatureList.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ featListDisplay.setSelected(!featListDisplay.isSelected());
+ repaint();
+ }
+ });
+ optionsmenu.add(showFeatureList);
+
+ f.setJMenuBar(menuBar);
+ f.setVisible(true);
+ }
+
+ public static String[] getImageFormats()
+ {
+ final String fmts[] = javax.imageio.ImageIO.getWriterFormatNames();
+ final HashSet<String> list = new HashSet<String>();
+ for(int i=0; i<fmts.length; i++)
+ list.add(fmts[i].toLowerCase());
+
+ final String tmpFmts[] = new String[list.size()+1];
+ System.arraycopy(list.toArray(), 0, tmpFmts, 0, list.size());
+ tmpFmts[tmpFmts.length-1] = "svg";
+ Arrays.sort(tmpFmts);
+
+ return tmpFmts;
+ }
+
+ /**
+ *
+ * Print to a jpeg or png file
+ *
+ */
+ public void print()
+ {
+ // file chooser
+ final StickyFileChooser fc = new StickyFileChooser();
+ File fselect = new File(fc.getCurrentDirectory()+
+ System.getProperty("file.separator")+
+ "artemis.png");
+ fc.setSelectedFile(fselect);
+
+ // file name prefix
+ Box YBox = Box.createVerticalBox();
+ JLabel labFormat = new JLabel("Select Format:");
+ Font font = labFormat.getFont();
+ labFormat.setFont(font.deriveFont(Font.BOLD));
+ YBox.add(labFormat);
+
+ Box bacross = Box.createHorizontalBox();
+ final JComboBox formatSelect = new JComboBox(getImageFormats());
+ formatSelect.setSelectedItem("png");
+ formatSelect.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ String selected;
+ if(fc.getSelectedFile() != null)
+ {
+ selected = fc.getSelectedFile().getAbsolutePath();
+ String fmts[] = getImageFormats();
+ for(int i=0; i<fmts.length; i++)
+ selected = selected.replaceAll("."+fmts[i]+"$", "");
+ }
+ else
+ selected = "artemis";
+
+ fc.setSelectedFile(new File(selected+"."+
+ formatSelect.getSelectedItem()));
+ }
+ });
+
+ Dimension d = formatSelect.getPreferredSize();
+ formatSelect.setMaximumSize(d);
+ bacross.add(Box.createHorizontalGlue());
+ bacross.add(formatSelect);
+ YBox.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ bacross.add(selectDisplay);
+ bacross.add(Box.createHorizontalGlue());
+ YBox.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ bacross.add(groupsDisplay);
+ bacross.add(Box.createHorizontalGlue());
+ YBox.add(bacross);
+
+ if(entry.getBasePlotGroup().getNumberBasePlots() > 0)
+ {
+ bacross = Box.createHorizontalBox();
+ bacross.add(plotsDisplay);
+ bacross.add(Box.createHorizontalGlue());
+ YBox.add(bacross);
+ }
+
+ if(entry.getBamPanel() != null && entry.getBamPanel().isVisible())
+ {
+ bacross = Box.createHorizontalBox();
+ bacross.add(jamDisplay);
+ bacross.add(Box.createHorizontalGlue());
+ YBox.add(bacross);
+ }
+
+ if(entry.getVcfView() != null && entry.getVcfView().isVisible())
+ {
+ bacross = Box.createHorizontalBox();
+ bacross.add(vcfDisplay);
+ bacross.add(Box.createHorizontalGlue());
+ YBox.add(bacross);
+ }
+
+ if(!entry.getOneLinePerEntryDisplay().isVisible())
+ {
+ bacross = Box.createHorizontalBox();
+ bacross.add(onelineDisplay);
+ bacross.add(Box.createHorizontalGlue());
+ YBox.add(bacross);
+ }
+
+ bacross = Box.createHorizontalBox();
+ bacross.add(featDisplay);
+ bacross.add(Box.createHorizontalGlue());
+ YBox.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ bacross.add(baseDisplay);
+ bacross.add(Box.createHorizontalGlue());
+ YBox.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ bacross.add(featListDisplay);
+ bacross.add(Box.createHorizontalGlue());
+ YBox.add(bacross);
+
+ // file prefix & format options
+ fc.setAccessory(YBox);
+ int n = fc.showSaveDialog(null);
+ if(n == JFileChooser.CANCEL_OPTION)
+ return;
+
+ // remove file extension
+ String fsave = fc.getSelectedFile().getAbsolutePath().toLowerCase();
+
+ if(fsave.endsWith(".svg"))
+ {
+ createSVG(fc.getSelectedFile());
+ return;
+ }
+
+ if(fsave.endsWith(".png") ||
+ fsave.endsWith(".jpg") ||
+ fsave.endsWith(".jpeg") )
+ {
+ int ind = fsave.lastIndexOf(".");
+ fsave = fc.getSelectedFile().getAbsolutePath();
+ fsave = fsave.substring(0,ind);
+ }
+ else
+ fsave = fc.getSelectedFile().getAbsolutePath();
+
+ // image type
+ String ftype = (String)formatSelect.getSelectedItem();
+ try
+ {
+ RenderedImage rendImage = createImage();
+ writeImageToFile(rendImage, new File(fsave+"."+ftype),
+ ftype);
+ }
+ catch(NoClassDefFoundError ex)
+ {
+ JOptionPane.showMessageDialog(this,
+ "This option requires Java 1.4 or higher.");
+ }
+ }
+
+ private void createSVG(final File fout)
+ {
+ final DOMImplementation domImpl =
+ GenericDOMImplementation.getDOMImplementation();
+ final Document doc = domImpl.createDocument(
+ "http://www.w3.org/2000/svg", "svg", null);
+
+ SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(doc);
+ ctx.setComment("Generated by Artemis with Batik SVG Generator");
+ final SVGGraphics2D svgG = new SVGGraphics2D(ctx, true);
+ svgG.setFont(Options.getOptions().getFont());
+ final FontMetrics fm = svgG.getFontMetrics();
+ final Dimension d = getImageSize();
+ svgG.setSVGCanvasSize( new Dimension(
+ d.width+fm.stringWidth(" "), d.height+fm.getHeight()) );
+ paintComponent(svgG);
+
+ try
+ {
+ final Writer out = new OutputStreamWriter(
+ new FileOutputStream(fout), "UTF-8");
+ svgG.stream(out, true);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ e.printStackTrace();
+ }
+ catch (SVGGraphics2DIOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+
+ return;
+ }
+
+ protected void doPrintActions()
+ {
+ final PrinterJob pj=PrinterJob.getPrinterJob();
+ pj.setPrintable(PrintArtemis.this);
+ pj.printDialog();
+ try
+ {
+ pj.print();
+ }
+ catch (Exception PrintException) {}
+ }
+
+ /**
+ * Returns a generated image
+ * @param pageIndex page number
+ * @return image
+ */
+ private RenderedImage createImage()
+ {
+ setImageSize();
+ // Create a buffered image in which to draw
+ BufferedImage bufferedImage = new BufferedImage(
+ width,height,
+ BufferedImage.TYPE_INT_RGB);
+ // Create a graphics contents on the buffered image
+ Graphics2D g2d = bufferedImage.createGraphics();
+ paintComponent(g2d);
+
+ return bufferedImage;
+ }
+
+
+ /**
+ * Write out the image
+ * @param image image
+ * @param file file to write image to
+ * @param type type of image
+ */
+ private void writeImageToFile(RenderedImage image,
+ File file, String type)
+ {
+ try
+ {
+ javax.imageio.ImageIO.write(image,type,file);
+ }
+ catch ( IOException e )
+ {
+ System.out.println("Java 1.4+ is required");
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ *
+ * The method @print@ must be implemented for @Printable@ interface.
+ * Parameters are supplied by system.
+ *
+ */
+ public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException
+ {
+ setImageSize();
+ Graphics2D g2 = (Graphics2D)g.create();
+
+// RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);
+ Dimension d = this.getSize(); //get size of document
+ double panelWidth = d.width; //width in pixels
+ double panelHeight = d.height; //height in pixels
+
+ if(panelWidth == 0)
+ {
+ d = this.getPreferredSize();
+ panelWidth = d.width;
+ panelHeight = d.height;
+ }
+ double pageHeight = pf.getImageableHeight(); //height of printer page
+ double pageWidth = pf.getImageableWidth(); //width of printer page
+ double scale = pageWidth/panelWidth;
+ int totalNumPages = (int)Math.ceil(scale * panelHeight / pageHeight);
+ // Make sure not print empty pages
+ if(pageIndex >= totalNumPages)
+ return Printable.NO_SUCH_PAGE;
+
+ // Shift Graphic to line up with beginning of print-imageable region
+ g2.translate(pf.getImageableX(), pf.getImageableY());
+ // Shift Graphic to line up with beginning of next page to print
+ g2.translate(0f, -pageIndex*pageHeight);
+ // Scale the page so the width fits...
+ g2.scale(scale, scale);
+ paintComponent(g2);
+ return Printable.PAGE_EXISTS;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/ProcessWatcher.java b/uk/ac/sanger/artemis/components/ProcessWatcher.java
new file mode 100644
index 0000000..be01364
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ProcessWatcher.java
@@ -0,0 +1,194 @@
+/* ProcessWatcher.java
+ *
+ * created: Mon Oct 4 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ProcessWatcher.java,v 1.1 2004-06-09 09:47:13 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Logger;
+
+import java.io.*;
+
+/**
+ * Objects of this class watch a Process object and then display a
+ * MessageFrame window when the process finishes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ProcessWatcher.java,v 1.1 2004-06-09 09:47:13 tjc Exp $
+ **/
+
+public class ProcessWatcher
+ implements Runnable {
+ /**
+ * Create a new ProcessWatcher object for the given Process. When the
+ * process finishes a MessageFrame will alert the user and all
+ * ProcessWatcherListeners will be informed.
+ * @param process The Process to watch.
+ * @param name The name of the process to watch.
+ **/
+ public ProcessWatcher (final Process process, final String name) {
+ this (process, name, true);
+ }
+
+ /**
+ * Create a new ProcessWatcher object for the given Process. When the
+ * process finishes a MessageFrame will alert the user if and only if the
+ * alert_user argument is true and all ProcessWatcherListeners will be
+ * informed.
+ * @param process The Process to watch.
+ * @param name The name of the process to watch.
+ * @param alert_user The user will be informed when a process ends if and
+ * only if this is true.
+ **/
+ public ProcessWatcher (final Process process, final String name,
+ final boolean alert_user) {
+ this.process = process;
+ this.name = name;
+ this.alert_user = alert_user;
+ }
+
+ /**
+ * This code will wait for the Process to finish then display the return
+ * value in a MessageFrame.
+ **/
+ public void run () {
+ try {
+ final Reader reader = new InputStreamReader (process.getErrorStream ());
+ getLogger ().log (reader);
+ } catch (IOException e) {
+ final MessageFrame message_frame =
+ new MessageFrame ("error while reading output of: " + name);
+
+ message_frame.setVisible (true);
+ }
+
+ try {
+ final Reader reader = new InputStreamReader (process.getInputStream ());
+ getLogger ().log (reader);
+ } catch (IOException e) {
+ final MessageFrame message_frame =
+ new MessageFrame ("error while reading output of: " + name);
+
+ message_frame.setVisible (true);
+ }
+
+ while (true) {
+ try {
+ final int return_value = process.waitFor ();
+
+ for (int i = 0 ; i < listeners.size () ; ++i) {
+ final ProcessWatcherEvent event =
+ new ProcessWatcherEvent (process, return_value);
+ final ProcessWatcherListener listener =
+ (ProcessWatcherListener) listeners.elementAt (i);
+ listener.processFinished (event);
+ }
+
+ final boolean core_dumped = (return_value & 0x80) != 0;
+
+ getLogger ().log ("\n--------------------------------------" +
+ "---------------------\n\n");
+
+ final String log_message;
+
+ if (core_dumped) {
+ log_message = name + " process dumped core";
+ new MessageFrame (log_message +
+ " - check the log window").setVisible (true);
+ } else {
+ final int sig_number = return_value & 0x7f;
+
+ if (sig_number > 0) {
+ log_message = name + " process received signal: " + sig_number;
+ new MessageFrame (log_message +
+ " - check the log window").setVisible (true);
+ } else {
+ final int exit_code = return_value >> 8;
+
+ if (exit_code == 0) {
+ log_message = name + " process completed";
+ if (alert_user) {
+ final MessageFrame message_frame =
+ new MessageFrame (log_message);
+ message_frame.setVisible (true);
+ }
+ } else {
+ log_message =
+ name + " process finished with exit code: " + exit_code;
+ new MessageFrame (log_message +
+ " - check the log window").setVisible (true);
+ }
+ }
+ }
+
+ getLogger ().log (log_message + "\n");
+
+ return;
+ } catch (InterruptedException e) {
+ // go around the loop again
+ }
+ }
+ }
+
+ /**
+ * Return the global Logger object.
+ **/
+ private static Logger getLogger () {
+ return Splash.getLogger ();
+ }
+
+ /**
+ * Add the given object as a ProcessWatcherListener for this ProcessWatcher.
+ **/
+ public void addProcessWatcherListener (final ProcessWatcherListener l) {
+ listeners.addElement (l);
+ }
+
+ /**
+ * Remove the given object as a ProcessWatcherListener for this
+ * ProcessWatcher.
+ **/
+ public void removeProcessWatcherListener (final ProcessWatcherListener l) {
+ listeners.removeElement (l);
+ }
+
+ /**
+ * The Process reference that was passed to the constructor.
+ **/
+ private Process process;
+
+ /**
+ * The program name that was passed to the constructor.
+ **/
+ private String name;
+
+ /**
+ * The alert_user argument that was passed to the constructor.
+ **/
+ private boolean alert_user;
+
+ /**
+ * The list of objects that are listening for ProcessWatcher events.
+ **/
+ private final java.util.Vector listeners = new java.util.Vector ();
+}
diff --git a/uk/ac/sanger/artemis/components/ProcessWatcherEvent.java b/uk/ac/sanger/artemis/components/ProcessWatcherEvent.java
new file mode 100644
index 0000000..61576c0
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ProcessWatcherEvent.java
@@ -0,0 +1,70 @@
+/* ProcessWatcherEvent.java
+ *
+ * created: Tue Feb 29 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ProcessWatcherEvent.java,v 1.1 2004-06-09 09:47:14 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * This event is sent when the process that is watched by ProcessWatcher
+ * finishes.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ProcessWatcherEvent.java,v 1.1 2004-06-09 09:47:14 tjc Exp $
+ **/
+
+public class ProcessWatcherEvent {
+ /**
+ * Create a new ProcessWatcherEvent object.
+ * @param process The Process that has finished.
+ * @param exit_code The exit code of the process that has finished.
+ **/
+ public ProcessWatcherEvent (final Process process, final int exit_code) {
+ this.process = process;
+ this.exit_code = exit_code;
+ }
+
+ /**
+ * Return the process that was passed to the constructor.
+ **/
+ public Process getProcess () {
+ return process;
+ }
+
+ /**
+ * Return the exit_code that was passed to the constructor.
+ **/
+ public int getExitCode () {
+ return exit_code;
+ }
+
+ /**
+ * The process that was passed to the constructor.
+ **/
+ final Process process;
+
+ /**
+ * The exit_code that was passed to the constructor.
+ **/
+ final int exit_code;
+}
diff --git a/uk/ac/sanger/artemis/components/ProcessWatcherListener.java b/uk/ac/sanger/artemis/components/ProcessWatcherListener.java
new file mode 100644
index 0000000..723155a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ProcessWatcherListener.java
@@ -0,0 +1,42 @@
+/* ProcessWatcherListener.java
+ *
+ * created: Tue Feb 29 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ProcessWatcherListener.java,v 1.1 2004-06-09 09:47:15 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * This interface is implemented by those classes that need to be notified
+ * when a Process finishes. The Process must be watched with a
+ * ProcessWatcher object.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ProcessWatcherListener.java,v 1.1 2004-06-09 09:47:15 tjc Exp $
+ **/
+
+public interface ProcessWatcherListener {
+ /**
+ * Invoked by a ProcessWatcher object when a Process finishes.
+ **/
+ void processFinished (final ProcessWatcherEvent event);
+}
diff --git a/uk/ac/sanger/artemis/components/ProgressBarFrame.java b/uk/ac/sanger/artemis/components/ProgressBarFrame.java
new file mode 100644
index 0000000..f3b88ae
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ProgressBarFrame.java
@@ -0,0 +1,69 @@
+/* ExternalProgram.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+package uk.ac.sanger.artemis.components;
+
+import java.awt.Color;
+
+import javax.swing.JFrame;
+import javax.swing.JProgressBar;
+
+public class ProgressBarFrame extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+
+ public ProgressBarFrame(int seconds, String name)
+ {
+ super();
+ setUndecorated(true);
+ final int max = (seconds*1000)/40;
+ final JProgressBar progressBar = new JProgressBar(0,max);
+ progressBar.setStringPainted(true);
+ progressBar.setString("Sending "+name+" process now!");
+ progressBar.setBackground(Color.white);
+
+ SwingWorker batchWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ try
+ {
+ for(int i=0; i<max; i++)
+ {
+ Thread.sleep(40);
+ progressBar.setValue(i);
+ }
+ dispose();
+ }
+ catch(InterruptedException intr){}
+ return null;
+ }
+ };
+
+ getContentPane().add(progressBar);
+ pack();
+ Utilities.centreFrame(this);
+ setVisible(true);
+ batchWorker.start();
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/ProgressThread.java b/uk/ac/sanger/artemis/components/ProgressThread.java
new file mode 100644
index 0000000..73608fe
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ProgressThread.java
@@ -0,0 +1,69 @@
+/* ArtemisMain.java
+ *
+ * created: Tue May 11 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import javax.swing.*;
+import java.awt.Dimension;
+import java.awt.Color;
+
+public class ProgressThread extends Thread
+{
+ private JFrame frame;
+ private JFrame progress_frame;
+ private String msg;
+ private JProgressBar progressBar = new JProgressBar();
+
+
+ public ProgressThread(JFrame frame, String msg)
+ {
+ this.frame = frame;
+ this.msg = msg;
+ }
+
+ public void run()
+ {
+ try
+ {
+ progress_frame = new JFrame(msg);
+ Dimension d = progress_frame.getToolkit().getScreenSize();
+ progressBar.setIndeterminate(true);
+ progressBar.setBackground(Color.white);
+ progress_frame.getContentPane().add(progressBar);
+ progress_frame.pack();
+ progress_frame.setLocation(
+ ((int)d.getWidth()-progress_frame.getWidth())/2,
+ ((int)d.getHeight()-progress_frame.getHeight())/2);
+ progress_frame.setVisible(true);
+ }
+ catch(NoSuchMethodError nsme){}
+ }
+
+ public void finished()
+ {
+ if(progress_frame != null)
+ progress_frame.setVisible(false);
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/ProjectProperty.java b/uk/ac/sanger/artemis/components/ProjectProperty.java
new file mode 100644
index 0000000..1210f22
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ProjectProperty.java
@@ -0,0 +1,938 @@
+/* ProjectProperty
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2012 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.JToolBar;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.database.DatabaseEntrySource;
+import uk.ac.sanger.artemis.components.database.DatabaseJPanel;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.FileDocument;
+
+/**
+ * Project file management system using a properties file.
+ *
+ * Example of the syntax for defining a project in the property file:
+ * project.Pknowlsei.sequence = Pknowlsei:Pk_strainH_chr01
+ * project.Pknowlsei.chado = genedb-db.sanger.ac.uk:5432/snapshot?genedb_ro
+ * project.Pknowlsei.title = Pknowlsei
+ */
+public class ProjectProperty extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private static HashMap<String, HashMap<String, String>> centralProjects;
+ private static HashMap<String, HashMap<String, String>> userProjects;
+ private Splash splash;
+ private DatabaseEntrySource entry_source;
+
+ private final static int REFERENCE = 1;
+ private final static int ANNOTATION = 2;
+ private final static int NEXT_GEN_DATA = 3;
+ private final static int CHADO = 4;
+ private final static int USERPLOT = 5;
+ private final static int LOGUSERPLOT = 6;
+ private final static int VCF = 7;
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(ProjectProperty.class);
+
+ private final static String[] TYPES =
+ { "title", "sequence", "annotation", "bam", "vcf", "userplot", "log_userplot", "chado" };
+
+ public ProjectProperty()
+ {
+ this(null);
+
+ final javax.swing.plaf.FontUIResource font_ui_resource =
+ Options.getOptions().getFontUIResource();
+ java.util.Enumeration<Object> keys = UIManager.getDefaults().keys();
+ while(keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ Object value = UIManager.get(key);
+ if(value instanceof javax.swing.plaf.FontUIResource)
+ UIManager.put(key, font_ui_resource);
+ }
+ }
+
+ public ProjectProperty(Splash splash)
+ {
+ super("Project File Manager");
+ this.splash = splash;
+ InputStream ins =
+ this.getClass().getClassLoader().getResourceAsStream("etc/project.properties");
+
+ try
+ {
+ logger4j.debug("Reading properties from: "+
+ this.getClass().getClassLoader().getResource("etc/project.properties").toURI());
+ }
+ catch (URISyntaxException e1){}
+ catch (NullPointerException e2) {}
+
+ final Properties projectProps = new Properties();
+ try
+ {
+ projectProps.load(ins);
+ ins.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (NullPointerException e2) {}
+
+ centralProjects = getProjectMap(projectProps);
+ projectProps.clear();
+
+ final String [] propFiles =
+ {
+ "project.properties",
+ System.getProperty("user.home") + File.separator + ".artemis.project.properties"
+ };
+
+ for(int i=0; i<propFiles.length; i++)
+ {
+ final Document doc =
+ new FileDocument(new File(propFiles[i]));
+ if(doc.readable())
+ {
+ try
+ {
+ ins = doc.getInputStream();
+ projectProps.load(ins);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ logger4j.debug("Reading properties from: "+propFiles[i]);
+ }
+ }
+
+ userProjects = getProjectMap(projectProps);
+ createProjectViewer((JPanel) getContentPane());
+ pack();
+ setVisible(true);
+ }
+
+ private void createProjectViewer(JPanel panel)
+ {
+ final DefaultListModel model = new DefaultListModel();
+
+ final JList projectList = new JList(model);
+ final JScrollPane jspList = new JScrollPane(projectList);
+ Object[] items = centralProjects.keySet().toArray();
+ Arrays.sort(items);
+ for (int i=0; i<items.length; i++)
+ model.add(i, items[i]);
+
+ items = userProjects.keySet().toArray();
+ Arrays.sort(items);
+ for (int i=0; i<items.length; i++)
+ model.add(i, items[i]);
+
+ final Box yBox = Box.createVerticalBox();
+ final JScrollPane jspProp = new JScrollPane(yBox);
+ final LaunchActionListener listener = new LaunchActionListener();
+ projectList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ projectList.setVisibleRowCount(-1);
+ panel.add(jspList, BorderLayout.WEST);
+ panel.add(jspProp, BorderLayout.CENTER);
+
+ // Add / remove project buttons
+
+ final JToolBar toolBar = new JToolBar();
+ panel.add(toolBar, BorderLayout.PAGE_START);
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ panel.setPreferredSize(new Dimension((int)(screen.width/2.5),screen.height/3));
+ panel.setBackground(Color.WHITE);
+ final JButton addProjectButton = new JButton("+");
+ addProjectButton.setOpaque(false);
+ Font font = addProjectButton.getFont().deriveFont(Font.BOLD).deriveFont(14.f);
+ addProjectButton.setFont(font);
+ addProjectButton.setToolTipText("ADD PROJECT");
+ addProjectButton.setForeground(new Color(35, 149, 35));
+ addProjectButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ addProject(projectList);
+ }
+ });
+ toolBar.add(addProjectButton);
+
+ final JButton removeProjectButton = new JButton("-");
+ removeProjectButton.setOpaque(false);
+ removeProjectButton.setFont(font);
+ removeProjectButton.setToolTipText("REMOVE PROJECT");
+ removeProjectButton.setForeground(new Color(149, 35, 35));
+ removeProjectButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ removeProject(projectList, yBox, listener);
+ }
+ });
+ toolBar.add(removeProjectButton);
+
+ final JButton saveProperties = new JButton("SAVE");
+ saveProperties.setFont(font.deriveFont(Font.PLAIN));
+ saveProperties.setToolTipText("SAVE PROJECT PROPERTIES");
+ saveProperties.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ writeProperties();
+ }
+ });
+ toolBar.add(Box.createHorizontalGlue());
+ toolBar.add(saveProperties);
+
+ final JButton openArt = new JButton("OPEN");
+ openArt.addActionListener(listener);
+ final JButton closeButton = new JButton("CLOSE");
+ closeButton .addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ dispose();
+ }
+ });
+
+ Box xBox = Box.createHorizontalBox();
+ xBox.add(openArt);
+ xBox.add(closeButton);
+ xBox.add(Box.createHorizontalGlue());
+
+ panel.add(xBox, BorderLayout.SOUTH);
+
+ final GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.ipadx = 10;
+ c.ipady = 10;
+
+
+ projectList.addListSelectionListener(new ListSelectionListener()
+ {
+ public void valueChanged(ListSelectionEvent e)
+ {
+ if(e.getValueIsAdjusting() == false &&
+ projectList.getSelectedIndex() > -1)
+ refreshProperties(projectList, yBox, listener);
+ }
+ });
+ }
+
+ private void addProject(final JList projectList)
+ {
+ DefaultListModel model = (DefaultListModel) projectList.getModel();
+
+ String projName =
+ JOptionPane.showInputDialog(ProjectProperty.this,
+ "Project Name", "New Project", JOptionPane.QUESTION_MESSAGE);
+ if(projName == null)
+ return;
+
+ if(model.contains(projName))
+ {
+ JOptionPane.showMessageDialog(ProjectProperty.this,
+ projName+" is already a project. Please provide a unique project name.",
+ "Project Name", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ final HashMap<String, String> hMap = new HashMap<String, String>();
+ hMap.put("sequence", "");
+ userProjects.put(projName, hMap);
+ model.add(model.getSize(), projName);
+ projectList.repaint();
+ projectList.setSelectedIndex(model.getSize()-1);
+ }
+
+ private void removeProject(final JList projectList, final Box yBox, final LaunchActionListener listener)
+ {
+ if(projectList.getSelectedValue() == null)
+ {
+ JOptionPane.showMessageDialog(ProjectProperty.this,
+ "Select a project from the list to be removed.",
+ "Remove", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ DefaultListModel model = (DefaultListModel) projectList.getModel();
+ int status = JOptionPane.showConfirmDialog(
+ ProjectProperty.this, "Remove "+projectList.getSelectedValue()+"?",
+ "Remove Project", JOptionPane.YES_NO_OPTION);
+ if(status != JOptionPane.YES_OPTION)
+ return;
+ userProjects.remove(projectList.getSelectedValue());
+ model.remove(projectList.getSelectedIndex());
+ projectList.repaint();
+ yBox.removeAll();
+ yBox.repaint();
+ listener.setSettings(null);
+ }
+
+ /**
+ * Refresh components in the properties panel.
+ * @param projectList
+ * @param yBox
+ * @param listener
+ */
+ private void refreshProperties(final JList projectList,
+ final Box yBox,
+ final LaunchActionListener listener)
+ {
+ yBox.removeAll();
+
+ final HashMap<String, String> projProps;
+ if(centralProjects.containsKey(projectList.getSelectedValue()))
+ projProps = centralProjects.get(projectList.getSelectedValue());
+ else
+ projProps = userProjects.get(projectList.getSelectedValue());
+
+ final HashMap<Integer, Vector<JTextField>> settings = new HashMap<Integer, Vector<JTextField>>();
+ // order the keys
+ Object keys[] = projProps.keySet().toArray();
+ Arrays.sort(keys, new TypeComparator());
+
+ for(final Object key: keys)
+ {
+ final String keyStr = (String) key;
+ final Vector<JTextField> vText = new Vector<JTextField>();
+
+ Border lineBorder = BorderFactory.createLineBorder(Color.DARK_GRAY);
+ TitledBorder title = BorderFactory.createTitledBorder(
+ lineBorder, keyStr);
+ //title.setTitlePosition(TitledBorder.LEFT);
+
+ final JPanel propPanel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridy = 0;
+
+ c.fill = GridBagConstraints.BOTH;
+ propPanel.setBorder(title);
+ propPanel.setBackground(Color.WHITE);
+
+ //
+ final Vector<JCheckBox> checkBoxes = new Vector<JCheckBox>();
+ final JButton toggle = new JButton("Toggle");
+ toggle.setToolTipText("toggle "+keyStr+" selection");
+ toggle.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ for(JCheckBox cb: checkBoxes)
+ cb.setSelected(!cb.isSelected());
+ }
+ });
+
+ final Vector<String> anns = splitLine(projProps.get(keyStr).trim());
+ for (int i=0; i<anns.size(); i++)
+ {
+ c.gridx = 0;
+ addProperyToPanel(projectList, propPanel, vText, c, i, anns.get(i), projProps, keyStr, yBox, listener, checkBoxes);
+ }
+
+ if (!keyStr.equals("title") && !keyStr.equals("chado"))
+ {
+ Box xBox = Box.createHorizontalBox();
+ final JButton selectButton = new JButton(
+ keyStr.startsWith("seq") ? "Select " : "Add file");
+ selectButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ StickyFileChooser fileChooser = new StickyFileChooser();
+ int status = fileChooser.showOpenDialog(ProjectProperty.this);
+
+ if(status == StickyFileChooser.APPROVE_OPTION)
+ {
+ if(keyStr.startsWith("seq"))
+ vText.get(0).setText(fileChooser.getSelectedFile().getAbsolutePath());
+ else
+ {
+ projProps.put(keyStr, projProps.get(keyStr)+" "+
+ fileChooser.getSelectedFile().getAbsolutePath());
+ refreshProperties(projectList, yBox, listener);
+ }
+ }
+ }
+ });
+ c.gridy = c.gridy+1;
+
+ c.gridx = 0;
+ xBox.add(selectButton);
+ xBox.add(Box.createHorizontalGlue());
+ if(checkBoxes.size() > 1) // add toggle option
+ xBox.add(toggle, c);
+
+ c.gridwidth = 2;
+ propPanel.add(xBox, c);
+ c.gridwidth = 1;
+ }
+
+ yBox.add(propPanel);
+
+ if(keyStr.startsWith("seq"))
+ settings.put(ProjectProperty.REFERENCE, vText);
+ else if(keyStr.equals("annotation"))
+ settings.put(ProjectProperty.ANNOTATION, vText);
+ else if(keyStr.equals("bam"))
+ settings.put(ProjectProperty.NEXT_GEN_DATA, vText);
+ else if(keyStr.equals("vcf") || keyStr.equals("bcf"))
+ settings.put(ProjectProperty.VCF, vText);
+ else if(keyStr.equals("chado"))
+ settings.put(ProjectProperty.CHADO, vText);
+ else if(keyStr.equals("userplot"))
+ settings.put(ProjectProperty.USERPLOT, vText);
+ else if(keyStr.equals("log_userplot"))
+ settings.put(ProjectProperty.LOGUSERPLOT, vText);
+ }
+
+ // ADD property
+ Box xBox = Box.createHorizontalBox();
+ final JButton addPropertyButton = new JButton("NEW PROPERTY");
+ final JComboBox propertyList = new JComboBox(TYPES);
+ xBox.add(addPropertyButton);
+ xBox.add(propertyList);
+ xBox.add(Box.createHorizontalGlue());
+ yBox.add(xBox);
+ addPropertyButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ String key = (String) propertyList.getSelectedItem();
+ if(!projProps.containsKey(key))
+ {
+ projProps.put(key, "");
+ refreshProperties(projectList, yBox, listener);
+ }
+ }
+ });
+
+ //
+ yBox.add(Box.createVerticalGlue());
+ yBox.revalidate();
+ yBox.repaint();
+
+ listener.setSettings(settings);
+ }
+
+ private Vector<String> splitLine(String line)
+ {
+ Vector<String> parts = new Vector<String>();
+ int index = line.indexOf(" ");
+ if(index < 0)
+ parts.add(line);
+ else
+ {
+ int startIndex = 0;
+ line = line.replaceAll("\\s+", " ");
+ while((index = line.indexOf(" ", startIndex)) > -1)
+ {
+ if(line.charAt(index-1) == '\\')
+ {
+ startIndex = index+1;
+ continue;
+ }
+ parts.add(line.substring(0, index));
+ line = line.substring(index+1);
+ startIndex = 0;
+ }
+ parts.add(line);
+ }
+
+ return parts;
+ }
+
+ private void addProperyToPanel(final JList projectList,
+ final JPanel propPanel,
+ final Vector<JTextField> vText,
+ final GridBagConstraints c,
+ final int index,
+ final String ann,
+ final HashMap<String, String> projProps,
+ final String key,
+ final Box yBox,
+ final LaunchActionListener listener,
+ final Vector<JCheckBox> cbs)
+ {
+ final JTextField qta = new JTextField(67);
+ vText.add(qta);
+
+ if(key.equals("title"))
+ qta.setText(removeSpaceEscape(ann));
+ else
+ qta.setText(ann);
+ qta.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
+ qta.getDocument().addDocumentListener(new DocumentListener()
+ {
+ private void update()
+ {
+ final String anns[];
+ if(key.equals("title")) // only takes one value
+ anns = new String[]{ escapeSpace(projProps.get(key).trim()) };
+ else
+ anns = projProps.get(key).trim().split("\\s+");
+ String value = "";
+ for(int i=0;i<anns.length;i++)
+ {
+ if(i == index)
+ value += " "+qta.getText();
+ else
+ value += " "+anns[i];
+ }
+
+ if(index == anns.length)
+ value += " "+qta.getText();
+
+ projProps.put(key, value);
+ }
+
+ public void changedUpdate(DocumentEvent e)
+ {
+ update();
+ }
+ public void insertUpdate(DocumentEvent e)
+ {
+ update();
+ }
+ public void removeUpdate(DocumentEvent e)
+ {
+ update();
+ }
+ });
+
+ // REMOVE PROPERTY
+ Box xButtons = Box.createHorizontalBox();
+ final JButton removeProperty = new JButton("X");
+ removeProperty.setOpaque(false);
+ Font font = removeProperty.getFont().deriveFont(Font.BOLD);
+ removeProperty.setFont(font);
+ removeProperty.setToolTipText("REMOVE");
+ removeProperty.setForeground(new Color(139, 35, 35));
+ removeProperty.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int status = JOptionPane.showConfirmDialog(
+ ProjectProperty.this, "Remove "+key+"?",
+ "Remove", JOptionPane.YES_NO_OPTION);
+ if(status != JOptionPane.YES_OPTION)
+ return;
+ final String anns[] = projProps.get(key).trim().split("\\s+");
+ String value = "";
+ for(int i=0;i<anns.length;i++)
+ {
+ if(i != index)
+ value += " "+anns[i];
+ }
+ if(value.equals(""))
+ projProps.remove(key);
+ else
+ projProps.put(key, value.trim());
+ refreshProperties(projectList, yBox, listener);
+ }
+ });
+ xButtons.add(removeProperty);
+ //
+ if(!key.equals("title") && !key.startsWith("seq") && !key.equals("chado"))
+ {
+ final JCheckBox useProperty = new JCheckBox("",true);
+ useProperty.addItemListener(new ItemListener(){
+ public void itemStateChanged(ItemEvent arg0)
+ {
+ qta.setEnabled(useProperty.isSelected());
+ }
+ });
+
+ xButtons.add(useProperty);
+ cbs.add(useProperty);
+ }
+ xButtons.add(Box.createHorizontalGlue());
+
+ c.gridy = c.gridy+1;
+ propPanel.add(qta, c);
+ c.gridx = c.gridx+1;
+ propPanel.add(xButtons, c);
+ }
+
+ /**
+ * Create a project hash of the properties.
+ * @param projectProps
+ * @return
+ */
+ protected static HashMap<String, HashMap<String, String>> getProjectMap(final Properties projectProps)
+ {
+ final HashMap<String, HashMap<String, String>> projects =
+ new HashMap<String, HashMap<String, String>>();
+
+ for (Entry<Object, Object> propItem : projectProps.entrySet())
+ {
+ String key = (String) propItem.getKey();
+ String value = (String) propItem.getValue();
+
+ if (key.startsWith("project."))
+ {
+ key = key.substring(8);
+ int ind = key.indexOf(".");
+ if (ind > -1)
+ {
+ String projName = key.substring(0, ind);
+ key = key.substring(ind + 1);
+ final HashMap<String, String> thisProj;
+ if (projects.containsKey(projName))
+ thisProj = projects.get(projName);
+ else
+ thisProj = new HashMap<String, String>();
+ thisProj.put(key, value);
+
+ projects.put(projName, thisProj);
+ }
+ }
+ }
+ return projects;
+ }
+
+ /**
+ * Write or re-write properties and insert/update the user.dir property
+ */
+ protected static void writeProperties()
+ {
+ writeProperties(new File( System.getProperty("user.home") +
+ File.separator + ".artemis.project.properties"), userProjects);
+ }
+
+ /**
+ * Write or re-write properties file
+ * @param propFile properties file
+ */
+ protected static void writeProperties(final File propFile,
+ HashMap<String, HashMap<String, String>> userProjects)
+ {
+ if(userProjects == null)
+ return;
+
+ try
+ {
+ if(userProjects.size() > 0)
+ {
+ propFile.renameTo(new File(propFile.getAbsolutePath()+".bak"));
+ final BufferedWriter bufferedwriter = new BufferedWriter(new FileWriter(propFile));
+ for (String project: userProjects.keySet())
+ {
+ bufferedwriter.write("#");
+ bufferedwriter.newLine();
+
+ HashMap<String, String> projProps = userProjects.get(project);
+ for(final String key: projProps.keySet())
+ {
+ final String val;
+ if(key.equals("title"))
+ val = escapeSpace(projProps.get(key).trim());
+ else
+ val = projProps.get(key).trim().replaceAll("\\s{2,}", " ");
+
+ bufferedwriter.write("project."+project+"."+key+"="+val );
+ bufferedwriter.newLine();
+ }
+
+ // unfortunately Properties.store() adds a timestamp as a comment
+ /*myProps.clear();
+ HashMap<String, String> projProps = userProjects.get(project);
+ for(final String key: projProps.keySet())
+ myProps.setProperty("project."+project+"."+key,
+ projProps.get(key).trim().replaceAll("\\s+", " "));
+ myProps.store(bufferedwriter, null);*/
+ }
+
+ bufferedwriter.close();
+ }
+ else
+ propFile.delete();
+ }
+ catch (FileNotFoundException filenotfoundexception)
+ {
+ System.err.println(propFile.getAbsolutePath()+" read error");
+ }
+ catch (IOException e)
+ {
+ System.err.println(propFile.getAbsolutePath()+" i/o error");
+ }
+ }
+
+ /**
+ * Open a database entry
+ * @param splash
+ * @param featureName gene or sequence ID
+ */
+ private void openDatabase(final Splash splash, final String featureName)
+ {
+ final String loc = System.getProperty("chado");
+ if( entry_source == null ||
+ !entry_source.getLocation().endsWith(loc) )
+ {
+ entry_source = new DatabaseEntrySource();
+ boolean promptUser = true;
+ if(System.getProperty("read_only") != null)
+ {
+ promptUser = false;
+ entry_source.setReadOnly(true);
+ }
+ if(!entry_source.setLocation(promptUser))
+ return;
+ }
+ DatabaseJPanel.getEntryEditFromDatabase(
+ entry_source, splash, ProjectProperty.this, featureName);
+ }
+
+
+ /**
+ * Escape the spaces with a double backslash (i.e. '\\ ').
+ * @param s
+ * @return
+ */
+ private static String escapeSpace(String s)
+ {
+ s = removeSpaceEscape(s).replace(" ", "\\\\ ");
+ return s;
+ }
+
+ private static String removeSpaceEscape(String s)
+ {
+ return s.replace("\\ ", " ");
+ }
+
+ class LaunchActionListener implements ActionListener
+ {
+ private HashMap<Integer, Vector<JTextField>> settings;
+
+ private void setSettings(HashMap<Integer, Vector<JTextField>> settings)
+ {
+ this.settings = settings;
+ }
+
+ private String[] getArgs()
+ {
+ try
+ {
+ System.getProperties().remove("bam");
+ System.getProperties().remove("chado");
+ System.getProperties().remove("userplot");
+ System.getProperties().remove("loguserplot");
+ }
+ catch(Exception e){ e.printStackTrace(); }
+
+ boolean seenSequence = false;
+ final Set<Integer> keys = settings.keySet();
+ final Vector<String> vargs = new Vector<String>();
+ final Vector<String> vann = new Vector<String>();
+
+ for(Integer key: keys)
+ {
+ final Vector<JTextField> vText = settings.get(key);
+ switch(key)
+ {
+ case ProjectProperty.REFERENCE:
+ String ref = vText.get(0).getText().trim();
+ if(!ref.equals(""))
+ seenSequence = true;
+ vargs.add( ref );
+ break;
+ case ProjectProperty.ANNOTATION:
+ for(JTextField ann: vText)
+ if(ann.isEnabled())
+ vann.add( ann.getText().trim() );
+ break;
+ case ProjectProperty.NEXT_GEN_DATA :
+ setBam(vText);
+ break;
+ case ProjectProperty.VCF :
+ setBam(vText);
+ break;
+ case ProjectProperty.USERPLOT:
+ String userplot = "";
+ for(JTextField ann: vText)
+ {
+ if(ann.isEnabled())
+ userplot += ","+ann.getText().trim();
+ }
+ if(!userplot.equals(""))
+ System.setProperty("userplot", userplot.replaceFirst(",", ""));
+ break;
+ case ProjectProperty.LOGUSERPLOT:
+ String loguserplot = "";
+ for(JTextField ann: vText)
+ {
+ if(ann.isEnabled())
+ loguserplot += ","+ann.getText().trim();
+ }
+ if(!loguserplot.equals(""))
+ System.setProperty("loguserplot", loguserplot.replaceFirst(",", ""));
+ break;
+ case ProjectProperty.CHADO:
+ seenSequence = true;
+ System.setProperty("chado", vText.get(0).getText().trim());
+ break;
+ }
+ }
+
+ if(!seenSequence)
+ JOptionPane.showMessageDialog(ProjectProperty.this,
+ "No sequence file entered for this project.",
+ "Sequence Entry Missing", JOptionPane.WARNING_MESSAGE);
+
+ String[] args = new String[vargs.size()+(vann.size()*2)];
+ for(int i=0; i<vargs.size(); i++)
+ args[i] = vargs.get(i);
+ for(int i=0; i<vann.size(); i++)
+ {
+ args[vargs.size()+(i*2)] = "+";
+ args[vargs.size()+(i*2)+1] = vann.get(i);
+ }
+ return args;
+ }
+
+ private void setBam(final Vector<JTextField> vText)
+ {
+ String bam = "";
+ for(JTextField ann: vText)
+ if(ann.isEnabled())
+ bam += ","+ann.getText().trim();
+ if(!bam.equals(""))
+ {
+ if(System.getProperty("bam") != null)
+ bam += ","+System.getProperty("bam");
+ System.setProperty("bam", bam.replaceFirst(",", ""));
+ }
+ }
+
+ public void actionPerformed(ActionEvent arg0)
+ {
+ if(settings == null)
+ {
+ JOptionPane.showMessageDialog(ProjectProperty.this,
+ "Select a project.", "No Project", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ final String[] args = getArgs();
+ if(System.getProperty("chado") != null &&
+ args != null &&
+ args.length == 1 &&
+ args[0].indexOf(":") == -1)
+ {
+ openDatabase(splash, args[0]);
+ return;
+ }
+
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ try
+ {
+ final ArtemisMain main_window;
+ if (splash == null)
+ {
+ main_window = new ArtemisMain(args);
+ main_window.setVisible(true);
+ }
+ else
+ main_window = (ArtemisMain) splash;
+ main_window.readArgsAndOptions(args, ProjectProperty.this);
+ }
+ finally
+ {
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+ }
+ }
+
+ class TypeComparator implements Comparator<Object>
+ {
+ public int compare(Object o1, Object o2)
+ {
+ String s1 = (String)o1;
+ String s2 = (String)o2;
+ if( s1.equals("title") )
+ return -1;
+ else if(s2.equals("title"))
+ return 1;
+
+ return s1.compareTo(s2);
+ }
+ }
+
+ public static void main(String args[])
+ {
+ new ProjectProperty();
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/QualifierChoice.java b/uk/ac/sanger/artemis/components/QualifierChoice.java
new file mode 100644
index 0000000..aec3288
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/QualifierChoice.java
@@ -0,0 +1,185 @@
+/* QualifierChoice.java
+ *
+ * created: Tue Sep 7 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.Dimension;
+import java.awt.Toolkit;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.EntryInformation;
+
+import javax.swing.JComboBox;
+
+/**
+ * This is a Choice component that shows only the qualifier names for a given
+ * key.
+ * @author Kim Rutherford
+ **/
+
+public class QualifierChoice extends JComboBox
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /** The Key that was passed to the constructor. */
+ private Key key = null;
+
+ /** The qualifier name that was passed to the constructor. */
+ private String default_qualifier = null;
+
+ /** The EntryInformation object that was passed to the constructor. */
+ private EntryInformation entry_information;
+
+ private boolean isGFF;
+
+ /**
+ * Create a new QualifierChoice component for the given Key with the given
+ * qualifier as the default.
+ * @param entry_information The object to get the list of possible
+ * qualifiers from.
+ * @param default_qualifier The name of the Qualifier that should be shown
+ * initially. If null the first (alphabetically) is selected.
+ **/
+ public QualifierChoice (final EntryInformation entry_information,
+ final Key key, final String default_qualifier,
+ final boolean isGFF)
+ {
+ this.entry_information = entry_information;
+ this.key = key;
+ this.isGFF = isGFF;
+
+ if (default_qualifier != null &&
+ entry_information.isValidQualifier (key, default_qualifier))
+ this.default_qualifier = default_qualifier;
+ else
+ this.default_qualifier = null;
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ int screen_height = screen.height;
+
+ final int MAX_VISIBLE_ROWS;
+ if(screen_height < 1024)
+ MAX_VISIBLE_ROWS = 20;
+ else
+ MAX_VISIBLE_ROWS = 30;
+
+ setMaximumRowCount (MAX_VISIBLE_ROWS);
+ setEditable(true);
+ update ();
+ }
+
+ /**
+ * Change the qualifiers shown in this component to be those of the given
+ * Key.
+ **/
+ public void setKey (final Key key)
+ {
+ if (this.key != key)
+ {
+ this.key = key;
+ update ();
+ }
+ }
+
+ /**
+ * Select the given qualifier_name.
+ **/
+ private void setSelectedQualifierByName (final String qualifier_name)
+ {
+ final int index = indexOf (qualifier_name);
+
+ if (index == -1)
+ {
+ // add the key
+ addItem (qualifier_name);
+ setSelectedItem (qualifier_name);
+ }
+ else
+ setSelectedIndex (index);
+ }
+
+ /**
+ * Return the index in the Choice component of the given qualifier_name.
+ **/
+ private int indexOf (final String qualifier_name)
+ {
+ for (int i = 0 ; i < getItemCount () ; ++i)
+ {
+ if (getItemAt (i).equals (qualifier_name))
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Update the Choice to refect the current Key.
+ **/
+ private void update ()
+ {
+ removeAllItems ();
+ StringVector qualifier_names =
+ entry_information.getValidQualifierNames (key);
+
+ if (qualifier_names == null)
+ qualifier_names = new StringVector ("note");
+
+ if (default_qualifier != null &&
+ !qualifier_names.contains (default_qualifier))
+ {
+ qualifier_names.add (default_qualifier);
+ }
+
+ qualifier_names.sort ();
+
+ final StringVector invisible_qualifiers =
+ Options.getOptions ().getInvisibleQualifiers (isGFF);
+
+ for (int i = 0 ; i < qualifier_names.size () ; ++i)
+ {
+ final String qualifier_name = (String)qualifier_names.elementAt (i);
+ if (!invisible_qualifiers.contains (qualifier_name))
+ addItem (qualifier_name);
+ }
+
+ if (default_qualifier == null)
+ {
+ if (indexOf ("note") != -1)
+ setSelectedQualifierByName ("note");
+ else
+ {
+ if (indexOf ("locus_tag") != -1)
+ setSelectedQualifierByName ("locus_tag");
+ else if( this.getModel().getSize() > 0)
+ setSelectedIndex (0);
+ }
+ }
+ else
+ setSelectedQualifierByName (default_qualifier);
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/QualifierEditor.java b/uk/ac/sanger/artemis/components/QualifierEditor.java
new file mode 100644
index 0000000..3a87681
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/QualifierEditor.java
@@ -0,0 +1,361 @@
+/* QualifierEditor.java
+ *
+ * created: Tue Oct 23 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/QualifierEditor.java,v 1.5 2007-07-09 13:07:38 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierInfo;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.QualifierParseException;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+
+import javax.swing.*;
+
+/**
+ * This component allows qualifiers to be added to or replaced in several
+ * features at once.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: QualifierEditor.java,v 1.5 2007-07-09 13:07:38 tjc Exp $
+ **/
+
+public class QualifierEditor extends JFrame {
+ /**
+ * Create a new QualifierEditor for the given features.
+ **/
+ public QualifierEditor (final FeatureVector features,
+ final EntryGroup entry_group) {
+ super (getFrameTitle (features));
+
+ this.features = features;
+ this.entry_group = entry_group;
+
+ final Font font = Options.getOptions ().getFont ();
+
+ final Feature first_feature = features.elementAt (0);
+
+ final EntryInformation entry_information =
+ first_feature.getEntry ().getEntryInformation ();
+
+ setFont (font);
+
+ boolean isGFF = false;
+ if(entry_group.getDefaultEntry().getEMBLEntry() instanceof GFFDocumentEntry)
+ isGFF = true;
+ final QualifierChoice qualifier_choice =
+ new QualifierChoice (entry_information, first_feature.getKey (), null, isGFF);
+
+ final JPanel outer_qualifier_choice_panel = new JPanel ();
+ final JPanel qualifier_choice_panel = new JPanel ();
+ outer_qualifier_choice_panel.setLayout (new BorderLayout ());
+
+ outer_qualifier_choice_panel.add (qualifier_choice_panel, "West");
+
+ final JButton qualifier_button = new JButton ("Insert qualifier:");
+
+ qualifier_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ final String qualifier_name =
+ (String) qualifier_choice.getSelectedItem ();
+ final QualifierInfo qualifier_info =
+ entry_information.getQualifierInfo (qualifier_name);
+
+ if (qualifier_info == null) {
+ new MessageDialog (QualifierEditor.this, "internal error: no " +
+ "qualifier info for " + qualifier_name);
+ } else {
+ qualifier_text_area.append ("/" + qualifier_name);
+
+ switch (qualifier_info.getType ()) {
+ case QualifierInfo.QUOTED_TEXT:
+ qualifier_text_area.append ("=\"\"");
+ break;
+
+ case QualifierInfo.NO_VALUE:
+ case QualifierInfo.OPTIONAL_QUOTED_TEXT:
+ break;
+
+ default:
+ qualifier_text_area.append ("=");
+ }
+
+ qualifier_text_area.append ("\n");
+ }
+ }
+
+ });
+
+ qualifier_choice_panel.add (qualifier_button);
+
+ qualifier_choice_panel.add (qualifier_choice);
+
+ getContentPane ().add (outer_qualifier_choice_panel, "North");
+
+ qualifier_text_area = new QualifierTextArea ();
+
+ add_button.setFont (getFont ());
+ replace_button.setFont (getFont ());
+ close_button.setFont (getFont ());
+
+ add_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ addOrReplace (false);
+ }
+ });
+
+ replace_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ addOrReplace (true);
+ }
+ });
+
+ close_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ dispose ();
+ }
+ });
+
+ button_panel.setFont (getFont ());
+
+ button_panel.add (replace_button);
+ button_panel.add (add_button);
+ button_panel.add (close_button);
+
+ getContentPane ().add (qualifier_text_area, "Center");
+ getContentPane ().add (button_panel, "South");
+
+ addWindowListener (new WindowAdapter () {
+ public void windowClosing (WindowEvent event) {
+ dispose ();
+ }
+ });
+
+ pack ();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+ }
+
+ /**
+ * Add to or replace the qualifiers in all the features that were passed to
+ * the constructor with the qualifiers from qualifier_text_area.
+ * @param replace If false any existing qualifiers of the same name in the
+ * features are left unchanged. If true existing qualifiers of the same
+ * name in the features will be deleted.
+ **/
+ private void addOrReplace (final boolean replace) {
+ try
+ {
+ entry_group.getActionController().startAction();
+
+ // this will contain one QualifierVector object for each Feature in the
+ // features vector (in the same order)
+ final Vector qualifier_vector_vector = new Vector();
+ final int features_size = features.size();
+
+ for(int i = 0; i<features_size; ++i)
+ {
+ final Feature this_feature = features.elementAt(i);
+
+ final Entry this_feature_entry = this_feature.getEntry();
+
+ if(this_feature_entry == null)
+ {
+ // feature has already been deleted
+ qualifier_vector_vector.addElement(null);
+ }
+ else
+ {
+ final EntryInformation entry_information =
+ this_feature_entry.getEntryInformation();
+
+ try
+ {
+ final QualifierVector qualifiers =
+ qualifier_text_area.getParsedQualifiers(entry_information);
+
+ qualifier_vector_vector.addElement(qualifiers);
+
+ }
+ catch(QualifierParseException e)
+ {
+ new MessageDialog(this,
+ "error while parsing: " + e.getMessage());
+ return;
+ }
+ }
+ }
+
+ if(qualifier_vector_vector.size() != features_size)
+ throw new Error("Internal error in QualifierEditor.add() - " +
+ "mismatched array sizes");
+
+ for(int feature_index = 0; feature_index < features_size;
+ ++feature_index)
+ {
+ final Feature this_feature = features.elementAt(feature_index);
+ this_feature.resetColour();
+
+ if(qualifier_vector_vector.elementAt(feature_index) == null)
+ continue;
+
+ final QualifierVector qualifier_vector =
+ (QualifierVector)qualifier_vector_vector.elementAt(feature_index);
+
+ final int qualifier_vector_size = qualifier_vector.size();
+
+ for(int qualifier_index = 0; qualifier_index < qualifier_vector_size;
+ ++qualifier_index)
+ {
+ final Qualifier this_qualifier =
+ (Qualifier)qualifier_vector.elementAt(qualifier_index);
+
+ if(replace)
+ {
+ try
+ {
+ this_feature.setQualifier(this_qualifier);
+ }
+ catch(EntryInformationException e)
+ {
+ new MessageDialog(this,
+ "failed to add qualifiers to: " +
+ this_feature.getIDString() + ": " +
+ e.getMessage());
+ }
+ catch(ReadOnlyException e)
+ {
+ new MessageDialog(this,
+ "failed to add qualifiers to read-only " +
+ "feature: " + this_feature.getIDString() +
+ ": " + e.getMessage());
+ }
+ }
+ else
+ {
+ try
+ {
+ this_feature.addQualifierValues(this_qualifier);
+ }
+ catch(EntryInformationException e)
+ {
+ new MessageDialog(this,
+ "failed to add qualifiers to: " +
+ this_feature.getIDString() + ": " +
+ e.getMessage());
+ }
+ catch(ReadOnlyException e)
+ {
+ new MessageDialog(this,
+ "failed to add qualifiers to read-only " +
+ "feature: " + this_feature.getIDString() +
+ ": " + e.getMessage());
+ }
+
+ }
+ }
+ }
+ }
+ finally
+ {
+ entry_group.getActionController().endAction();
+ }
+ }
+
+ /**
+ * Return an appropriate String to use for the title of this JFrame.
+ **/
+ static private String getFrameTitle (final FeatureVector features) {
+ boolean etc_flag = false;
+
+ final StringBuffer buffer = new StringBuffer ();
+
+ final int MAX_LENGTH = 80;
+
+ for (int i = 0 ; i < features.size () ; ++i) {
+ final Feature this_feature = features.elementAt (i);
+
+ final String feature_name = this_feature.getIDString ();
+
+ if (feature_name == null) {
+ etc_flag = true;
+ continue;
+ }
+
+ if (buffer.length () + feature_name.length () < MAX_LENGTH) {
+ if (buffer.length () == 0) {
+ buffer.append ("Add or replace qualifiers of: ");
+ buffer.append (feature_name);
+ } else {
+ buffer.append (", ").append (feature_name);
+ }
+ } else {
+ etc_flag = true;
+ break;
+ }
+ }
+
+ if (buffer.length () == 0) {
+ buffer.append ("Add or replace qualifiers");
+ } else {
+ if (etc_flag) {
+ buffer.append (", ...");
+ }
+ }
+
+ return buffer.toString ();
+ }
+
+ private QualifierTextArea qualifier_text_area;
+
+ private JButton add_button = new JButton ("Add");
+ private JButton replace_button = new JButton ("Replace");
+ private JButton close_button = new JButton ("Close");
+
+ private FlowLayout flow_layout =
+ new FlowLayout (FlowLayout.CENTER, 25, 5);
+
+ private JPanel button_panel = new JPanel (flow_layout);
+
+ /**
+ * The Feature objects that were passed to the constructor.
+ **/
+ private FeatureVector features = new FeatureVector ();
+
+ /**
+ * The EntryGroup that contains the Features (passed to the constructor).
+ **/
+ private EntryGroup entry_group;
+}
diff --git a/uk/ac/sanger/artemis/components/QualifierTextArea.java b/uk/ac/sanger/artemis/components/QualifierTextArea.java
new file mode 100644
index 0000000..bf89465
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/QualifierTextArea.java
@@ -0,0 +1,457 @@
+/* QualifierTextArea.java
+ *
+ * created: Tue Oct 23 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/QualifierTextArea.java,v 1.17 2008-08-04 08:33:45 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.editor.BrowserControl;
+import uk.ac.sanger.artemis.editor.DataCollectionPane;
+import uk.ac.sanger.artemis.io.QualifierParseException;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.EmblStreamFeature;
+import uk.ac.sanger.artemis.util.StringVector;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Point;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Vector;
+
+import javax.swing.JComponent;
+import javax.swing.JTextPane;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Style;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+import javax.swing.text.StyledDocument;
+
+/**
+ * This component is a JTextPane that understands qualifiers.
+ * It provides hyperlinks to databases using a StyledDocument
+ * (rather than using HyperlinkListener) so it can remain editable.
+ **/
+public class QualifierTextArea extends JTextPane
+ implements MouseMotionListener
+{
+ private static final long serialVersionUID = 1L;
+ private static Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ private static Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+ private static Cursor chand = new Cursor(Cursor.HAND_CURSOR);
+ private static Style DEFAULT_STYLE =
+ StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
+ private static StringVector dbsLinks;
+ public static Vector<String> DATABASES = new Vector<String>();
+
+ private boolean useHyperlinks = true;
+
+ /**
+ * Create a new QualifierTextArea containing no text.
+ **/
+ public QualifierTextArea ()
+ {
+ super();
+ initStyles();
+ int nrows = (Options.getOptions ().getPropertyTruthValue ("alicat_mode") ||
+ Options.getOptions ().getPropertyTruthValue ("val_mode") ?
+ 40 :
+ 18);
+ int ncolumns = 81;
+
+ if(dbsLinks == null)
+ {
+ dbsLinks = Options.getOptions().getOptionValues("hyperlinks");
+ for(int i=0; i<dbsLinks.size(); i+=2)
+ {
+ String dbs[] = ((String)dbsLinks.get(i)).split("\\+");
+ for(int j=0; j<dbs.length; j++)
+ DATABASES.add(dbs[j]);
+ }
+ }
+
+ setPreferredSize(new Dimension( ncolumns*getColumnWidth(), nrows*getRowHeight() ));
+ setBackground (Color.white);
+
+ try // no such method in java1.3
+ {
+ setDragEnabled(true);
+ }
+ catch(java.lang.NoSuchMethodError err){}
+
+ addMouseListener(new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ if(e.getClickCount() == 1)
+ handleMouseSingleClick(getHyperlinkTextAtMouseEvent(e),
+ QualifierTextArea.this);
+ }
+ });
+
+ super.addMouseMotionListener(this);
+ }
+
+
+ public void append(final String s)
+ {
+ StyledDocument doc = super.getStyledDocument();
+ try
+ {
+ doc.insertString(doc.getLength(), s, getLogicalStyle());
+
+ for(String db: DATABASES)
+ setStyleForHyperLinks(s, db);
+ }
+ catch(BadLocationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Add hyperlink style
+ */
+ private void initStyles()
+ {
+ // Makes text Blue
+ Style style = addStyle("Blue", null);
+ StyleConstants.setForeground(style, Color.blue);
+
+ // Inherits from "Blue"; makes text red and underlined
+ style = addStyle("Blue Underline", style);
+ StyleConstants.setUnderline(style, true);
+ }
+
+ /**
+ * Override to ensure hyperlinks are set
+ */
+ public void setText(String text)
+ {
+ super.setText(text);
+ // ensure we have the default style set
+ getStyledDocument().setCharacterAttributes(0, text.length(),
+ DEFAULT_STYLE, true);
+ for(String db: DATABASES)
+ setStyleForHyperLinks(text, db);
+ }
+
+ /**
+ * Parse and return the qualifiers in this TextArea in a QualifierVector.
+ **/
+ public QualifierVector
+ getParsedQualifiers (final EntryInformation entry_information)
+ throws QualifierParseException
+ {
+ final String qualifier_string = getText ();
+ return getQualifiersFromString (qualifier_string,
+ entry_information);
+ }
+
+ /**
+ * Return a QualifierVector containing the qualifiers from a String.
+ * @param qual_string contains the qualifiers to parse
+ */
+ private static QualifierVector
+ getQualifiersFromString (final String qual_string,
+ final EntryInformation entry_information)
+ throws QualifierParseException
+ {
+
+ try
+ {
+ final StringReader string_reader = new StringReader (qual_string);
+ final QualifierVector embl_qualifiers =
+ EmblStreamFeature.readQualifiers (string_reader,
+ entry_information);
+
+ string_reader.close();
+ return embl_qualifiers;
+ }
+ catch (IOException exception)
+ {
+ throw (new QualifierParseException (exception.getMessage ()));
+ }
+ }
+
+ /**
+ * Analogous to JTextArea column
+ * @return
+ */
+ private int getColumnWidth()
+ {
+ FontMetrics metrics = getFontMetrics(getFont());
+ return metrics.charWidth('m');
+ }
+
+ /**
+ * Analogous to JTextArea row
+ * @return
+ */
+ private int getRowHeight()
+ {
+ FontMetrics metrics = getFontMetrics(getFont());
+ return metrics.getHeight();
+ }
+
+ /**
+ * This routine sets the hyerlink style for a given database
+ * @param s
+ * @param db
+ */
+ private void setStyleForHyperLinks(final String s,
+ final String db)
+ {
+ if(!isUseHyperlinks())
+ return;
+
+ int ind = 0;
+ while((ind = indexOfIgnoreCase(s, db+":", ind)) > -1)
+ {
+ if(ind == 0 || s.charAt(ind-1) != '%')
+ {
+ int ind2 = getEndOfLink(s,ind);
+ int ind3 = s.indexOf('/', ind);
+
+ if(ind3 < 0 || ind3 > ind2)
+ getStyledDocument().setCharacterAttributes(ind, ind2-ind,
+ getStyle("Blue Underline"), true);
+ }
+
+ ind = ind+1;
+ }
+ }
+
+ public static int getEndOfLink(String s, int ind)
+ {
+ final char endOfLinkChar[] = {
+ ' ',
+ ';',
+ ')',
+ ']',
+ ';',
+ ',',
+ '\"',
+ '|',
+ '\n' };
+
+
+ int ind2 = s.indexOf(endOfLinkChar[0],ind);
+ int ind3;
+
+ for(int i=1; i<endOfLinkChar.length; i++)
+ {
+ ind3 = s.indexOf(endOfLinkChar[i],ind);
+ if(ind3>-1 && (ind3<ind2 || ind2 == -1))
+ ind2 = ind3;
+ }
+
+ return ind2;
+ }
+
+ private int getStartOfLink(final String s)
+ {
+ int lastIndexLink = -1;
+ for(String db: DATABASES)
+ {
+ int index = lastIndexOfIgnoreCase(s, db+":");
+ if(index > lastIndexLink)
+ lastIndexLink = index;
+ }
+ return lastIndexLink;
+ }
+
+
+ /**
+ * Process double click event.
+ * @param hyperlinkText
+ * @param c
+ */
+ public static void handleMouseSingleClick(
+ final String hyperlinkText,
+ final JComponent c)
+ {
+ if(hyperlinkText == null)
+ return;
+
+ for(int i=0; i<dbsLinks.size(); i+=2)
+ {
+ String names[] = ((String)dbsLinks.get(i)).split("\\+");
+
+ for(int j=0; j<names.length; j++)
+ {
+ if(indexOfIgnoreCase(hyperlinkText, names[j], 0) > -1)
+ {
+ String id[] = hyperlinkText.split(":");
+ if(id.length < 2)
+ return;
+
+ String link = (String)dbsLinks.get(i+1);
+
+ if(link.equals("srs_url"))
+ sendToBrowser( getSrsLink(hyperlinkText), c );
+ else
+ sendToBrowser(link + id[1], c);
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Get SRS hyperlink
+ * @param hyperlinkText
+ * @return
+ */
+ private static String getSrsLink(String hyperlinkText)
+ {
+ String cmd = DataCollectionPane.getSrsSite() +
+ "/wgetz?-e+[" + hyperlinkText + "]";
+
+ int ind = cmd.indexOf("UniProt:");
+ // link to uniprot accession
+ if(ind > -1)
+ cmd = cmd.substring(0, ind + 7) + "-acc:"
+ + cmd.substring(ind + 8);
+
+ ind = cmd.indexOf("UniProtKB:");
+ // link to uniprotkb accession
+ if(ind > -1)
+ cmd = cmd.substring(0, ind + 7) + "-acc:"
+ + cmd.substring(ind + 10);
+
+ if(cmd.indexOf("ebi.ac.uk") > -1)
+ cmd = cmd + "+-vn+2";
+ return cmd;
+ }
+
+ /**
+ * Send hyperlink to browser
+ * @param cmd
+ */
+ private static void sendToBrowser(final String cmd, final JComponent c)
+ {
+ SwingWorker browserLaunch = new SwingWorker()
+ {
+ public Object construct()
+ {
+ c.setCursor(cbusy);
+ BrowserControl.displayURL(cmd);
+ c.setCursor(cdone);
+ return null;
+ }
+ };
+ browserLaunch.start();
+ }
+
+
+ private int lastIndexOfIgnoreCase(String a, String b)
+ {
+ return a.toLowerCase().lastIndexOf(b.toLowerCase());
+ }
+
+ private static int indexOfIgnoreCase(String a, String b, int fromPos)
+ {
+ return a.toLowerCase().indexOf(b.toLowerCase(), fromPos);
+ }
+
+ public void mouseDragged(MouseEvent e){}
+ public void mouseMoved(MouseEvent e)
+ {
+ String hyperlinkText = getHyperlinkTextAtMouseEvent(e);
+ if(hyperlinkText == null)
+ setCursor(cdone);
+ else
+ setCursor(chand);
+ }
+
+ /**
+ * Get hyperlink text from a MouseEvent position. Return null if
+ * no hyperlink found.
+ * @param e
+ * @return
+ */
+ private String getHyperlinkTextAtMouseEvent(final MouseEvent e)
+ {
+ final Point pt = new Point(e.getX(), e.getY());
+ final int pos = viewToModel(pt);
+
+ try
+ {
+ final int viewPos = modelToView(pos).x;
+ if(Math.abs(viewPos-e.getX())>5)
+ return null;
+ }
+ catch(BadLocationException e2){}
+
+
+ final int start;
+ if(pos < 15)
+ start = 0;
+ else
+ start = pos - 15;
+
+ int length = 30;
+ if( (start+30) > getStyledDocument().getLength() )
+ length = getStyledDocument().getLength()-start;
+
+ try
+ {
+ String textAtPosition = getStyledDocument().getText(start, length);
+ int indEnd = getEndOfLink(textAtPosition, (pos-start));
+ if(indEnd < 0)
+ return null;
+
+ textAtPosition = textAtPosition.substring(0, indEnd);
+
+ int indStart = getStartOfLink(textAtPosition);
+ if( indStart < 0 ||
+ (indStart > 0 && textAtPosition.charAt(indStart-1) == '%') )
+ return null;
+ return textAtPosition.substring(indStart);
+ }
+ catch(BadLocationException e1)
+ {
+ //e1.printStackTrace();
+ }
+ return null;
+ }
+
+ private boolean isUseHyperlinks()
+ {
+ return useHyperlinks;
+ }
+
+
+ public void setUseHyperlinks(boolean useHyperlinks)
+ {
+ this.useHyperlinks = useHyperlinks;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/RunBlastAtNCBI.java b/uk/ac/sanger/artemis/components/RunBlastAtNCBI.java
new file mode 100644
index 0000000..2ae2b6a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/RunBlastAtNCBI.java
@@ -0,0 +1,261 @@
+/* RunBlastAtNCBI
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+package uk.ac.sanger.artemis.components;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.editor.BrowserControl;
+
+/**
+ * Used to POST data to the NCBI URLAPI (qBLAST) web services.
+ */
+class RunBlastAtNCBI extends Thread
+{
+ private String data;
+
+ public RunBlastAtNCBI(final String data)
+ {
+ this.data = data;
+ }
+
+ public void run()
+ {
+ try
+ {
+ // Send data
+ URL url = new URL("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi");
+ URLConnection conn = url.openConnection();
+ conn.setDoOutput(true);
+ OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
+ wr.write(data);
+ wr.flush();
+
+ // Get the response
+ BufferedReader rd = new BufferedReader(new InputStreamReader(conn
+ .getInputStream()));
+ String urlResults = url + "?RID=";
+ String line;
+ String eta = "10";
+ while ((line = rd.readLine()) != null)
+ {
+ int index;
+ if ((index = line.indexOf("RID =")) > -1)
+ {
+ line = line.substring(index + 5).trim();
+ urlResults = urlResults.concat(line);
+ if (line.equals(""))
+ {
+ JOptionPane.showMessageDialog(null, line, "Blast search error",
+ JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ }
+ else if ((index = line.indexOf("RTOE =")) > -1)
+ eta = line.substring(index + 6).trim();
+ }
+ wr.close();
+ rd.close();
+
+ int waitTime = Integer.parseInt(eta);
+ ProgressBarFrame progress = new ProgressBarFrame(waitTime, "BLAST");
+ Thread.sleep(waitTime * 1000);
+ BrowserControl.displayURL(urlResults + "&CMD=Get&OLD_BLAST=false");
+
+ progress.dispose();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Utility for appending key - value pairs to the data used to POST
+ * @param data
+ * @param key
+ * @param value
+ * @return
+ * @throws UnsupportedEncodingException
+ */
+ private static String addData(String data, String key, String value)
+ throws UnsupportedEncodingException
+ {
+ data += "&" +URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(value, "UTF-8");
+ return data;
+ }
+
+ /**
+ * Allow the user to define the blast options.
+ * @param programName
+ * @param residues
+ * @return
+ */
+ public static String setData(String programName, String residues)
+ {
+ GridBagLayout layout = new GridBagLayout();
+ GridBagConstraints c = new GridBagConstraints();
+ JPanel dataPanel = new JPanel(layout);
+ int rows = 0;
+
+ c.gridx = 0;
+ c.gridy = rows;
+ c.anchor = GridBagConstraints.EAST;
+ JTextField textField = new JTextField("nr",14);
+ dataPanel.add(new JLabel("NCBI Database"), c);
+ c.gridx = 1;
+ dataPanel.add(textField, c);
+
+ c.gridx = 0;
+ c.gridy = ++rows;
+ JTextField hitField = new JTextField("500",14);
+ dataPanel.add(new JLabel("Number of hits to keep"), c);
+ c.gridx = 1;
+ dataPanel.add(hitField, c);
+
+ // Gap open and gap close costs
+ // Space separated float values
+ // ''5 2'' for nuc-nuc, ''11 1'' for proteins, non-affine for megablast
+ c.gridx = 0;
+ c.gridy = ++rows;
+ JTextField gapOpenField = new JTextField((programName.equals("blastn")) ? "5": "11",14);
+ dataPanel.add(new JLabel("Gap open cost"), c);
+ c.gridx = 1;
+ dataPanel.add(gapOpenField, c);
+
+ c.gridx = 0;
+ c.gridy = ++rows;
+ JTextField gapCloseField = new JTextField((programName.equals("blastn")) ? "2": "1",14);
+ dataPanel.add(new JLabel("Gap close cost"), c);
+ c.gridx = 1;
+ dataPanel.add(gapCloseField, c);
+
+ c.gridx = 0;
+ c.gridy = ++rows;
+ JTextField expectField = new JTextField("10.0",14);
+ dataPanel.add(new JLabel("Expect value"), c);
+ c.gridx = 1;
+ dataPanel.add(expectField, c);
+
+ c.gridx = 0;
+ c.gridy = ++rows;
+ JComboBox filterField = new JComboBox(new String[]{
+ "None", "Low Complexity", "Human Repeats", "Masked" });
+ dataPanel.add(new JLabel("Filter"), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ dataPanel.add(filterField, c);
+
+
+ c.gridx = 0;
+ c.gridy = ++rows;
+
+ String serviceOptions[];
+ if(programName.equals("blastn"))
+ serviceOptions = new String[]{"plain", "megablast"};
+ else
+ serviceOptions = new String[]{"plain"}; // psi blast support?
+
+ JComboBox serviceField = new JComboBox(serviceOptions);
+ c.anchor = GridBagConstraints.EAST;
+ dataPanel.add(new JLabel("Blast service"), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ dataPanel.add(serviceField, c);
+
+ int status = JOptionPane.showConfirmDialog(null, dataPanel,
+ "Options for "+programName, JOptionPane.OK_CANCEL_OPTION);
+
+ if(status != JOptionPane.OK_OPTION)
+ return null;
+
+ try
+ {
+ String data = URLEncoder.encode("CMD", "UTF-8") + "=" +
+ URLEncoder.encode("Put", "UTF-8");
+ data = addData(data, "QUERY", residues);
+ data = addData(data, "DATABASE", textField.getText());
+ data = addData(data, "HITLIST_SIZE", hitField.getText());
+ if(getFilterOption(filterField) != null)
+ data = addData(data, "FILTER", getFilterOption(filterField));
+ data = addData(data, "EXPECT", expectField.getText());
+ data = addData(data, "FORMAT_TYPE", "HTML");
+ data = addData(data, "PROGRAM", programName);
+ data = addData(data, "CLIENT", "web");
+ data = addData(data, "SERVICE", (String) serviceField.getSelectedItem());
+
+ if(((String) serviceField.getSelectedItem()).equals("megablast"))
+ data = addData(data, "MEGABLAST", "yes");
+ else
+ data = addData(data, "GAPCOSTS", gapOpenField.getText()+" "+gapCloseField.getText());
+
+ return data;
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Blast filter options.
+ * @param filterField
+ * @return
+ */
+ private static String getFilterOption(JComboBox filterField)
+ {
+ String sel = (String) filterField.getSelectedItem();
+ if(sel.equals("None"))
+ return null;
+ else if(sel.equals("Low Complexity"))
+ return "L";
+ else if(sel.equals("Human Repeats"))
+ return "R";
+ else
+ return "m";
+ }
+
+ public static void main(String args[])
+ {
+ String data =
+ //BlastAtNCBI.setData("blastp", "ATHIEDLHNITSNQLYETYRTEKLSTSQLLLDSTVXTIDKNLSQHDQVLREDRLR");
+ RunBlastAtNCBI.setData("blastn", "aggctgttttccacagatttcacagtattggttcaaatggtcaaaaattgttttaaccagt");
+ RunBlastAtNCBI blast= new RunBlastAtNCBI(data);
+ blast.start();
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/RunMenu.java b/uk/ac/sanger/artemis/components/RunMenu.java
new file mode 100644
index 0000000..9d59213
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/RunMenu.java
@@ -0,0 +1,391 @@
+/* RunMenu.java
+ *
+ * created: Fri Jan 22 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/RunMenu.java,v 1.13 2009-05-13 15:53:14 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.InvalidKeyException;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Hashtable;
+import java.awt.event.*;
+
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+
+
+/**
+ * A JMenu of external commands/functions.
+ *
+ * @author Kim Rutherford
+ * @version $Id: RunMenu.java,v 1.13 2009-05-13 15:53:14 tjc Exp $
+ **/
+
+public class RunMenu extends SelectionMenu
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+ private JMenu fastaMenu = null;
+ private JMenu fastaMenuOptions = null;
+ private Hashtable<String, JMenu> blastMenu = null;
+ private Hashtable<String, JMenu> blastMenuOptions = null;
+
+ /**
+ * Create a new RunMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param menu_name The name of the new menu.
+ **/
+ public RunMenu(final JFrame frame, final Selection selection,
+ final String menu_name)
+ {
+ super(frame, menu_name, selection);
+
+ addNCBISearches(selection);
+ addPfamSearches(selection);
+ if(Options.isUnixHost())
+ {
+ addSeparator();
+ final ExternalProgramVector external_programs = Options.getOptions()
+ .getExternalPrograms();
+
+ boolean sanger_options = Options.getOptions().getPropertyTruthValue(
+ "sanger_options");
+
+ final int external_programs_size = external_programs.size();
+ for(int i = 0; i < external_programs_size; ++i)
+ makeMenuItem(external_programs.elementAt(i), sanger_options);
+
+ addSeparator();
+
+ for(int i = 0; i < external_programs_size; ++i)
+ makeOptionsMenuItem(external_programs.elementAt(i));
+ }
+ }
+
+ /**
+ * Create a new RunMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ **/
+ public RunMenu(final JFrame frame,
+ final Selection selection)
+ {
+ this(frame, selection, "Run");
+ }
+
+ private void addPfamSearches(final Selection selection)
+ {
+ final JMenuItem ncbiSearchLinks = new JMenuItem("Pfam Search");
+ add(ncbiSearchLinks);
+ ncbiSearchLinks.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ final FeatureVector features = selection.getAllFeatures();
+
+ if(features.size() != 1)
+ {
+ JOptionPane.showMessageDialog(RunMenu.this,
+ "Selected a single feature to send to Pfam for searching.",
+ "Pfam Search", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ final String residues = features.elementAt(0).getTranslation().toString().toUpperCase();
+
+ RunPfamSearchThread pfamSearch = new RunPfamSearchThread(residues);
+ pfamSearch.start();
+ }
+ });
+
+
+ final JMenuItem rfam = new JMenuItem("Rfam Search");
+ add(rfam);
+ rfam.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ final FeatureVector features = selection.getAllFeatures();
+
+ if(features.size() != 1)
+ {
+ JOptionPane.showMessageDialog(RunMenu.this,
+ "Selected a single feature to send to Rfam for searching.",
+ "Rfam Search", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ final String residues = features.elementAt(0).getTranslationBases();
+
+ RunPfamSearchThread pfamSearch = new RunPfamSearchThread(
+ residues, RunPfamSearchThread.rfamUrl);
+ pfamSearch.start();
+ }
+ });
+ }
+
+ /**
+ * Add menu for NCBI web searches
+ * @param selection
+ */
+ private void addNCBISearches(final Selection selection)
+ {
+ final JMenu ncbiSearchLinks = new JMenu("NCBI Searches");
+ add(ncbiSearchLinks);
+
+ final ExternalProgramVector ncbi_protein =
+ Options.getOptions().getNCBIPrograms();
+
+ for(int i = 0; i < ncbi_protein.size(); ++i)
+ {
+ final ExternalProgram program = (ExternalProgram)ncbi_protein.elementAt(i);
+ final String programName = program.getName();
+
+ final JMenuItem programMenu = new JMenuItem(programName);
+ ncbiSearchLinks.add(programMenu);
+ programMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ final FeatureVector features = selection.getAllFeatures();
+
+ if(features.size() != 1)
+ {
+ JOptionPane.showMessageDialog(RunMenu.this,
+ "Selected a single feature to send to NCBI for searching.",
+ "NCBI Search", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+
+ final StringWriter writer = new StringWriter();
+ try
+ {
+ if(program.getType() == ExternalProgram.AA_PROGRAM)
+ features.elementAt(0).writeAminoAcidsOfFeature(writer);
+ else
+ features.elementAt(0).writeBasesOfFeature(writer);
+
+ writer.close();
+ final String data = RunBlastAtNCBI.setData(programName, writer.toString());
+ if(data != null)
+ {
+ RunBlastAtNCBI blastSearch = new RunBlastAtNCBI(data);
+ blastSearch.start();
+ }
+ }
+ catch(IOException ioe){}
+ //BrowserControl.displayURL(program.getProgramOptions()+residues);
+ }
+ });
+ }
+ }
+
+ /**
+ * Make a new menu item for running the given ExternalProgram object.
+ * @param program Create two menu items for this program.
+ **/
+ private void makeMenuItem(final ExternalProgram program,
+ final boolean sanger_options)
+ {
+ final JMenuItem new_menu;
+ final String program_name = program.getName();
+
+ if(program.getType() == ExternalProgram.AA_PROGRAM ||
+ program.getType() == ExternalProgram.DNA_PROGRAM &&
+ sanger_options)
+ {
+ final String options_string = program.getProgramOptions();
+
+ if(options_string.length() > 0)
+ {
+ if(program_name.startsWith("fasta") ||
+ program_name.indexOf("blast")>-1)
+ new_menu = new JMenuItem(options_string);
+ else
+ new_menu = new JMenuItem("Run " + program_name + " (" +
+ options_string + ") on selected features");
+ }
+ else
+ new_menu =
+ new JMenuItem("Run " + program_name + " on selected features");
+ }
+ else
+ new_menu =
+ new JMenuItem("Run " + program_name + " on selected features");
+
+ new_menu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(!checkForSelectionFeatures())
+ return;
+
+ final FeatureVector selection_features =
+ getSelection().getAllFeatures();
+
+ try
+ {
+ final ExternalProgramMonitor monitor =
+ program.run(selection_features, Splash.getLogger());
+
+ if(monitor == null)
+ return;
+
+ monitor.addExternalProgramListener(new ExternalProgramListener()
+ {
+ public void statusChanged(final ExternalProgramEvent e)
+ {
+ if(e.getType() == ExternalProgramEvent.FINISHED)
+ new MessageFrame(e.getMessage()).setVisible(true);
+ }
+ });
+ new Thread(monitor).start();
+ }
+ catch(InvalidKeyException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "execution failed: " + e.getMessage());
+ }
+ catch(EntryInformationException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "execution of " + program_name +
+ " failed because: " + e.getMessage());
+ }
+ catch(ReadOnlyException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "execution of " + program_name +
+ " failed because one of the features is " +
+ "read only");
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "execution of " + program_name +
+ " failed because of an I/O error: " +
+ e);
+ }
+ catch(ExternalProgramException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "execution of " + program_name +
+ " failed: " + e.getMessage());
+ }
+ }
+ });
+
+ if(program_name.startsWith("fasta"))
+ {
+ if(fastaMenu == null)
+ {
+ fastaMenu = new JMenu("Run fasta on selected features against");
+ add(fastaMenu);
+ }
+ fastaMenu.add(new_menu);
+ }
+ else if(program.getName().indexOf("blast")>-1)
+ {
+ if(blastMenu == null)
+ blastMenu = new Hashtable<String, JMenu>();
+
+ if(!blastMenu.containsKey(program.getName()))
+ {
+ JMenu topMenu = new JMenu("Run "+program.getName()+
+ " on selected features against");
+ blastMenu.put(program.getName(), topMenu);
+ add(topMenu);
+ }
+
+ JMenu topMenu = blastMenu.get(program.getName());
+ topMenu.add(new_menu);
+ }
+ else
+ add(new_menu);
+ }
+
+ /**
+ * Make a new options menu item for the given ExternalProgram object.
+ * @param program Create two menu items for this program.
+ **/
+ private void makeOptionsMenuItem(final ExternalProgram program)
+ {
+ if(!(program.getType() == ExternalProgram.AA_PROGRAM ||
+ program.getType() == ExternalProgram.DNA_PROGRAM))
+ return;
+
+ final JMenuItem new_options_menu;
+ final String program_name = program.getName();
+
+ if(program_name.startsWith("fasta"))
+ {
+ if(fastaMenuOptions == null)
+ {
+ fastaMenuOptions = new JMenu("Set " + program_name + " options");
+ add(fastaMenuOptions);
+ }
+ new_options_menu = new JMenuItem(program.getProgramOptions());
+ fastaMenuOptions.add(new_options_menu);
+ }
+ else if(program_name.indexOf("blast")>-1)
+ {
+ if(blastMenuOptions == null)
+ blastMenuOptions = new Hashtable<String, JMenu>();
+
+ String menuStr = "Set " + program_name + " options";
+ if(!blastMenuOptions.containsKey(menuStr))
+ {
+ JMenu topMenu = new JMenu(menuStr);
+ blastMenuOptions.put(menuStr, topMenu);
+ add(topMenu);
+ }
+
+ JMenu topMenu = blastMenuOptions.get(menuStr);
+ new_options_menu = new JMenuItem(program.getProgramOptions());
+ topMenu.add(new_options_menu);
+ }
+ else
+ {
+ new_options_menu = new JMenuItem("Set " + program_name + " options");
+ add(new_options_menu);
+ }
+
+ new_options_menu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ new ExternalProgramOptions(program);
+ }
+ });
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/RunPfamSearchThread.java b/uk/ac/sanger/artemis/components/RunPfamSearchThread.java
new file mode 100644
index 0000000..9dd070d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/RunPfamSearchThread.java
@@ -0,0 +1,143 @@
+/* RunPfamSearch.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+
+import javax.swing.JOptionPane;
+
+import uk.ac.sanger.artemis.editor.BrowserControl;
+
+/**
+ * Pfam and Rfam sequence search
+ */
+public class RunPfamSearchThread extends Thread
+{
+ protected static String pfamUrl = "http://pfam.sanger.ac.uk/search/sequence";
+ protected static String rfamUrl = "http://rfam.sanger.ac.uk/search/sequence";
+ private String searchURL = pfamUrl;
+
+ private String residues;
+
+ public RunPfamSearchThread(final String residues)
+ {
+ this.residues = residues;
+ }
+
+ public RunPfamSearchThread(final String residues, final String searchURL)
+ {
+ this.residues = residues;
+ this.searchURL = searchURL;
+ }
+
+ public void run()
+ {
+ boolean isPfam = searchURL.equals(pfamUrl);
+
+ try
+ {
+ // Construct data
+ String data = URLEncoder.encode("seq", "UTF-8") + "="
+ + URLEncoder.encode(residues, "UTF-8");
+ data += "&" + URLEncoder.encode("output", "UTF-8") + "="
+ + URLEncoder.encode("xml", "UTF-8");
+
+ // Send data
+ URL url = new URL(searchURL);
+
+ URLConnection conn = url.openConnection();
+ conn.setDoOutput(true);
+
+ if(!isPfam)
+ conn.addRequestProperty("Accept", "text/xml");
+
+ OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
+ wr.write(data);
+ wr.flush();
+
+ // Get the response
+ BufferedReader rd = new BufferedReader(
+ new InputStreamReader(conn.getInputStream()));
+ String urlResults = searchURL + (isPfam ? "/results?" : "/");
+
+ String line;
+ String eta = "5";
+ while ((line = rd.readLine()) != null)
+ {
+ int index;
+ if((index = line.indexOf("job_id=")) > -1)
+ {
+ line = line.substring(index+8, line.length()-2);
+ if(isPfam)
+ urlResults = urlResults.concat("jobId="+line);
+ else
+ urlResults = urlResults.concat(line);
+ }
+ else if((index = line.indexOf("<estimated_time>")) > -1)
+ {
+ eta = line.substring(index+16);
+ index = eta.indexOf("<");
+ if(index > -1)
+ eta = eta.substring(0, index);
+ }
+ else if((index = line.indexOf("<error>")) > -1)
+ {
+ line = line.substring(index+7);
+ index = line.indexOf("<");
+ if(index > -1)
+ line = line.substring(0, index);
+ JOptionPane.showMessageDialog(null,
+ line, "Pfam search error", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ }
+ wr.close();
+ rd.close();
+
+ int waitTime = Integer.parseInt(eta);
+ ProgressBarFrame progress = new ProgressBarFrame(waitTime, (isPfam ? "Pfam" : "Rfam"));
+ URL result = new URL(urlResults);
+ Thread.sleep(waitTime*1000);
+
+ int cnt = 0;
+ while(((HttpURLConnection) result.openConnection()).getResponseCode() == 204 &&
+ cnt < 500)
+ {
+ cnt++;
+ Thread.sleep(500);
+ }
+
+ BrowserControl.displayURL(urlResults);
+ progress.dispose();
+ }
+ catch (Exception e){ e.printStackTrace(); }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/ScoreChangeEvent.java b/uk/ac/sanger/artemis/components/ScoreChangeEvent.java
new file mode 100644
index 0000000..0d567b9
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ScoreChangeEvent.java
@@ -0,0 +1,58 @@
+/* ScoreChangeEvent.java
+ *
+ * created: Thu Oct 21 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ScoreChangeEvent.java,v 1.1 2004-06-09 09:47:22 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * The adjustment event emitted when a score changes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ScoreChangeEvent.java,v 1.1 2004-06-09 09:47:22 tjc Exp $
+ **/
+
+public class ScoreChangeEvent extends java.util.EventObject {
+ /**
+ * Create new ScoreChangeEvent.
+ **/
+ public ScoreChangeEvent (final Object source, final int value) {
+ super (source);
+ this.value = value;
+ }
+
+ /**
+ * Return the new score value that caused this event (as passed to the
+ * constructor).
+ **/
+ public int getValue () {
+ return value;
+ }
+
+
+ /**
+ * The new score value that caused this event (as passed to the
+ * constructor).
+ **/
+ final int value;
+}
diff --git a/uk/ac/sanger/artemis/components/ScoreChangeListener.java b/uk/ac/sanger/artemis/components/ScoreChangeListener.java
new file mode 100644
index 0000000..0cde0f0
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ScoreChangeListener.java
@@ -0,0 +1,40 @@
+/* ScoreAdjustmentListener.java
+ *
+ * created: Thu Oct 21 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ScoreChangeListener.java,v 1.1 2004-06-09 09:47:28 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * The listener interface for receiving score change events.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ScoreChangeListener.java,v 1.1 2004-06-09 09:47:28 tjc Exp $
+ **/
+
+public interface ScoreChangeListener {
+ /**
+ * Invoked when the value of the score has changed.
+ **/
+ void scoreChanged (final ScoreChangeEvent event);
+}
diff --git a/uk/ac/sanger/artemis/components/ScoreChanger.java b/uk/ac/sanger/artemis/components/ScoreChanger.java
new file mode 100644
index 0000000..1375dd3
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ScoreChanger.java
@@ -0,0 +1,222 @@
+/* ScoreChanger.java
+ *
+ * created: Thu Oct 21 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ScoreChanger.java,v 1.1 2004-06-09 09:47:29 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+/**
+ * This is a JFrame component that contains ScoreScrollbar components.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ScoreChanger.java,v 1.1 2004-06-09 09:47:29 tjc Exp $
+ **/
+
+public class ScoreChanger extends JFrame {
+ /**
+ * Create a new ScoreChanger.
+ * @param minimum_value The minimum allowable value for the scroll bars.
+ * @param maximum_value The maximum allowable value for the scroll bars.
+ **/
+ public ScoreChanger (final String name,
+ final ScoreChangeListener minimum_listener,
+ final ScoreChangeListener maximum_listener,
+ final int minimum_value, final int maximum_value)
+ throws IllegalArgumentException {
+
+ super (name);
+
+ this.minimum_listener = minimum_listener;
+ this.maximum_listener = maximum_listener;
+ this.minimum_value = minimum_value;
+ this.maximum_value = maximum_value;
+
+ getContentPane ().setLayout (new GridLayout (5, 1));
+
+ minimum_label = new JLabel ();
+
+ getContentPane ().add (minimum_label);
+
+ minimum_score_scrollbar =
+ new ScoreScrollbar (minimum_value, maximum_value);
+
+ final ScoreChangeListener this_minimum_listener =
+ new ScoreChangeListener () {
+ public void scoreChanged (ScoreChangeEvent event) {
+ minimum_label.setText ("Minimum Cutoff: " + event.getValue ());
+ }
+ };
+
+ minimum_score_scrollbar.addScoreChangeListener (this_minimum_listener);
+
+ getContentPane ().add (minimum_score_scrollbar);
+
+ maximum_label = new JLabel ();
+
+ getContentPane ().add (maximum_label);
+
+ maximum_score_scrollbar =
+ new ScoreScrollbar (minimum_value, maximum_value);
+
+ final ScoreChangeListener this_maximum_listener =
+ new ScoreChangeListener () {
+ public void scoreChanged (ScoreChangeEvent event) {
+ maximum_label.setText ("Maximum Cutoff: " + event.getValue ());
+ }
+ };
+
+ maximum_score_scrollbar.addScoreChangeListener (this_maximum_listener);
+
+ getContentPane ().add (maximum_score_scrollbar);
+
+ final FlowLayout flow_layout =
+ new FlowLayout (FlowLayout.CENTER, 18, 0);
+
+ final JPanel button_panel = new JPanel (flow_layout);
+
+ final JButton reset_button = new JButton ("Reset");
+
+ reset_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ reset ();
+ }
+ });
+
+ button_panel.add (reset_button);
+
+
+ final JButton close_button = new JButton ("Close");
+
+ close_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ dispose ();
+ }
+ });
+
+
+ button_panel.add (close_button);
+
+ getContentPane ().add (button_panel);
+
+ addWindowListener (new WindowAdapter () {
+ public void windowClosing (WindowEvent event) {
+ dispose ();
+ }
+ });
+
+ pack ();
+
+ setSize (270, getSize ().height);
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+
+ reset ();
+
+ minimum_score_scrollbar.addScoreChangeListener (minimum_listener);
+ maximum_score_scrollbar.addScoreChangeListener (maximum_listener);
+ }
+
+ /**
+ * Call reset (), clean up the listeners then call super.dispose ().
+ **/
+ public void dispose () {
+ reset ();
+
+ minimum_score_scrollbar.removeScoreChangeListener (minimum_listener);
+ maximum_score_scrollbar.removeScoreChangeListener (maximum_listener);
+
+ super.dispose ();
+ }
+
+ /**
+ * Reset the minimum_score_scrollbar to 0 and the maximum_score_scrollbar
+ * to 100.
+ **/
+ private void reset () {
+ minimum_score_scrollbar.setValue (minimum_value);
+ maximum_score_scrollbar.setValue (maximum_value);
+
+ minimum_label.setText ("Minimum Cutoff: " + minimum_value);
+ maximum_label.setText ("Maximum Cutoff: " + maximum_value);
+
+ final ScoreChangeEvent minimum_event =
+ new ScoreChangeEvent (this, minimum_value);
+
+ minimum_listener.scoreChanged (minimum_event);
+
+ final ScoreChangeEvent maximum_event =
+ new ScoreChangeEvent (this, maximum_value);
+
+ maximum_listener.scoreChanged (maximum_event);
+ }
+
+ /**
+ * The ScoreChangeListener for the minimum score that was passed to the
+ * constructor.
+ **/
+ final ScoreChangeListener minimum_listener;
+
+ /**
+ * The minimum score scrollbar the created by the constructor.
+ **/
+ final ScoreScrollbar minimum_score_scrollbar;
+
+ /**
+ * The ScoreChangeListener for the maximum score that was passed to the
+ * constructor.
+ **/
+ final ScoreChangeListener maximum_listener;
+
+ /**
+ * The maximum score scrollbar the created by the constructor.
+ **/
+ final ScoreScrollbar maximum_score_scrollbar;
+
+ /**
+ * The minimum allowable value for the scroll bars.
+ **/
+ final int minimum_value;
+
+ /**
+ * The maximum allowable value for the scroll bars.
+ **/
+ final int maximum_value;
+
+ /**
+ * A Label that shows something like this: "Minimum Cutoff: 0"
+ **/
+ final JLabel minimum_label;
+
+ /**
+ * A Label that shows something like this: "Maximum Cutoff: 100"
+ **/
+ final JLabel maximum_label;
+}
+
diff --git a/uk/ac/sanger/artemis/components/ScoreScrollbar.java b/uk/ac/sanger/artemis/components/ScoreScrollbar.java
new file mode 100644
index 0000000..d234bb8
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ScoreScrollbar.java
@@ -0,0 +1,120 @@
+/* ScoreScrollbar.java
+ *
+ * created: Thu Oct 21 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ScoreScrollbar.java,v 1.1 2004-06-09 09:47:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+/**
+ * This component is a Scrollbar that generates ScoreChange events when the
+ * value changes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ScoreScrollbar.java,v 1.1 2004-06-09 09:47:31 tjc Exp $
+ **/
+
+public class ScoreScrollbar extends JScrollBar
+ implements AdjustmentListener {
+ /**
+ * Constructs a new horizontal score scroll bar with an initial value of 0.
+ * @param minimum_value The minimum allowable value for the scroll bar.
+ * @param maximum_value The maximum allowable value for the scroll bar.
+ **/
+ public ScoreScrollbar (final int minimum_value, final int maximum_value) {
+ this (Scrollbar.HORIZONTAL, minimum_value, minimum_value, maximum_value);
+ }
+
+ /**
+ * Constructs a new score scroll bar with the specified orientation.
+ *
+ * The orientation argument must take one of the two values
+ * java.awt.Scrollbar.HORIZONTAL, or java.awt.Scrollbar.VERTICAL,
+ * indicating a horizontal or vertical scroll bar, respectively.
+ * @param orientation indicates the orientation of the scroll bar.
+ * @param value The initial value of the scrollbar.
+ * @param minimum_value The minimum allowable value for the scroll bar.
+ * @param maximum_value The maximum allowable value for the scroll bar.
+ * @exception IllegalArgumentException when an illegal value for the
+ * orientation argument is supplied or if the value parameter is less
+ * than minimum_value or greater than maximum_value.
+ **/
+ public ScoreScrollbar (final int orientation, final int value,
+ final int minimum_value, final int maximum_value)
+ throws IllegalArgumentException {
+ super (orientation,
+ (value < minimum_value || value > maximum_value ?
+ minimum_value :
+ value),
+ 1, minimum_value, maximum_value + 1);
+ }
+
+ /**
+ * Add the given ScoreChangeListener as a listener for ScoreChange events
+ * from this components.
+ **/
+ public void addScoreChangeListener (final ScoreChangeListener l) {
+ score_change_listeners.addElement (l);
+
+ if (score_change_listeners.size () == 1) {
+ addAdjustmentListener (this);
+ }
+ }
+
+ /**
+ * Remove the given ScoreChangeListener as a listener for ScoreChange
+ * events from this components.
+ **/
+ public void removeScoreChangeListener (final ScoreChangeListener l) {
+ score_change_listeners.addElement (l);
+
+ if (score_change_listeners.size () == 0) {
+ removeAdjustmentListener (this);
+ }
+ }
+
+ /**
+ * Implementation of the AdjustmentListener interface.
+ **/
+ public void adjustmentValueChanged (AdjustmentEvent e) {
+ for (int i = 0 ; i < score_change_listeners.size () ; ++i) {
+ final ScoreChangeEvent event =
+ new ScoreChangeEvent (this, getValue ());
+ final ScoreChangeListener this_listener =
+ (ScoreChangeListener)(score_change_listeners.elementAt (i));
+ this_listener.scoreChanged (event);
+ }
+ }
+
+ /**
+ * The ScoreChangeListener objects that have been added with
+ * addScoreChangeListener ().
+ **/
+ private final java.util.Vector score_change_listeners =
+ new java.util.Vector ();
+}
+
diff --git a/uk/ac/sanger/artemis/components/SearchResultViewer.java b/uk/ac/sanger/artemis/components/SearchResultViewer.java
new file mode 100644
index 0000000..1569c9a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/SearchResultViewer.java
@@ -0,0 +1,131 @@
+/* SearchResultViewer.java
+ *
+ * created: Thu Feb 24 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SearchResultViewer.java,v 1.5 2008-01-23 14:22:49 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.ExternalProgram;
+import uk.ac.sanger.artemis.ExternalProgramException;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.ZipFileDocument;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+
+import javax.swing.JButton;
+
+
+
+/**
+ * A component that displays the results of external searches, with the
+ * ability to send the results to a netscape process.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: SearchResultViewer.java,v 1.5 2008-01-23 14:22:49 tjc Exp $
+ **/
+
+public class SearchResultViewer extends FileViewer
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new SearchResultViewer component.
+ * @param title The name to attach to the new JFrame.
+ * @param file_name The file to read into the new viewer.
+ **/
+ public SearchResultViewer(final String label,
+ final Document document)
+ throws IOException
+ {
+ super (label, false, false, false);
+
+ try
+ {
+ readFile(document.getReader());
+ }
+ catch (IOException e)
+ {
+ System.out.println("error while reading results: " + e);
+ dispose();
+ throw e;
+ }
+ setVisible(true);
+ if(!Options.getOptions().getPropertyTruthValue("sanger_options"))
+ return;
+
+ final JButton to_browser = new JButton("Send to browser");
+ getButtonPanel().add(to_browser);
+
+ to_browser.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ String fileName = document.toString();
+ try
+ {
+ if(document instanceof ZipFileDocument)
+ fileName = ((ZipFileDocument)document).writeTmpFile(
+ SearchResultViewer.this.getText());
+
+ sendToBrowser(fileName);
+ }
+ catch (IOException e)
+ {
+ System.out.println ("error while reading results: " + e);
+ new MessageDialog(SearchResultViewer.this,
+ "Message",
+ "Send to browser failed: " + e);
+ }
+ catch(ExternalProgramException e)
+ {
+ System.out.println("error while reading results: " + e);
+ new MessageDialog(SearchResultViewer.this,
+ "Message",
+ "Send to browser failed: " + e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Mark up the contents of the given file (which should contain blast or
+ * fasta output) and send it to a web browser (with netscape -remote).
+ **/
+ protected static void sendToBrowser(final String file_name)
+ throws IOException, ExternalProgramException
+ {
+ final String[] arguments =
+ {
+ file_name
+ };
+
+ final Process process =
+ ExternalProgram.startProgram("results_to_netscape", arguments);
+
+ new ProcessWatcher(process, "results_to_netscape", false);
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/SegmentBorder.java b/uk/ac/sanger/artemis/components/SegmentBorder.java
new file mode 100644
index 0000000..072926d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/SegmentBorder.java
@@ -0,0 +1,113 @@
+/* SegmentBorder.java
+ *
+ * created: Fri Nov 19 2004
+ *
+ * This file is part of Artemis
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.*;
+
+/**
+ *
+ **/
+
+public class SegmentBorder
+{
+
+ private boolean highlight_feature;
+ private boolean highlight_segment;
+ private boolean draw_arrow;
+ private int x;
+ private int y;
+ private int width;
+ private int feature_direction;
+ public static Color HIGHLIGHT_BORDER_COLOUR = new Color(140,25,25);
+
+ /**
+ * Information stored in this object is used to draw the feature
+ * segment borders.
+ * @param highlight_feature true if the feature is highlighted
+ * @param highlight_segment true if the segment is highlighted
+ * @param draw_arrow true if an arrow is to be drawn on this segment
+ * @param x top right hand x position of the segment
+ * @param y top right hand y position of the segment
+ * @param width segment width
+ * @param feature_direction feature direction
+ *
+ */
+ public SegmentBorder(final boolean highlight_feature,
+ final boolean highlight_segment,
+ final boolean draw_arrow, int x, int y, int width,
+ int feature_direction)
+ {
+ this.highlight_feature = highlight_feature;
+ this.highlight_segment = highlight_segment;
+ this.draw_arrow = draw_arrow;
+
+ this.x = x;
+ this.y = y;
+ this.width = width;
+
+ this.feature_direction = feature_direction;
+ }
+
+
+ protected void drawSegmentBorder(Graphics g, int height, int arrowWidth)
+ {
+ final Graphics2D g2d = (Graphics2D)g;
+ if(highlight_feature) // highlight selected features
+ {
+ // selected - highlight by drawing a thicker line
+ final BasicStroke stroke = (BasicStroke)g2d.getStroke();
+
+ if(highlight_segment)
+ {
+ g2d.setColor(HIGHLIGHT_BORDER_COLOUR);
+ g2d.setStroke(new BasicStroke(4.f));
+ }
+ else
+ g2d.setStroke(new BasicStroke(3.f));
+
+ g2d.drawRect(x, y, width, height);
+ g2d.setColor(Color.black);
+ g2d.setStroke(stroke);
+ }
+ else
+ g.drawRect(x, y, width, height);
+
+ // draw the arrow point
+ if(draw_arrow)
+ {
+ int xpos = x;
+ int arrow_tip_x = x + feature_direction * arrowWidth;
+ if(feature_direction ==1)
+ {
+ xpos += width;
+ arrow_tip_x += width;
+ }
+
+ final int arrow_tip_y = y + (height/2);
+
+ g.drawLine(xpos, y, arrow_tip_x, arrow_tip_y);
+ g.drawLine(arrow_tip_x, arrow_tip_y, xpos, y+height);
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/SelectMenu.java b/uk/ac/sanger/artemis/components/SelectMenu.java
new file mode 100644
index 0000000..4d801fa
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/SelectMenu.java
@@ -0,0 +1,1051 @@
+/* SelectMenu.java
+ *
+ * created: Thu Jan 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SelectMenu.java,v 1.13 2008-05-21 14:14:04 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.KeyVector;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.Location;
+
+
+import java.awt.Cursor;
+import java.awt.Toolkit;
+import java.awt.event.*;
+import java.util.Vector;
+import java.util.Enumeration;
+
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.KeyStroke;
+
+
+/**
+ * This menu has contains items such a "Select all", "Select none" and
+ * "Select by key".
+ *
+ * @author Kim Rutherford
+ **/
+
+public class SelectMenu extends SelectionMenu
+{
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The EntryGroup object that was passed to the constructor.
+ **/
+ private EntryGroup entry_group;
+
+ /**
+ * The GotoEventSource object that was passed to the constructor.
+ **/
+ private GotoEventSource goto_event_source;
+
+ /**
+ * The shortcut for Select All
+ **/
+ final static KeyStroke SELECT_ALL_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_A,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ /**
+ * The shortcut for Select None
+ **/
+ final static KeyStroke SELECT_NONE_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_N,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+
+ /**
+ * Warn if the user tries to select more than this number of features.
+ **/
+ final private int MAX_FEATURE_TO_SELECT_COUNT = 10000;
+
+ /** busy cursor */
+ private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ /** done cursor */
+ private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+
+
+ /**
+ * Create a new SelectMenu object and all it's menu items.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param goto_event_source The object the we will call makeBaseVisible ()
+ * on.
+ * @param entry_group The EntryGroup object where features and exons are
+ * selected from.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ * @param menu_name The name of the new menu.
+ **/
+ public SelectMenu(final JFrame frame,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group,
+ final String menu_name)
+ {
+ this(frame,selection,goto_event_source,entry_group,
+ base_plot_group,null,null,menu_name);
+ }
+
+ /**
+ * Create a new SelectMenu object and all it's menu items.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param goto_event_source The object the we will call makeBaseVisible ()
+ * on.
+ * @param entry_group The EntryGroup object where features and exons are
+ * selected from.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ * @param menu_name The name of the new menu.
+ **/
+ public SelectMenu(final JFrame frame,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group,
+ final AlignmentViewer alignQueryViewer,
+ final AlignmentViewer alignSubjectViewer,
+ final String menu_name)
+ {
+ super(frame, menu_name, selection);
+
+ this.entry_group = entry_group;
+ this.goto_event_source = goto_event_source;
+
+ JMenuItem selector_item = new JMenuItem("Feature Selector ...");
+ selector_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ new Selector(selection, goto_event_source, getEntryGroup(),
+ base_plot_group);
+ }
+ });
+ add(selector_item);
+
+ addSeparator();
+
+ JMenuItem all_item = new JMenuItem("All");
+ all_item.setAccelerator(SELECT_ALL_KEY);
+ all_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ frame.setCursor(cbusy);
+ selectAll();
+ frame.setCursor(cdone);
+ }
+ });
+ add(all_item);
+
+ JMenuItem all_bases_item = new JMenuItem("All Bases");
+ all_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ frame.setCursor(cbusy);
+ selectAllBases();
+ frame.setCursor(cdone);
+ }
+ });
+ add(all_bases_item);
+
+
+ if(alignQueryViewer != null || alignSubjectViewer != null)
+ {
+ JMenuItem all_diffs = new JMenuItem("All Features in Non-matching Regions");
+ all_diffs.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ final JCheckBox cbox_strict = new JCheckBox("Strictly no overlapping regions",true);
+
+ int n = JOptionPane.showConfirmDialog(null, cbox_strict,
+ "Selecting Features in Non-matching Regions",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(n == JOptionPane.CANCEL_OPTION)
+ return;
+
+ frame.setCursor(cbusy);
+ Vector diffs = null;
+ if(alignQueryViewer == null || alignSubjectViewer == null)
+ {
+ if(alignQueryViewer != null)
+ diffs = alignQueryViewer.getDifferenceCoords(false);
+ else
+ diffs = alignSubjectViewer.getDifferenceCoords(true);
+ }
+ else
+ {
+ final Vector diffs1 = alignQueryViewer.getDifferenceCoords(false);
+ final Vector diffs2 = alignSubjectViewer.getDifferenceCoords(true);
+ diffs = AddMenu.union(diffs1,diffs2);
+ }
+
+ if(diffs != null)
+ selectAllFeatures(diffs,cbox_strict.isSelected());
+ frame.setCursor(cdone);
+ }
+ });
+
+ add(all_diffs);
+
+ }
+
+ JMenuItem none_item = new JMenuItem("None");
+ none_item.setAccelerator(SELECT_NONE_KEY);
+ none_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ clearSelection();
+ }
+ });
+ add(none_item);
+
+ JMenuItem select_by_key_item = new JMenuItem("By Key");
+ select_by_key_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ selectByKeyPopup();
+ }
+ });
+
+ add(select_by_key_item);
+
+ JMenuItem select_non_pseudo_cds_item =
+ new JMenuItem("CDS Features without /pseudogene");
+ select_non_pseudo_cds_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ // select all CDS features that do not have the /pseudo or /pseudogene qualifier
+ final FeaturePredicateConjunction predicate = new FeaturePredicateConjunction(
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
+ FeaturePredicateConjunction.AND);
+
+ clearSelection();
+
+ selectFeaturesByPredicate(predicate);
+ }
+ });
+ add(select_non_pseudo_cds_item);
+
+
+ final boolean isDatabaseGroup = GeneUtils.isDatabaseEntry( getEntryGroup() );
+ final JMenuItem select_all_cds_item;
+ if(isDatabaseGroup)
+ {
+ select_non_pseudo_cds_item.setEnabled(false);
+ select_all_cds_item = new JMenuItem("All "+
+ DatabaseDocument.EXONMODEL+" Features");
+ }
+ else
+ select_all_cds_item = new JMenuItem("All CDS Features");
+ select_all_cds_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ final FeatureKeyPredicate predicate;
+ if(isDatabaseGroup)
+ predicate = new FeatureKeyPredicate(new Key(DatabaseDocument.EXONMODEL));
+ else
+ predicate = new FeatureKeyPredicate(Key.CDS);
+
+ clearSelection();
+
+ selectFeaturesByPredicate(predicate);
+ }
+ });
+ add(select_all_cds_item);
+
+ JMenuItem select_same_type_item = new JMenuItem("Same Key");
+ select_same_type_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ selectSameKey();
+ }
+ });
+ add(select_same_type_item);
+
+ JMenuItem select_matching_qualifiers_item =
+ new JMenuItem("Features Matching Qualifier");
+ select_matching_qualifiers_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ selectMatchingQualifiers();
+ }
+ });
+ add(select_matching_qualifiers_item);
+
+ JMenuItem select_orf_item = new JMenuItem("Open Reading Frame");
+ select_orf_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ selectOrf();
+ }
+ });
+ add(select_orf_item);
+
+ final JMenuItem select_features_in_range_item =
+ new JMenuItem("Features Overlapping Selection");
+ select_features_in_range_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ selectOverlappingFeatures();
+ }
+ });
+
+ add(select_features_in_range_item);
+
+
+ final JMenuItem select_features_contained_item =
+ new JMenuItem("Features Within Selection");
+ select_features_contained_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ selectContainedFeatures();
+ }
+ });
+
+ add(select_features_contained_item);
+
+ JMenuItem select_base_range_item = new JMenuItem("Base Range ...");
+ select_base_range_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ selectBaseRange();
+ }
+ });
+ add(select_base_range_item);
+
+ JMenuItem select_aa_range_in_feature_item = new JMenuItem("Feature AA Range ...");
+ select_aa_range_in_feature_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ selectFeatureAARange();
+ }
+ });
+ add(select_aa_range_in_feature_item);
+
+ addSeparator();
+
+ JMenuItem selection_toggle_item = new JMenuItem("Toggle Selection");
+ selection_toggle_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ toggleSelection();
+ }
+ });
+ add(selection_toggle_item);
+ }
+
+
+ /**
+ * Create a new SelectMenu object and all it's menu items.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param goto_event_source The object the we will call makeBaseVisible ()
+ * on.
+ * @param entry_group The EntryGroup object where features and exons are
+ * selected from.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ public SelectMenu(final JFrame frame,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group)
+ {
+ this (frame, selection, goto_event_source, entry_group,
+ base_plot_group, "Select");
+ }
+
+
+ /**
+ * Select all the features in the active entries. If there are more than
+ * 1000 features the user is asked for confirmation.
+ **/
+ private void selectAll()
+ {
+ if(getEntryGroup().getAllFeaturesCount() >
+ MAX_FEATURE_TO_SELECT_COUNT)
+ {
+ final YesNoDialog dialog =
+ new YesNoDialog(getParentFrame(),
+ "Are you sure you want to select " +
+ getEntryGroup().getAllFeaturesCount() +
+ " features?");
+
+ if(!dialog.getResult ())
+ return;
+ }
+
+ final FeatureVector all_features = getEntryGroup().getAllFeatures();
+
+ if(getEntryGroup() instanceof SimpleEntryGroup)
+ {
+ // special case for speed
+ getSelection().set(all_features);
+ }
+ else
+ {
+ clearSelection();
+ getSelection().add(all_features);
+ }
+ }
+
+ /**
+ * Select all the bases.
+ **/
+ private void selectAllBases()
+ {
+ try
+ {
+ final Strand strand = getEntryGroup().getBases().getForwardStrand();
+
+ final MarkerRange new_range =
+ strand.makeMarkerRangeFromPositions(1, strand.getSequenceLength());
+
+ getSelection().setMarkerRange(new_range);
+ }
+ catch (OutOfRangeException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+
+ /**
+ *
+ * Select all features in a given collection of regions.
+ * @param regions Vector of coordinates of non-matching region.
+ * @param isStrict true if not allowing any overlap with matching region.
+ *
+ */
+ private void selectAllFeatures(final Vector regions, final boolean isStrict)
+ {
+
+ final FeaturePredicate predicate = new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ final Location loc = feature.getLocation();
+ Key key = feature.getKey();
+ if(key.equals("source"))
+ return false;
+
+ final int feat_start = loc.getFirstBase();
+ final int feat_end = loc.getLastBase();
+// final int feat_len = feature.getBaseCount();
+
+ int diff_start;
+ int diff_end;
+ Integer coords[];
+
+ Enumeration eDiffs = regions.elements();
+ while(eDiffs.hasMoreElements())
+ {
+ coords = (Integer[])eDiffs.nextElement();
+ diff_start = coords[0].intValue();
+ diff_end = coords[1].intValue();
+
+ if( isStrict && feat_start >= diff_start && feat_start <= diff_end &&
+ feat_end >= diff_start && feat_end <= diff_end )
+ return true;
+ else if( !isStrict &&
+ ((feat_start >= diff_start && feat_start <= diff_end) ||
+ (feat_end >= diff_start && feat_end <= diff_end) ||
+ (diff_start >= feat_start && diff_start <= feat_end) ||
+ (diff_end >= feat_start && diff_end <= feat_end )) )
+ return true;
+ else if(diff_start > feat_end)
+ return false;
+ }
+
+ return false;
+ }
+ };
+
+ selectFeaturesByPredicate(predicate);
+ }
+
+ /**
+ * Remove the all Features and FeatureSegments in the EntryGroup from the
+ * Selection. If the current EntryGroup is a SimpleEntryGroup then this
+ * method just calls getSelection ().clear ();
+ **/
+ private void clearSelection ()
+ {
+ if (getEntryGroup () instanceof SimpleEntryGroup) {
+ // special case for speed
+ getSelection ().clear ();
+ } else {
+ getSelection ().setMarkerRange (null);
+
+ final FeatureEnumeration test_enumerator = getEntryGroup ().features ();
+
+ while (test_enumerator.hasMoreFeatures ()) {
+ final Feature this_feature = test_enumerator.nextFeature ();
+
+ getSelection ().remove (this_feature);
+ getSelection ().removeSegmentsOf (this_feature);
+ }
+ }
+ }
+
+ /**
+ * Invert the selection - after calling this method the selection will
+ * contain only those features that were not in the selection before the
+ * call.
+ **/
+ private void toggleSelection () {
+ final FeatureVector selected_features = getSelection ().getAllFeatures ();
+
+ final FeatureVector all_features = getEntryGroup ().getAllFeatures ();
+
+ final FeatureVector new_selection = new FeatureVector ();
+
+ for (int i = 0 ; i < all_features.size () ; ++i) {
+ final Feature this_feature = all_features.elementAt (i);
+
+ if (!selected_features.contains (this_feature)) {
+ new_selection.add (this_feature);
+ }
+ }
+
+ clearSelection ();
+
+ getSelection ().add (new_selection);
+ }
+
+
+ /**
+ * Popup a TextRequester component and then select all the features that
+ * have the same key as the user types.
+ **/
+ private void selectByKeyPopup () {
+ final KeyChooser key_chooser;
+
+ final EntryInformation default_entry_information =
+ Options.getArtemisEntryInformation ();
+
+ key_chooser = new KeyChooser (default_entry_information,
+ new Key ("misc_feature"));
+
+ key_chooser.getKeyChoice ().addItemListener (new ItemListener () {
+ public void itemStateChanged (ItemEvent _) {
+ selectByKey (key_chooser.getKeyChoice ().getSelectedItem ());
+ key_chooser.setVisible (false);
+ key_chooser.dispose ();
+ }
+ });
+
+ key_chooser.getOKButton ().addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent _) {
+ selectByKey (key_chooser.getKeyChoice ().getSelectedItem ());
+ key_chooser.setVisible (false);
+ key_chooser.dispose ();
+ }
+ });
+
+ key_chooser.setVisible (true);
+ }
+
+ /**
+ * Select all the features that have the given key.
+ **/
+ private void selectByKey (final Key key) {
+ final FeaturePredicate predicate = new FeatureKeyPredicate (key);
+
+ selectFeaturesByPredicate (predicate);
+ }
+
+ /**
+ * Select all the features that have the given key and contains a qualifier
+ * with the given name and value.
+ * If key is null select any feature with the contains a qualifier with the
+ * given name and value.
+ **/
+ private void selectByQualifier (final Key key,
+ final String name, final String value) {
+ final FeaturePredicate predicate =
+ new FeatureKeyQualifierPredicate (key, name, value);
+
+ selectFeaturesByPredicate (predicate);
+ }
+
+ /**
+ * Select all the features that have the same key as the currently selected
+ * features.
+ **/
+ private void selectSameKey () {
+ if (! checkForSelectionFeatures ()) {
+ return;
+ }
+
+ final KeyVector seen_keys = new KeyVector ();
+
+ final FeatureVector selected_features = getSelection ().getAllFeatures ();
+
+ for (int i = 0 ; i < selected_features.size () ; ++i) {
+ final Feature current_feature = selected_features.elementAt (i);
+
+ if (!seen_keys.contains (current_feature.getKey ())) {
+ seen_keys.add (current_feature.getKey ());
+ }
+ }
+
+ clearSelection ();
+
+ for (int i = 0 ; i < seen_keys.size () ; ++i) {
+ selectByKey ((Key)seen_keys.get(i));
+ }
+ }
+
+ /**
+ * Ask the user for a qualifier name, list all of the qualifier with that
+ * name in the currently selected feature then select all features that
+ * have that name and qualifier.
+ **/
+ private void selectMatchingQualifiers () {
+ if (!checkForSelectionFeatures (getParentFrame (), getSelection ())) {
+ return;
+ }
+
+ final FeatureVector selected_features = getSelection ().getAllFeatures ();
+
+ if (selected_features.size () > 1) {
+ new MessageDialog (getParentFrame (), "select only one feature");
+ return;
+ }
+
+ final Feature selected_feature = selected_features.elementAt (0);
+
+ final StringVector qualifier_names =
+ Feature.getAllQualifierNames (selected_features);
+
+ if (qualifier_names.size () == 0) {
+ new MessageDialog (getParentFrame (), "feature has no qualifiers");
+ return;
+ }
+
+ final ChoiceFrame name_choice_frame =
+ new ChoiceFrame ("Select a qualifier name ...", qualifier_names);
+
+ final JComboBox name_choice = name_choice_frame.getChoice ();
+
+ class SelectListener implements ActionListener, ItemListener {
+ public void doStuff () {
+ selectMatchingQualifiersHelper (selected_feature,
+ (String) name_choice.getSelectedItem ());
+ name_choice_frame.setVisible (false);
+ name_choice_frame.dispose ();
+ }
+
+ public void itemStateChanged (ItemEvent _) {
+ doStuff ();
+ // need to remove() because itemStateChanged() is called twice on the
+ // Tru64 1.4.1 JVM
+ name_choice.removeItemListener (this);
+ }
+
+ public void actionPerformed (ActionEvent _) {
+ doStuff ();
+ name_choice_frame.getOKButton ().removeActionListener (this);
+ }
+ };
+
+ final SelectListener listener = new SelectListener ();
+
+ name_choice.addItemListener (listener);
+ name_choice_frame.getOKButton ().addActionListener (listener);
+
+ name_choice_frame.setVisible (true);
+ }
+
+ /**
+ * Pop up a list of the values of the qualifiers with the given name in the
+ * given feature then select all features that have a qualifier with the
+ * same and value.
+ **/
+ private void selectMatchingQualifiersHelper (final Feature feature,
+ final String name) {
+ final Qualifier qualifier;
+
+ try {
+ qualifier = feature.getQualifierByName (name);
+ } catch (InvalidRelationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ if (qualifier == null) {
+ throw new Error ("internal error - unexpected null reference");
+ }
+
+ final StringVector qualifier_values = qualifier.getValues ();
+
+ if (qualifier_values.size () == 0) {
+ new MessageDialog (getParentFrame (),
+ "qualifier " + name + " has no values in ");
+ return;
+ }
+
+ if (qualifier_values.size () == 1) {
+ selectByQualifier (null, name, (String)qualifier_values.elementAt (0));
+ } else {
+ final ChoiceFrame value_choice_frame =
+ new ChoiceFrame ("Select a qualifier value", qualifier_values);
+
+ final JComboBox value_choice = value_choice_frame.getChoice ();
+
+ value_choice.addItemListener (new ItemListener () {
+ public void itemStateChanged (ItemEvent _) {
+ selectByQualifier (null, name,
+ (String) value_choice.getSelectedItem ());
+ value_choice_frame.setVisible (false);
+ value_choice_frame.dispose ();
+ }
+ });
+
+ value_choice_frame.getOKButton ().addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent _) {
+ selectByQualifier (null, name,
+ (String) value_choice.getSelectedItem ());
+ value_choice_frame.setVisible (false);
+ value_choice_frame.dispose ();
+ }
+ });
+
+ value_choice_frame.setVisible (true);
+ }
+ }
+
+ /**
+ * Select all the features that match the given predicate.
+ **/
+ private void selectFeaturesByPredicate (final FeaturePredicate predicate) {
+ final FeatureVector new_selection_features = new FeatureVector ();
+
+ final FeatureEnumeration feature_enum = getEntryGroup ().features ();
+
+ while (feature_enum.hasMoreFeatures ()) {
+ final Feature current_feature = feature_enum.nextFeature ();
+
+ if (predicate.testPredicate (current_feature)) {
+ new_selection_features.add (current_feature);
+ }
+ }
+
+ clearSelection ();
+
+ getSelection ().add (new_selection_features);
+ }
+
+ /**
+ * If a range of bases is selected, then select the ORF around those
+ * bases.
+ **/
+ private void selectOrf () {
+ if (!checkForSelectionRange ()) {
+ return;
+ }
+
+ final MarkerRange range = getSelection ().getMarkerRange ();
+ final MarkerRange start_orf_range =
+ Strand.getORFAroundMarker (range.getStart (), true);
+
+ /*final*/ Marker end_marker;
+
+ // get the marker of the first base of the last codon in the range (we
+ // want to keep in the same frame)
+ try {
+ final int start_end_diff = range.getCount () - 1;
+
+ final int mod_length = start_end_diff - start_end_diff % 3;
+
+ end_marker = range.getStart ().moveBy (mod_length);
+ } catch (OutOfRangeException e) {
+ end_marker = range.getStart ();
+ }
+
+ final MarkerRange end_orf_range =
+ Strand.getORFAroundMarker (end_marker, true);
+
+ MarkerRange new_range = range;
+
+ if (start_orf_range != null) {
+ new_range = new_range.extendRange (start_orf_range);
+ }
+
+ if (end_orf_range != null) {
+ new_range = new_range.extendRange (end_orf_range);
+ }
+
+ if (start_orf_range == null && end_orf_range == null &&
+ range.getCount () <= 6) { // 6 == two codons
+ new MessageDialog (getParentFrame (),
+ "there is no open reading frame at the selected " +
+ "bases");
+ return;
+ }
+
+ getSelection ().setMarkerRange (new_range);
+ }
+
+ /**
+ * This method will ask the user for a range of bases (such as 100..200),
+ * using a MarkerRangeRequester component, then selects that range.
+ **/
+ private void selectBaseRange () {
+ final MarkerRangeRequester range_requester =
+ new MarkerRangeRequester ("enter a range of bases (eg. 100..200):",
+ 18, "");
+
+ final MarkerRangeRequesterListener listener =
+ new MarkerRangeRequesterListener () {
+ public void actionPerformed (final MarkerRangeRequesterEvent event) {
+ final MarkerRange marker_range =
+ event.getMarkerRange (getEntryGroup ().getBases ());
+
+ getSelection ().setMarkerRange (marker_range);
+
+ makeBaseVisible (getSelection ().getStartBaseOfSelection ());
+ }
+ };
+
+ range_requester.addMarkerRangeRequesterListener (listener);
+
+ range_requester.setVisible (true);
+ }
+
+ /**
+ * This method will ask the user for a range of amino acids in a feature
+ * (such as 100..200), using a Requester component, then selects that
+ * range on the sequence.
+ **/
+ private void selectFeatureAARange () {
+ if (!checkForSelectionFeatures (getParentFrame (), getSelection ())) {
+ return;
+ }
+
+ final FeatureVector selected_features = getSelection ().getAllFeatures ();
+
+ if (selected_features.size () > 1) {
+ new MessageDialog (getParentFrame (), "select only one feature");
+ return;
+ }
+
+ final Feature selected_feature = selected_features.elementAt (0);
+
+ final MarkerRangeRequester range_requester =
+ new MarkerRangeRequester ("enter a range of amino acids (eg. 100..200):",
+ 18, "");
+
+ final MarkerRangeRequesterListener listener =
+ new MarkerRangeRequesterListener () {
+ public void actionPerformed (final MarkerRangeRequesterEvent event) {
+ final Range range = event.getRawRange ();
+
+ if (range == null) {
+ return;
+ }
+
+ final int start_position = range.getStart ();
+ final int end_position = range.getEnd ();
+
+ final int start_pos_in_feature = (start_position - 1) * 3 + 1;
+ final int end_pos_in_feature = (end_position - 1) * 3 + 3;
+
+ try {
+ final Marker start_marker =
+ selected_feature.getPositionInSequence (start_pos_in_feature);
+
+ final Marker end_marker =
+ selected_feature.getPositionInSequence (end_pos_in_feature);
+
+ final MarkerRange marker_range =
+ new MarkerRange (start_marker.getStrand (),
+ start_marker.getPosition (),
+ end_marker.getPosition ());
+
+ getSelection ().setMarkerRange (marker_range);
+
+ makeBaseVisible (getSelection ().getStartBaseOfSelection ());
+ } catch (OutOfRangeException e) {
+ new MessageDialog (getParentFrame (),
+ "amino acid range is out of range " +
+ "for this feature");
+ }
+ }
+ };
+
+ range_requester.addMarkerRangeRequesterListener (listener);
+
+ range_requester.setVisible (true);
+ }
+
+ /**
+ * If there are some selected bases, select the feature in that range
+ * (replacing the orginal selection). If some features are selected,
+ * select those features that overlap the selected features (and unselect
+ * the original features).
+ **/
+ private void selectOverlappingFeatures () {
+ final MarkerRange selected_marker_range =
+ getSelection ().getMarkerRange ();
+
+ if (selected_marker_range == null) {
+ final FeatureVector selected_features =
+ getSelection ().getAllFeatures ();
+
+ if (selected_features.size () == 0) {
+ new MessageDialog (getParentFrame (), "nothing selected");
+ return;
+ } else {
+ final FeatureVector new_selection = new FeatureVector ();
+
+ for (int i = 0 ; i < selected_features.size () ; ++i) {
+ final Feature this_feature = selected_features.elementAt (i);
+
+ final Range this_feature_raw_range = this_feature.getMaxRawRange ();
+
+ try {
+ final FeatureVector overlapping_features =
+ getEntryGroup ().getFeaturesInRange (this_feature_raw_range);
+
+ for (int overlapping_feature_index = 0 ;
+ overlapping_feature_index < overlapping_features.size () ;
+ ++overlapping_feature_index) {
+ final Feature overlapping_feature =
+ overlapping_features.elementAt (overlapping_feature_index);
+
+ if (!new_selection.contains (overlapping_feature)) {
+ new_selection.add (overlapping_feature);
+ }
+ }
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ for (int i = 0 ; i < selected_features.size () ; ++i) {
+ new_selection.remove (selected_features.elementAt (i));
+ }
+
+ getSelection ().set (new_selection);
+ }
+ } else {
+ try {
+ final Range raw_range = selected_marker_range.getRawRange ();
+ getSelection ().set (getEntryGroup ().getFeaturesInRange (raw_range));
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+
+
+ /**
+ * If there are some selected bases select the features that are
+ * fully contained within that selection.
+ **/
+ private void selectContainedFeatures () {
+ final MarkerRange selected_marker_range =
+ getSelection ().getMarkerRange ();
+
+ if (selected_marker_range == null) {
+ new MessageDialog (getParentFrame (), "sequence range is not selected");
+ return;
+ } else {
+ try {
+ final FeatureVector featuresInRange =
+ getEntryGroup ().getFeaturesInRange (selected_marker_range.getRawRange ());
+ final FeatureVector featuresContainedByRange = new FeatureVector();
+ for(int i=0; i<featuresInRange.size(); i++)
+ {
+ Feature f = featuresInRange.elementAt(i);
+ Range maxR = f.getMaxRawRange();
+ if(selected_marker_range.getRawStart().getRawPosition() <= maxR.getStart() &&
+ selected_marker_range.getRawEnd().getRawPosition() >= maxR.getEnd())
+ featuresContainedByRange.add(f);
+ }
+ getSelection ().set (featuresContainedByRange);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+
+ /**
+ * This method sends an GotoEvent to all the GotoEvent listeners that will
+ * make the given base visible.
+ **/
+ private void makeBaseVisible (final Marker base_marker) {
+ goto_event_source.gotoBase (base_marker);
+ }
+
+ /**
+ * Return the EntryGroup that was passed to the constructor.
+ **/
+ private EntryGroup getEntryGroup () {
+ return entry_group;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/SelectionInfoDisplay.java b/uk/ac/sanger/artemis/components/SelectionInfoDisplay.java
new file mode 100644
index 0000000..9d455dd
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/SelectionInfoDisplay.java
@@ -0,0 +1,487 @@
+/* SelectionInfoDisplay.java
+ *
+ * created: Tue Dec 15 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SelectionInfoDisplay.java,v 1.7 2005-01-06 11:21:06 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.io.StreamQualifier;
+import uk.ac.sanger.artemis.io.QualifierInfo;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+
+import javax.swing.*;
+
+/**
+ * This class displays information about the selection in a Label.
+ *
+ * @author Kim Rutherford
+ * @version $Id: SelectionInfoDisplay.java,v 1.7 2005-01-06 11:21:06 tjc Exp $
+ **/
+public class SelectionInfoDisplay extends CanvasPanel
+ implements SelectionChangeListener
+{
+ /**
+ * Create a new SelectionInfoDisplay component.
+ * @param entry_group The EntryGroup that this component will display.
+ * @param selection The Selection that this component will display.
+ **/
+ public SelectionInfoDisplay(final EntryGroup entry_group,
+ final Selection selection)
+ {
+ this.entry_group = entry_group;
+ this.selection = selection;
+
+ getSelection().addSelectionChangeListener(this);
+
+ addComponentListener(new ComponentAdapter()
+ {
+ public void componentResized(ComponentEvent e)
+ {
+ setSize(getSize().width,
+ getFontHeight()+1);
+ repaint();
+ }
+ public void componentShown(ComponentEvent e)
+ {
+ setSize(getSize().width,
+ getFontHeight()+1);
+ repaint();
+ }
+ });
+
+ setBackground(new Color(230,230,230));
+ setSize(80, getFontHeight());
+ }
+
+ /**
+ *
+ **/
+ public Dimension getPreferredSize()
+ {
+ return new Dimension(10, getFontHeight());
+ }
+
+ /**
+ *
+ **/
+ public Dimension getMinimumSize()
+ {
+ return new Dimension(10, getFontHeight());
+ }
+
+ /**
+ * Implementation of the SelectionChangeListener interface. We listen to
+ * SelectionChange events so that we can update the list to reflect the
+ * current selection.
+ **/
+ public void selectionChanged(SelectionChangeEvent event)
+ {
+ repaint();
+ }
+
+ /**
+ * Draw the label.
+ **/
+ public void paintComponent(final Graphics g)
+ {
+ super.paintComponent(g);
+ if(!isVisible ())
+ return;
+
+ final FeatureVector features = getSelection().getAllFeatures();
+ final StringBuffer new_text = new StringBuffer();
+
+ new_text.append(markerRangeText(getSelection(), entry_group));
+
+ int base_total = 0;
+ int aa_total = 0;
+ boolean saw_a_non_cds = false;
+
+ if(features.size () > 0)
+ {
+ final StringBuffer feature_names = new StringBuffer ();
+
+ if(features.size () < 100)
+ {
+ if(features.size () > 1)
+ feature_names.append (" (");
+
+ // show up to 10 features
+ for(int i = 0 ; i < features.size () ; ++i)
+ {
+ final Feature current_feature = features.elementAt (i);
+
+ base_total += current_feature.getBaseCount ();
+ aa_total += current_feature.getAACount ();
+
+ if(!current_feature.getKey().equals ("CDS"))
+ saw_a_non_cds = true;
+
+ if(i < 10)
+ {
+ if(i != 0)
+ feature_names.append(' ');
+
+ feature_names.append(current_feature.getIDString ());
+ }
+ }
+
+ if(features.size() > 10)
+ feature_names.append ("...");
+
+ if(features.size() == 1)
+ {
+ // only one feature so append some qualifiers
+ feature_names.append(" (");
+ feature_names.append(getQualifierString(features.elementAt (0)));
+ }
+ feature_names.append(")");
+ }
+
+ if(features.size() == 1)
+ new_text.append ("Selected feature: ");
+ else
+ new_text.append (features.size () + " selected features ");
+
+ if(features.size() < 100)
+ {
+ // show a count of the number of bases and amino acids in the selected
+ // feature
+ if(features.size() > 1)
+ {
+ new_text.append ("total bases " + base_total);
+
+ if(!saw_a_non_cds)
+ new_text.append (" total amino acids " + aa_total);
+ }
+ else
+ {
+ if(features.size() == 1)
+ {
+ new_text.append ("bases " + base_total);
+ if(!saw_a_non_cds)
+ new_text.append (" amino acids " + aa_total);
+
+ try
+ {
+ final Feature thisFeature = features.elementAt(0);
+ final FeatureSegmentVector this_feature_segments = thisFeature.getSegments();
+ if ( (!saw_a_non_cds || thisFeature.getKey().equals("exon") ) &&
+ this_feature_segments.size() > 1)
+ {
+ // show the exon number
+ final FeatureSegmentVector selected_segments = selection.getSelectedSegments();
+ for (int i = 0; i < this_feature_segments.size(); ++i)
+ {
+ FeatureSegment current_segment = this_feature_segments.elementAt(i);
+ if (selected_segments.indexOf(current_segment) != -1)
+ new_text.append(" exon no. " + (i + 1));
+ }
+ }
+ }
+ catch (Exception e){}
+ }
+ }
+ }
+
+ new_text.append(" ");
+ new_text.append(feature_names.toString());
+ }
+
+
+ String text = new_text.toString();
+
+ if(text.length () > 0)
+ {
+ if (text.length () > 150)
+ {
+ // call substring () just to keep the String a sensible length
+ text = text.substring (0, 150);
+ }
+ }
+ else
+ text = "Nothing selected";
+
+// g.setColor(new Color (230, 230, 230));
+// g.fillRect(0, 0, getCanvasWidth(), getCanvasHeight());
+
+ g.setColor(Color.black);
+ g.drawString(text, 2, getFontMaxAscent() + 1);
+ }
+
+ /**
+ * Return a String containing the qualifiers (except the /note) of given
+ * feature.
+ **/
+ private String getQualifierString(final Feature feature)
+ {
+ final QualifierVector qualifiers = feature.getQualifiers();
+
+ // if we see a /note or /fasta_file it gets saved for later
+ final Vector saved_qualifiers = new Vector();
+
+ final StringBuffer string_buffer = new StringBuffer();
+
+ final EntryInformation entry_information =
+ feature.getEntry().getEntryInformation();
+
+ for (int i = 0 ; i < qualifiers.size () ; ++i)
+ {
+ final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt (i);
+
+ if (this_qualifier.getName ().equals ("note") ||
+ this_qualifier.getName ().endsWith ("_file"))
+ {
+ // save the qualifier and put it last
+ saved_qualifiers.addElement (this_qualifier);
+ continue;
+ }
+
+ if (string_buffer.length () > 0)
+ {
+ // put spaces between the qualifiers
+ string_buffer.append (' ');
+ }
+
+ final QualifierInfo qualifier_info =
+ entry_information.getQualifierInfo (this_qualifier.getName ());
+
+ string_buffer.append (StreamQualifier.toString (qualifier_info,
+ this_qualifier));
+ }
+
+ for(int i = 0 ; i < saved_qualifiers.size () ; ++i)
+ {
+ if(string_buffer.length () > 0)
+ {
+ // put spaces between the qualifiers
+ string_buffer.append (' ');
+ }
+
+ final Qualifier this_qualifier =
+ (Qualifier) saved_qualifiers.elementAt (i);
+
+ final QualifierInfo qualifier_info =
+ entry_information.getQualifierInfo (this_qualifier.getName ());
+
+ string_buffer.append (StreamQualifier.toString (qualifier_info,
+ this_qualifier));
+ }
+
+ return string_buffer.toString ();
+ }
+
+ /**
+ * If the selection contains a MarkerRange (a range of bases) then return a
+ * String in this form: "Selected Bases on forward strand: <start>..<end> ",
+ * otherwise return "". This method is also used by the SelectionViewer
+ * class.
+ * @param current_selection The selection to summarize.
+ * @param entry_group The EntryGroup that the selection refers to.
+ **/
+ static String markerRangeText(final Selection current_selection,
+ final EntryGroup entry_group)
+ {
+ final MarkerRange marker_range = current_selection.getMarkerRange ();
+
+ if (marker_range == null)
+ return "";
+ else
+ {
+ final StringBuffer buffer = new StringBuffer ();
+
+ final int start_pos = marker_range.getStart ().getPosition ();
+ final int end_pos = marker_range.getEnd ().getPosition ();
+
+ if(marker_range.getStrand ().isForwardStrand ())
+ {
+ if(start_pos == end_pos)
+ {
+ buffer.append ("One selected base on forward strand: " +
+ start_pos + " ");
+ }
+ else
+ {
+ buffer.append ((end_pos - start_pos + 1) +
+ " selected bases on forward strand: " +
+ start_pos + ".." + end_pos + " ");
+ }
+ }
+ else
+ {
+ if(start_pos == end_pos)
+ {
+ buffer.append ("One selected base on reverse strand: " +
+ marker_range.getStart ().getPosition () +
+ " = complement (" +
+ marker_range.getEnd ().getRawPosition () + ") ");
+ }
+ else
+ {
+ buffer.append ((end_pos - start_pos + 1) +
+ " selected bases on reverse strand: " +
+ marker_range.getStart ().getPosition () + ".." +
+ marker_range.getEnd ().getPosition () +
+ " = complement (" +
+ marker_range.getEnd ().getRawPosition () + ".." +
+ marker_range.getStart ().getRawPosition () + ") ");
+ }
+ }
+
+ if(marker_range.getCount () >= 3)
+ {
+ // check if this codon is in a feature
+
+ final Range raw_range = marker_range.getRawRange ();
+
+ final FeatureVector features;
+
+ try
+ {
+ features = entry_group.getFeaturesInRange (raw_range);
+ }
+ catch (OutOfRangeException e)
+ {
+ return "illegal marker range";
+ }
+
+ for(int i = 0 ; i < features.size () ; ++i)
+ {
+ final Feature this_feature = features.elementAt (i);
+
+ if(!this_feature.isProteinFeature ())
+ {
+ // only display protein features
+ continue;
+ }
+
+ if(marker_range.isForwardMarker () !=
+ this_feature.isForwardFeature ())
+ {
+ // only display feature positions in features on the same strand
+ // as the selected bases
+ continue;
+ }
+
+ final String this_feature_id = this_feature.getIDString ();
+
+ final Marker start_marker = marker_range.getStart ();
+
+ final Marker end_marker = marker_range.getEnd ();
+
+ final int start_position_in_feature =
+ this_feature.getFeaturePositionFromMarker (start_marker);
+
+ final int start_codon_position =
+ start_position_in_feature - this_feature.getCodonStart () + 1;
+
+ String start_string = null;
+
+ if(start_position_in_feature != -1)
+ {
+ if (start_codon_position % 3 == 0)
+ {
+ start_string = "codon " + (start_codon_position / 3 + 1);
+ }
+ }
+
+ String end_string = null;
+
+ final int end_position_in_feature =
+ this_feature.getFeaturePositionFromMarker (end_marker);
+
+ final int end_codon_position =
+ end_position_in_feature - this_feature.getCodonStart () - 1;
+
+ if (end_position_in_feature != -1)
+ {
+ if (start_codon_position != end_codon_position &&
+ end_codon_position % 3 == 0)
+ {
+ end_string = "codon " + (end_codon_position / 3 + 1);
+ }
+ }
+
+ if(!(start_string == null && end_string == null))
+ {
+ buffer.append (" (");
+
+ if(start_string != null)
+ buffer.append (start_string);
+
+ if(start_string != null && end_string != null)
+ buffer.append (" to ");
+
+ if(end_string != null)
+ buffer.append (end_string);
+
+ buffer.append(" in feature " + this_feature.getIDString () +
+ ") ");
+ }
+ }
+ }
+
+ return buffer.append(" ").toString();
+ }
+ }
+
+ /**
+ * Return the Selection reference that was passed to the constructor.
+ **/
+ private Selection getSelection ()
+ {
+ return selection;
+ }
+
+ /**
+ * Return the Selection object that was passed to the constructor.
+ **/
+ private EntryGroup getEntryGroup ()
+ {
+ return entry_group;
+ }
+
+ /**
+ * The reference of the EntryGroup object that was passed to the
+ * constructor.
+ **/
+ private EntryGroup entry_group;
+
+ /**
+ * This is a reference to the Selection object that created this
+ * component.
+ **/
+ private final Selection selection;
+}
+
diff --git a/uk/ac/sanger/artemis/components/SelectionMenu.java b/uk/ac/sanger/artemis/components/SelectionMenu.java
new file mode 100644
index 0000000..5bc71bd
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/SelectionMenu.java
@@ -0,0 +1,551 @@
+/* SelectionMenu.java
+ *
+ * created: Sun Jan 10 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SelectionMenu.java,v 1.9 2008-06-11 15:17:43 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+
+import java.awt.*;
+import java.util.Vector;
+
+/**
+ * This a super class for EditMenu, ViewMenu and GotoMenu. It is a JMenu that
+ * knows how to get hold of the Selection. It also has the method
+ * getParentFrame() to find the owning JFrame of the menu.
+ *
+ * @author Kim Rutherford
+ * @version $Id: SelectionMenu.java,v 1.9 2008-06-11 15:17:43 tjc Exp $
+ **/
+
+public class SelectionMenu extends JMenu
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /** The Selection that was passed to the constructor. */
+ private Selection selection;
+
+ /** The JFrame reference that was passed to the constructor. */
+ private JFrame frame = null;
+
+ /**
+ * Create a new SelectionMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param name The string to use as the menu title.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ **/
+ public SelectionMenu(final JFrame frame,
+ final String menu_name,
+ final Selection selection)
+ {
+ super(menu_name);
+ this.frame = frame;
+ this.selection = selection;
+ }
+
+ /**
+ * Return the JFrame that was passed to the constructor.
+ **/
+ public JFrame getParentFrame()
+ {
+ return frame;
+ }
+
+ /**
+ * Check that the are some Features in the current selection. If there are
+ * some features then return true. If there are no features selected then
+ * popup a message saying so and return false.
+ **/
+ protected boolean checkForSelectionFeatures()
+ {
+ return checkForSelectionFeatures(getParentFrame(), getSelection());
+ }
+
+ /**
+ * Check that the are some Features in the given selection. If there are
+ * some features then return true. If there are no features selected then
+ * popup a message saying so and return false.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The selected features to check.
+ **/
+ static boolean checkForSelectionFeatures(final JFrame frame,
+ final Selection selection)
+ {
+ final FeatureVector features_to_check = selection.getAllFeatures();
+
+ if(features_to_check.size() == 0)
+ {
+ new MessageDialog(frame, "No features selected");
+ return false;
+ }
+ else
+ return true;
+ }
+
+ /**
+ * Check that the are some Features in the current selection and that the
+ * selection isn't too big. If there is more than one feature in the
+ * selection and less than the given maximum then return true. If there
+ * are no features selected then popup a message saying so and return
+ * false. If there are more selected features than the given maximum
+ * than display the message in a YesNoDialog component and return the
+ * result of the dialog.
+ **/
+ protected boolean checkForSelectionFeatures(final int maximum_size,
+ final String message)
+ {
+ return checkForSelectionFeatures(getParentFrame(), getSelection(),
+ maximum_size, message);
+ }
+
+ /**
+ * Check that the are some Features in the given selection and that the
+ * selection isn't too big. If there is more than one feature in the
+ * selection and less than the given maximum then return true. If there
+ * are no features selected then popup a message saying so and return
+ * false. If there are more selected features than the given maximum
+ * than display the message in a YesNoDialog component and return the
+ * result of the dialog.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The selected features to check.
+ **/
+ static boolean checkForSelectionFeatures(final JFrame frame,
+ final Selection selection,
+ final int maximum_size,
+ final String message)
+ {
+ final FeatureVector features_to_check = selection.getAllFeatures();
+
+ if(features_to_check.size() == 0)
+ {
+ new MessageDialog(frame, "No features selected");
+ return false;
+ }
+ else
+ {
+ if(features_to_check.size() > maximum_size)
+ {
+ final YesNoDialog dialog =
+ new YesNoDialog(frame, message);
+
+ return dialog.getResult();
+ }
+ else
+ return true;
+ }
+ }
+
+ /**
+
+ * Check that there are only CDS features in the given selection, that there
+ * are some features selected and that the selection isn't too big. If
+ * there is more than one feature in the selection and less than the given
+ * maximum then return true. If there are no features selected then popup
+ * a message saying so and return false. If there are more selected
+ * features than the given maximum than display the message in a
+ * YesNoDialog component and return the result of the dialog.
+ * @param frame The Frame to use for MessageDialog components.
+ * @param selection The selected features to check.
+ **/
+ static boolean checkForSelectionCDSFeatures(final JFrame frame,
+ final Selection selection,
+ final int maximum_size,
+ final String max_message)
+ {
+ final FeatureVector features_to_check = selection.getAllFeatures();
+
+ if(features_to_check.size() == 0)
+ {
+ new MessageDialog(frame, "No CDS features selected");
+ return false;
+ }
+ else
+ {
+ for(int i = 0 ; i < features_to_check.size() ; ++i)
+ {
+ final Feature this_feature = features_to_check.elementAt(i);
+ if(!this_feature.isCDS())
+ {
+ final String message =
+ "a non-CDS feature(" + this_feature.getIDString() +
+ ") is selected - can't continue";
+ final MessageDialog dialog =
+ new MessageDialog(frame, message);
+ return false;
+ }
+ }
+
+ if(features_to_check.size() > maximum_size)
+ {
+ final YesNoDialog dialog =
+ new YesNoDialog(frame, max_message);
+
+ return dialog.getResult();
+ }
+ else
+ return true;
+ }
+ }
+
+ /**
+ * Check that the are some FeatureSegments in the current selection and
+ * that the selection isn't too big. If there is more than one
+ * FeatureSegments in the selection and less than the given maximum then
+ * return true. If there are no FeatureSegments selected then popup a
+ * message saying so and return false. If there are more selected
+ * FeatureSegments than the given maximum than display the message in a
+ * YesNoDialog component and return the result of the dialog.
+ **/
+ protected boolean checkForSelectionFeatureSegments(final int maximum_size,
+ final String message)
+ {
+ final FeatureSegmentVector segments_to_check =
+ getSelection().getAllSegments();
+
+ if(segments_to_check.size() == 0)
+ {
+ new MessageDialog(getParentFrame(), "No exons selected");
+ return false;
+ }
+ else
+ {
+ if(segments_to_check.size() > maximum_size)
+ {
+ final YesNoDialog dialog =
+ new YesNoDialog(getParentFrame(), message);
+
+ return dialog.getResult();
+ }
+ else
+ return true;
+ }
+ }
+
+ /**
+ * Check that the current selection contains a MarkerRange. If it does
+ * then return true. If not then popup a message saying so and return
+ * false.
+ **/
+ protected boolean checkForSelectionRange()
+ {
+ return checkForSelectionRange(getParentFrame(), getSelection());
+ }
+
+ /**
+ * Check that the current selection contains a MarkerRange. If it does
+ * then return true. If not then popup a message saying so and return
+ * false.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The selected features to check.
+ **/
+ public static boolean checkForSelectionRange(final JFrame frame,
+ final Selection selection)
+ {
+ final MarkerRange marker_range = selection.getMarkerRange();
+
+ if(marker_range == null)
+ {
+ new MessageDialog(frame, "No bases selected");
+ return false;
+ }
+ else
+ return true;
+ }
+
+ /**
+ * Return the Selection object that was passed to the constructor.
+ **/
+ protected Selection getSelection()
+ {
+ return selection;
+ }
+
+ /**
+ *
+ **/
+ protected static KeyStroke makeMenuKeyStroke(final int key_code)
+ {
+ return KeyStroke.getKeyStroke(key_code,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+ }
+
+ private void getJMenuItems(JMenu menu, Vector<JMenuItem> menu_items)
+ {
+ final Component menus[] = menu.getMenuComponents();
+ for(int i=0; i<menus.length; i++)
+ {
+ if(menus[i] instanceof JMenuItem)
+ menu_items.add((JMenuItem) menus[i]);
+ }
+ }
+
+ private Vector<JMenuItem> getSelectionMenuItems()
+ {
+ final Vector<JMenuItem> menu_items = new Vector<JMenuItem>();
+ final Component menus[] = getMenuComponents();
+ for(int i=0; i<menus.length; i++)
+ {
+ if(menus[i] instanceof JMenu)
+ {
+ menu_items.add((JMenu)menus[i]);
+ getJMenuItems((JMenu)menus[i], menu_items);
+ }
+ else if(menus[i] instanceof JMenuItem)
+ menu_items.add((JMenuItem)menus[i]);
+ }
+ return menu_items;
+ }
+
+ protected Vector<String> getUsedShortCutKeys()
+ {
+ final Vector<String> sc = new Vector<String>();
+ final Vector<JMenuItem> menu_items = getSelectionMenuItems();
+ for(JMenuItem mi : menu_items)
+ if(mi.getAccelerator() != null)
+ sc.add(getKeyText(mi.getAccelerator().getKeyCode()));
+ return sc;
+ }
+
+ protected JScrollPane getShortCuts(Vector<String> usedShortCutKeys)
+ {
+ usedShortCutKeys.add("C"); // Create menu
+ usedShortCutKeys.add("G"); // Goto menu
+ final Vector<JMenuItem> menu_items = getSelectionMenuItems();
+ final Box bdown = Box.createVerticalBox();
+ final String alist[] =
+ { "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", "--"};
+ final String mod_list[] = { "Default", "Alt", "Ctrl", "Shift" };
+
+ for(final JMenuItem mi : menu_items)
+ {
+ final Box bacross = Box.createHorizontalBox();
+ if(mi instanceof JMenu)
+ {
+ bacross.add(new JLabel( mi.getText() ));
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+ continue;
+ }
+
+ bacross.add(new JLabel(mi.getText()));
+
+// short cut
+ final JComboBox combo = new JComboBox(alist);
+ combo.setRenderer(new ComboBoxRenderer(usedShortCutKeys));
+ final JComboBox mod_combo = new JComboBox(mod_list);
+ final KeyStroke ks = mi.getAccelerator();
+ if(ks != null)
+ {
+ combo.setSelectedItem( getKeyText(ks.getKeyCode()) );
+ mod_combo.setSelectedItem( getModifierFromInt(ks.getModifiers()) );
+ }
+ else
+ {
+ combo.setSelectedItem("--");
+ mod_combo.setSelectedItem("Default");
+ }
+
+ Dimension dim = combo.getPreferredSize();
+ dim = new Dimension(100, dim.height);
+ combo.setPreferredSize(dim);
+ combo.setMaximumSize(dim);
+ combo.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(e.getStateChange() == ItemEvent.SELECTED)
+ setAccelerator(combo,mod_combo,mi);
+ }
+ });
+
+// modifier
+ mod_combo.setPreferredSize(dim);
+ mod_combo.setMaximumSize(dim);
+ mod_combo.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(e.getStateChange() == ItemEvent.SELECTED)
+ setAccelerator(combo,mod_combo,mi);
+ }
+ });
+
+ bacross.add(Box.createHorizontalGlue());
+ bacross.add(combo);
+ bacross.add(mod_combo);
+ bdown.add(bacross);
+ }
+
+ bdown.add(Box.createVerticalGlue());
+
+ final JScrollPane jsp = new JScrollPane(bdown);
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ jsp.setPreferredSize(new Dimension(jsp.getPreferredSize().width,
+ screen.height/2));
+ return jsp;
+ }
+
+ private void setAccelerator(final JComboBox combo, final JComboBox mod_combo,
+ final JMenuItem mi)
+ {
+ if(combo.getSelectedItem() == "--")
+ mi.setAccelerator(null);
+ else
+ {
+ char c[] = ((String)combo.getSelectedItem()).toCharArray();
+ int modifier = getModifierFromString((String)mod_combo.getSelectedItem());
+ mi.setAccelerator(KeyStroke.getKeyStroke(c[0], modifier));
+ }
+
+ if(ShortCut.usingCache())
+ new ShortCut(getText(), mi.getText(), mi.getAccelerator());
+ }
+
+ private int getModifierFromString(String modStr)
+ {
+ int modifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
+ if( modStr.equals("Alt") )
+ modifier = InputEvent.ALT_MASK;
+ else if( modStr.equals("Ctrl") )
+ modifier = InputEvent.CTRL_MASK;
+ else if( modStr.equals("Shift") )
+ modifier = InputEvent.SHIFT_MASK;
+ return modifier;
+ }
+
+ private String getModifierFromInt(int mod)
+ {
+ if( (InputEvent.ALT_MASK & mod) == InputEvent.ALT_MASK)
+ return "Alt";
+ else if( (InputEvent.CTRL_MASK & mod) == InputEvent.CTRL_MASK)
+ return "Ctrl";
+ else if( (InputEvent.SHIFT_MASK & mod) == InputEvent.SHIFT_MASK)
+ return "Shift";
+ return "Default";
+ }
+
+ public static String getKeyText(int keyCode)
+ {
+ if(keyCode >= KeyEvent.VK_0 && keyCode <= KeyEvent.VK_9 ||
+ keyCode >= KeyEvent.VK_A && keyCode <= KeyEvent.VK_Z)
+ return String.valueOf((char)keyCode);
+
+ switch(keyCode)
+ {
+ case KeyEvent.VK_BACK_SPACE: return "BACK_SPACE";
+ case KeyEvent.VK_CONTROL: return "CONTROL";
+ case KeyEvent.VK_LEFT: return "LEFT";
+ case KeyEvent.VK_UP: return "UP";
+ case KeyEvent.VK_RIGHT: return "RIGHT";
+ case KeyEvent.VK_DOWN: return "DOWN";
+
+ case KeyEvent.VK_DELETE: return "DELETE";
+
+ case KeyEvent.VK_BACK_QUOTE: return "BACK_QUOTE";
+
+ case KeyEvent.VK_KP_UP: return "KP_UP";
+ case KeyEvent.VK_KP_DOWN: return "KP_DOWN";
+ case KeyEvent.VK_KP_LEFT: return "KP_LEFT";
+ case KeyEvent.VK_KP_RIGHT: return "KP_RIGHT";
+ }
+
+ return "unknown(0x" + Integer.toString(keyCode, 16) + ")";
+ }
+
+ /**
+ * True if this menu has editable shortcuts
+ * @return
+ */
+ protected boolean isEditableShortCutMenu()
+ {
+ if(this instanceof SelectMenu ||
+ this instanceof EditMenu ||
+ this instanceof ViewMenu )
+ return true;
+ return false;
+ }
+
+ /**
+ * Override to apply any cached shortcuts
+ */
+ public JMenuItem add(JMenuItem menuItem)
+ {
+ JMenuItem mi = super.add(menuItem);
+ if(isEditableShortCutMenu() && ShortCut.usingCache())
+ ShortCut.applyShortCutFromCache(getText(), menuItem);
+ return mi;
+ }
+
+}
+
+class ComboBoxRenderer implements ListCellRenderer
+{
+ protected DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
+ private Vector<String> usedShortCutKeys;
+ public ComboBoxRenderer(Vector<String> usedShortCutKeys)
+ {
+ this.usedShortCutKeys = usedShortCutKeys;
+ }
+
+ public Component getListCellRendererComponent(JList list, Object value, int index,
+ boolean isSelected, boolean cellHasFocus)
+ {
+ JLabel renderer = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+ if (usedShortCutKeys.contains(value))
+ renderer.setBackground(Color.lightGray);
+ return renderer;
+ }
+}
+
+/**
+ * Editable shortcut sub-menu
+ */
+class SelectionSubMenu extends JMenu
+{
+ private static final long serialVersionUID = 1L;
+ private String parentMenuStr;
+ public SelectionSubMenu(SelectionMenu parentMenu, String str)
+ {
+ super(str);
+ parentMenuStr = parentMenu.getText();
+ }
+
+ public JMenuItem add(JMenuItem menuItem)
+ {
+ JMenuItem mi = super.add(menuItem);
+ if(ShortCut.usingCache())
+ ShortCut.applyShortCutFromCache(parentMenuStr, menuItem);
+ return mi;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/SelectionViewer.java b/uk/ac/sanger/artemis/components/SelectionViewer.java
new file mode 100644
index 0000000..4db4a3c
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/SelectionViewer.java
@@ -0,0 +1,352 @@
+/* SelectionViewer.java
+ *
+ * created: Thu Mar 4 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SelectionViewer.java,v 1.1 2004-06-09 09:47:45 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+
+/**
+ * This component displays the current selection in a FileViewer component.
+ *
+ * @author Kim Rutherford
+ * @version $Id: SelectionViewer.java,v 1.1 2004-06-09 09:47:45 tjc Exp $
+ **/
+
+public class SelectionViewer
+ implements SelectionChangeListener, EntryGroupChangeListener {
+ /**
+ * Create a new SelectionViewer object from the given Selection.
+ **/
+ public SelectionViewer (final Selection selection,
+ final EntryGroup entry_group) {
+ file_viewer = new FileViewer ("Artemis Selection View");
+
+ this.selection = selection;
+ this.entry_group = entry_group;
+
+ readSelection ();
+
+ selection.addSelectionChangeListener (this);
+
+ file_viewer.addWindowListener (new WindowAdapter () {
+ public void windowClosed (WindowEvent event) {
+ stopListening ();
+ }
+ });
+
+ entry_group.addEntryGroupChangeListener (this);
+ }
+
+ /**
+ * Remove this object as a selection change listener.
+ **/
+ public void stopListening () {
+ selection.removeSelectionChangeListener (this);
+ entry_group.removeEntryGroupChangeListener (this);
+ }
+
+ /**
+ * Implementation of the SelectionChangeListener interface. We listen to
+ * SelectionChange events so that we can update the view to reflect the
+ * current selection.
+ **/
+ public void selectionChanged (SelectionChangeEvent event) {
+ readSelection ();
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can get rid of the JFrame when the
+ * EntryGroup is no longer in use (for example when the EntryEdit is
+ * closed).
+ **/
+ public void entryGroupChanged (final EntryGroupChangeEvent event) {
+ switch (event.getType ()) {
+ case EntryGroupChangeEvent.DONE_GONE:
+ entry_group.removeEntryGroupChangeListener (this);
+ file_viewer.dispose ();
+ break;
+ }
+ }
+
+ /**
+ * Read the selection into this SelectionViewer object.
+ **/
+ public void readSelection () {
+ final String base_selection_text =
+ SelectionInfoDisplay.markerRangeText (selection, entry_group);
+
+ final FeatureVector selection_features = selection.getAllFeatures ();
+
+ final StringBuffer buffer = new StringBuffer ();
+
+ final int MAX_FEATURES = 50;
+
+ if (selection_features.size () > MAX_FEATURES) {
+ buffer.append ("first " + MAX_FEATURES + " features:\n\n");
+ }
+
+ for (int i = 0 ; i < selection_features.size () && i < 50 ; ++i) {
+ buffer.append (selection_features.elementAt (i).toString ());
+ }
+
+ if (selection_features.size () > 0) {
+ buffer.append ("\n");
+ }
+
+ if (base_selection_text != null && base_selection_text.length () > 0) {
+ buffer.append (base_selection_text).append ("\n\n");
+ }
+
+
+ // this is the maximum number of bases to show
+ final int display_base_count;
+
+ // this is the maximum number of residues to show
+ final int display_residues_count;
+
+ if (selection_features.size () == 0) {
+ display_base_count = 2000;
+ display_residues_count = 1000;
+ } else {
+ display_base_count = 600;
+ display_residues_count = 300;
+ }
+
+
+ final String selection_bases = selection.getSelectedBases ();
+
+ if (selection_bases != null && selection_bases.length () > 0) {
+ final StringVector base_summary = getBaseSummary (selection_bases);
+
+ for (int i = 0 ; i < base_summary.size () ; ++i) {
+ buffer.append (base_summary.elementAt (i)).append ("\n");
+ }
+ }
+
+ if (selection_features.size () == 1) {
+ double score =
+ selection_features.elementAt (0).get12CorrelationScore ();
+
+ score = Math.round (score * 100) / 100.0;
+
+ buffer.append ("\nCorrelation score of the selected feature: " +
+ score + "\n");
+ }
+
+ buffer.append ("\n");
+
+ if (selection_features.size () > 1) {
+ double correlation_score_total = 0;
+
+ double max_gc_content = -999;
+ double min_gc_content = 999;
+
+ double max_correlation_score = -999999;
+ double min_correlation_score = 999999;
+
+ for (int i = 0; i < selection_features.size () ; ++i) {
+ final Feature this_feature = selection_features.elementAt (i);
+
+ final double correlation_score = this_feature.get12CorrelationScore ();
+ final double gc_content = this_feature.getPercentGC ();
+
+ correlation_score_total += correlation_score;
+
+ if (min_correlation_score > correlation_score) {
+ min_correlation_score = correlation_score;
+ }
+
+ if (max_correlation_score < correlation_score) {
+ max_correlation_score = correlation_score;
+ }
+
+ if (min_gc_content > gc_content) {
+ min_gc_content = gc_content;
+ }
+
+ if (max_gc_content < gc_content) {
+ max_gc_content = gc_content;
+ }
+ }
+
+ min_gc_content = Math.round (min_gc_content * 100) / 100.0;
+ max_gc_content = Math.round (max_gc_content * 100) / 100.0;
+ min_correlation_score = Math.round (min_correlation_score * 100) / 100.0;
+ max_correlation_score = Math.round (max_correlation_score * 100) / 100.0;
+
+ final double correlation_score_average =
+ Math.round (correlation_score_total / selection_features.size () *
+ 100) / 100.0;
+
+ buffer.append ("Average correlation score of the selected features: " +
+ correlation_score_average + "\n\n");
+
+ buffer.append ("Min GC percentage of the selected features: " +
+ min_gc_content + "\n");
+ buffer.append ("Max GC percentage of the selected features: " +
+ max_gc_content + "\n");
+ buffer.append ("\n");
+ buffer.append ("Min correlation score of the selected features: " +
+ min_correlation_score + "\n");
+ buffer.append ("Max correlation score of the selected features: " +
+ max_correlation_score + "\n");
+ buffer.append ("\n");
+ }
+
+
+ if (selection_bases.length () > 0 && selection_features.size () <= 1) {
+ if (selection_bases.length () > display_base_count) {
+ // display the first display_base_count/2 bases and the last
+ // display_base_count/2 bases
+ buffer.append ("first " + display_base_count / 2 +
+ " bases of selection:\n");
+ final String start_sub_string =
+ selection_bases.substring (0, display_base_count / 2);
+ buffer.append (start_sub_string).append ("\n\n");
+ buffer.append ("last " + display_base_count / 2 +
+ " bases of selection:\n");
+ final String sub_string =
+ selection_bases.substring (selection_bases.length () -
+ display_base_count / 2);
+ buffer.append (sub_string).append ("\n\n");
+ } else {
+ buffer.append ("bases of selection:\n");
+ buffer.append (selection_bases).append ("\n\n");
+ }
+
+ if (selection_bases.length () >= 3) {
+ final int residues_to_display;
+
+ if (selection_bases.length () / 3 < display_residues_count) {
+ residues_to_display = selection_bases.length () / 3;
+
+ buffer.append ("translation of the selected bases:\n");
+ } else {
+ residues_to_display = display_residues_count;
+
+ buffer.append ("translation of the first " +
+ display_residues_count * 3 +
+ " selected bases:\n");
+ }
+
+ final String residue_bases =
+ selection_bases.substring (0, residues_to_display * 3);
+
+ final String residues =
+ AminoAcidSequence.getTranslation (residue_bases, true).toString ();
+
+ buffer.append (residues.toUpperCase ()).append ("\n\n");
+ }
+ }
+
+ file_viewer.setText (buffer.toString ());
+ }
+
+ /**
+ * Return a summary of the base content of the given String. The return
+ * value is a StringVector containing each line to be output. We return a
+ * vector so that the caller can do things like indenting the lines.
+ * @return null if the bases String is empty or null
+ **/
+ public static StringVector getBaseSummary (final String bases) {
+ if (bases == null || bases.length () == 0) {
+ return null;
+ }
+
+ final StringVector return_vector = new StringVector ();
+
+ long counts [] = new long [257];
+
+ for (int i = 0 ; i < bases.length () ; ++i) {
+ final char this_char = bases.charAt (i);
+
+ if (this_char > 255) {
+ // don't know what this is, but it isn't a base
+ ++counts[256];
+ } else {
+ ++counts[this_char];
+ }
+ }
+
+ for (int i = 0 ; i < 256 ; ++i) {
+ if (counts[i] > 0) {
+ return_vector.add (new String (Character.toUpperCase ((char)i) +
+ " content: " + counts[i] +
+ " (" + 10000 * counts[i] /
+ bases.length () / 100.0 + "%)"));
+ }
+ }
+
+ final long non_ambiguous_base_count =
+ counts['g'] + counts['c'] + counts['a'] + counts['t'];
+
+ if (non_ambiguous_base_count == bases.length ()) {
+ return_vector.add ("(no ambiguous bases)");
+ }
+
+ return_vector.add ("");
+
+ return_vector.add (new String ("GC percentage: " +
+ (Math.round (100.0 *
+ (counts['g'] + counts['c']) /
+ bases.length () * 100) /
+ 100.0)));
+
+ if (non_ambiguous_base_count > 0 &&
+ non_ambiguous_base_count < bases.length ()) {
+ return_vector.add (new String ("GC percentage of non-ambiguous bases: " +
+ (Math.round (100.0 * (counts['g'] +
+ counts['c']) /
+ non_ambiguous_base_count *
+ 100) / 100.0)));
+ }
+
+ return return_vector;
+ }
+
+ /**
+ * This is the selection object that we are viewing.
+ **/
+ final private Selection selection;
+
+ /**
+ * The FileViewer object that is displaying the selection.
+ **/
+ final private FileViewer file_viewer;
+
+ /**
+ * The EntryGroup that was passed to the constructor.
+ **/
+ final EntryGroup entry_group;
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/Selector.java b/uk/ac/sanger/artemis/components/Selector.java
new file mode 100644
index 0000000..3fd45fa
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/Selector.java
@@ -0,0 +1,1050 @@
+/* Selector.java
+ *
+ * created: Tue Apr 11 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/Selector.java,v 1.11 2008-01-17 16:00:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+
+import uk.ac.sanger.artemis.util.StringVector;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.StringTokenizer;
+
+import javax.swing.*;
+
+/**
+ * This component allows the user to set the selection by filtering the
+ * features in an EntryGroup on key and contents.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: Selector.java,v 1.11 2008-01-17 16:00:31 tjc Exp $
+ **/
+
+public class Selector extends JFrame
+ implements EntryGroupChangeListener {
+
+ private static final long serialVersionUID = 1L;
+ private JCheckBox by_key_button;
+ private JCheckBox by_qualifier_button;
+ private JCheckBox by_motif_button;
+ private JCheckBox less_than_bases_button;
+ private JCheckBox greater_than_bases_button;
+ private JCheckBox less_than_exons_button;
+ private JCheckBox greater_than_exons_button;
+ private JCheckBox ignore_case_checkbox;
+ private JCheckBox match_any_word_checkbox;
+ private JCheckBox forward_strand_checkbox;
+ private JCheckBox reverse_strand_checkbox;
+ private JCheckBox intron_pattern_button;
+ private KeyChoice key_selector;
+ private QualifierChoice qualifier_selector;
+ private JTextField qualifier_text;
+ private JTextField motif_text;
+ private JTextField less_than_bases_text;
+ private JTextField greater_than_bases_text;
+ private JTextField less_than_exons_text;
+ private JTextField greater_than_exons_text;
+
+ /**
+ * If checked the search text is allowed to match a substring of a
+ * qualifier value.
+ **/
+ final private JCheckBox partial_match_checkbox;
+
+ /**
+ * The EntryGroup object that was passed to the constructor.
+ **/
+ final private EntryGroup entry_group;
+
+ /**
+ * This is the Selection that was passed to the constructor.
+ **/
+ final private Selection selection;
+
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(Selector.class);
+
+ /**
+ * Create a new Selector that van set the given Selection.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group The component will choose features from this
+ * EntryGroup.
+ * @param goto_event_source The object the we will call gotoBase() on.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ public Selector(final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group)
+ {
+ super("Artemis Feature Selector");
+
+ this.selection = selection;
+ this.entry_group = entry_group;
+
+ final Font default_font = Options.getOptions().getFont();
+
+ setFont(default_font);
+
+ GridBagLayout gridbag = new GridBagLayout();
+ getContentPane().setLayout(gridbag);
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.WEST;
+ c.weighty = 0;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+
+ final JLabel top_label = new JLabel("Select by:");
+ gridbag.setConstraints(top_label, c);
+ getContentPane().add(top_label);
+
+
+ by_key_button = new JCheckBox("Key: ", false);
+ final JPanel by_key_panel = new JPanel();
+ by_key_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ by_key_panel.add(by_key_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints(by_key_panel, c);
+ getContentPane().add(by_key_panel);
+
+// final EntryInformation default_entry_information =
+// Options.getArtemisEntryInformation();
+
+ Entry default_entry = getEntryGroup().getDefaultEntry();
+ if(default_entry == null)
+ default_entry = getEntryGroup().elementAt(0);
+
+ final EntryInformation default_entry_information =
+ default_entry.getEntryInformation();
+
+ key_selector = new KeyChoice(default_entry_information);
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(key_selector, c);
+ getContentPane().add(key_selector);
+
+ key_selector.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent _)
+ {
+ by_key_button.setSelected(true);
+ }
+ });
+
+ by_key_button.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(!by_key_button.isSelected())
+ by_qualifier_button.setSelected(false);
+ }
+ });
+
+
+ by_qualifier_button = new JCheckBox("Qualifier: ", false);
+ final JPanel by_qualifier_panel = new JPanel();
+ by_qualifier_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ by_qualifier_panel.add(by_qualifier_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints(by_qualifier_panel, c);
+ getContentPane().add(by_qualifier_panel);
+
+ boolean isGFF = false;
+ if(getEntryGroup().getDefaultEntry() != null &&
+ getEntryGroup().getDefaultEntry().getEMBLEntry() instanceof GFFDocumentEntry)
+ isGFF = true;
+ qualifier_selector = new QualifierChoice(default_entry_information,
+ key_selector.getSelectedItem(),
+ null, isGFF);
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(qualifier_selector, c);
+ getContentPane().add(qualifier_selector);
+
+ qualifier_selector.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent _)
+ {
+ by_qualifier_button.setSelected(true);
+ by_key_button.setSelected(true);
+ }
+ });
+
+ key_selector.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent _)
+ {
+ qualifier_selector.setKey(key_selector.getSelectedItem());
+ }
+ });
+
+ by_qualifier_button.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(by_qualifier_button.isSelected())
+ {
+ if(!by_key_button.isSelected())
+ by_key_button.setSelected(true);
+ }
+ }
+ });
+
+
+ final JLabel qualifier_text_label =
+ new JLabel(" Containing this text: ");
+ c.gridwidth = 2;
+ gridbag.setConstraints(qualifier_text_label, c);
+ getContentPane().add(qualifier_text_label);
+
+ qualifier_text = new JTextField("", 18);
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(qualifier_text, c);
+ getContentPane().add(qualifier_text);
+
+
+ ignore_case_checkbox = new JCheckBox("Ignore Case", true);
+
+ final JPanel ignore_case_panel = new JPanel();
+
+ ignore_case_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ ignore_case_panel.add(ignore_case_checkbox);
+
+ c.gridwidth = 2;
+ gridbag.setConstraints(ignore_case_panel, c);
+ getContentPane().add(ignore_case_panel);
+
+
+ partial_match_checkbox = new JCheckBox("Allow Partial Match", true);
+
+ final JPanel partial_match_panel = new JPanel();
+
+ partial_match_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ partial_match_panel.add(partial_match_checkbox);
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(partial_match_panel, c);
+ getContentPane().add(partial_match_panel);
+
+
+ match_any_word_checkbox = new JCheckBox("Match Any Word", false);
+
+ final JPanel match_any_word_panel = new JPanel();
+
+ match_any_word_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ match_any_word_panel.add(match_any_word_checkbox);
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(match_any_word_panel, c);
+ getContentPane().add(match_any_word_panel);
+
+ andSeparator(gridbag, c);
+
+ less_than_bases_button = new JCheckBox("Up to: ", false);
+ final JPanel less_than_bases_panel = new JPanel();
+ less_than_bases_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ less_than_bases_panel.add(less_than_bases_button);
+ c.gridwidth = 3;
+ gridbag.setConstraints(less_than_bases_panel, c);
+ getContentPane().add(less_than_bases_panel);
+
+ less_than_bases_text = new JTextField("", 18);
+
+ gridbag.setConstraints(less_than_bases_text, c);
+ getContentPane().add(less_than_bases_text);
+
+ final JLabel less_than_bases_label = new JLabel(" bases long");
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(less_than_bases_label, c);
+ getContentPane().add(less_than_bases_label);
+
+ andSeparator(gridbag, c);
+
+ greater_than_bases_button = new JCheckBox("At least: ", false);
+ final JPanel greater_than_bases_panel = new JPanel();
+ greater_than_bases_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ greater_than_bases_panel.add(greater_than_bases_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints(greater_than_bases_panel, c);
+ getContentPane().add(greater_than_bases_panel);
+
+ greater_than_bases_text = new JTextField("", 18);
+
+ gridbag.setConstraints(greater_than_bases_text, c);
+ getContentPane().add(greater_than_bases_text);
+
+ final JLabel greater_than_bases_label = new JLabel(" bases long");
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(greater_than_bases_label, c);
+ getContentPane().add(greater_than_bases_label);
+
+ andSeparator(gridbag, c);
+
+ less_than_exons_button = new JCheckBox("Up to: ", false);
+ final JPanel less_than_exons_panel = new JPanel();
+ less_than_exons_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ less_than_exons_panel.add(less_than_exons_button);
+ c.gridwidth = 3;
+ gridbag.setConstraints(less_than_exons_panel, c);
+ getContentPane().add(less_than_exons_panel);
+
+ less_than_exons_text = new JTextField("", 18);
+
+ gridbag.setConstraints(less_than_exons_text, c);
+ getContentPane().add(less_than_exons_text);
+
+ final JLabel less_than_exons_label = new JLabel(" exons long");
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(less_than_exons_label, c);
+
+
+ getContentPane().add(less_than_exons_label);
+
+ andSeparator(gridbag, c);
+
+ greater_than_exons_button = new JCheckBox("At least: ", false);
+ final JPanel greater_than_exons_panel = new JPanel();
+ greater_than_exons_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ greater_than_exons_panel.add(greater_than_exons_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints(greater_than_exons_panel, c);
+ getContentPane().add(greater_than_exons_panel);
+
+ greater_than_exons_text = new JTextField("", 18);
+
+ gridbag.setConstraints(greater_than_exons_text, c);
+ getContentPane().add(greater_than_exons_text);
+
+ final JLabel greater_than_exons_label = new JLabel(" exons long");
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(greater_than_exons_label, c);
+ getContentPane().add(greater_than_exons_label);
+
+ andSeparator(gridbag, c);
+
+ intron_pattern_button = new JCheckBox("Contains introns without GT/GC start and AG end", false);
+ final JPanel intron_pattern_panel = new JPanel();
+ intron_pattern_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ intron_pattern_panel.add(intron_pattern_button);
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(intron_pattern_panel, c);
+ getContentPane().add(intron_pattern_panel);
+
+
+ {
+ // leave a blank line to give the user a visual clue
+ final JLabel blank_label = new JLabel("");
+
+ gridbag.setConstraints(blank_label, c);
+ getContentPane().add(blank_label);
+
+ final JLabel and_label = new JLabel("And by:");
+
+ gridbag.setConstraints(and_label, c);
+ getContentPane().add(and_label);
+ }
+
+
+ by_motif_button = new JCheckBox("Amino acid motif: ", false);
+ final JPanel by_motif_panel = new JPanel();
+ by_motif_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ by_motif_panel.add(by_motif_button);
+ c.gridwidth = 2;
+ gridbag.setConstraints(by_motif_panel, c);
+ getContentPane().add(by_motif_panel);
+
+ motif_text = new JTextField("", 18);
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(motif_text, c);
+ getContentPane().add(motif_text);
+
+ {
+ // leave a blank line to give the user a visual clue
+ final JLabel blank_label = new JLabel("");
+
+ gridbag.setConstraints(blank_label, c);
+ getContentPane().add(blank_label);
+ }
+
+ final JPanel strand_panel = new JPanel();
+
+ strand_panel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+ forward_strand_checkbox = new JCheckBox("Forward Strand Features", true);
+ strand_panel.add(forward_strand_checkbox);
+ forward_strand_checkbox.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent _)
+ {
+ if(!forward_strand_checkbox.isSelected() &&
+ !reverse_strand_checkbox.isSelected())
+ // make sure one of the strand is always selected
+ reverse_strand_checkbox.setSelected(true);
+ }
+ });
+
+
+ reverse_strand_checkbox = new JCheckBox("Reverse Strand Features", true);
+ strand_panel.add(reverse_strand_checkbox);
+ reverse_strand_checkbox.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent _)
+ {
+ if(!reverse_strand_checkbox.isSelected() &&
+ !forward_strand_checkbox.isSelected())
+ // make sure one of the strand is always selected
+ forward_strand_checkbox.setSelected(true);
+ }
+ });
+
+
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(strand_panel, c);
+ getContentPane().add(strand_panel);
+
+
+
+
+
+ final JButton select_button = new JButton("Select");
+
+ select_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ selection.set(getSelected());
+ }
+ });
+
+ final JButton view_button = new JButton("View");
+
+ view_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final FeaturePredicate predicate =
+ new FeatureFromVectorPredicate(getSelected());
+
+ String title = "All features";
+
+ if(by_key_button.isSelected())
+ title += " with key \"" + key_selector.getSelectedItem() + "\"";
+
+ if(by_qualifier_button.isSelected())
+ {
+ if(qualifier_text.getText().trim().length() > 0)
+ title += " with qualifier \"" + qualifier_selector.getSelectedItem() +
+ "\" containing text \"" + qualifier_text.getText() + "\"";
+ else
+ title += " with qualifier \"" +
+ qualifier_selector.getSelectedItem() + "\"";
+ }
+
+ if(forward_strand_checkbox.isSelected() &&
+ !reverse_strand_checkbox.isSelected())
+ title += " on the forward strand";
+
+ if(!forward_strand_checkbox.isSelected() &&
+ reverse_strand_checkbox.isSelected())
+ title += " on the reverse strand";
+
+ if(by_motif_button.isSelected())
+ title += " with motif: " + motif_text.getText().trim().toUpperCase();
+
+
+ if(less_than_bases_button.isSelected())
+ title += " at most " + less_than_bases_text.getText().trim() +
+ " bases long";
+
+ if(greater_than_bases_button.isSelected())
+ title += " at least " + greater_than_bases_text.getText().trim() +
+ " bases long";
+
+ if(less_than_exons_button.isSelected())
+ title += " at most " + less_than_exons_text.getText().trim() +
+ " exons long";
+
+
+ if(greater_than_exons_button.isSelected())
+ title += " at least " + greater_than_exons_text.getText().trim() +
+ " exons long";
+
+ if(intron_pattern_button.isSelected())
+ title += " containing introns without GT/GC start and AG end";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(entry_group, predicate, title);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame(title,
+ getSelection(),
+ goto_event_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible(true);
+ }
+ });
+
+
+ final JButton close_button = new JButton("Close");
+
+ close_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Selector.this.dispose();
+ }
+ });
+
+ final FlowLayout flow_layout =
+ new FlowLayout(FlowLayout.CENTER, 15, 5);
+
+ final JPanel bottom_button_panel = new JPanel(flow_layout);
+
+ bottom_button_panel.add(select_button);
+ bottom_button_panel.add(view_button);
+ bottom_button_panel.add(close_button);
+
+ gridbag.setConstraints(bottom_button_panel, c);
+ getContentPane().add(bottom_button_panel);
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ getEntryGroup().removeEntryGroupChangeListener(Selector.this);
+ Selector.this.dispose();
+ }
+ });
+
+ getEntryGroup().addEntryGroupChangeListener(this);
+ pack();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation(new Point((screen.width - getSize().width) / 2,
+ (screen.height - getSize().height) / 2));
+
+ setVisible(true);
+ }
+
+ private void andSeparator(GridBagLayout gridbag,
+ GridBagConstraints c)
+ {
+ // leave a blank line to give the user a visual clue
+ final JLabel blank_label = new JLabel("");
+
+ gridbag.setConstraints(blank_label, c);
+ getContentPane().add(blank_label);
+
+ final JLabel and_label = new JLabel("And:");
+
+ gridbag.setConstraints(and_label, c);
+ getContentPane().add(and_label);
+ }
+
+ /**
+ * Implementation of the EntryGroupChangeListener interface. We listen to
+ * EntryGroupChange events so that we can get rid of the Navigator when the
+ * EntryGroup is no longer in use(for example when the EntryEdit is
+ * closed).
+ **/
+ public void entryGroupChanged(final EntryGroupChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryGroupChangeEvent.DONE_GONE:
+ getEntryGroup().removeEntryGroupChangeListener(this);
+ dispose();
+ break;
+ }
+ }
+
+ /**
+ * Return those features that match the current setting of the Selector.
+ **/
+ private FeatureVector getSelected()
+ {
+ final FeaturePredicate key_and_qualifier_predicate;
+ final FeaturePredicate motif_predicate;
+
+ if(by_key_button.isSelected())
+ {
+ if(by_qualifier_button.isSelected())
+ {
+ final String search_text = qualifier_text.getText().trim();
+ final String qualifier_name =
+ (String) qualifier_selector.getSelectedItem();
+
+ if(search_text.length() == 0)
+ {
+ key_and_qualifier_predicate =
+ new FeatureKeyQualifierPredicate(key_selector.getSelectedItem(),
+ qualifier_name,
+ true);
+ }
+ else
+ {
+ if(match_any_word_checkbox.isSelected())
+ {
+ final FeaturePredicateVector temp_predicates =
+ new FeaturePredicateVector();
+
+ //final StringVector words =
+ // StringVector.getStrings(search_text, " ");
+
+ final StringTokenizer tok = new StringTokenizer(search_text, " \n");
+
+ while(tok.hasMoreTokens())
+ {
+ final String this_word = tok.nextToken().trim();
+ final FeaturePredicate new_predicate =
+ new FeatureKeyQualifierPredicate(key_selector.getSelectedItem(),
+ qualifier_name,
+ this_word,
+ partial_match_checkbox.isSelected(),
+ ignore_case_checkbox.isSelected());
+
+ temp_predicates.add(new_predicate);
+ }
+
+ key_and_qualifier_predicate =
+ new FeaturePredicateConjunction(temp_predicates,
+ FeaturePredicateConjunction.OR);
+ }
+ else
+ {
+ key_and_qualifier_predicate =
+ new FeatureKeyQualifierPredicate(key_selector.getSelectedItem(),
+ qualifier_name,
+ search_text,
+ partial_match_checkbox.isSelected(),
+ ignore_case_checkbox.isSelected());
+
+ }
+ }
+ }
+ else
+ {
+ final String search_text = qualifier_text.getText().trim();
+ if(search_text.length() == 0)
+ {
+ key_and_qualifier_predicate =
+ new FeatureKeyPredicate(key_selector.getSelectedItem());
+ }
+ else
+ {
+ if(match_any_word_checkbox.isSelected())
+ {
+ final FeaturePredicateVector temp_predicates =
+ new FeaturePredicateVector();
+
+ final StringVector words =
+ StringVector.getStrings(search_text, " ");
+
+
+ for(int i = 0 ; i < words.size() ; ++i)
+ {
+ final String this_word =(String)words.elementAt(i);
+ final FeaturePredicate new_predicate =
+ new FeatureKeyQualifierPredicate(key_selector.getSelectedItem(),
+ null, // match any qialifier
+ this_word,
+ partial_match_checkbox.isSelected(),
+ ignore_case_checkbox.isSelected());
+ temp_predicates.add(new_predicate);
+ }
+
+ key_and_qualifier_predicate =
+ new FeaturePredicateConjunction(temp_predicates,
+ FeaturePredicateConjunction.OR);
+ }
+ else
+ {
+ key_and_qualifier_predicate =
+ new FeatureKeyQualifierPredicate(key_selector.getSelectedItem(),
+ null, // match any qialifier
+ search_text,
+ partial_match_checkbox.isSelected(),
+ ignore_case_checkbox.isSelected());
+ }
+ }
+ }
+ }
+ else
+ key_and_qualifier_predicate = null;
+
+ if(by_motif_button.isSelected())
+ {
+ final AminoAcidSequence amino_acid_sequence =
+ new AminoAcidSequence(motif_text.getText().trim());
+
+ motif_predicate =
+ new FeaturePatternPredicate(amino_acid_sequence);
+ }
+ else
+ motif_predicate = null;
+
+
+ FeaturePredicate less_than_bases_predicate = null;
+ if(less_than_bases_button.isSelected() &&
+ less_than_bases_text.getText().trim().length() > 0)
+ {
+ try
+ {
+ final int less_than_bases_int =
+ Integer.valueOf(less_than_bases_text.getText().trim()).intValue();
+
+ less_than_bases_predicate = new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ if(feature.getBaseCount() <= less_than_bases_int)
+ return true;
+ else
+ return false;
+ }
+ };
+ }
+ catch(NumberFormatException e)
+ {
+ new MessageDialog(this,
+ "warning this is not a number: " +
+ less_than_bases_text.getText());
+ less_than_bases_predicate = null;
+ }
+ }
+
+
+ FeaturePredicate greater_than_bases_predicate = null;
+ if(greater_than_bases_button.isSelected() &&
+ greater_than_bases_text.getText().trim().length() > 0)
+ {
+ try
+ {
+ final int greater_than_bases_int =
+ Integer.valueOf(greater_than_bases_text.getText().trim()).intValue();
+
+ greater_than_bases_predicate = new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ if(feature.getBaseCount() >= greater_than_bases_int)
+ return true;
+ else
+ return false;
+ }
+ };
+ }
+ catch(NumberFormatException e)
+ {
+ new MessageDialog(this,
+ "warning this is not a number: " +
+ greater_than_bases_text.getText());
+ greater_than_bases_predicate = null;
+ }
+ }
+
+
+
+ FeaturePredicate less_than_exons_predicate = null;
+ if(less_than_exons_button.isSelected() &&
+ less_than_exons_text.getText().trim().length() > 0)
+ {
+ try
+ {
+ final int less_than_exons_int =
+ Integer.valueOf(less_than_exons_text.getText().trim()).intValue();
+
+ less_than_exons_predicate = new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ if(feature.getSegments().size() <= less_than_exons_int)
+ return true;
+ else
+ return false;
+ }
+ };
+ }
+ catch(NumberFormatException e)
+ {
+ new MessageDialog(this,
+ "warning this is not a number: " +
+ less_than_exons_text.getText());
+ less_than_exons_predicate = null;
+ }
+ }
+
+
+ FeaturePredicate greater_than_exons_predicate = null;
+ if(greater_than_exons_button.isSelected() &&
+ greater_than_exons_text.getText().trim().length() > 0)
+ {
+ try
+ {
+ final int greater_than_exons_int =
+ Integer.valueOf(greater_than_exons_text.getText().trim()).intValue();
+
+ greater_than_exons_predicate = new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ if(feature.getSegments().size() >= greater_than_exons_int)
+ return true;
+ else
+ return false;
+ }
+ };
+ }
+ catch(NumberFormatException e)
+ {
+ new MessageDialog(this,
+ "warning this is not a number: " +
+ greater_than_exons_text.getText());
+ greater_than_exons_predicate = null;
+ }
+ }
+
+
+ FeaturePredicate intron_pattern_predicate = null;
+ if(intron_pattern_button.isSelected())
+ intron_pattern_predicate = getIntronPredicate();
+
+ FeaturePredicate predicate = null;
+ if(!by_key_button.isSelected() && !by_motif_button.isSelected())
+ {
+ // default to selecting all features
+ predicate = new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ return true;
+ }
+ };
+ }
+ else
+ {
+ if(motif_predicate != null && key_and_qualifier_predicate != null)
+ {
+ predicate =
+ new FeaturePredicateConjunction(key_and_qualifier_predicate,
+ motif_predicate,
+ FeaturePredicateConjunction.AND);
+ }
+ else
+ {
+ if(motif_predicate != null)
+ predicate = motif_predicate;
+ else
+ predicate = key_and_qualifier_predicate;
+ }
+ }
+
+ if(less_than_bases_predicate != null)
+ {
+ predicate =
+ new FeaturePredicateConjunction(predicate,
+ less_than_bases_predicate,
+ FeaturePredicateConjunction.AND);
+ }
+
+ if(greater_than_bases_predicate != null)
+ {
+ predicate =
+ new FeaturePredicateConjunction(predicate,
+ greater_than_bases_predicate,
+ FeaturePredicateConjunction.AND);
+ }
+
+ if(less_than_exons_predicate != null)
+ {
+ predicate =
+ new FeaturePredicateConjunction(predicate,
+ less_than_exons_predicate,
+ FeaturePredicateConjunction.AND);
+ }
+
+ if(greater_than_exons_predicate != null)
+ {
+ predicate =
+ new FeaturePredicateConjunction(predicate,
+ greater_than_exons_predicate,
+ FeaturePredicateConjunction.AND);
+ }
+
+ if(intron_pattern_predicate !=null)
+ {
+ predicate =
+ new FeaturePredicateConjunction(predicate,
+ intron_pattern_predicate,
+ FeaturePredicateConjunction.AND);
+ }
+
+ final FeatureEnumeration test_enumerator = entry_group.features();
+ final FeatureVector return_features = new FeatureVector();
+
+ while(test_enumerator.hasMoreFeatures())
+ {
+ final Feature this_feature = test_enumerator.nextFeature();
+
+ if(predicate.testPredicate(this_feature))
+ {
+ if(this_feature.isForwardFeature())
+ {
+ if(forward_strand_checkbox.isSelected())
+ return_features.add(this_feature);
+ }
+ else
+ {
+ if(reverse_strand_checkbox.isSelected())
+ return_features.add(this_feature);
+ }
+ }
+ }
+
+ return return_features;
+ }
+
+ protected static FeaturePredicate getIntronPredicate()
+ {
+ return new FeaturePredicate()
+ {
+ public boolean testPredicate(final Feature feature)
+ {
+ if(feature.getSegments().size() < 2)
+ return false;
+
+ final Location location = feature.getLocation().copy();
+ final RangeVector ranges = location.getRanges();
+ Collections.sort(ranges, new Comparator<Range>(){
+ public int compare(final Range r1, final Range r2)
+ {
+ return r1.getStart()-r2.getStart();
+ }
+ });
+
+ final Strand strand = feature.getStrand();
+ for(int i = 0; i < ranges.size () -1; ++i)
+ {
+ final int end_of_range_1 =
+ ((Range)ranges.elementAt(i)).getEnd ();
+ final int start_of_range_2 =
+ ((Range)ranges.elementAt(i+1)).getStart ();
+
+ // ignore - the exons overlap so there is no room for an intron
+ if(end_of_range_1 > start_of_range_2)
+ continue;
+
+ try
+ {
+ Range feature_range = new Range(end_of_range_1 + 1,
+ start_of_range_2 - 1);
+
+ final char bases[];
+ if(location.isComplement())
+ {
+ final char tmp_bases[] = strand.getRawSubSequenceC(feature_range);
+ final char tmp2_bases[] = new char[feature_range.getCount()];
+
+ for(int j=0; j<tmp2_bases.length; j++)
+ tmp2_bases[j] = tmp_bases[j];
+ bases = Bases.reverseComplement( tmp2_bases );
+ }
+ else
+ bases = strand.getRawSubSequenceC(feature_range);
+
+ if(bases.length < 3)
+ return true;
+
+
+ int length = feature_range.getCount();
+ if( bases[0] != 'g' ||
+ (bases[1] != 't' && bases[1] != 'c') ||
+ bases[length-1] != 'g' ||
+ bases[length-2] != 'a' )
+ {
+ if(bases[length-1] != 'g' || bases[length-2] != 'a')
+ if(location.isComplement())
+ logger4j.info("INTRON SPLICE SITE MISSING AG: "+end_of_range_1);
+ else
+ logger4j.info("INTRON SPLICE SITE MISSING AG: "+start_of_range_2);
+
+ else
+ if(location.isComplement())
+ logger4j.info("INTRON SPLICE SITE MISSING GT/GC: "+start_of_range_2);
+ else
+ logger4j.info("INTRON SPLICE SITE MISSING GT/GC: "+end_of_range_1);
+ return true;
+ }
+
+ }
+ catch(uk.ac.sanger.artemis.util.OutOfRangeException oore)
+ {
+ }
+ }
+
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Return the Selection object that was passed to the constructor.
+ **/
+ private Selection getSelection()
+ {
+ return selection;
+ }
+
+ /**
+ * Return the EntryGroup object that was passed to the constructor.
+ **/
+ private EntryGroup getEntryGroup()
+ {
+ return entry_group;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/SequenceComboBox.java b/uk/ac/sanger/artemis/components/SequenceComboBox.java
new file mode 100644
index 0000000..1203c59
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/SequenceComboBox.java
@@ -0,0 +1,107 @@
+/* SequenceComboBox
+ *
+ * created: 2012
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2012 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Vector;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.text.JTextComponent;
+
+import uk.ac.sanger.artemis.components.genebuilder.AutoCompleteComboDocument;
+
+public abstract class SequenceComboBox extends JComboBox implements IndexReferenceListener
+{
+ private static final long serialVersionUID = 1L;
+ private ArrayList<IndexReferenceListener> indexReferenceListeners = new ArrayList<IndexReferenceListener>();
+
+ public SequenceComboBox(Vector<String> sequenceNames)
+ {
+ super(sequenceNames);
+ JTextComponent editor = (JTextComponent) getEditor().getEditorComponent();
+ editor.setDocument(new AutoCompleteComboDocument(this));
+ setEditable(true);
+ setMaximumRowCount(20);
+
+ addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ fireAction(new IndexReferenceEvent(SequenceComboBox.this));
+ }
+ });
+ }
+
+ public SequenceComboBox(String sequenceNames[])
+ {
+ this(new Vector<String>(Arrays.asList(sequenceNames)));
+ }
+
+ public void indexReferenceChanged(IndexReferenceEvent event)
+ {
+ final String contig =
+ ((String) ((SequenceComboBox)event.getSource()).getSelectedItem()).trim();
+ if(!contains(contig))
+ return;
+ if(!getSelectedItem().equals(contig))
+ setSelectedItem(contig);
+
+ update(event);
+ }
+
+ private void fireAction(IndexReferenceEvent event)
+ {
+ update(event);
+ for(IndexReferenceListener l: indexReferenceListeners)
+ l.indexReferenceChanged(event);
+ }
+
+ private boolean contains(Object o)
+ {
+ final DefaultComboBoxModel model = (DefaultComboBoxModel) getModel();
+ for(int i = 0; i < model.getSize(); i++)
+ {
+ Object obj = model.getElementAt(i);
+ if(obj.equals(o))
+ return true;
+ }
+ return false;
+ }
+
+ abstract public void update(IndexReferenceEvent event);
+
+ /**
+ * Adds the specified index reference listener to receive
+ * change events from this object.
+ * @param l the event change listener.
+ **/
+ public void addIndexReferenceListener(IndexReferenceListener l)
+ {
+ indexReferenceListeners.add(l);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/SequenceViewer.java b/uk/ac/sanger/artemis/components/SequenceViewer.java
new file mode 100644
index 0000000..c98f94b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/SequenceViewer.java
@@ -0,0 +1,333 @@
+/* SequenceViewer.java
+ *
+ * created: Sat Dec 19 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/SequenceViewer.java,v 1.1 2004-06-09 09:47:47 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.StringWriter;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.text.Style;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyledDocument;
+
+/**
+ * This component provides a viewer for dna or amino acid sequences. The
+ * units are numbered automatically, given a view like this:
+ * <pre>
+ * ATGATGATGATGATATGCATGATCG
+ * 10 20
+ * </pre>
+ * @author Kim Rutherford
+ * @version $Id: SequenceViewer.java,v 1.1 2004-06-09 09:47:47 tjc Exp $
+ **/
+
+public class SequenceViewer extends FileViewer {
+ /**
+ * Create a new SequenceViewer component.
+ * @param title The name to attach to the new JFrame.
+ * @param include_numbers If true then the sequence will be numbered
+ * (every second line of the display will be numbers rather than
+ * sequence).
+ **/
+ public SequenceViewer (final String title,
+ final boolean include_numbers) {
+ super (title, true, false, true);
+ this.include_numbers = include_numbers;
+ initMenu();
+ }
+
+ private void initMenu()
+ {
+ final JMenuBar mBar = getJMenuBar();
+ final JMenu viewMenu = new JMenu("View");
+ mBar.add(viewMenu);
+
+ final ButtonGroup group = new ButtonGroup();
+ final JRadioButtonMenuItem none = new JRadioButtonMenuItem("No Colour");
+ none.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ if(none.isSelected())
+ clearColour();
+ }
+ });
+ viewMenu.add(none);
+ group.add(none);
+
+ final JRadioButtonMenuItem taylor = new JRadioButtonMenuItem("Taylor Colour");
+ taylor.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ if(taylor.isSelected())
+ colourTaylor();
+ }
+ });
+ viewMenu.add(taylor);
+ group.add(taylor);
+
+ final JRadioButtonMenuItem rasmol = new JRadioButtonMenuItem("Rasmol Colour");
+ rasmol.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ colourRasmol();
+ }
+ });
+ viewMenu.add(rasmol);
+ group.add(rasmol);
+
+ final JRadioButtonMenuItem stops = new JRadioButtonMenuItem("Stop Codon Colour");
+ stops.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ colourStops();
+ }
+ });
+ viewMenu.add(stops);
+ group.add(stops);
+
+ final JRadioButtonMenuItem nuc = new JRadioButtonMenuItem("Nucleotide Colour");
+ nuc.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ colourNuc();
+ }
+ });
+ viewMenu.add(nuc);
+ group.add(nuc);
+
+
+ stops.setSelected(true);
+ // add clear style
+ final Style style = getTextPane().addStyle("clear", null);
+ StyleConstants.setBackground(style, Color.white);
+ }
+
+ /**
+ * Set the sequence to display.
+ * @param comment_line A comment to put on the first line. This may be
+ * null in which case there is no comment line. This String should not
+ * be "\n" terminated.
+ * @param sequence The sequence to display. This may be null in which case
+ * there is no comment line. This String should not be "\n" terminated.
+ **/
+ public void setSequence (final String comment_line, final String sequence) {
+ this.comment_line = comment_line;
+ this.sequence = sequence;
+
+ setView ();
+ }
+
+ /**
+ * Set the sequence to display with no comment line.
+ * @param sequence The sequence to display. This may be null in which case
+ * there is no comment line. This String should not be "\n" terminated.
+ **/
+ public void setSequence (final String sequence) {
+ this.comment_line = null;
+ this.sequence = sequence;
+
+ setView ();
+ }
+
+ /**
+ * Write the sequence and comment_line in the view.
+ **/
+ private void setView () {
+ final StringWriter string_writer = new StringWriter ();
+
+ final PrintWriter writer = new PrintWriter (string_writer);
+
+ if (comment_line != null) {
+ writer.println (comment_line);
+ }
+
+ String rest_of_sequence = sequence;
+
+ final int UNITS_PER_LINE = 60;
+
+ int line_count = 0;
+
+ while (rest_of_sequence != null) {
+ final String this_line;
+
+ if (rest_of_sequence.length () < UNITS_PER_LINE) {
+ this_line = rest_of_sequence;
+ rest_of_sequence = null;
+ } else {
+ this_line = rest_of_sequence.substring (0, UNITS_PER_LINE);
+ rest_of_sequence = rest_of_sequence.substring (UNITS_PER_LINE);
+ }
+
+
+ writer.println (this_line);
+
+ if (include_numbers) {
+ final int COUNT_INTERVAL = 10;
+
+ for (int i = 1 ;
+ i <= this_line.length () / COUNT_INTERVAL;
+ ++i) {
+ final int this_unit_count =
+ i * COUNT_INTERVAL + line_count * UNITS_PER_LINE;
+
+ final int number_of_spaces =
+ COUNT_INTERVAL - String.valueOf (this_unit_count).length ();
+
+ for (int space_index = 0 ;
+ space_index < number_of_spaces ;
+ ++space_index) {
+ writer.print (' ');
+ }
+
+ writer.print (this_unit_count);
+ }
+
+ writer.println ();
+ }
+
+ ++line_count;
+ }
+
+ writer.flush ();
+
+ setText (string_writer.toString ());
+
+ colourStops();
+ }
+
+ private void colourStops()
+ {
+ clearColour();
+ final StyledDocument doc = getTextPane().getStyledDocument();
+ final Style style = getTextPane().addStyle("Red", null);
+ StyleConstants.setBackground(style, Color.red);
+
+ final Matcher matcher = STOP_CODON_PATTERN.matcher(getText());
+ while( matcher.find() )
+ {
+ doc.setCharacterAttributes(matcher.start(), matcher.end()-matcher.start(),
+ getTextPane().getStyle("Red"), true);
+ }
+ }
+
+ /**
+ * Use Taylor colour scheme
+ * W.R.Taylor Protein Eng. vol.10 no. 7 pp743-746, 1997
+ */
+ private void colourTaylor()
+ {
+ clearColour();
+ applyColourScheme(org.emboss.jemboss.editor.SequenceProperties.taylorColor);
+ }
+
+ /**
+ * Use Rasmol colour scheme
+ */
+ private void colourRasmol()
+ {
+ clearColour();
+ applyColourScheme(org.emboss.jemboss.editor.SequenceProperties.rasmolColor);
+ }
+
+ /**
+ * Use Rasmol colour scheme
+ */
+ private void colourNuc()
+ {
+ clearColour();
+ applyColourScheme(org.emboss.jemboss.editor.SequenceProperties.baseColor);
+ }
+
+
+ private void applyColourScheme(final Hashtable<String, Color> colourHash)
+ {
+ final Enumeration<String> keys = colourHash.keys();
+ final StyledDocument doc = getTextPane().getStyledDocument();
+
+ while(keys.hasMoreElements())
+ {
+ final String aa = keys.nextElement();
+ Style style = getTextPane().addStyle(aa, null);
+ StyleConstants.setBackground(style, colourHash.get(aa));
+ }
+
+ final String seq = getText().toUpperCase();
+ int seqStart = 0;
+ if(seq.startsWith(">"))
+ {
+ final Matcher matcher = EOL_PATTERN.matcher(getText());
+ if(matcher.find())
+ seqStart = matcher.start();
+ if(seqStart < 0)
+ seqStart = 0;
+ }
+
+ for(int i=seqStart; i<seq.length(); i++)
+ {
+ String c = Character.toString( seq.charAt(i) );
+ if(getTextPane().getStyle(c) != null)
+ doc.setCharacterAttributes(i, 1, getTextPane().getStyle(c), true);
+ }
+ }
+
+ private void clearColour()
+ {
+ final StyledDocument doc = getTextPane().getStyledDocument();
+ doc.setCharacterAttributes(0, getText().length(), getTextPane().getStyle("clear"), true);
+ }
+
+
+ /** Pattern for locating stop codons */
+ private static Pattern STOP_CODON_PATTERN = Pattern.compile("[\\*#+]");
+ private static Pattern EOL_PATTERN = Pattern.compile("[\r\n]");
+
+ /** The sequence to display. */
+ private String sequence = "";
+
+ /** A comment to put on the first line. If null don't display any comment. */
+ private String comment_line = null;
+
+ /** If true then the amino acids will be numbered (every second line of the
+ display will be numbers rather than sequence). */
+ private final boolean include_numbers;
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/ShortCut.java b/uk/ac/sanger/artemis/components/ShortCut.java
new file mode 100644
index 0000000..7cb6f9b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ShortCut.java
@@ -0,0 +1,135 @@
+/* ShortCut.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import javax.swing.JMenuItem;
+import javax.swing.KeyStroke;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.database.DatabaseJPanel;
+
+/**
+ * Used to cache shortcut keystroke between sessions
+ */
+class ShortCut implements Serializable
+{
+ private static final long serialVersionUID = 1L;
+ private String shortCutName;
+ private String menuName;
+ private KeyStroke ks;
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(DatabaseJPanel.class);
+ private static String SHORTCUT_CACHE_PATH = Options.CACHE_PATH + File.separatorChar + "MenuShortCuts";
+ private static String REGEXP_REPLACE = "[ /]";
+
+ ShortCut(final String menuName, final String shortCutName, final KeyStroke ks)
+ {
+ this.menuName = menuName;
+ this.shortCutName = shortCutName;
+ this.ks = ks;
+ writeCache();
+ }
+
+ private void writeCache()
+ {
+ try
+ {
+ File dir = new File(SHORTCUT_CACHE_PATH);
+ if(!dir.exists())
+ dir.mkdirs();
+ FileOutputStream fos = new FileOutputStream(SHORTCUT_CACHE_PATH +
+ File.separatorChar + menuName + "#" + shortCutName.replaceAll(REGEXP_REPLACE, "_"));
+ ObjectOutputStream out = new ObjectOutputStream(fos);
+ out.writeObject(ShortCut.this);
+ out.close();
+ }
+ catch(Exception ex)
+ {
+ ex.getMessage();
+ }
+ }
+
+ /**
+ * Return true if shortcut_cache flag is set.
+ * @return
+ */
+ protected static boolean usingCache()
+ {
+ return Options.getOptions().getPropertyTruthValue("shortcut_cache");
+ }
+
+ private static File[] getCachedShortCuts()
+ {
+ File cacheDir = new File(SHORTCUT_CACHE_PATH);
+ if(cacheDir.exists())
+ return cacheDir.listFiles();
+ return null;
+ }
+
+ protected static void applyShortCutFromCache(final String menuName, final JMenuItem menuItem)
+ {
+ File[] shorts = getCachedShortCuts();
+ if(shorts != null)
+ {
+ String shortCutName = menuItem.getText();
+ for(int i=0; i<shorts.length; i++)
+ {
+ if(shorts[i].getName().equals(menuName + "#" + shortCutName.replaceAll(REGEXP_REPLACE, "_")) ||
+ shorts[i].getName().equals(menuName + ":" + shortCutName.replaceAll(REGEXP_REPLACE, "_")))
+ {
+ try
+ {
+ FileInputStream fis = new FileInputStream(shorts[i]);
+ ObjectInputStream in = new ObjectInputStream(fis);
+ ShortCut sc = (ShortCut) in.readObject();
+ in.close();
+ menuItem.setAccelerator(sc.ks);
+
+ logger4j.debug("USING CACHED SHORTCUT ("+menuItem.getText()+
+ ") FROM: "+shorts[i].getAbsolutePath());
+ }
+ catch (FileNotFoundException e)
+ {
+ System.err.println(e.getMessage());
+ }
+ catch (IOException e)
+ {
+ System.err.println(e.getMessage());
+ }
+ catch (ClassNotFoundException e)
+ {
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/Splash.java b/uk/ac/sanger/artemis/components/Splash.java
new file mode 100644
index 0000000..34a6d38
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/Splash.java
@@ -0,0 +1,1204 @@
+/* Splash.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.EntrySourceVector;
+import uk.ac.sanger.artemis.Logger;
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.util.InputStreamProgressEvent;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+/**
+ * Base class that creates a generic "Splash Screen"
+ * @author Kim Rutherford
+ **/
+
+abstract public class Splash extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+
+ /** Do any necessary cleanup then exit. */
+ abstract protected void exit();
+
+ /** A label for status and error messages. */
+ final private JLabel status_line = new JLabel("");
+
+ /** The program name that was passed to the constructor. */
+ private String program_name;
+
+ /** program version passed to the constructor. */
+ private String program_version;
+
+ /** JComponent to draw the main splash screen into */
+ private JComponent helix_canvas;
+
+ private JMenuBar menu_bar;
+ protected JMenu file_menu;
+ protected JMenu options_menu;
+
+ private JCheckBoxMenuItem geneCode[];
+ private String geneticCode;
+
+ private static boolean save_wd_properties = false;
+ public static boolean save_display_name = false;
+ public static boolean save_systematic_names = false;
+ /** The Artemis LogViewer. */
+ private final static LogViewer logger = new LogViewer();
+ private static String optionsLogString[];
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(Splash.class);
+
+ public Splash(final String program_title,
+ final String program_name)
+ {
+ super(program_title);
+ initLogger();
+
+ logger4j.info(System.getProperty("java.version"));
+ logger4j.info(System.getProperty("java.vendor"));
+ logger4j.info(System.getProperty("java.home"));
+ logger4j.info(System.getProperty("os.name"));
+ logger4j.info("Max. Heap Memory / Mb: "+ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax()/1000000);
+ logger4j.info("Starting application: "+program_name);
+
+ if(optionsLogString != null)
+ {
+ for(int i=0; i<optionsLogString.length; i++)
+ logger4j.info(optionsLogString[i]);
+ }
+ }
+
+ /**
+ * Create a new JFrame for a Splash screen.
+ * @param program_name The full name of the program.
+ * @param program_title The name to use in the title.
+ * @param program_version The version string.
+ **/
+ public Splash(final String program_name,
+ final String program_title,
+ final String program_version)
+ {
+ this(program_title+" "+program_version, program_name);
+
+ this.program_name = program_name;
+ this.program_version = program_version;
+
+ final ClassLoader cl = this.getClass().getClassLoader();
+ try
+ {
+ String line;
+ InputStream in = cl.getResourceAsStream("etc/versions");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ while((line = reader.readLine()) != null)
+ {
+ if(line.startsWith(program_title))
+ {
+ this.program_version = line.substring( program_title.length() ).trim();
+ setTitle(program_title+" "+this.program_version);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ logger4j.debug(ex.getMessage());
+ }
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ exitApp();
+ }
+ });
+
+ //final javax.swing.LookAndFeel look_and_feel =
+ // javax.swing.UIManager.getLookAndFeel();
+
+ final javax.swing.plaf.FontUIResource font_ui_resource =
+ Options.getOptions().getFontUIResource();
+
+ final Enumeration<Object> keys = UIManager.getDefaults().keys();
+ while(keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ Object value = UIManager.get(key);
+ if(value instanceof javax.swing.plaf.FontUIResource)
+ UIManager.put(key, font_ui_resource);
+ }
+
+ getContentPane().setLayout(new BorderLayout());
+ makeAllMenus();
+ helix_canvas = makeHelixCanvas();
+ status_line.setFont(Options.getOptions().getFont());
+
+ final int font_height =
+ this.getFontMetrics(status_line.getFont()).getHeight()+10;
+
+ status_line.setMinimumSize(new Dimension(100, font_height));
+ status_line.setPreferredSize(new Dimension(100, font_height));
+ status_line.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createRaisedBevelBorder(), BorderFactory.createLoweredBevelBorder()));
+
+ getContentPane().add(helix_canvas, "Center");
+ getContentPane().add(status_line, "South");
+
+ //ClassLoader cl = this.getClass().getClassLoader();
+ ImageIcon icon = new ImageIcon(cl.getResource("images/icon.gif"));
+
+ if(icon != null)
+ {
+ final Image icon_image = icon.getImage();
+ MediaTracker tracker = new MediaTracker(this);
+ tracker.addImage(icon_image, 0);
+
+ try
+ {
+ tracker.waitForAll();
+ setIconImage(icon_image);
+ }
+ catch(InterruptedException e)
+ {
+ // ignore and continue
+ }
+ }
+
+ pack();
+ setSize(460, 250);
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation(new Point((screen.width - getSize().width) / 2,
+ (screen.height - getSize().height) / 2));
+ registerForMacOSXEvents();
+ }
+
+ public static void initLogger()
+ {
+ logger.log("");
+ logger.getFileViewer().setHideOnClose(true);
+ final InputStream options_input_stream =
+ Splash.class.getResourceAsStream("/etc/log4j.properties");
+
+ if(options_input_stream != null)
+ {
+ Properties logProperties = new Properties();
+ try
+ {
+ logProperties.load(options_input_stream);
+ org.apache.log4j.PropertyConfigurator.configure(logProperties);
+ }
+ catch(FileNotFoundException e)
+ {
+ }
+ catch(IOException e)
+ {
+ }
+ }
+ }
+
+ public static void appendToLog(String s)
+ {
+ if(optionsLogString == null)
+ optionsLogString = new String[]{s};
+ else
+ {
+ String logStringTmp[] = new String[optionsLogString.length];
+ for(int i=0; i<logStringTmp.length; i++)
+ logStringTmp[i] = optionsLogString[i];
+ optionsLogString = new String[logStringTmp.length+1];
+
+ for(int i=0; i<logStringTmp.length; i++)
+ optionsLogString[i] = logStringTmp[i];
+
+ optionsLogString[optionsLogString.length-1] = s;
+ }
+ }
+
+ /**
+ * Generic registration with the Mac OS X application menu
+ * Checks the platform, then attempts to register with the Apple EAWT
+ */
+ private void registerForMacOSXEvents()
+ {
+ if(isWindows())
+ setWorkingDirectory();
+ if(isMac())
+ {
+ setWorkingDirectory();
+ try
+ {
+ // Generate and register the OSXAdapter, passing it a hash of all the methods we wish to
+ // use as delegates for various com.apple.eawt.ApplicationListener methods
+ Class<?> splashClass = Class.forName("uk.ac.sanger.artemis.components.Splash");
+ OSXAdapter.setQuitHandler(this,
+ splashClass.getDeclaredMethod("exitApp", (Class[])null));
+ OSXAdapter.setAboutHandler(this,
+ splashClass.getDeclaredMethod("about", (Class[])null));
+ //OSXAdapter.setPreferencesHandler(this,
+ // splashClass.getDeclaredMethod("preferences", (Class[])null));
+ OSXAdapter.setFileHandler(this,
+ splashClass.getDeclaredMethod("loadFile", new Class[] { String.class }));
+ }
+ catch (Exception e)
+ {
+ logger4j.error("Error while loading the OSXAdapter:");
+ logger4j.error(e.getMessage());
+ }
+ }
+ logger4j.info("Working directory: "+System.getProperty("user.dir"));
+ }
+
+ private boolean isMac()
+ {
+ return System.getProperty("mrj.version") != null ||
+ System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0;
+ }
+
+ private boolean isWindows()
+ {
+ String os = System.getProperty("os.name").toLowerCase();
+ logger4j.info("os.name "+os);
+ return (os.indexOf("win") >= 0);
+ }
+
+ protected void about()
+ {
+ ClassLoader cl = this.getClass().getClassLoader();
+ ImageIcon icon = new ImageIcon(cl.getResource("images/icon.gif"));
+
+ JOptionPane.showMessageDialog(this,
+ getTitle()+ "\nthis is free software and is distributed"+
+ "\nunder the terms of the GNU General Public License.",
+ "About", JOptionPane.INFORMATION_MESSAGE,
+ icon);
+ }
+
+ /**
+ * Web start properties need to begin with "javaws." or "jnlp.
+ */
+ protected static void processJnlpAttributes()
+ {
+ // JNLP properties
+ final Properties properties = System.getProperties();
+ for(String key : properties.stringPropertyNames())
+ {
+ if( key.equals("jnlp.black_belt_mode") ||
+ key.equals("jnlp.chado") ||
+ key.equals("jnlp.offset") ||
+ key.equals("jnlp.artemis.environment") ||
+ key.equals("jnlp.sanger_options") ||
+ key.equals("jnlp.read_only") ||
+ key.startsWith("jnlp.bam") ||
+ key.startsWith("jnlp.userplot") ||
+ key.startsWith("jnlp.loguserplot") ||
+ key.startsWith("jnlp.show_") )
+ System.setProperty(key.substring(5), System.getProperty(key));
+ }
+ }
+
+ protected void loadFile(final String fileName)
+ {
+ if(this instanceof ArtemisMain)
+ ((ArtemisMain)this).readArgsAndOptions(new String[]{ fileName }, this);
+ }
+
+
+ /**
+ * Return a JComponent object that will display a helix and a short
+ * copyright notice.
+ **/
+ private JComponent makeHelixCanvas()
+ {
+ return new JPanel()
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public void update(Graphics g)
+ {
+ paint(g);
+ }
+
+ /**
+ * Draws the splash screen text.
+ **/
+ public int textPaint(final Graphics graphics)
+ {
+ FontMetrics fm = this.getFontMetrics(graphics.getFont());
+ final int font_height = fm.getHeight() + 3;
+
+ Graphics2D g = (Graphics2D) graphics;
+ g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ g.setColor(Color.black);
+ final int left_margin = 150;
+ final int yPos = helix_height+5;
+
+ g.drawString(program_name,
+ left_margin, yPos+font_height);
+ g.drawString(program_version,
+ left_margin, yPos+(font_height * 2));
+ g.drawString(geneticCode,
+ left_margin, yPos+(font_height * 3));
+
+ g.drawString("Copyright 1998 - 2014",
+ left_margin, yPos+(font_height * 9 / 2));
+ g.drawString("Genome Research Limited",
+ left_margin, yPos+(font_height * 11 / 2));
+
+ return font_height;
+ }
+
+ public void paint(final Graphics g)
+ {
+ /*final boolean simple_splash_screen =
+ Options.getOptions().getPropertyTruthValue("simple_splash_screen");*/
+
+ g.setColor(Color.white);
+
+ g.fillRect(0, 0, this.getSize().width, this.getSize().height);
+
+ if(helix == null)
+ {
+ ClassLoader cl = this.getClass().getClassLoader();
+ ImageIcon helix_icon = new ImageIcon(cl.getResource("images/PSUlogo.gif"));
+ helix = helix_icon.getImage();
+
+ tracker = new MediaTracker(this);
+ tracker.addImage(helix, 0);
+
+ try
+ {
+ tracker.waitForAll();
+ helix_height = helix.getHeight(this);
+ //helix_width = helix.getWidth(this);
+ }
+ catch(InterruptedException e)
+ {
+ return;
+ }
+ }
+
+ g.drawImage(helix,
+ 0, 0, this);
+ //helix_height, this);
+
+ textPaint(g);
+ }
+
+ private MediaTracker tracker = null;
+
+ /** Sanger DNA logo. This is set in paint() */
+ private Image helix = null;
+
+
+ /** height of the Sanger DNA logo. This is set in paint(). */
+ private int helix_height;
+
+ };
+ }
+
+ /**
+ * Return the reference of the Label used as a status line.
+ **/
+ public JLabel getStatusLabel()
+ {
+ return status_line;
+ }
+
+ /**
+ * The possible sources for reading Entry objects.
+ **/
+ public EntrySourceVector getEntrySources(final JFrame frame)
+ {
+ return Utilities.getEntrySources(frame, stream_progress_listener);
+ }
+
+ /**
+ * Return an InputStreamProgressListener which updates the error label with
+ * the current number of chars read while reading
+ **/
+ public InputStreamProgressListener getInputStreamProgressListener()
+ {
+ return stream_progress_listener;
+ }
+
+ /**
+ * Force the options files to be re-read and the EntryEdit components to be
+ * redisplayed.
+ **/
+ private void resetOptions()
+ {
+ Options.getOptions().reset();
+ }
+
+ /**
+ * Make all the menus and menu items for the main window. Also sets up
+ * suitable ActionListener objects for each item.
+ */
+ private void makeAllMenus()
+ {
+ menu_bar = new JMenuBar();
+ file_menu = new JMenu("File");
+ file_menu.setMnemonic(KeyEvent.VK_F);
+
+ options_menu = new JMenu("Options");
+ options_menu.setMnemonic(KeyEvent.VK_O);
+
+ menu_bar.add(file_menu);
+ menu_bar.add(options_menu);
+
+ setJMenuBar(menu_bar);
+
+ ActionListener menu_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ resetOptions();
+ }
+ };
+ makeMenuItem(options_menu, "Re-read Options", menu_listener);
+
+ final JCheckBoxMenuItem enable_direct_edit_item =
+ new JCheckBoxMenuItem("Enable Direct Editing");
+ enable_direct_edit_item.setState(Options.getOptions().canDirectEdit());
+ enable_direct_edit_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ final boolean item_state = enable_direct_edit_item.getState();
+ Options.getOptions().setDirectEdit(item_state);
+ }
+ });
+ options_menu.add(enable_direct_edit_item);
+
+ options_menu.addSeparator();
+ options_menu.add(new JLabel(" --- Genetic Codes Tables ---"));
+
+ makeGeneticCodeMenu(options_menu);
+ options_menu.addSeparator();
+
+ final JCheckBoxMenuItem j2ssh_option = new JCheckBoxMenuItem(
+ "Send Searches via SSH");
+
+ j2ssh_option.setState((System.getProperty("j2ssh") != null));
+ j2ssh_option.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ final boolean item_state = j2ssh_option.getState();
+ if(item_state)
+ System.setProperty("j2ssh", "");
+ else
+ System.setProperty("j2ssh", "false");
+ }
+ });
+ options_menu.add(j2ssh_option);
+ options_menu.addSeparator();
+
+
+ final JCheckBoxMenuItem autohide_option = new JCheckBoxMenuItem(
+ "Auto hide scrollbar");
+ autohide_option.setState((System.getProperty("autohide") != null));
+
+ autohide_option.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ final boolean item_state = autohide_option.getState();
+ if(item_state)
+ System.setProperty("autohide", "");
+ else
+ System.setProperty("autohide", "false");
+ }
+ });
+ options_menu.add(autohide_option);
+ options_menu.addSeparator();
+
+
+ final JCheckBoxMenuItem highlight_active_entry_item =
+ new JCheckBoxMenuItem("Highlight Active Entry");
+ final boolean highlight_active_entry_state =
+ Options.getOptions().highlightActiveEntryFlag();
+ highlight_active_entry_item.setState(highlight_active_entry_state);
+ highlight_active_entry_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ final boolean item_state = highlight_active_entry_item.getState();
+ Options.getOptions().setHighlightActiveEntryFlag(item_state);
+ }
+ });
+ options_menu.add(highlight_active_entry_item);
+
+ if(Options.getOptions().getProperty("black_belt_mode") != null)
+ {
+ final JCheckBoxMenuItem black_belt_mode_item =
+ new JCheckBoxMenuItem("Black Belt Mode");
+
+ black_belt_mode_item.setState(Options.isBlackBeltMode());
+ black_belt_mode_item.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ final boolean item_state = black_belt_mode_item.getState();
+ if(item_state)
+ Options.getOptions().put("black_belt_mode", "true");
+ else
+ Options.getOptions().put("black_belt_mode", "false");
+ }
+ });
+ options_menu.add(black_belt_mode_item);
+ }
+
+ options_menu.addSeparator();
+ menu_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ showLog();
+ }
+ };
+ makeMenuItem(options_menu, "Show Log Window", menu_listener);
+
+ menu_listener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ Options.getOptions().setProperty("artemis.user.dir.prompt","yes");
+ setWorkingDirectory();
+ }
+ };
+ makeMenuItem(options_menu, "Set Working Directory...", menu_listener);
+
+ JMenu lafMenu = new JMenu("Look and Feel");
+ final LookAndFeelInfo[] lafInfo = UIManager.getInstalledLookAndFeels();
+ ButtonGroup group = new ButtonGroup();
+ for(int i=0; i<lafInfo.length; i++)
+ {
+ JCheckBoxMenuItem laf = new JCheckBoxMenuItem(lafInfo[i].getName());
+ group.add(laf);
+ lafMenu.add(laf);
+ final LookAndFeelInfo thisLAF = lafInfo[i];
+
+ if(UIManager.getLookAndFeel().getClass().getName().equals(thisLAF.getClassName()))
+ laf.setSelected(true);
+
+ laf.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ if(event.getStateChange() == ItemEvent.DESELECTED)
+ return;
+
+ Frame[] frames = JFrame.getFrames();
+ try
+ {
+ UIManager.setLookAndFeel(thisLAF.getClassName());
+ }
+ catch(Exception e1)
+ {
+ e1.printStackTrace();
+ return;
+ }
+
+ for(int i = 0; i < frames.length; i++)
+ {
+ SwingUtilities.updateComponentTreeUI(frames[i]);
+ frames[i].repaint();
+ }
+ logger4j.debug("Set look and feel to: " + thisLAF.getClassName());
+ }
+ });
+ }
+ options_menu.add(lafMenu);
+
+ // list JFrames that are open
+ final JMenu framesMenu = new JMenu("Windows");
+ menu_bar.add(framesMenu);
+ framesMenu.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ framesMenu.removeAll();
+ Frame[] frames = JFrame.getFrames();
+ for(int i=0;i<frames.length;i++)
+ {
+ if( !(frames[i] instanceof JFrame) ||
+ !frames[i].isVisible())
+ continue;
+
+ final JFrame thisFrame = (JFrame)frames[i];
+ JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(thisFrame.getTitle(), false);
+ if(thisFrame.isActive())
+ menuItem.setSelected(true);
+ framesMenu.add(menuItem);
+
+ menuItem.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ thisFrame.toFront();
+ thisFrame.requestFocus();
+ }
+ });
+ }
+ }
+ });
+ }
+
+ /**
+ *
+ * Set the working directory, used by the file manager.
+ *
+ */
+ public static void setWorkingDirectory()
+ {
+ final JTextField wdir = new JTextField(System.getProperty("user.dir")+" ");
+
+ if(Options.getOptions() != null &&
+ Options.getOptions().getProperty("artemis.user.dir") != null)
+ wdir.setText( Options.getOptions().getProperty("artemis.user.dir") );
+
+ if( Options.getOptions().getProperty("artemis.user.dir.prompt") != null &&
+ !Options.getOptions().getPropertyTruthValue("artemis.user.dir.prompt") )
+ return;
+
+ Box bdown = Box.createVerticalBox();
+ Box bacross = Box.createHorizontalBox();
+ JButton browse = new JButton("Browse...");
+ browse.addActionListener(new ActionListener ()
+ {
+ public void actionPerformed (ActionEvent e)
+ {
+ final StickyFileChooser file_dialog = new StickyFileChooser();
+ file_dialog.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ final int status = file_dialog.showOpenDialog(null);
+ if(status == JFileChooser.APPROVE_OPTION)
+ wdir.setText(file_dialog.getSelectedFile().getAbsolutePath());
+ }
+ });
+ bacross.add(wdir);
+ bacross.add(browse);
+ bdown.add(bacross);
+
+ bacross = Box.createHorizontalBox();
+ JCheckBox saveDir = new JCheckBox("Save between sessions", false);
+ bacross.add(saveDir);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+
+ bacross = Box.createHorizontalBox();
+ JCheckBox hide = new JCheckBox("Do not show this prompt again", false);
+ bacross.add(hide);
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+
+ Object[] possibleValues = { "OK" };
+ JOptionPane.showOptionDialog(null,
+ bdown,
+ "Set Working Directory",
+ JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE,null,
+ possibleValues, possibleValues[0]);
+
+ if(hide.isSelected() ||
+ Options.getOptions().getProperty("artemis.user.dir.prompt") != null )
+ {
+ Options.getOptions().setProperty("artemis.user.dir.prompt",
+ Boolean.toString(!hide.isSelected()));
+ }
+
+ if( (new File(wdir.getText().trim())).exists() )
+ {
+ System.setProperty("user.dir", wdir.getText().trim());
+ if(saveDir.isSelected())
+ save_wd_properties = true;
+ }
+ }
+
+ protected static void exitApp()
+ {
+ if(save_wd_properties || save_display_name || save_systematic_names ||
+ Options.getOptions().getProperty("artemis.user.dir.prompt") != null)
+ saveProperties();
+
+ // write the user project properties
+ ProjectProperty.writeProperties();
+ System.exit(0);
+ }
+
+ /**
+ *
+ * Save properties (working directory) between sessions.
+ *
+ */
+ private static void saveProperties()
+ {
+ String uhome = System.getProperty("user.home");
+ String fs = System.getProperty("file.separator");
+ String prop = uhome+fs+".artemis_options";
+
+ writeProperties(prop);
+ }
+
+ /**
+ *
+ * Write or re-write properties and insert/update the user.dir property
+ * @param jemProp properties file
+ * @param uHome user working directory
+ *
+ */
+ private static void writeProperties(final String prop)
+ {
+ File file_txt = new File(prop);
+ File file_tmp = new File(prop + ".tmp");
+ try
+ {
+ if(file_txt.exists())
+ {
+ BufferedReader bufferedreader = new BufferedReader(new FileReader(file_txt));
+ BufferedWriter bufferedwriter = new BufferedWriter(new FileWriter(file_tmp));
+ String line;
+
+ boolean prompt_saved = false;
+
+ while ((line = bufferedreader.readLine()) != null)
+ {
+ if(line.startsWith("artemis.user.dir") && save_wd_properties)
+ {
+ line = addEscapeChars("artemis.user.dir="+System.getProperty("user.dir"));
+ save_wd_properties = false;
+ }
+
+ if(line.startsWith("artemis.user.dir.prompt"))
+ {
+ line = addEscapeChars("artemis.user.dir.prompt="+
+ Options.getOptions().getProperty("artemis.user.dir.prompt"));
+ prompt_saved = true;
+ }
+
+ if(line.startsWith("display_name_qualifiers") && save_display_name)
+ {
+ String str = "display_name_qualifiers=";
+ StringVector strs = Options.getOptions().getDisplayQualifierNames();
+ for(int i=0; i<strs.size(); i++)
+ str = str.concat(" "+strs.get(i));
+ line = addEscapeChars(str);
+ save_display_name = false;
+ }
+
+ if(line.startsWith("systematic_name_qualifiers") && save_systematic_names)
+ {
+ String str = "systematic_name_qualifiers=";
+ StringVector strs = Options.getOptions().getSystematicQualifierNames();
+ for(int i=0; i<strs.size(); i++)
+ str = str.concat(" "+strs.get(i));
+ line = addEscapeChars(str);
+ save_systematic_names = false;
+ }
+ bufferedwriter.write(line);
+ bufferedwriter.newLine();
+ }
+
+ if(save_wd_properties)
+ {
+ bufferedwriter.write(
+ addEscapeChars("artemis.user.dir="+System.getProperty("user.dir")));
+ bufferedwriter.newLine();
+ }
+
+ if(!prompt_saved && Options.getOptions().getProperty("artemis.user.dir.prompt") != null)
+ {
+ bufferedwriter.write("artemis.user.dir.prompt="+
+ Options.getOptions().getProperty("artemis.user.dir.prompt"));
+ bufferedwriter.newLine();
+ }
+
+ if(save_display_name)
+ {
+ String str = "display_name_qualifiers=";
+ StringVector strs = Options.getOptions().getDisplayQualifierNames();
+ for(int i=0; i<strs.size(); i++)
+ str = str.concat(" "+strs.get(i));
+ bufferedwriter.write(addEscapeChars(str));
+ bufferedwriter.newLine();
+ }
+
+ if(save_systematic_names)
+ {
+ String str = "systematic_name_qualifiers=";
+ StringVector strs = Options.getOptions().getSystematicQualifierNames();
+ for(int i=0; i<strs.size(); i++)
+ str = str.concat(" "+strs.get(i));
+ bufferedwriter.write(addEscapeChars(str));
+ bufferedwriter.newLine();
+ }
+
+ bufferedreader.close();
+ bufferedwriter.close();
+ file_txt.delete();
+ file_tmp.renameTo(file_txt);
+ }
+ else // no existing options file
+ {
+ BufferedWriter bufferedwriter = new BufferedWriter(new FileWriter(file_txt));
+
+ if(save_wd_properties)
+ {
+ bufferedwriter.write(
+ addEscapeChars("artemis.user.dir="+System.getProperty("user.dir")));
+ bufferedwriter.newLine();
+ }
+
+ if(save_display_name)
+ {
+ String str = "display_name_qualifiers=";
+ StringVector strs = Options.getOptions().getDisplayQualifierNames();
+ for(int i=0; i<strs.size(); i++)
+ str = str.concat(" "+strs.get(i));
+ bufferedwriter.write(addEscapeChars(str));
+ bufferedwriter.newLine();
+ }
+
+ if(save_systematic_names)
+ {
+ String str = "systematic_name_qualifiers=";
+ StringVector strs = Options.getOptions().getSystematicQualifierNames();
+ for(int i=0; i<strs.size(); i++)
+ str = str.concat(" "+strs.get(i));
+ bufferedwriter.write(addEscapeChars(str));
+ bufferedwriter.newLine();
+ }
+ bufferedwriter.close();
+ }
+ }
+ catch (FileNotFoundException filenotfoundexception)
+ {
+ System.err.println(prop+" read error");
+ }
+ catch (IOException e)
+ {
+ System.err.println(prop+" i/o error");
+ }
+
+ }
+
+
+ /**
+ *
+ * Add in escape chars (for windows) to the backslash chars
+ * @param l string to insert escape characters to
+ *
+ */
+ private static String addEscapeChars(String l)
+ {
+ int n = l.indexOf("\\");
+
+ while( n > -1)
+ {
+ l = l.substring(0,n)+"\\"+l.substring(n,l.length());
+ n = l.indexOf("\\",n+2);
+ }
+ return l;
+ }
+
+ /**
+ *
+ * Construct menu for genetic code tables.
+ *
+ */
+ private void makeGeneticCodeMenu(final JMenu options_menu)
+ {
+ // available genetic codes
+ StringVector v_genetic_codes = Options.getOptions().getOptionValues("genetic_codes");
+ String gcodes[] = (String[])v_genetic_codes.toArray(new String[v_genetic_codes.size()]);
+
+ // get the default
+ StringVector gcode_default = Options.getOptions().getOptionValues("genetic_code_default");
+
+ // determine default genetic code table
+ int default_code = 0;
+ if(gcode_default != null)
+ {
+ String defS = (String)gcode_default.elementAt(0);
+ if(defS.length() < 3)
+ {
+ try
+ {
+ int num = Integer.parseInt(defS);
+ if(num > 0 && num <= gcodes.length)
+ default_code = num-1;
+ else
+ System.err.println(defS+" is not a valid number");
+ }
+ catch(NumberFormatException nfe)
+ {
+ System.err.println(defS+" is not a valid number");
+ }
+ }
+ }
+
+ ButtonGroup gcodeGroup = new ButtonGroup();
+ geneCode = new JCheckBoxMenuItem[gcodes.length];
+
+ for(int i = 0; i< gcodes.length; i++)
+ {
+ if(gcodes[i].equals("-"))
+ continue;
+
+ int ind1;
+ while((ind1 = gcodes[i].indexOf("_")) > -1)
+ gcodes[i] = gcodes[i].substring(0,ind1) + " " +
+ gcodes[i].substring(ind1+1,gcodes[i].length());
+
+ String num = Integer.toString(i+1);
+ final String gc_name = num+". "+gcodes[i];
+ geneCode[i] = new JCheckBoxMenuItem(gc_name);
+ gcodeGroup.add(geneCode[i]);
+ final int menuNum = i;
+ geneCode[i].setActionCommand(num);
+
+ geneCode[i].addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent event)
+ {
+ if(geneCode[menuNum].getState())
+ {
+ geneticCode = gc_name;
+ String tab = "translation_table_"+geneCode[menuNum].getActionCommand();
+ String startCodons = "start_codons_"+geneCode[menuNum].getActionCommand();
+
+ StringVector options_file_table =
+ Options.getOptions().getOptionValues(tab);
+
+ if(options_file_table != null)
+ {
+ if(options_file_table.size() == 64)
+ {
+ StringBuffer sbuff = new StringBuffer();
+ for(int i = 0; i < 64; ++i)
+ sbuff.append(options_file_table.elementAt(i)+" ");
+
+ Options.getOptions().setGeneticCode(sbuff.toString());
+ }
+ else
+ {
+ StringVector table = Options.getOptions().getOptionValues("translation_table_1");
+
+ for(int i = 0; i < options_file_table.size(); ++i)
+ {
+ String cod_plus_aa = (String)options_file_table.elementAt(i);
+// System.out.println(cod_plus_aa);
+ final int codon_index = Bases.getIndexOfBase(cod_plus_aa.charAt(0)) * 16 +
+ Bases.getIndexOfBase(cod_plus_aa.charAt(1)) * 4 +
+ Bases.getIndexOfBase(cod_plus_aa.charAt(2));
+
+// System.out.println(cod_plus_aa.substring(3)+" "+codon_index+" "+
+// table.elementAt(codon_index));
+ table.setElementAt(cod_plus_aa.substring(3), codon_index);
+ }
+
+ StringBuffer sbuff = new StringBuffer();
+ for(int i = 0; i < 64; ++i)
+ sbuff.append(table.elementAt(i)+" ");
+
+ Options.getOptions().setGeneticCode(sbuff.toString());
+ }
+
+ options_file_table =
+ Options.getOptions().getOptionValues(startCodons);
+
+ if(options_file_table != null)
+ {
+ StringBuffer sbuff = new StringBuffer();
+ for(int i = 0; i < options_file_table.size(); ++i)
+ sbuff.append(options_file_table.elementAt(i)+" ");
+
+ Options.getOptions().setProperty("start_codons",sbuff.toString());
+ }
+ }
+
+ AminoAcidSequence.setGeneCode();
+
+ if(helix_canvas != null)
+ helix_canvas.repaint();
+ }
+ }
+ });
+ options_menu.add(geneCode[i]);
+
+ if(i == default_code)
+ geneCode[i].setState(true);
+ }
+ }
+
+ /**
+ * Set the Genetic Code number
+ * @param geneticCodeValue
+ */
+ public void setTranslationTable(final String geneticCodeValue)
+ {
+ for(int i=0;i<geneCode.length;i++)
+ {
+ if(geneCode[i] == null)
+ continue;
+
+ if( geneCode[i].getActionCommand().equals(geneticCodeValue) &&
+ !geneCode[i].getState() )
+ {
+ int val = JOptionPane.showConfirmDialog(null,
+ "Change translation table to:\n"+
+ geneCode[i].getText(),
+ "Confirm Translation Table Change",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(val == JOptionPane.CANCEL_OPTION)
+ return;
+ logger4j.debug("SET GENETIC CODE translationTable="
+ + geneticCodeValue);
+ geneCode[i].setState(true);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Make a new menu item in the given menu, with its label given the
+ * String and add the given ActionListener to it.
+ */
+ protected static void makeMenuItem(JMenu menu, String name,
+ ActionListener listener)
+ {
+ JMenuItem new_item = new JMenuItem(name);
+ menu.add(new_item);
+ new_item.addActionListener(listener);
+ if(name.equals("Open ..."))
+ new_item.setAccelerator(KeyStroke.getKeyStroke
+ (KeyEvent.VK_O, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); // InputEvent.CTRL_MASK));
+ }
+
+ /**
+ * Return a Logger for warnings/errors/messages.
+ **/
+ protected static Logger getLogger()
+ {
+ return logger;
+ }
+
+ /**
+ * Return the JComponent that the Splash screen is drawing on.
+ **/
+ public JComponent getCanvas()
+ {
+ return helix_canvas;
+ }
+
+ /**
+ * Make the LogViewer visible.
+ **/
+ public static void showLog()
+ {
+ logger.setVisible(true);
+ }
+
+ /**
+ * An InputStreamProgressListener used to update the error label with the
+ * current number of chars read.
+ **/
+ private final InputStreamProgressListener stream_progress_listener =
+ new InputStreamProgressListener() {
+ public void progressMade(final InputStreamProgressEvent event)
+ {
+ final int char_count = event.getCharCount();
+ if(char_count == -1)
+ getStatusLabel().setText("");
+ else
+ getStatusLabel().setText("chars read so far: " + char_count);
+ }
+
+ public void progressMade(String progress)
+ {
+ getStatusLabel().setText(progress);
+ }
+
+ };
+}
diff --git a/uk/ac/sanger/artemis/components/StickyFileChooser.java b/uk/ac/sanger/artemis/components/StickyFileChooser.java
new file mode 100644
index 0000000..caf1e98
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/StickyFileChooser.java
@@ -0,0 +1,95 @@
+/* StickyFileChooser.java
+ *
+ * created: Mon Sep 1 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/StickyFileChooser.java,v 1.1 2004-06-09 09:47:49 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Options;
+import java.io.File;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+
+/**
+ * A JFileChooser that remembers which directory it is in for next time.
+ * @author Kim Rutherford
+ **/
+
+public class StickyFileChooser extends JFileChooser
+{
+ private static final long serialVersionUID = 1L;
+ /**
+ * Used to remember the directory the JFileChooser was in when the user
+ * pressed OK. This is used as the starting directory next time.
+ **/
+ private static File last_directory = null;
+
+ /**
+ * Create a new StickyFileChooser and set the current directory to what is
+ * was after the last call to StickyFileChooser.showOpenDialog() or
+ * StickyFileChooser.showSaveDialog().
+ **/
+ public StickyFileChooser()
+ {
+ super();
+
+ if (last_directory == null)
+ {
+ if (Options.getOptions ().getProperty ("default_directory") != null)
+ {
+ final String default_directory =
+ Options.getOptions().getProperty("default_directory");
+ setCurrentDirectory (new File (default_directory));
+ }
+ else
+ {
+ if (last_directory == null)
+ last_directory = new File (System.getProperty ("user.dir"));
+ }
+ }
+
+ setCurrentDirectory(last_directory);
+ setSize(620, 460);
+ }
+
+ /**
+ * Calls super.showOpenDialog() then remembers the current directory.
+ **/
+ public int showOpenDialog(JFrame owner)
+ {
+ int status = super.showOpenDialog(owner);
+ last_directory = getCurrentDirectory ();
+ return status;
+ }
+
+ /**
+ * Calls super.showSaveDialog() then remembers the current directory.
+ **/
+ public int showSaveDialog(JFrame owner)
+ {
+ int status = super.showSaveDialog (owner);
+ last_directory = getCurrentDirectory ();
+ return status;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/SwingWorker.java b/uk/ac/sanger/artemis/components/SwingWorker.java
new file mode 100644
index 0000000..6d03d4d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/SwingWorker.java
@@ -0,0 +1,150 @@
+/********************************************************************
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Library General Public
+* License as published by the Free Software Foundation; either
+* version 2 of the License, or (at your option) any later version.
+*
+* This library 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
+* Library General Public License for more details.
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this library; if not, write to the
+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+********************************************************************/
+
+package uk.ac.sanger.artemis.components;
+
+import javax.swing.SwingUtilities;
+
+/**
+* This is the 3rd version of SwingWorker (also known as
+* SwingWorker 3), an abstract class that you subclass to
+* perform GUI-related work in a dedicated thread. For
+* instructions on using this class, see:
+*
+* http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html
+*
+* Note that the API changed slightly in the 3rd version:
+* You must now invoke start() on the SwingWorker after
+* creating it.
+*/
+public abstract class SwingWorker {
+ private Object value; // see getValue(), setValue()
+ private Thread thread;
+
+ /**
+ * Class to maintain reference to current worker thread
+ * under separate synchronization control.
+ */
+ private static class ThreadVar {
+ private Thread thread;
+ ThreadVar(Thread t) { thread = t; }
+ synchronized Thread get() { return thread; }
+ synchronized void clear() { thread = null; }
+ }
+
+ private ThreadVar threadVar;
+
+ /**
+ * Get the value produced by the worker thread, or null if it
+ * hasn't been constructed yet.
+ */
+ protected synchronized Object getValue() {
+ return value;
+ }
+
+ /**
+ * Set the value produced by worker thread
+ */
+ private synchronized void setValue(Object x) {
+ value = x;
+ }
+
+ /**
+ * Compute the value to be returned by the <code>get</code> method.
+ */
+ public abstract Object construct();
+
+ /**
+ * Called on the event dispatching thread (not on the worker thread)
+ * after the <code>construct</code> method has returned.
+ */
+ public void finished() {
+ }
+
+ /**
+ * A new method that interrupts the worker thread. Call this method
+ * to force the worker to stop what it's doing.
+ */
+ public void interrupt() {
+ Thread t = threadVar.get();
+ if (t != null) {
+ t.interrupt();
+ }
+ threadVar.clear();
+ }
+
+ /**
+ * Return the value created by the <code>construct</code> method.
+ * Returns null if either the constructing thread or the current
+ * thread was interrupted before a value was produced.
+ *
+ * @return the value created by the <code>construct</code> method
+ */
+ public Object get() {
+ while (true) {
+ Thread t = threadVar.get();
+ if (t == null) {
+ return getValue();
+ }
+ try {
+ t.join();
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt(); // propagate
+ return null;
+ }
+ }
+ }
+
+
+ /**
+ * Start a thread that will call the <code>construct</code> method
+ * and then exit.
+ */
+ public SwingWorker() {
+ final Runnable doFinished = new Runnable() {
+ public void run() { finished(); }
+ };
+
+ Runnable doConstruct = new Runnable() {
+ public void run() {
+ try {
+ setValue(construct());
+ }
+ finally {
+ threadVar.clear();
+ }
+
+ SwingUtilities.invokeLater(doFinished);
+ }
+ };
+
+ Thread t = new Thread(doConstruct);
+ threadVar = new ThreadVar(t);
+ }
+
+ /**
+ * Start the worker thread.
+ */
+ public void start() {
+ Thread t = threadVar.get();
+ if (t != null) {
+ t.start();
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/TextDialog.java b/uk/ac/sanger/artemis/components/TextDialog.java
new file mode 100644
index 0000000..f263cb4
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/TextDialog.java
@@ -0,0 +1,119 @@
+/* TextDialog.java
+ *
+ * created: Mon Jan 11 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/TextDialog.java,v 1.1 2004-06-09 09:47:52 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+
+import javax.swing.*;
+
+/**
+ * A popup TextField JDialog with an OK and a Cancel button.
+ *
+ * @author Kim Rutherford
+ * @version $Id: TextDialog.java,v 1.1 2004-06-09 09:47:52 tjc Exp $
+ **/
+
+public class TextDialog extends JDialog {
+ /**
+ * Create a new TextDialog component with the given prompt. Other
+ * components can listen for TextDialogEvent object.
+ * @param parent The parent window.
+ * @param prompt A message that is displayed in the component beside the
+ * TextArea that the user types into. This String is also used as the
+ * JFrame title.
+ * @param width The width of the new TextField.
+ * @param initial_text The initial text to put in the TextField.
+ **/
+ public TextDialog (final JFrame parent,
+ final String prompt,
+ final int width,
+ final String initial_text) {
+ super (parent, prompt, true);
+
+ getContentPane ().add (new JLabel (prompt), "North");
+
+ final JPanel panel = new JPanel ();
+
+ panel.add (ok_button);
+ ok_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ text = text_field.getText ();
+ TextDialog.this.dispose ();
+ }
+ });
+
+ panel.add (cancel_button);
+ cancel_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ text = null;
+ TextDialog.this.dispose ();
+ }
+ });
+
+ getContentPane ().add (panel, "South");
+
+ text_field = new JTextField (initial_text, width);
+
+ text_field.addKeyListener (new KeyAdapter () {
+ public void keyTyped(final KeyEvent e) {
+ if (e.getKeyChar () == '\n') {
+ text = text_field.getText ();
+ TextDialog.this.dispose ();
+ }
+ }
+ });
+
+ getContentPane ().add (text_field, "Center");
+
+ pack ();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+ }
+
+ /**
+ * Wait for a user action then return the text if the user hit OK or null
+ * if the user hit Cancel.
+ **/
+ public String getText () {
+ setVisible (true);
+
+ return text;
+ }
+
+ private final JButton ok_button = new JButton ("OK");
+ private final JButton cancel_button = new JButton ("Cancel");
+ private JTextField text_field = null;
+
+ /**
+ * Set to null if and only if the user cancels the dialog, otherwise
+ * contains the text that the user entered.
+ **/
+ private String text = null;
+}
diff --git a/uk/ac/sanger/artemis/components/TextFieldSink.java b/uk/ac/sanger/artemis/components/TextFieldSink.java
new file mode 100644
index 0000000..536400a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/TextFieldSink.java
@@ -0,0 +1,179 @@
+/********************************************************************
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Library General Public
+* License as published by the Free Software Foundation; either
+* version 2 of the License, or (at your option) any later version.
+*
+* This library 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
+* Library General Public License for more details.
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this library; if not, write to the
+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*
+* Copyright (C) Genome Research Limited
+*
+********************************************************************/
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.datatransfer.*;
+
+import javax.swing.*;
+import java.awt.event.*;
+import javax.swing.border.*;
+
+import uk.ac.sanger.artemis.components.database.DatabaseTreeNode;
+
+import java.awt.dnd.*;
+import java.awt.*;
+import java.io.*;
+
+
+/**
+*
+* Extends JTextField to enable pasting & drag and drop into it.
+*
+*/
+public class TextFieldSink extends JTextField implements DropTargetListener
+{
+ private DatabaseTreeNode dbNode = null;
+
+ public TextFieldSink(String text, int columns)
+ {
+ super(text, columns);
+ //pasting
+ addMouseListener(new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ if(e.getClickCount() == 2)
+ {
+ pasteText();
+ e.consume();
+ };
+ }
+ });
+
+ setDropTarget(new DropTarget(this,this));
+ }
+
+ public TextFieldSink()
+ {
+ this("",0);
+ }
+
+ public void pasteText()
+ {
+ Clipboard c = this.getToolkit().getSystemClipboard();
+
+ Transferable t = c.getContents(this);
+ if(t==null)
+ {
+ this.getToolkit().beep();
+ return;
+ }
+ try
+ {
+ if(t.isDataFlavorSupported(DataFlavor.stringFlavor))
+ {
+ String s = (String) t.getTransferData(DataFlavor.stringFlavor);
+ this.replaceSelection(s);
+ }
+ else
+ this.getToolkit().beep();
+ }
+ catch (UnsupportedFlavorException ex) { this.getToolkit().beep(); }
+ catch (IOException ex) { this.getToolkit().beep(); }
+
+ }
+
+ protected static Border dropBorder = new BevelBorder(BevelBorder.LOWERED);
+ protected static Border endBorder =
+ BorderFactory.createLineBorder(Color.black);
+
+ public void dragEnter(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(DataFlavor.stringFlavor) ||
+ e.isDataFlavorSupported(DatabaseTreeNode.DATABASETREENODE))
+ {
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ this.setBorder(dropBorder);
+ }
+ }
+
+ public void dragExit(DropTargetEvent e)
+ {
+ this.setBorder(null);
+ }
+
+ public void drop(DropTargetDropEvent e)
+ {
+ this.setBorder(endBorder);
+ Transferable t = e.getTransferable();
+
+ if(t.isDataFlavorSupported(DatabaseTreeNode.DATABASETREENODE))
+ {
+ try
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ dbNode =
+ (DatabaseTreeNode)t.getTransferData(DatabaseTreeNode.DATABASETREENODE);
+
+ String name = dbNode.getOrganismCommonName();
+ this.replaceSelection(name+":featureId="+dbNode.getFeatureId());
+
+ e.dropComplete(true);
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ catch(UnsupportedFlavorException e1)
+ {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ catch(IOException e1)
+ {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ else if(t.isDataFlavorSupported(DataFlavor.stringFlavor))
+ {
+ e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+ try
+ {
+ String dropS = (String)t.getTransferData(DataFlavor.stringFlavor);
+ this.replaceSelection(dropS);
+ e.dropComplete(true);
+ }
+ catch (Exception ex) {}
+ }
+ else
+ {
+ e.rejectDrop();
+ return;
+ }
+ return;
+ }
+
+ public void dragOver(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(DataFlavor.stringFlavor) ||
+ e.isDataFlavorSupported(DatabaseTreeNode.DATABASETREENODE))
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+ public void dropActionChanged(DropTargetDragEvent e) {}
+
+ public DatabaseTreeNode getDbNode()
+ {
+ return dbNode;
+ }
+}
+
+
+
+
diff --git a/uk/ac/sanger/artemis/components/TextRequester.java b/uk/ac/sanger/artemis/components/TextRequester.java
new file mode 100644
index 0000000..ddf3bc6
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/TextRequester.java
@@ -0,0 +1,158 @@
+/* TextRequester.java
+ *
+ * created: Mon Jan 11 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/TextRequester.java,v 1.1 2004-06-09 09:47:54 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Options;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+
+import javax.swing.*;
+
+/**
+ * A popup JTextField with an OK and a Cancel button.
+ *
+ * @author Kim Rutherford
+ * @version $Id: TextRequester.java,v 1.1 2004-06-09 09:47:54 tjc Exp $
+ **/
+
+public class TextRequester extends JFrame {
+ /**
+ * Create a new TextRequester component with the given prompt. Other
+ * components can listen for TextRequesterEvent object.
+ * @param prompt A message that is displayed in the component beside the
+ * TextArea that the user types into. This String is also used as the
+ * JFrame title.
+ * @param width The width of the JTextField in the new requester.
+ * @param initial_text The initial text to put in the JTextField.
+ **/
+ public TextRequester (final String prompt,
+ final int width,
+ final String initial_text) {
+ super (prompt);
+
+ getContentPane ().add (new JLabel (prompt), "North");
+
+ final JPanel panel = new JPanel ();
+
+ panel.add (ok_button);
+ ok_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ performOK ();
+ }
+ });
+
+ panel.add (cancel_button);
+ cancel_button.addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent e) {
+ performCancel ();
+ }
+ });
+
+ getContentPane ().add (panel, "South");
+
+ text_field = new JTextField (initial_text, width);
+
+ text_field.addKeyListener (new KeyAdapter () {
+ public void keyTyped(final KeyEvent e) {
+ if (e.getKeyChar () == '\n') {
+ performOK ();
+ }
+ }
+ });
+
+ getContentPane ().add (text_field, "Center");
+
+ pack ();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+ }
+
+ /**
+ * Add the given object as a listen for TextRequester events from this
+ * TextRequester.
+ **/
+ public void addTextRequesterListener (final TextRequesterListener l) {
+ listeners.addElement (l);
+ }
+
+ /**
+ * Return the text the is currently displayed in this requester.
+ **/
+ protected String getText () {
+ return text_field.getText ();
+ }
+
+ /**
+ * Send a TextRequesterEvent of type OK to all the listeners.
+ **/
+ protected void performOK () {
+ final TextRequesterEvent new_event =
+ new TextRequesterEvent (this, getText (), TextRequesterEvent.OK);
+
+ sendEvent (new_event);
+
+ TextRequester.this.dispose ();
+ }
+
+ /**
+ * Send a TextRequesterEvent of type CANCEL to all the listeners.
+ **/
+ protected void performCancel () {
+ final TextRequesterEvent new_event =
+ new TextRequesterEvent (this, getText (), TextRequesterEvent.CANCEL);
+
+ sendEvent (new_event);
+
+ TextRequester.this.dispose ();
+ }
+
+ /**
+ * Send the given TextRequesterEvent to all the object that are listening
+ * for the event.
+ **/
+ private void sendEvent (final TextRequesterEvent event) {
+ for (int i = 0 ; i < listeners.size () ; ++i) {
+ final TextRequesterListener listener =
+ ((TextRequesterListener) listeners.elementAt (i));
+
+ listener.actionPerformed (event);
+ }
+ }
+
+ private final JButton ok_button = new JButton ("OK");
+ private final JButton cancel_button = new JButton ("Cancel");
+ private JTextField text_field = null;
+
+ /**
+ * This contains the objects that are listening for TextRequester events
+ * from this TextRequester.
+ **/
+ private Vector listeners = new Vector ();
+}
diff --git a/uk/ac/sanger/artemis/components/TextRequesterEvent.java b/uk/ac/sanger/artemis/components/TextRequesterEvent.java
new file mode 100644
index 0000000..b1e3951
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/TextRequesterEvent.java
@@ -0,0 +1,87 @@
+/* TextRequesterEvent.java
+ *
+ * created: Wed Jan 13 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/TextRequesterEvent.java,v 1.1 2004-06-09 09:47:55 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * This event is sent when the user presses OK or Cancel in a TextRequester
+ * component.
+ *
+ * @author Kim Rutherford
+ * @version $Id: TextRequesterEvent.java,v 1.1 2004-06-09 09:47:55 tjc Exp $
+ **/
+
+public class TextRequesterEvent extends java.util.EventObject {
+ /**
+ * Event type - This event was generated by pressing the OK button.
+ **/
+ final public static int OK = 1;
+
+ /**
+ * Event type - This event was generated by pressing the CANCEL button.
+ **/
+ final public static int CANCEL = 2;
+
+ /**
+ * Create a new TextRequesterEvent object.
+ * @param source The TextRequester that generated the event.
+ * @param requester_text The contents of the TextField in the TextRequester.
+ * @param type The type of event.
+ **/
+ public TextRequesterEvent (final TextRequester source,
+ final String requester_text,
+ final int type) {
+ super (source);
+ this.requester_text = requester_text;
+ this.type = type;
+ }
+
+ /**
+ * Return the type of this event as passed to the constructor.
+ **/
+ public int getType () {
+ return type;
+ }
+
+ /**
+ * Return the TextRequester contents String that was passed to the
+ * constructor.
+ **/
+ public String getRequesterText () {
+ return requester_text;
+ }
+
+ /**
+ * This is the type of this event, as passed to the constructor
+ **/
+ private int type;
+
+ /**
+ * The TextRequester contents String that was passed to the constructor.
+ **/
+ private String requester_text;
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/TextRequesterListener.java b/uk/ac/sanger/artemis/components/TextRequesterListener.java
new file mode 100644
index 0000000..0b70582
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/TextRequesterListener.java
@@ -0,0 +1,44 @@
+/* TextRequesterListener.java
+ *
+ * created: Sun Jan 17 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/TextRequesterListener.java,v 1.1 2004-06-09 09:47:56 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+/**
+ * This interface is implemented by those classes that need to listen for
+ * TextRequesterEvents.
+ *
+ * @author Kim Rutherford
+ * @version $Id: TextRequesterListener.java,v 1.1 2004-06-09 09:47:56 tjc Exp $
+ **/
+
+public interface TextRequesterListener {
+ /**
+ * Invoked when the user presses the OK or Cancel button on a TextRequester
+ * component.
+ **/
+ void actionPerformed (final TextRequesterEvent event);
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/TransferAnnotationTool.java b/uk/ac/sanger/artemis/components/TransferAnnotationTool.java
new file mode 100644
index 0000000..c8c7de2
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/TransferAnnotationTool.java
@@ -0,0 +1,1149 @@
+/* TransferAnnotationTool.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingConstants;
+import javax.swing.border.EtchedBorder;
+import javax.swing.border.TitledBorder;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.filetree.LocalAndRemoteFileManager;
+import uk.ac.sanger.artemis.components.genebuilder.GeneEdit;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.genebuilder.ortholog.MatchPanel;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.PartialSequence;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class TransferAnnotationTool extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private static String[] NON_TRANSFERABLE_QUALIFIERS =
+ {
+ "ID",
+ "feature_id",
+ "Derives_from",
+ "feature_relationship_rank",
+ "Parent",
+ "isObsolete",
+ "Start_range",
+ "End_range",
+ "timelastmodified",
+ "cytoplasm_location",
+ "cytoplasmic_polypeptide_region",
+ "membrane_structure",
+ "non_cytoplasm_location",
+ "non_cytoplasmic_polypeptide_region",
+ "orthologous_to",
+ "paralogous_to",
+ "pepstats_file",
+ "PlasmoAP_score",
+ "polypeptide_domain",
+ "fasta_file",
+ "blastp_file",
+ "blastn_file",
+ "systematic_id",
+ "transmembrane",
+ "transmembrane_polypeptide_region",
+ "previous_systematic_id"
+ };
+ private MatchPanel matchPanel;
+
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(TransferAnnotationTool.class);
+
+ protected static Color STEEL_BLUE = new Color(25, 25, 112);
+
+ /**
+ * Tool for transferring annotation from one feature to other feature(s)
+ * @param feature
+ * @param entryGroup
+ * @param geneNames
+ */
+ public TransferAnnotationTool(final Feature feature,
+ final EntryGroup entryGroup,
+ final MatchPanel matchPanel)
+ {
+ super("Transfer Annotation Tool :: "+ feature.getIDString());
+ this.matchPanel = matchPanel;
+
+ List<String> geneNames = null;
+ if(matchPanel != null)
+ geneNames = matchPanel.getGeneNameList();
+
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ JPanel pane = new JPanel(new GridBagLayout());
+ JScrollPane jsp = new JScrollPane(panel);
+ panel.setBackground(Color.white);
+ pane.setBackground(Color.white);
+ panel.add(pane);
+
+ JPanel framePanel = (JPanel)getContentPane();
+ framePanel.add(jsp, BorderLayout.CENTER);
+ framePanel.setPreferredSize(new Dimension(600,600));
+
+ final Vector<JCheckBox> geneNameCheckBoxes = new Vector<JCheckBox>();
+ final Vector<QualifierPanel> qualifierPanels = new Vector<QualifierPanel>();
+
+ addMainPanel(feature, pane, qualifierPanels,
+ geneNameCheckBoxes, geneNames);
+ addBottomButtons(qualifierPanels, geneNameCheckBoxes,
+ framePanel, entryGroup);
+ pack();
+ setVisible(true);
+ }
+
+ /**
+ * Construct the panel for setting up the gene list and the
+ * list of qualifiers to transfer.
+ * @param feature
+ * @param pane
+ * @param qualifierCheckBoxes
+ * @param geneNameCheckBoxes
+ * @param geneNames
+ */
+ private void addMainPanel(final Feature feature,
+ final JPanel pane,
+ final Vector<QualifierPanel> qualifierPanels,
+ final Vector<JCheckBox> geneNameCheckBoxes,
+ final List<String> geneNames)
+ {
+ GridBagConstraints c = new GridBagConstraints();
+ int nrows = 0;
+
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.gridx = 2;
+ c.gridy = 0;
+ c.ipadx = 50;
+
+ JLabel geneLabel = new JLabel("Qualifier(s)");
+ geneLabel.setFont(geneLabel.getFont().deriveFont(Font.BOLD));
+ pane.add(geneLabel, c);
+
+ c.gridy = 0;
+ c.gridx = 0;
+ JLabel label = new JLabel("Gene List");
+ label.setFont(label.getFont().deriveFont(Font.BOLD));
+ pane.add(label, c);
+
+ nrows+=3;
+ c.gridx = 2;
+ c.gridy = nrows;
+ c.anchor = GridBagConstraints.WEST;
+
+ addQualifierPanel(feature, qualifierPanels, c, nrows, pane);
+ nrows+=2;
+
+ if(feature.getEmblFeature() instanceof GFFStreamFeature)
+ {
+ GFFStreamFeature gffFeature =
+ ((GFFStreamFeature) feature.getEmblFeature());
+ if (gffFeature.getChadoGene() != null)
+ {
+ String id = GeneUtils.getUniqueName(gffFeature);
+ ChadoCanonicalGene chadoGene = gffFeature.getChadoGene();
+ Feature gene = (Feature) chadoGene.getGene().getUserData();
+
+ if(!id.equals( GeneUtils.getUniqueName(((GFFStreamFeature)chadoGene.getGene())) ))
+ addQualifierPanel(gene, qualifierPanels, c, nrows, pane);
+
+ nrows+=2;
+
+ String transcriptName =
+ chadoGene.getTranscriptFromName(GeneUtils.getUniqueName(gffFeature));
+
+ if(transcriptName != null)
+ {
+ GFFStreamFeature transcript =
+ (GFFStreamFeature) chadoGene.getFeatureFromId(transcriptName);
+ addQualifierPanel((Feature)transcript.getUserData(),
+ qualifierPanels, c, nrows, pane);
+ nrows+=2;
+
+ Set<uk.ac.sanger.artemis.io.Feature> children = chadoGene.getChildren(transcript);
+ Iterator<uk.ac.sanger.artemis.io.Feature> it = children.iterator();
+
+ while(it.hasNext())
+ {
+ GFFStreamFeature kid = (GFFStreamFeature)it.next();
+ if(id.equals( GeneUtils.getUniqueName(((GFFStreamFeature)kid)) ))
+ continue;
+ addQualifierPanel((Feature)kid.getUserData(), qualifierPanels,
+ c, nrows, pane);
+ nrows+=2;
+ }
+ }
+ }
+ }
+
+ c.gridx = 0;
+ c.gridy = 3;
+ c.gridheight = nrows;
+ c.fill = GridBagConstraints.BOTH;
+
+ final Box geneNameBox = Box.createVerticalBox();
+ pane.add(geneNameBox, c);
+
+ if(geneNames != null)
+ {
+ for(int i = 0; i < geneNames.size(); i++)
+ {
+ JCheckBox cb = new JCheckBox((String) geneNames.get(i),true);
+ geneNameBox.add(cb);
+ geneNameCheckBoxes.add(cb);
+ }
+ }
+
+ c.gridy = 1;
+ c.gridheight = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.gridx = 2;
+ final JButton toggle = new JButton("Toggle");
+ toggle.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ for(int i=0; i<qualifierPanels.size(); i++)
+ {
+ QualifierPanel qP = qualifierPanels.get(i);
+ Enumeration<JCheckBox> enumQualifiers = qP.getQualifierCheckBoxes().keys();
+ while(enumQualifiers.hasMoreElements())
+ {
+ JCheckBox cb = enumQualifiers.nextElement();
+ cb.setSelected(!cb.isSelected());
+ }
+ }
+ }
+ });
+ pane.add(toggle, c);
+
+ Box xBox = Box.createHorizontalBox();
+ final JButton toggleGeneList = new JButton("Toggle");
+ toggleGeneList.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ for(int i = 0; i < geneNameCheckBoxes.size(); i++)
+ {
+ JCheckBox cb = geneNameCheckBoxes.get(i);
+ cb.setSelected(!cb.isSelected());
+ }
+ geneNameBox.repaint();
+ }
+ });
+ xBox.add(toggleGeneList);
+
+ final JButton addGenes = new JButton("Add");
+ addGenes.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ JTextArea geneNameTextArea = new JTextArea();
+ geneNameTextArea.setEditable(true);
+ JScrollPane jsp = new JScrollPane(geneNameTextArea);
+
+ int res = JOptionPane.showConfirmDialog(TransferAnnotationTool.this,
+ jsp, "Paste Feature Names to Add",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(res == JOptionPane.CANCEL_OPTION)
+ return;
+
+ String geneNames[] = geneNameTextArea.getText().split("\\s");
+ for(int i=0;i<geneNames.length; i++)
+ {
+ if(geneNames[i] == null || geneNames[i].equals(""))
+ continue;
+ JCheckBox cb = new JCheckBox(geneNames[i],true);
+ geneNameBox.add(cb);
+ geneNameCheckBoxes.add(cb);
+ }
+ pane.revalidate();
+ }
+ });
+ xBox.add(addGenes);
+ c.gridx = 0;
+ pane.add(xBox, c);
+
+ final List<String> clusterList = (matchPanel == null ? null : matchPanel.getGeneNameList(true));
+ if(clusterList != null && !geneNames.contains(clusterList.get(0)))
+ {
+ final JButton importCluster = new JButton("Import Cluster Names");
+ importCluster.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ for(String n: clusterList)
+ {
+ if(n == null || n.equals(""))
+ continue;
+ JCheckBox cb = new JCheckBox(n,true);
+ geneNameBox.add(cb);
+ geneNameCheckBoxes.add(cb);
+ }
+ importCluster.setEnabled(false);
+ pane.revalidate();
+ }
+ });
+ c.gridy = 2;
+ pane.add(importCluster, c);
+ }
+ }
+
+ /**
+ * Add a panel to display a given features qualifiers.
+ * @param f
+ * @param qualifierPanels
+ * @param c
+ * @param nrows
+ * @param pane
+ */
+ private void addQualifierPanel(Feature f,
+ Vector<QualifierPanel> qualifierPanels,
+ GridBagConstraints c,
+ int nrows,
+ JPanel pane)
+ {
+ QualifierPanel qPanel = new QualifierPanel(f,f.getKey().getKeyString());
+ if(qPanel.nrows == 0)
+ return;
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.WEST;
+ c.weightx = 100;
+ qualifierPanels.add(qPanel);
+ c.gridy = ++nrows;
+
+ JLabel l = new JLabel(f.getIDString());
+ l.setFont(l.getFont().deriveFont(Font.BOLD));
+ l.setForeground(STEEL_BLUE);
+ pane.add(l, c);
+
+ c.gridy = ++nrows;
+ pane.add(qPanel, c);
+ c.weightx = 0.d;
+ }
+
+
+ /**
+ * Add panel for the transfer and close button.
+ * @param qualifierCheckBoxes
+ * @param geneNameCheckBoxes
+ * @param framePanel
+ * @param feature
+ * @param entryGroup
+ */
+ private void addBottomButtons(final Vector<QualifierPanel> qualifierPanels,
+ final Vector<JCheckBox> geneNameCheckBoxes,
+ final JPanel framePanel,
+ final EntryGroup entryGroup)
+ {
+ final JCheckBox sameKeyCheckBox = new JCheckBox("Add to feature of same key", true);
+
+ final JCheckBox overwriteCheckBox = new JCheckBox("Overwrite", false);
+ overwriteCheckBox.setToolTipText("overwrite rather than append values");
+
+ final JCheckBox cvCheckBox = new JCheckBox("Set evidence as ISO and link to source in WITH/FROM", false);
+ cvCheckBox.setToolTipText("for GO and Product qualifiers set the evidence as ISO (Inferred from\n"+
+ "Sequence Orthology) and add a link to the source in the WITH/FROM field");
+
+ Box buttonBox = Box.createHorizontalBox();
+ final JButton transfer = new JButton(">>TRANSFER");
+ transfer.setToolTipText("transfer annotation");
+ transfer.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(overwriteCheckBox.isSelected())
+ {
+ int res = JOptionPane.showConfirmDialog(TransferAnnotationTool.this,
+ "Overwrite selected annotation?", "Overwrite", JOptionPane.OK_CANCEL_OPTION);
+ if(res == JOptionPane.CANCEL_OPTION)
+ return;
+ }
+
+ final boolean autoHistorySetting = LocalAndRemoteFileManager.isAutomaticHistory();
+ StringBuffer summary = new StringBuffer();
+ try
+ {
+ LocalAndRemoteFileManager.setAutomaticHistory(false);
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ StringBuffer buff = new StringBuffer();
+
+ for(int i = 0; i < qualifierPanels.size(); i++)
+ {
+ QualifierPanel qP = qualifierPanels.get(i);
+ int res = transferAnnotation(qP.getQualifierCheckBoxes(),
+ geneNameCheckBoxes, qP.getFeature(), entryGroup,
+ sameKeyCheckBox.isSelected(),
+ overwriteCheckBox.isSelected(),
+ cvCheckBox.isSelected(),
+ buff, summary);
+ if(res == -1)
+ break;
+ }
+
+ if(buff.length() > 0)
+ logger4j.debug("TRANSFERRED ANNOTATION SUMMARY:\n"+buff.toString());
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+
+ if(summary.length()>0)
+ {
+ final JTextArea list = new JTextArea(summary.toString());
+ final JScrollPane jsp = new JScrollPane(list);
+ jsp.setPreferredSize(new Dimension(300,200));
+ JOptionPane.showMessageDialog(
+ TransferAnnotationTool.this, jsp,
+ "Summary of Genes Changed",
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ }
+ finally
+ {
+ LocalAndRemoteFileManager.setAutomaticHistory(autoHistorySetting);
+ }
+ }
+ });
+ Box yBox = Box.createVerticalBox();
+ yBox.add(transfer);
+ yBox.add(sameKeyCheckBox);
+ yBox.add(overwriteCheckBox);
+ yBox.add(cvCheckBox);
+ buttonBox.add(yBox);
+
+ final JButton close = new JButton("CLOSE");
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ dispose();
+ }
+ });
+ yBox = Box.createVerticalBox();
+ yBox.add(close);
+ yBox.add(Box.createVerticalGlue());
+ buttonBox.add(yBox);
+ buttonBox.add(Box.createHorizontalGlue());
+ framePanel.add(buttonBox, BorderLayout.SOUTH);
+ }
+
+
+ /**
+ * Returns true if this qualifier is non-transferable
+ * @param qualifierName
+ * @return
+ */
+ protected static boolean isNonTransferable(String qualifierName)
+ {
+ for(int i=0; i<NON_TRANSFERABLE_QUALIFIERS.length; i++)
+ {
+ if(NON_TRANSFERABLE_QUALIFIERS[i].equals(qualifierName))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Transfer selected qualifiers to the list of features defined
+ * by the selected names.
+ * @param qualifierCheckBoxes - list of qualifier check boxes
+ * @param geneNameTextArea - text with a list of feature names to transfer to
+ * @param feature - feature to copy from
+ * @param entryGroup
+ * @param sameKey
+ * @param overwrite
+ */
+ protected static int transferAnnotation(
+ final Hashtable<JCheckBox, Vector<JCheckBox>> qualifierCheckBoxes,
+ final Vector<JCheckBox> geneNameCheckBoxes,
+ final Feature orginatingFeature,
+ final EntryGroup entryGroup,
+ final boolean sameKey,
+ final boolean overwrite,
+ final boolean setEvidenceAndWithFrom,
+ final StringBuffer buff,
+ final StringBuffer genesUpdated)
+ {
+ // transfer selected annotation to genes
+ final QualifierVector qualifiers = orginatingFeature.getQualifiers();
+ final QualifierVector qualifiersToTransfer = new QualifierVector();
+
+ Enumeration<JCheckBox> enumQualifiers = qualifierCheckBoxes.keys();
+ while(enumQualifiers.hasMoreElements())
+ {
+ JCheckBox cb = enumQualifiers.nextElement();
+ if (cb.isSelected())
+ {
+ Vector<JCheckBox> qualifierValuesCheckBox = qualifierCheckBoxes.get(cb);
+ final StringVector values = qualifiers.getQualifierByName(cb.getText()).getValues();
+ StringVector valuesToTransfer = new StringVector(values);
+
+ logger4j.debug("TRANSFER "+cb.getText());
+ for(int i=0; i<qualifierValuesCheckBox.size(); i++)
+ {
+ JCheckBox valuesCb = qualifierValuesCheckBox.get(i);
+ if(!valuesCb.isSelected())
+ {
+ valuesToTransfer.remove(valuesCb.getText());
+ logger4j.debug("NOT TRANSFERING "+valuesCb.getText());
+ }
+ }
+
+ if(valuesToTransfer.size() < 1)
+ continue;
+
+ valuesToTransfer = new StringVector( getTransferValues(
+ setEvidenceAndWithFrom, orginatingFeature, cb.getText(), valuesToTransfer) );
+
+ qualifiersToTransfer.addElement(new Qualifier(cb.getText(), valuesToTransfer));
+ }
+ }
+
+ int count = 0;
+ for(int i =0; i<geneNameCheckBoxes.size(); i++)
+ {
+ if( geneNameCheckBoxes.get(i).isSelected() )
+ count++;
+ }
+
+ if(count < 1)
+ {
+ JOptionPane.showMessageDialog(null, "No genes selected.",
+ "Warning", JOptionPane.WARNING_MESSAGE);
+ return -1;
+ }
+
+ String geneNames[] = new String[count];
+ count = 0;
+ for(int i =0; i<geneNameCheckBoxes.size(); i++)
+ {
+ JCheckBox cb = geneNameCheckBoxes.get(i);
+ if( cb.isSelected() )
+ {
+ geneNames[count] = cb.getText();
+ logger4j.debug("TRANSFER ANNOTATION TO "+geneNames[count]);
+ count++;
+ }
+ }
+
+ final String key = orginatingFeature.getKey().getKeyString();
+ final FeatureVector features = entryGroup.getAllFeatures();
+
+ // transfer selected annotation
+ entryGroup.getActionController().startAction();
+ geneNames = transfer(features, qualifiersToTransfer, key, sameKey, overwrite,
+ GeneUtils.isDatabaseEntry(entryGroup), geneNames, genesUpdated);
+ entryGroup.getActionController().endAction();
+
+ //
+ // Commit changes to genes not in Artemis but in the database
+ //
+ Vector<String> genesNotFound = null;
+ if (geneNames != null &&
+ orginatingFeature.getEntry().getEMBLEntry() instanceof DatabaseDocumentEntry)
+ {
+ DatabaseDocumentEntry db_entry =
+ (DatabaseDocumentEntry) orginatingFeature.getEntry().getEMBLEntry();
+ DatabaseDocument doc = (DatabaseDocument) db_entry.getDocument();
+
+ for (int i = 0; i < geneNames.length; i++)
+ {
+ DatabaseDocumentEntry newDbEntry = GeneEdit.makeGeneEntry(null,
+ geneNames[i], doc, null);
+
+ if (newDbEntry == null)
+ {
+ if (genesNotFound == null)
+ genesNotFound = new Vector<String>();
+ genesNotFound.add(geneNames[i]);
+ continue;
+ }
+
+ char[] c = new char[1];
+ PartialSequence ps = new PartialSequence(c, 100, 0, null, null);
+ newDbEntry.setPartialSequence(ps);
+ Entry entry = null;
+ try
+ {
+ entry = new Entry(newDbEntry);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ SimpleEntryGroup entry_group = new SimpleEntryGroup();
+ entry_group.addElement(entry);
+
+ ChadoTransactionManager ctm = new ChadoTransactionManager();
+ entry_group.addFeatureChangeListener(ctm);
+ entry_group.addEntryChangeListener(ctm);
+ ctm.setEntryGroup(entry_group);
+
+ transfer(entry.getAllFeatures(), qualifiersToTransfer, key, sameKey,
+ overwrite, true, geneNames, genesUpdated);
+
+ for(int j=0; j<ctm.getTransactionCount(); j++)
+ buff.append(ctm.getTransactionAt(j).getLogComment()+"\n");
+ ChadoTransactionManager.commit((DatabaseDocument) newDbEntry
+ .getDocument(), false, ctm);
+
+ entry_group.removeFeatureChangeListener(ctm);
+ entry_group.removeEntryChangeListener(ctm);
+ // if(newDbEntry != null)
+ // GeneEdit.showGeneEditor(null, geneNames[i], newDbEntry);
+ }
+ }
+
+ if(genesNotFound != null)
+ JOptionPane.showMessageDialog(null,
+ "Gene(s) Not Found:\n"+genesNotFound.toString(),
+ "Gene(s) Not Found", JOptionPane.WARNING_MESSAGE);
+ return 0;
+ }
+
+ /**
+ *
+ * @param features
+ * @param qualifiersToTransfer
+ * @param key
+ * @param sameKey
+ * @param isDatabaseEntry
+ * @param geneNames
+ * @return
+ */
+ private static String[] transfer(final FeatureVector features,
+ final QualifierVector qualifiersToTransfer,
+ final String key,
+ final boolean sameKey,
+ final boolean overwrite,
+ final boolean isDatabaseEntry,
+ String[] geneNames,
+ final StringBuffer genesUpdated)
+ {
+ final TransferFeaturePredicate predicate = new TransferFeaturePredicate(
+ key, sameKey, isDatabaseEntry, geneNames);
+
+ for (int i = 0; i < features.size(); i++)
+ {
+ Feature thisFeature = features.elementAt(i);
+ if (predicate.testPredicate(thisFeature))
+ {
+ StringBuffer qualifierBuffer = new StringBuffer();
+ for (int j = 0; j < qualifiersToTransfer.size(); j++)
+ {
+ Qualifier newQualifier = qualifiersToTransfer.elementAt(j);
+ String qualifierName = newQualifier.getName();
+ try
+ {
+ if(overwrite)
+ {
+ thisFeature.setQualifier(newQualifier);
+ qualifierBuffer.append(" "+qualifierName+" (overwritten)\n"+
+ parseStringVector(newQualifier.getValues()));
+ }
+ else
+ {
+ final StringVector oldValues;
+ if (thisFeature.getQualifierByName(newQualifier.getName()) == null)
+ oldValues = null;
+ else
+ oldValues = thisFeature.getQualifierByName(
+ newQualifier.getName()).getValues();
+
+ final Qualifier newQualifierTmp = getQualifierWithoutDuplicateValues(
+ newQualifier, oldValues);
+ if (newQualifierTmp == null)
+ continue;
+ thisFeature.addQualifierValues(newQualifierTmp);
+ qualifierBuffer.append(" "+qualifierName+" (added)\n"+
+ parseStringVector(newQualifier.getValues()));
+ }
+ }
+ catch (Exception e1)
+ {
+ e1.printStackTrace();
+ }
+ }
+
+ geneNames = removeArrayElement(geneNames, predicate.getGeneName());
+ if(qualifierBuffer.length() > 0)
+ genesUpdated.append(thisFeature.getSystematicName()+
+ " ("+key+")\n"+qualifierBuffer);
+ }
+ }
+ return geneNames;
+ }
+
+ /**
+ * Get a StringBuffer representation of the values in a StringVector
+ * @param v
+ * @return
+ */
+ private static StringBuffer parseStringVector(final StringVector v)
+ {
+ StringBuffer buff = new StringBuffer();
+ for(int i=0; i<v.size(); i++)
+ buff.append(" "+v.elementAt(i)+"\n");
+ return buff;
+ }
+
+ /**
+ * Return a qualifier copy of the qualifier provided that does not contain
+ * any of the values in the StringVector.
+ * @param newQualifier
+ * @param oldValues
+ * @return
+ * @throws InvalidRelationException
+ */
+ protected static Qualifier getQualifierWithoutDuplicateValues(
+ final Qualifier qualifier,
+ final StringVector values) throws InvalidRelationException
+ {
+ final Qualifier newQualifier;
+ if (values == null || values.size() < 1)
+ newQualifier = qualifier;
+ else
+ {
+ StringVector newValues = qualifier.getValues();
+ StringVector valuesToAdd = new StringVector();
+
+ for (int k = 0; k < newValues.size(); k++)
+ {
+ if(!values.contains(newValues.get(k)))
+ {
+ if(qualifier.getName().equals("history"))
+ {
+ if(!uk.ac.sanger.artemis.components.genebuilder.cv.HistoryBox.contains(values, (String)newValues.get(k)))
+ valuesToAdd.add(newValues.get(k));
+ }
+ else
+ valuesToAdd.add(newValues.get(k));
+ }
+ }
+
+ if(valuesToAdd.size() == 0)
+ return null;
+ newQualifier = new Qualifier(qualifier.getName(), valuesToAdd);
+ }
+ return newQualifier;
+ }
+
+ /**
+ * Remove a string from an array of strings. If the string appears multiple
+ * times in the array this method will delete all occurrences.
+ * @param strArr
+ * @param str
+ * @return
+ */
+ private static String[] removeArrayElement(final String strArr[], final String str)
+ {
+ if(strArr.length == 1)
+ {
+ if(strArr[0].equals(str))
+ return null;
+ return strArr;
+ }
+ String[] newarray = new String[strArr.length - 1];
+ int count = 0;
+ for (int i = 0; i < strArr.length; i++)
+ {
+ if (strArr[i].equals(str))
+ continue;
+
+ // not found str return original array
+ if (count >= newarray.length)
+ return strArr;
+ newarray[count] = strArr[i];
+ count++;
+ }
+
+ if (count < newarray.length)
+ {
+ String[] tmparray = new String[count];
+ System.arraycopy(newarray, 0, tmparray, 0, count);
+ newarray = tmparray;
+ }
+ return newarray;
+ }
+
+ /**
+ * Optionally transfer GO fields with evidence code ISO and link back to
+ * the original source in the WITH/FROM column.
+ * @param setEvidenceAndWithFrom
+ * @param feature
+ * @param qName
+ * @param values
+ * @return
+ */
+ private static StringVector getTransferValues(final boolean setEvidenceAndWithFrom,
+ final Feature feature,
+ final String qName,
+ final StringVector values)
+ {
+ if(!setEvidenceAndWithFrom)
+ return values;
+
+ if(qName.equals("GO") || qName.equals("product"))
+ {
+ final StringVector tvalues = new StringVector();
+ final String gene = getGeneName(feature);
+ for (int i = 0; i < values.size(); i++)
+ {
+ String val = changeField("evidence=",
+ "Inferred from Sequence Orthology",
+ null, values.get(i));
+
+ if(gene != null)
+ val = changeField("with=",
+ "GeneDB:"+gene, "|", val);
+ tvalues.add(val);
+ }
+ return tvalues;
+ }
+ return values;
+ }
+
+ private static String getGeneName(Feature feature)
+ {
+ try
+ {
+ return
+ ((GFFStreamFeature)feature.getEmblFeature()).getChadoGene().getGeneUniqueName();
+ }
+ catch(Exception e){}
+ return null;
+ }
+
+ /**
+ * Replace or add the value of a field in a qualifier string
+ * @param fieldName
+ * @param newFieldStr
+ * @param separator
+ * @param qualStr
+ * @return
+ */
+ private static String changeField(final String fieldName,
+ final String newFieldStr,
+ final String separator,
+ String qualStr)
+ {
+ int idx1 = qualStr.toLowerCase().indexOf(fieldName.toLowerCase());
+ int idx2 = qualStr.indexOf(";", idx1);
+ int len = fieldName.length();
+ if(idx2 > idx1 && idx1 > -1)
+ {
+ if(separator != null)
+ qualStr = qualStr.substring(0, idx2) + separator + newFieldStr +
+ qualStr.substring(idx2);
+ else
+ qualStr = qualStr.substring(0, idx1+len) + newFieldStr +
+ qualStr.substring(idx2);
+ }
+ else if(idx1 > -1)
+ {
+ if(separator != null)
+ qualStr = qualStr + separator + newFieldStr;
+ else
+ qualStr = qualStr.substring(0, idx1+len) + newFieldStr;
+ }
+ else if(!newFieldStr.equals(""))
+ qualStr = qualStr + ";" +
+ fieldName + newFieldStr;
+ return qualStr;
+ }
+}
+
+class QualifierPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ private Hashtable<JCheckBox, Vector<JCheckBox>> qualifierCheckBoxes = new Hashtable<JCheckBox, Vector<JCheckBox>>();
+ private Feature feature;
+ protected int nrows = 0;
+
+ public QualifierPanel(Feature feature, String title)
+ {
+ super(new GridBagLayout());
+
+ this.feature = feature;
+
+ TitledBorder titleBorder = BorderFactory.createTitledBorder(
+ BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), title);
+ titleBorder.setTitleJustification(TitledBorder.LEFT);
+ titleBorder.setTitleColor(TransferAnnotationTool.STEEL_BLUE);
+ setBorder(titleBorder);
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.WEST;
+ c.ipadx = 0;
+ final QualifierVector qualifiers = feature.getQualifiers();
+
+ for(int i = 0; i < qualifiers.size(); i++)
+ {
+ nrows =
+ addQualifierComponents(qualifiers.get(i),
+ qualifierCheckBoxes, c, nrows);
+ }
+
+ setMinimumSize(new Dimension(
+ titleBorder.getMinimumSize(this).width,
+ getMinimumSize().height));
+ }
+
+ /**
+ * Add a qualifier to the list of transferable annotation
+ * @param qualifier
+ * @param qualifierCheckBoxes
+ * @param c
+ * @param nrows
+ * @return
+ */
+ private int addQualifierComponents(
+ final Qualifier qualifier,
+ final Hashtable<JCheckBox, Vector<JCheckBox>> qualifierCheckBoxes,
+ final GridBagConstraints c,
+ int nrows)
+ {
+ if(TransferAnnotationTool.isNonTransferable(qualifier.getName()))
+ return nrows;
+
+ final JCheckBox qualifierNameCheckBox = new JCheckBox(qualifier.getName(), false);
+ c.gridx = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.d;
+ Box qualifierValueBox = Box.createVerticalBox();
+
+ JButton detailsShowHide = new JButton("+");
+ final Vector<JCheckBox> qualifierValuesCheckBox = setExpanderButton(detailsShowHide,
+ qualifier, qualifierValueBox, qualifierNameCheckBox);
+
+ qualifierNameCheckBox.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(qualifierNameCheckBox.isSelected())
+ {
+ for(int i=0; i<qualifierValuesCheckBox.size(); i++)
+ {
+ JCheckBox cb = qualifierValuesCheckBox.get(i);
+ if(cb.isSelected())
+ return;
+ }
+ }
+
+ for(int i=0; i<qualifierValuesCheckBox.size(); i++)
+ {
+ JCheckBox cb = qualifierValuesCheckBox.get(i);
+ cb.setSelected(qualifierNameCheckBox.isSelected());
+ }
+ }
+ });
+ add(detailsShowHide, c);
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 100;
+ c.gridx = 2;
+
+ add(qualifierNameCheckBox, c);
+ qualifierCheckBoxes.put(qualifierNameCheckBox, qualifierValuesCheckBox);
+ c.gridy = ++nrows;
+ add(qualifierValueBox, c);
+ c.gridy = ++nrows;
+ return nrows;
+ }
+
+ /**
+ * Set up the expander button to display qualifier values.
+ * @param butt - expander button
+ * @param qualifier - the qualifer that is being displayed
+ * @param qualifierValueBox - Box containing the values
+ * @param qualifierNameCheckBox - JCheckBox for the given qualifier
+ * @param pane
+ * @return
+ */
+ private Vector<JCheckBox> setExpanderButton(final JButton butt,
+ final Qualifier qualifier,
+ final Box qualifierValueBox,
+ final JCheckBox qualifierNameCheckBox)
+ {
+ butt.setMargin(new Insets(0, 0, 0, 0));
+ butt.setHorizontalAlignment(SwingConstants.RIGHT);
+ butt.setHorizontalTextPosition(SwingConstants.RIGHT);
+ butt.setBorderPainted(false);
+ butt.setFont(butt.getFont().deriveFont(Font.BOLD));
+ butt.setForeground(TransferAnnotationTool.STEEL_BLUE);
+
+ butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if (butt.getText().equals("+"))
+ butt.setText("-");
+ else
+ butt.setText("+");
+
+ qualifierValueBox.setVisible(butt.getText().equals("-"));
+ revalidate();
+ }
+ });
+
+ // set-up qualifier values list
+ qualifierValueBox.setVisible(false);
+ final Vector<JCheckBox> qualifierValuesCheckBox = new Vector<JCheckBox>();
+ final StringVector values = qualifier.getValues();
+ if(values != null)
+ {
+ for (int i = 0; i < values.size(); i++)
+ {
+ final JCheckBox cb = new JCheckBox(values.get(i),
+ qualifierNameCheckBox.isSelected());
+ cb.setFont(cb.getFont().deriveFont(Font.ITALIC));
+ qualifierValueBox.add(cb);
+ qualifierValuesCheckBox.add(cb);
+ }
+ }
+ return qualifierValuesCheckBox;
+ }
+
+ protected Hashtable<JCheckBox, Vector<JCheckBox>> getQualifierCheckBoxes()
+ {
+ return qualifierCheckBoxes;
+ }
+
+ protected Feature getFeature()
+ {
+ return feature;
+ }
+}
+
+/**
+ * Test if the feature is nominated to have annotation transferred
+ * to it. For genes in a chado database it looks at the gene name
+ * and transcript name.
+ */
+class TransferFeaturePredicate implements FeaturePredicate
+{
+ private String geneName;
+ private String key;
+ private boolean sameKey;
+ private boolean isDatabaseEntry;
+ private String[] geneNames;
+
+ public TransferFeaturePredicate(final String key,
+ final boolean sameKey,
+ final boolean isDatabaseEntry,
+ final String[] geneNames)
+ {
+ this.key = key;
+ this.sameKey = sameKey;
+ this.isDatabaseEntry = isDatabaseEntry;
+ this.geneNames = geneNames;
+ }
+
+ public boolean testPredicate(Feature targetFeature)
+ {
+ String targetKey = targetFeature.getKey().getKeyString();
+ if (sameKey && !targetKey.equals(key))
+ return false;
+
+ Vector<String> chadoNames = null;
+ if (isDatabaseEntry)
+ {
+ GFFStreamFeature gffFeature =
+ ((GFFStreamFeature) targetFeature.getEmblFeature());
+ if (gffFeature.getChadoGene() != null)
+ {
+ chadoNames = new Vector<String>();
+
+ ChadoCanonicalGene chadoGene = gffFeature.getChadoGene();
+ chadoNames.add(chadoGene.getGeneUniqueName());
+ List<uk.ac.sanger.artemis.io.Feature> transcripts = chadoGene.getTranscripts();
+ for(int i=0;i<transcripts.size();i++)
+ {
+ GFFStreamFeature feature = (GFFStreamFeature) transcripts.get(i);
+ chadoNames.add(GeneUtils.getUniqueName(feature));
+ }
+ }
+ }
+
+ String thisFeatureSystematicName = targetFeature.getSystematicName();
+ for (int i = 0; i < geneNames.length; i++)
+ {
+ if(geneNames[i].equals(thisFeatureSystematicName) ||
+ (chadoNames != null && chadoNames.contains(geneNames[i])))
+ {
+ geneName = geneNames[i];
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected String getGeneName()
+ {
+ return geneName;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/TransferableContig.java b/uk/ac/sanger/artemis/components/TransferableContig.java
new file mode 100644
index 0000000..26dd45b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/TransferableContig.java
@@ -0,0 +1,82 @@
+/* TransferableContig.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.Feature;
+
+import java.awt.datatransfer.*;
+import java.io.*;
+import java.util.*;
+
+/**
+*
+* File node for remote file tree manager
+*
+*/
+public class TransferableContig
+ implements Transferable, Serializable
+{
+ final public static DataFlavor TRANSFERABLECONTIG =
+ new DataFlavor(TransferableContig.class, "contig feature");
+ static DataFlavor contig_flavors[] = { TRANSFERABLECONTIG };
+
+ private Feature feature;
+
+ public TransferableContig(final Feature feature)
+ {
+ this.feature = feature;
+ }
+
+// Transferable
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return contig_flavors;
+ }
+
+ public boolean isDataFlavorSupported(DataFlavor f)
+ {
+ return f.equals(TRANSFERABLECONTIG);
+ }
+
+ public Object getTransferData(DataFlavor d)
+ throws UnsupportedFlavorException, IOException
+ {
+ if(d.equals(TRANSFERABLECONTIG))
+ return this;
+ else throw new UnsupportedFlavorException(d);
+ }
+
+// Serializable
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException
+ {
+ out.defaultWriteObject();
+ }
+
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/UserDefinedQualifiers.java b/uk/ac/sanger/artemis/components/UserDefinedQualifiers.java
new file mode 100644
index 0000000..9d4544f
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/UserDefinedQualifiers.java
@@ -0,0 +1,868 @@
+/* UserDefinedQualifier
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.UIManager;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+import javax.swing.filechooser.FileFilter;
+
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+import uk.ac.sanger.artemis.components.genebuilder.cv.DatePanel;
+import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.util.URLDocument;
+
+/**
+ * Load and make available qualifiers from a list in a file
+ * of tag=value pairs and from OBO files.
+ */
+class UserDefinedQualifiers extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(UserDefinedQualifiers.class);
+
+ /* collection of loaded files */
+ private Vector<QualifierList> obos = new Vector<QualifierList>();
+
+ /* component containing list of available values */
+ private Box qualifierBox = Box.createHorizontalBox();
+
+ /* listener for adding qualifiers **/
+ private ActionListener addListener;
+
+ /* text area in the feature editor to add qualifiers to */
+ private QualifierTextArea qualifier_text_area;
+ private Selection selection;
+ private JPanel mainPanel;
+
+ /* line pattern for tag=value */
+ private static Pattern TAG_VALUE_PATTERN = Pattern.compile("^/?(\\w+) ?= ?([^\\n\\r]+)$");
+
+ UserDefinedQualifiers()
+ {
+ super("Qualifier List");
+ mainPanel = (JPanel) getContentPane();
+ MultiLineToolTipUI.initialize();
+ read();
+ createComponentToAddCvTerm();
+ createMenus();
+ }
+
+ /**
+ * Menu creation
+ */
+ private void createMenus()
+ {
+ final JMenuBar menuBar = new JMenuBar();
+ final JMenu fileMenu = new JMenu("File");
+ menuBar.add(fileMenu);
+ final JMenuItem importFile = new JMenuItem("Import qualifiers from file ...");
+ importFile.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ importFromFile();
+ }
+ });
+ fileMenu.add(importFile);
+
+ final JMenuItem importURL = new JMenuItem("Import qualifiers from URL ...");
+ importURL.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ importFromUrl();
+ }
+ });
+ fileMenu.add(importURL);
+
+ final JMenuItem pasteQualifiers = new JMenuItem("Paste qualifiers ...");
+ pasteQualifiers.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ final QualifierTextArea qualifierText = new QualifierTextArea();
+ int status = JOptionPane.showConfirmDialog(UserDefinedQualifiers.this,
+ qualifierText, "Paste in qualifier lists in the format: name = value",
+ JOptionPane.OK_CANCEL_OPTION);
+
+ if(status == JOptionPane.OK_OPTION)
+ {
+ QualifierList obo = new QualifierList();
+ obo.qualifiers = loadText(new ByteArrayInputStream(qualifierText.getText().getBytes()));
+ obos.add(obo);
+ createComponentToAddCvTerm();
+ mainPanel.revalidate();
+
+ writeQualifierList(qualifierText.getText());
+ }
+
+ }
+ });
+ fileMenu.add(pasteQualifiers);
+ fileMenu.addSeparator();
+
+ final JMenu removeFile = new JMenu("Qualifier source(s)");
+ removeFile.addMenuListener(new MenuListener(){
+ public void menuCanceled(MenuEvent e){}
+ public void menuDeselected(MenuEvent e){}
+
+ public void menuSelected(MenuEvent e)
+ {
+ removeFile.removeAll();
+ for(final QualifierList obo: obos)
+ {
+ final JCheckBoxMenuItem oboMenu =
+ new JCheckBoxMenuItem(obo.doc.getName(), obo.isAcive);
+ removeFile.add(oboMenu);
+ oboMenu.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ obo.isAcive = oboMenu.isSelected();
+ createComponentToAddCvTerm();
+ mainPanel.revalidate();
+ mainPanel.repaint();
+ }
+ });
+ }
+ }
+ });
+ fileMenu.add(removeFile);
+ setJMenuBar(menuBar);
+ }
+
+ /**
+ * Add components to the panel for adding CvTerm's to
+ * the annotation.
+ */
+ private void createComponentToAddCvTerm()
+ {
+ mainPanel.removeAll();
+ mainPanel.setLayout(new GridBagLayout());
+ final GridBagConstraints c = new GridBagConstraints();
+
+ final JCheckBox ignoreCase = new JCheckBox("Ignore case",true);
+ final Vector<String> names = getQualiferNames();
+ final JComboBox nameCombo = new JComboBox(names);
+ final JButton addButton = new JButton("ADD");
+ final JCheckBox addToAllSelected = new JCheckBox("add to all selected features", false);
+ final JTextField keyWord = new JTextField(45);
+ final Dimension d = new Dimension(500, ignoreCase.getPreferredSize().height);
+ final Dimension d2 = new Dimension(300, ignoreCase.getPreferredSize().height);
+
+ int row = 0;
+ c.gridx = 0;
+ c.gridy = row;
+ c.anchor = GridBagConstraints.EAST;
+
+ if(names.size() < 1)
+ {
+ c.gridwidth = 2;
+ mainPanel.add(new JLabel("Import a list of qualifiers from the File menu."), c);
+ c.gridwidth = 1;
+ ignoreCase.setEnabled(false);
+ nameCombo.setEnabled(false);
+ addButton.setEnabled(false);
+ addToAllSelected.setEnabled(false);
+ keyWord.setEnabled(false);
+ }
+ else
+ {
+ mainPanel.add(new JLabel("Name: "), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ mainPanel.add(nameCombo, c);
+ }
+
+ c.gridx = 2;
+ mainPanel.add(Box.createHorizontalStrut(150), c);
+
+ // keyword
+ keyWord.setSelectionStart(0);
+ keyWord.setSelectionEnd(keyWord.getText().length());
+ keyWord.setSelectedTextColor(Color.blue);
+ keyWord.setMinimumSize(d2);
+ keyWord.addActionListener(new ActionListener(){
+ // carry out search when enter key is pressed
+ public void actionPerformed(ActionEvent event)
+ {
+ searchQualifiers(keyWord.getText(), nameCombo,
+ ignoreCase.isSelected(), addButton, addToAllSelected, d);
+ }
+ });
+ c.gridy = ++row;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ mainPanel.add(new JLabel("Keywords: "),c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ mainPanel.add(keyWord,c);
+
+ c.gridy = ++row;
+ mainPanel.add(ignoreCase,c);
+
+ // search button
+ c.gridx = 0;
+ c.gridy = ++row;
+ c.anchor = GridBagConstraints.WEST;
+ final JButton search = new JButton("SEARCH");
+ search.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ searchQualifiers(keyWord.getText(), nameCombo,
+ ignoreCase.isSelected(), addButton, addToAllSelected, d);
+ }
+ });
+ mainPanel.add(search,c);
+
+ c.gridx = 0;
+ c.gridy = ++row;
+ c.gridwidth = 3;
+ qualifierBox.add(Box.createVerticalStrut(25));
+ mainPanel.add(qualifierBox, c);
+
+ c.gridx = 0;
+ c.gridy = ++row;
+ c.gridwidth = 1;
+ mainPanel.add(addButton, c);
+
+ c.gridx = 1;
+ final JButton closeButton = new JButton("CLOSE");
+ mainPanel.add(closeButton, c);
+ closeButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ dispose();
+ }
+ });
+
+ c.gridx = 0;
+ c.gridy = ++row;
+ c.gridwidth = 2;
+ mainPanel.add(addToAllSelected,c);
+
+ c.gridy = ++row;
+ mainPanel.add(Box.createVerticalStrut(15), c);
+ }
+
+ private StringVector searchOboQualifiers(final QualifierList obo,
+ final String qName,
+ final String keyWord,
+ final boolean ignoreCase)
+ {
+ final Qualifier q = obo.qualifiers.getQualifierByName(qName);
+ if(q == null)
+ return null;
+ final StringVector values = q.getValues();
+
+ if(keyWord != null && !keyWord.trim().equals(""))
+ {
+ final String keyWordLC = keyWord.toLowerCase();
+ final StringVector tmp = values.copy();
+ for(String val: tmp)
+ {
+ if(ignoreCase)
+ {
+ if(val.toLowerCase().indexOf(keyWordLC) == -1)
+ values.remove(val);
+ }
+ else if(val.indexOf(keyWord) == -1)
+ values.remove(val);
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Search the qualifiers for the selected qualifier name and value.
+ */
+ private void searchQualifiers(final String keyWord,
+ final JComboBox nameCombo,
+ final boolean ignoreCase,
+ final JButton addButton,
+ final JCheckBox addToAllSelected,
+ final Dimension d)
+ {
+ final String qName = (String) nameCombo.getSelectedItem();
+ final StringVector results = new StringVector();
+ for(QualifierList obo: obos)
+ {
+ if(!obo.isAcive)
+ continue;
+ StringVector oboResult = searchOboQualifiers(obo, qName, keyWord, ignoreCase);
+ if(oboResult != null)
+ results.add(oboResult);
+ }
+
+ final JExtendedComboBox valuesList = new JExtendedComboBox(results, true);
+ valuesList.getEditor().getEditorComponent().addMouseListener(
+ new ComboMouseListener(valuesList));
+
+ valuesList.setPreferredSize(d);
+ valuesList.setMaximumSize(d);
+
+ if(valuesList.getSelectedItem() != null &&
+ valuesList.getSelectedItem().equals("") &&
+ valuesList.getItemCount() > 2)
+ valuesList.setSelectedIndex(1);
+
+ qualifierBox.removeAll();
+ qualifierBox.add(valuesList);
+
+ if(addListener != null)
+ addButton.removeActionListener(addListener);
+ addListener = new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ String qval = (String) valuesList.getSelectedItem();
+ if(qName.equals("biological_process"))
+ qval = "/GO=aspect=P;"+qval+";date="+DatePanel.getDate();
+ else if(qName.equals("molecular_function"))
+ qval = "/GO=aspect=F;"+qval+";date="+DatePanel.getDate();
+ else if(qName.equals("cellular_component"))
+ qval = "/GO=aspect=C;"+qval+";date="+DatePanel.getDate();
+ else
+ qval = "/"+qName+"="+qval;
+
+ if(addToAllSelected.isSelected())
+ {
+ final FeatureVector features = selection.getAllFeatures();
+ if(!Options.isBlackBeltMode() && features.size() > 1)
+ {
+ int status = JOptionPane.showConfirmDialog(UserDefinedQualifiers.this,
+ "Add "+qName+" to "+features.size()+" selected features?",
+ "Add Qualifier To Selected Features",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+ }
+ final int idx = qval.indexOf("=");
+ final Qualifier q = new Qualifier(qval.substring(1, idx), qval.substring( idx+1 ));
+
+ try
+ {
+ for(int i=0; i<features.size(); i++)
+ features.elementAt(i).addQualifierValues(q);
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ else
+ qualifier_text_area.append(qval+"\n");
+ }
+ };
+ addButton.addActionListener(addListener);
+
+ mainPanel.revalidate();
+ }
+
+ /**
+ * Return a list of the qualifier names.
+ * @return
+ */
+ private Vector<String> getQualiferNames()
+ {
+ final Vector<String> names = new Vector<String>();
+ for(QualifierList obo: obos)
+ {
+ if(!obo.isAcive)
+ continue;
+ QualifierVector qualifiers = obo.qualifiers;
+ for(Qualifier q: qualifiers)
+ if(!names.contains(q.getName()))
+ names.add(q.getName());
+ }
+ return names;
+ }
+
+ /**
+ * Read qualifier lists
+ */
+ private void read()
+ {
+ URL url = ClassLoader.getSystemResource("etc/artemis.qualifiers");
+ if(url != null)
+ {
+ Document doc = new URLDocument(
+ ClassLoader.getSystemResource("etc/artemis.qualifiers"));
+ createObo(doc);
+ }
+
+ final String [] qualFiles =
+ {
+ "artemis.qualifiers",
+ System.getProperty("user.home") + File.separator + ".artemis.qualifiers"
+ };
+
+ for(String fileName: qualFiles)
+ {
+ Document doc = new FileDocument(new File(fileName));
+ if(doc.readable())
+ createObo(doc);
+ }
+ }
+
+ /**
+ * Load the qualifiers from an input stream.
+ * @param ins
+ * @throws IOException
+ */
+ private QualifierVector loadQualifiers(final Document doc) throws IOException
+ {
+ QualifierVector qualifiers = loadObo(doc);
+ if(qualifiers.size() == 0)
+ qualifiers = loadText(doc.getInputStream());
+ return qualifiers;
+ }
+
+ /**
+ * Load in qualifiers from an input stream that are in OBO format.
+ * @param ins
+ * @return
+ * @throws IOException
+ */
+ private QualifierVector loadObo(final Document doc) throws IOException
+ {
+ final LinePushBackReader reader = new LinePushBackReader(
+ new BufferedReader(new InputStreamReader(doc.getInputStream())));
+ final QualifierVector qualifiers = new QualifierVector();
+ try
+ {
+ String line;
+ while((line = reader.readLine()) != null)
+ {
+ if(line.startsWith("#"))
+ continue;
+
+ if(startOfStanza(line))
+ {
+ String val = null;
+ String name = null;
+ String id = null;
+ while((line = reader.readLine()) != null &&
+ !startOfStanza(line) &&
+ line.indexOf("import:") == -1)
+ {
+ if(line.startsWith("namespace:"))
+ name = line.substring(10).trim();
+ else if(line.startsWith("name:"))
+ val = line.substring(5).trim();
+ else if(line.startsWith("id:"))
+ id = line.substring(3).trim();
+ }
+
+ if(line != null)
+ reader.pushBack(line);
+
+ if(id != null)
+ {
+ if( name != null &&
+ (name.equals("biological_process") ||
+ name.equals("molecular_function") ||
+ name.equals("cellular_component")))
+ val = "term="+val+"; GOid="+id;
+ else
+ val = "term="+val+"; id="+id;
+ }
+ if(name != null)
+ qualifiers.addQualifierValues(new Qualifier(name, val));
+ }
+ else if(line.startsWith("import:"))
+ {
+ // import OBO file from a URL
+ final URLDocument urlDoc = new URLDocument(
+ new URL(line.substring(7).trim()) );
+ // check to ensure not recursively reading from same file
+ if(!doc.getLocation().equals(urlDoc.getLocation()))
+ createObo(urlDoc);
+ }
+ }
+ reader.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ return qualifiers;
+ }
+
+ /**
+ * Each unit of the OBO file starts with one of the three supported stanza
+ * types: [Term], [Typedef], and [Instance]. Parsers/serializers will round-trip
+ * (successfully load and save) unrecognized stanzas.
+ * @param line
+ * @return
+ */
+ private boolean startOfStanza(final String line)
+ {
+ return line.startsWith("[");
+ }
+
+ /**
+ * Load in qualifiers from an input stream that are in the format:
+ * name=value
+ * @param ins
+ * @return
+ */
+ private QualifierVector loadText(final InputStream ins)
+ {
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
+ final QualifierVector qualifiers = new QualifierVector();
+ try
+ {
+ String line;
+ while((line = reader.readLine()) != null)
+ {
+ Matcher m = TAG_VALUE_PATTERN.matcher(line);
+ if(m.matches())
+ qualifiers.addQualifierValues(new Qualifier(m.group(1), m.group(2)));
+ }
+ reader.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ return qualifiers;
+ }
+
+ /**
+ * Import OBO
+ */
+ private void importFromFile()
+ {
+ final StickyFileChooser fc = new StickyFileChooser();
+ //final JCheckBox autoImport = new JCheckBox("Automatically import", true);
+ //fc.setAccessory(autoImport);
+
+ OboFileFilter oboFilter = new OboFileFilter(new String[]{"obo", "OBO"}, "OBO files");
+ fc.addChoosableFileFilter(oboFilter);
+ fc.setFileFilter(oboFilter);
+ int status = fc.showOpenDialog(UserDefinedQualifiers.this);
+ if(status != StickyFileChooser.APPROVE_OPTION)
+ return;
+
+ final FileDocument doc = new FileDocument(fc.getSelectedFile());
+ if(!doc.readable())
+ {
+ JOptionPane.showMessageDialog(UserDefinedQualifiers.this,
+ "Cannot read file "+doc.getName(),
+ "Problem reading file", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ createObo(doc);
+
+ createComponentToAddCvTerm();
+ mainPanel.revalidate();
+ mainPanel.repaint();
+ //if(autoImport.isSelected())
+ writeQualifierList(fc.getSelectedFile().getAbsolutePath());
+ }
+
+ private void importFromUrl()
+ {
+ String s = (String)JOptionPane.showInputDialog(
+ UserDefinedQualifiers.this,
+ "URL:", "Import Qualifiers",
+ JOptionPane.PLAIN_MESSAGE,
+ null, null,
+ "http://www.geneontology.org/GO_slims/goslim_generic.obo");
+ if(s == null || s.trim().equals("") || s.trim().equals("http://"))
+ return;
+
+ try
+ {
+ String urlStr = s.trim();
+ createObo(new URLDocument(new URL(urlStr)));
+ createComponentToAddCvTerm();
+ mainPanel.revalidate();
+ mainPanel.repaint();
+
+ writeQualifierList(urlStr);
+ }
+ catch (MalformedURLException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void createObo(Document doc)
+ {
+ try
+ {
+ logger4j.debug("Reading qualifiers from: "+doc.getName());
+ final QualifierList obo = new QualifierList();
+ obo.doc = doc;
+ obo.qualifiers = loadQualifiers(doc);
+ obos.add(obo);
+ }
+ catch (IOException e)
+ {
+ logger4j.error(e.getMessage());
+ JOptionPane.showMessageDialog(UserDefinedQualifiers.this,
+ "Problem Loading:\n"+doc.getName()+"\n"+
+ e.getMessage(), "Problem Loading",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+
+ /**
+ * Write or append to artemis qualifiers list
+ * @param loc
+ * @param isOBO
+ */
+ private static void writeQualifierList(final String loc)
+ {
+ int status = JOptionPane.showConfirmDialog(
+ null,
+ "Automatically import:\n"+loc+"\nbetween session?",
+ "Import Option", JOptionPane.YES_NO_OPTION);
+ if(status != JOptionPane.YES_OPTION)
+ return;
+
+ final boolean isOBO;
+ if(loc.toLowerCase().endsWith(".obo"))
+ isOBO = true;
+ else
+ {
+ File f = new File(loc);
+ if(f.exists())
+ isOBO = true;
+ else
+ isOBO = false;
+ }
+
+ final String qualPath = System.getProperty("user.home") + File.separator + ".artemis.qualifiers";
+ try
+ {
+ final FileDocument doc = new FileDocument(new File(qualPath));
+ if(doc.readable())
+ {
+ final BufferedReader reader =
+ new BufferedReader(new InputStreamReader(doc.getInputStream()));
+ try
+ {
+ String line;
+ while((line = reader.readLine()) != null)
+ {
+ if(line.startsWith("import: file://"+loc))
+ {
+ System.out.println("Import line already exists :\n"+line);
+ return;
+ }
+ else if(line.startsWith("import: "+loc))
+ {
+ System.out.println("Import line already exists :\n"+line);
+ return;
+ }
+ }
+ }
+ finally
+ {
+ reader.close();
+ }
+ }
+
+ final File qualFile = new File(qualPath);
+ final boolean exists = qualFile.exists();
+ final BufferedWriter bufferedwriter = new BufferedWriter(
+ new FileWriter(qualFile, true));
+
+ if(!exists)
+ {
+ bufferedwriter.append("# User defined list of qualifiers in the format of:\n");
+ bufferedwriter.append("# name=value\n");
+ bufferedwriter.append("# The qualifiers are separated by lines.\n");
+ bufferedwriter.append("# OBO files can be used by defining import statements:\n");
+ bufferedwriter.append("#import: http://www.geneontology.org/GO_slims/goslim_generic.obo\n\n");
+ }
+
+ if(isOBO)
+ {
+ if(loc.startsWith("http://"))
+ bufferedwriter.append("\nimport: "+loc+"\n");
+ else
+ bufferedwriter.append("\nimport: file://"+loc+"\n");
+ }
+ else
+ bufferedwriter.append(loc+"\n");
+
+ bufferedwriter.close();
+ }
+ catch (FileNotFoundException filenotfoundexception)
+ {
+ System.err.println(qualPath+" read error");
+ }
+ catch (IOException e)
+ {
+ System.err.println(qualPath+" i/o error");
+ }
+ }
+
+ private class OboFileFilter extends FileFilter
+ {
+ private String[] suffix;
+ private String descr;
+ OboFileFilter(final String[] suffix, final String descr)
+ {
+ this.suffix = suffix;
+ this.descr = descr;
+ }
+
+ public boolean accept(final File file)
+ {
+ if(file.isDirectory())
+ return true;
+
+ for(String s: suffix)
+ {
+ if(file.getName().endsWith("." + s) )
+ return true;
+ }
+ return false;
+ }
+
+ public String getDescription()
+ {
+ return descr;
+ }
+ }
+
+ protected void setQualifierTextArea(final QualifierTextArea qualifier_text_area)
+ {
+ this.qualifier_text_area = qualifier_text_area;
+ }
+
+ protected void setSelection(final Selection selection)
+ {
+ this.selection = selection;
+ }
+
+ private class ComboMouseListener extends MouseAdapter
+ {
+ private JComboBox cb;
+ ComboMouseListener(JComboBox cb)
+ {
+ this.cb = cb;
+ }
+
+ public void mouseEntered(MouseEvent me)
+ {
+ if(cb.getSelectedItem() != null)
+ cb.setToolTipText(getWrappedStr((String) cb.getSelectedItem()));
+ }
+
+ private String getWrappedStr(String s)
+ {
+ final StringBuilder buff = new StringBuilder();
+ final int lineLen = 60;
+ for(int k=0; k<s.length(); k+=lineLen)
+ {
+ int end = k + lineLen;
+ if(end > s.length())
+ end = s.length();
+ buff.append ( s.substring(k,end) ).append("\n");
+ }
+ return buff.toString();
+ }
+ }
+
+
+ private class QualifierList
+ {
+ Document doc;
+ QualifierVector qualifiers;
+ boolean isAcive = true;
+ }
+
+ public static void main(String args[])
+ {
+ final javax.swing.plaf.FontUIResource font_ui_resource =
+ Options.getOptions().getFontUIResource();
+
+ final Enumeration<Object> keys = UIManager.getDefaults().keys();
+ while(keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ Object value = UIManager.get(key);
+ if(value instanceof javax.swing.plaf.FontUIResource)
+ UIManager.put(key, font_ui_resource);
+ }
+
+ final JFrame f = new UserDefinedQualifiers();
+ f.pack();
+ f.setVisible(true);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/Utilities.java b/uk/ac/sanger/artemis/components/Utilities.java
new file mode 100644
index 0000000..1f5cbd7
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/Utilities.java
@@ -0,0 +1,186 @@
+/* Utilities.java
+ *
+ * created: Tue Sep 18 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/Utilities.java,v 1.4 2007-02-22 19:45:18 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.EntrySourceVector;
+import uk.ac.sanger.artemis.Options;
+
+import java.awt.*;
+import java.net.MalformedURLException;
+import javax.swing.*;
+
+/**
+ * Utilities methods used by the uk.ac.sanger.artemis.components package.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: Utilities.java,v 1.4 2007-02-22 19:45:18 tjc Exp $
+ **/
+
+public class Utilities
+{
+ /**
+ * Return the JFrame that contains the given component.
+ **/
+ public static JFrame getComponentFrame(final JComponent component)
+ {
+ if(component.getTopLevelAncestor() instanceof JFrame)
+ return(JFrame) component.getTopLevelAncestor();
+ else
+ return null;
+ }
+
+ /**
+ * Move the given JFrame to the centre of the screen.
+ **/
+ public static void centreFrame(final JFrame frame)
+ {
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ final int x_position =(screen.width - frame.getSize().width) / 2;
+ int y_position =(screen.height - frame.getSize().height) / 2;
+
+ if(y_position < 10)
+ y_position = 10;
+
+ frame.setLocation(new Point(x_position, y_position));
+ }
+
+ /**
+ * Move the given JFrame to the right of the screen.
+ **/
+ public static void rightJustifyFrame(final JFrame frame)
+ {
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ int y_position =(screen.height - frame.getSize().height) / 2;
+
+ if(y_position < 10)
+ y_position = 10;
+
+ frame.setLocation(new Point(0, y_position));
+ }
+
+ /**
+ * Move the given JFrame to the centre of the screen.
+ **/
+ public static void centreJustifyFrame(final JFrame frame, final int y_position)
+ {
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ final int x_position =(screen.width - frame.getSize().width) / 2;
+
+ frame.setLocation(new Point(x_position, y_position));
+ }
+
+
+
+ /**
+ * Find the parent Frame of the given component and re-centre it on the
+ * screen.
+ **/
+ public static void centreOwningFrame(final JComponent component)
+ {
+ final JFrame frame = getComponentFrame(component);
+ centreFrame(frame);
+ }
+
+ /**
+ * Test to find if this is a multi-display/device environment
+ * @return true if multiple displays found
+ */
+ public static boolean isMultiDisplay()
+ {
+ GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+
+ if(ge.getScreenDevices().length>1)
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Returns a Vector containing the possible Entry sources for this
+ * application.
+ * @param frame The component that is creating the EntrySource objects.
+ * (Used for requesters.)
+ * @param listener InputStreamProgressEvent objects will be sent to this
+ * listener as progress on reading is made.
+ **/
+ public static EntrySourceVector getEntrySources(final JFrame frame,
+ final InputStreamProgressListener listener)
+ {
+ final EntrySourceVector return_vector = new EntrySourceVector();
+
+ return_vector.add(new FileDialogEntrySource(frame, listener));
+ return_vector.add(new DbfetchEntrySource(frame));
+
+ // return_vector.add(new BioJavaEntrySource());
+
+ // this doesn't work on a v1.2 system so it is taken out with perl when
+ // necessary
+ // CORBA_START_MARKER
+
+ // The location of the IOR for the corba server at EMBL. Can be
+ // overridden using the options file.
+ final String embl_ior_url =
+ Options.getOptions().getProperty("embl_ior_url");
+
+ if(embl_ior_url != null)
+ {
+ try
+ {
+ return_vector.add(new EMBLCorbaEntrySource(frame, embl_ior_url));
+ }
+ catch(MalformedURLException e)
+ {
+ new MessageDialog(frame, "the url given for the embl database is " +
+ "badly formatted: " + e.getMessage());
+ }
+ }
+
+ // The location of the IOR for the pathogens group read-write corba
+ // server.
+ final String db_ior_url =
+ Options.getOptions().getProperty("db_ior_url");
+
+ if(db_ior_url != null)
+ {
+ try
+ {
+ return_vector.add(new WritableEMBLCorbaEntrySource(frame,
+ db_ior_url));
+ }
+ catch(MalformedURLException e)
+ {
+ new MessageDialog(frame, "the url given for the embl database is " +
+ "badly formatted: " + e.getMessage());
+ }
+ }
+ // CORBA_END_MARKER
+
+ return return_vector;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/ValidateViewer.java b/uk/ac/sanger/artemis/components/ValidateViewer.java
new file mode 100644
index 0000000..adc6859
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ValidateViewer.java
@@ -0,0 +1,281 @@
+/* ValidateViewer
+ *
+ * created: 2013
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components;
+
+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 javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.EntryGroupChangeEvent;
+import uk.ac.sanger.artemis.EntryGroupChangeListener;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.ValidateFeature;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+public class ValidateViewer extends FileViewer implements EntryGroupChangeListener
+{
+ private static final long serialVersionUID = 1L;
+ private EntryGroup entryGrp;
+ private FeatureVector selectedFeatures;
+ private JCheckBox showFailedFeatures = new JCheckBox("failed features only", true);
+ private JCheckBox showObsoleteFeatures = new JCheckBox("obsolete features", false);
+ private boolean inAutoFix = false;
+
+ /**
+ * Viewer to display validation results
+ * @param entryGrp
+ * @param features
+ */
+ public ValidateViewer(final EntryGroup entryGrp,
+ final FeatureVector selectedFeatures)
+ {
+ super("Validation Report :: ", false, false, true);
+ this.entryGrp = entryGrp;
+ this.selectedFeatures = selectedFeatures;
+
+ update();
+ setVisible(true);
+
+ final JButton fixButton = new JButton("Auto-Fix");
+ fixButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final JPanel options = new JPanel( new GridLayout(3,1) );
+ final JCheckBox extendToNextStop = new JCheckBox("Fix stop codon", true);
+ extendToNextStop.setToolTipText(
+ "If the last codon is not a stop codon, but the next codon\n" +
+ "is, then the end of the feature is extended by 3 bases.");
+ options.add(extendToNextStop);
+
+ final JCheckBox boundary = new JCheckBox("Gene Model boundaries", true);
+ boundary.setToolTipText(
+ "Adjust boundary coordinates so that parent and child\n" +
+ "features are consistent.");
+ options.add(boundary);
+
+ final JCheckBox cdsPhase = new JCheckBox("CDS phase", true);
+ cdsPhase.setToolTipText("If no phase is set then set it to 0.");
+ options.add(cdsPhase);
+
+ if( entryGrp != null && !GeneUtils.isGFFEntry( entryGrp ) )
+ {
+ boundary.setSelected(false);
+ boundary.setEnabled(false);
+ cdsPhase.setSelected(false);
+ cdsPhase.setEnabled(false);
+ }
+
+ if(GeneUtils.isDatabaseEntry(entryGrp))
+ {
+ cdsPhase.setSelected(false);
+ cdsPhase.setEnabled(false);
+ }
+
+ int status =
+ JOptionPane.showConfirmDialog(ValidateViewer.this, options,
+ "Auto-Fix", JOptionPane.OK_CANCEL_OPTION);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+
+ try
+ {
+ ValidateFeature validate = new ValidateFeature(entryGrp);
+ entryGrp.getActionController().startAction();
+ inAutoFix = true;
+ final FeatureVector features = getFeatures();
+ for(int i=0; i<features.size(); i++)
+ {
+ final Feature f = features.elementAt(i);
+ if( extendToNextStop.isSelected() &&
+ !validate.hasValidStop(f.getEmblFeature()) )
+ f.fixStopCodon();
+
+ if(boundary.isSelected())
+ fixBoundary(f);
+
+ if(cdsPhase.isSelected())
+ fixCDSPhase(f);
+ }
+ }
+ catch (ReadOnlyException e1)
+ {
+ JOptionPane.showMessageDialog(ValidateViewer.this,
+ "Read only entry", "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ finally
+ {
+ inAutoFix = false;
+ entryGrp.getActionController().endAction();
+ update();
+ }
+ }
+ });
+
+ if(entryGrp.getDefaultEntry() != null &&
+ entryGrp.getDefaultEntry().isReadOnly())
+ fixButton.setEnabled(false);
+ button_panel.add(fixButton);
+
+ button_panel.add(showFailedFeatures);
+ showFailedFeatures.addItemListener(new ItemListener(){
+ public void itemStateChanged(ItemEvent arg0)
+ {
+ update();
+ }
+ });
+
+ if(GeneUtils.isGFFEntry(entryGrp))
+ button_panel.add(showObsoleteFeatures);
+ showObsoleteFeatures.addItemListener(new ItemListener(){
+ public void itemStateChanged(ItemEvent arg0)
+ {
+ update();
+ }
+ });
+
+ entryGrp.addEntryGroupChangeListener(new EntryGroupChangeListener(){
+ public void entryGroupChanged(EntryGroupChangeEvent event)
+ {
+ switch(event.getType())
+ {
+ case EntryGroupChangeEvent.DONE_GONE:
+ entryGrp.removeEntryGroupChangeListener(this);
+ dispose();
+ break;
+ }
+ }
+ });
+
+ entryGrp.addFeatureChangeListener(new FeatureChangeListener(){
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ if(inAutoFix)
+ return;
+
+ inAutoFix = true;
+ ValidateViewer.this.selectedFeatures = null;
+
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ update();
+ inAutoFix = false;
+ }
+ });
+ }
+ });
+ }
+
+ private void update()
+ {
+ final FeatureVector features = getFeatures();
+ super.setText("");
+ final ValidateFeature gffTest = new ValidateFeature(entryGrp);
+ int nfail = 0;
+ int total = 0;
+ for(int i=0; i<features.size(); i++)
+ {
+ uk.ac.sanger.artemis.io.Feature f = features.elementAt(i).getEmblFeature();
+ if(!showObsoleteFeatures.isSelected() &&
+ f instanceof GFFStreamFeature && GeneUtils.isObsolete((GFFStreamFeature)f))
+ continue;
+
+ if(!gffTest.featureValidate(f,
+ this, showFailedFeatures.isSelected()))
+ nfail++;
+ total++;
+ }
+
+ setTitle("Validation Report :: "+ total+
+ " feature(s) Pass: "+(total-nfail)+" Failed: "+nfail);
+ }
+
+ private FeatureVector getFeatures()
+ {
+ if(selectedFeatures == null)
+ selectedFeatures = entryGrp.getAllFeatures();
+ return selectedFeatures;
+ }
+
+ private void fixCDSPhase(final Feature feature) throws ReadOnlyException
+ {
+ if( !(feature.getEmblFeature() instanceof GFFStreamFeature) ||
+ GeneUtils.isDatabaseEntry(entryGrp) ||
+ !feature.isCDS())
+ return;
+
+ final GFFStreamFeature gffFeature = (GFFStreamFeature) feature.getEmblFeature();
+ if(ValidateFeature.isCDSPhaseOK(gffFeature))
+ return;
+
+ Qualifier q = new Qualifier("codon_start", "1");
+ try
+ {
+ gffFeature.setQualifier(q);
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void fixBoundary(final Feature feature)
+ {
+ if( ! (feature.getEmblFeature() instanceof GFFStreamFeature) )
+ return;
+
+ final GFFStreamFeature gffFeature = (GFFStreamFeature) feature.getEmblFeature();
+ final ChadoCanonicalGene gene = gffFeature.getChadoGene();
+ if(gene != null && !gene.getGene().isReadOnly() && GeneUtils.isBoundaryOK(gene) > 0)
+ {
+ //updatedFeatures.add(feature);
+ GeneUtils.checkGeneBoundary(gene, false);
+ }
+ }
+
+ public void entryGroupChanged(EntryGroupChangeEvent event)
+ {
+ entryGrp.removeEntryGroupChangeListener(ValidateViewer.this);
+ dispose();
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/ViewMenu.java b/uk/ac/sanger/artemis/components/ViewMenu.java
new file mode 100644
index 0000000..de929ec
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ViewMenu.java
@@ -0,0 +1,1959 @@
+/* ViewMenu.java
+ *
+ * created: Tue Dec 29 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ViewMenu.java,v 1.15 2009-04-22 08:50:13 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.components.filetree.FileList;
+import uk.ac.sanger.artemis.components.filetree.RemoteFileNode;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.ValidateFeature;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.j2ssh.FTProgress;
+import uk.ac.sanger.artemis.j2ssh.FileTransferProgressMonitor;
+
+import java.io.*;
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+import com.sshtools.j2ssh.sftp.FileAttributes;
+
+/**
+ * A popup menu with viewing commands.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ViewMenu.java,v 1.15 2009-04-22 08:50:13 tjc Exp $
+ **/
+
+public class ViewMenu extends SelectionMenu
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The EntryGroup that was passed to the constructor.
+ **/
+ private EntryGroup entry_group = null;
+
+ /**
+ * The Selection that was passed to the constructor.
+ **/
+ private Selection selection = null;
+
+ /**
+ * The GotoEventSource that was passed to the constructor.
+ **/
+ //private GotoEventSource goto_event_source = null;
+
+ private BasePlotGroup base_plot_group;
+
+ /**
+ * Create a new ViewMenu object.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group The EntryGroup object where new features/entries will
+ * be added.
+ * @param goto_event_source The object that we will call makeBaseVisible()
+ * on.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ * @param menu_name The name of the new menu.
+ **/
+ public ViewMenu(final JFrame frame,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group,
+ final String menu_name)
+ {
+ super(frame, menu_name, selection);
+
+ this.entry_group = entry_group;
+ this.selection = selection;
+
+ this.base_plot_group = base_plot_group;
+
+ final JMenuItem plot_features_item = new JMenuItem("Feature Plots");
+ plot_features_item.setAccelerator(PLOT_FEATURES_KEY);
+ plot_features_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ plotSelectedFeatures(getParentFrame(), getSelection());
+ }
+ });
+
+ final JMenuItem view_feature_item = new JMenuItem("Selected Features");
+ view_feature_item.setAccelerator(VIEW_FEATURES_KEY);
+ view_feature_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ viewSelectedFeatures(getParentFrame(), getSelection());
+ }
+ });
+
+ final JMenuItem view_selection_item = new JMenuItem("Selection");
+ view_selection_item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ new SelectionViewer(getSelection(), entry_group);
+ }
+ });
+
+ final JMenuItem feature_info_item = new JMenuItem("Feature Statistics");
+ feature_info_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ viewSelectedFeatureInfo();
+ }
+ });
+
+ final SelectionSubMenu view_bases = new SelectionSubMenu(this, "Bases");
+ final JMenuItem view_bases_item = new JMenuItem("Bases Of Selection");
+ view_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ viewSelectedBases(true, false);
+ }
+ });
+ view_bases.add(view_bases_item);
+
+ final JMenuItem view_bases_as_fasta_item =
+ new JMenuItem("Bases Of Selection As FASTA");
+ view_bases_as_fasta_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ viewSelectedBases(false, false);
+ }
+ });
+ view_bases.add(view_bases_as_fasta_item);
+ view_bases.addSeparator();
+
+ final JMenuItem view_exon_bases =
+ new JMenuItem("Bases Of Selected Exons As FASTA");
+ view_exon_bases.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ viewSelectedBases(false, true);
+ }
+ });
+ view_bases.add(view_exon_bases);
+
+ final SelectionSubMenu view_aa = new SelectionSubMenu(this, "Amino Acids");
+ final JMenuItem view_aa_item = new JMenuItem("Amino Acids Of Selection");
+ view_aa_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ viewSelectedAminoAcids(true);
+ }
+ });
+ view_aa.add(view_aa_item);
+
+ final JMenuItem view_aa_as_fasta_item =
+ new JMenuItem("Amino Acids Of Selection As FASTA");
+ view_aa_as_fasta_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ viewSelectedAminoAcids(false);
+ }
+ });
+ view_aa.add(view_aa_as_fasta_item);
+
+ final JMenuItem overview_item = new JMenuItem("Overview");
+ overview_item.setAccelerator(OVERVIEW_KEY);
+ overview_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ new EntryGroupInfoDisplay(getParentFrame(), entry_group);
+ }
+ });
+
+ final JMenuItem forward_overview_item = new JMenuItem("Forward Strand Overview");
+ forward_overview_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ new EntryGroupInfoDisplay(getParentFrame(), entry_group,
+ Bases.FORWARD);
+ }
+ });
+
+ final JMenuItem reverse_overview_item = new JMenuItem("Reverse Strand Overview");
+ reverse_overview_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ new EntryGroupInfoDisplay(getParentFrame(), entry_group,
+ Bases.REVERSE);
+ }
+ });
+
+ final JMenuItem view_cds_item = new JMenuItem("CDS Genes And Products");
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ view_cds_item.setEnabled(false);
+ view_cds_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ final FeaturePredicate feature_predicate =
+ new FeatureKeyPredicate(Key.CDS);
+
+ final String filter_name =
+ "CDS features (filtered from: " +
+ getParentFrame().getTitle() + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup(entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame(filter_name,
+ selection, goto_event_source,
+ filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.getFeatureList().setShowGenes(true);
+ feature_list_frame.getFeatureList().setShowProducts(true);
+
+ feature_list_frame.setVisible(true);
+ }
+ });
+
+ JMenu search_results_menu = null;
+
+ search_results_menu = new SelectionSubMenu(this, "Search Results");
+
+ final boolean sanger_options =
+ Options.getOptions().getPropertyTruthValue("sanger_options");
+
+ final ExternalProgramVector external_programs =
+ Options.getOptions().getExternalPrograms();
+
+ final StringVector external_program_names = new StringVector();
+
+ for(int i = 0 ; i < external_programs.size() ; ++i)
+ {
+ final ExternalProgram external_program =
+ external_programs.elementAt(i);
+
+ final String new_name = external_program.getName();
+
+ if(!external_program_names.contains(new_name))
+ external_program_names.add(new_name);
+ }
+
+ for(int i = 0 ; i < external_program_names.size() ; ++i)
+ {
+ final String external_program_name =
+ (String)external_program_names.elementAt(i);
+
+ final JMenuItem new_menu =
+ makeSearchResultsMenu(external_program_name, false, sanger_options);
+ search_results_menu.add(new_menu);
+ }
+
+ if(sanger_options)
+ {
+ search_results_menu.addSeparator();
+
+ for(int i = 0 ; i < external_program_names.size() ; ++i)
+ {
+ final String external_program_name =
+ (String)external_program_names.elementAt(i);
+
+ final JMenuItem new_menu =
+ makeSearchResultsMenu(external_program_name, true, sanger_options);
+ search_results_menu.add(new_menu);
+ }
+ }
+
+ final int MAX_FILTER_FEATURE_COUNT = 10000;
+
+ final JMenu feature_filters_menu = new SelectionSubMenu(this, "Feature Filters");
+
+ final JMenuItem bad_start_codons_item =
+ new JMenuItem("Suspicious Start Codons ...");
+ bad_start_codons_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showBadStartCodons(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+ final JMenuItem bad_stop_codons_item =
+ new JMenuItem("Suspicious Stop Codons ...");
+ bad_stop_codons_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showBadStopCodons(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+ final JMenuItem stop_codons_in_translation =
+ new JMenuItem("Stop Codons In Translation ...");
+ stop_codons_in_translation.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showStopsInTranslation(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+
+ final JMenuItem intronsSpliceSite =
+ new JMenuItem("Introns without GT/GC start and AG end ...");
+ intronsSpliceSite.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showIntrons(selection, entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+
+ final JMenuItem geneModelCheck =
+ new JMenuItem("Gene model boundary check ...");
+ geneModelCheck.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ geneBoundaryCheck(selection, entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+
+ final JMenuItem validate =
+ new JMenuItem("Validation checks ...");
+ validate.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ {
+ ValidateFeature gffTest = new ValidateFeature(getEntryGroup());
+ gffTest.featureListErrors(entry_group, selection, goto_event_source, base_plot_group);
+ }
+ }
+ });
+
+
+ final JMenuItem bad_feature_keys_item =
+ new JMenuItem("Non EMBL Keys ...");
+ bad_feature_keys_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showNonEMBLKeys(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+ final JMenuItem duplicated_keys_item =
+ new JMenuItem("Duplicated Features ...");
+ duplicated_keys_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showDuplicatedFeatures(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+ final JMenuItem overlapping_cds_features_item;
+
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ overlapping_cds_features_item =
+ new JMenuItem("Overlapping "+DatabaseDocument.EXONMODEL+" Features ...");
+ else
+ overlapping_cds_features_item =
+ new JMenuItem("Overlapping CDS Features ...");
+ overlapping_cds_features_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showOverlappingCDSs(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+ final JMenuItem same_stop_cds_features_item;
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ same_stop_cds_features_item = new JMenuItem(
+ DatabaseDocument.EXONMODEL+"s Sharing Stop Codons ...");
+ else
+ same_stop_cds_features_item = new JMenuItem("CDSs Sharing Stop Codons ...");
+ same_stop_cds_features_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showFeaturesWithSameStopCodons(getParentFrame(),
+ selection, entry_group,
+ goto_event_source,
+ base_plot_group);
+ }
+ });
+
+ final JMenuItem missing_qualifier_features_item =
+ new JMenuItem("Features Missing Required Qualifiers ...");
+ missing_qualifier_features_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showMissingQualifierFeatures(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+ final JMenuItem all_filters_item =
+ new JMenuItem("Apply All Filters Above ...");
+ all_filters_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(!checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ return;
+
+ showBadStartCodons(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+
+ showBadStopCodons(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+
+ showStopsInTranslation(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+
+ showNonEMBLKeys(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+
+ showDuplicatedFeatures(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+
+ showOverlappingCDSs(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+
+ showFeaturesWithSameStopCodons(getParentFrame(),
+ selection, entry_group,
+ goto_event_source,
+ base_plot_group);
+
+ showMissingQualifierFeatures(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+
+ showFilterByMultipleID(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+ final JMenuItem filter_by_key_item =
+ new JMenuItem("Filter By Key ...");
+ filter_by_key_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(checkEntryGroupSize(MAX_FILTER_FEATURE_COUNT))
+ showFilterByKey(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+
+ final JMenuItem filter_by_multiple_sys_id =
+ new JMenuItem("Duplicate Systematic Name Qualifier ...");
+ filter_by_multiple_sys_id.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ showFilterByMultipleID(getParentFrame(), selection,
+ entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+
+ final JMenuItem filter_by_selection_item =
+ new JMenuItem("Selected Features ...");
+ filter_by_selection_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ showFilterBySelection(getParentFrame(),
+ selection, entry_group, goto_event_source,
+ base_plot_group);
+ }
+ });
+
+ feature_filters_menu.add(bad_start_codons_item);
+ feature_filters_menu.add(bad_stop_codons_item);
+ feature_filters_menu.add(stop_codons_in_translation);
+ feature_filters_menu.add(intronsSpliceSite);
+ if(GeneUtils.isGFFEntry( getEntryGroup() ))
+ feature_filters_menu.add(geneModelCheck);
+
+ feature_filters_menu.add(bad_feature_keys_item);
+ feature_filters_menu.add(duplicated_keys_item);
+ feature_filters_menu.add(overlapping_cds_features_item);
+ feature_filters_menu.add(same_stop_cds_features_item);
+ feature_filters_menu.add(missing_qualifier_features_item);
+ feature_filters_menu.add(filter_by_multiple_sys_id);
+ feature_filters_menu.add(validate);
+
+ feature_filters_menu.addSeparator();
+ feature_filters_menu.add(all_filters_item);
+ feature_filters_menu.addSeparator();
+ feature_filters_menu.add(filter_by_key_item);
+ feature_filters_menu.add(filter_by_selection_item);
+
+ add(view_feature_item);
+ add(view_selection_item);
+ addSeparator();
+ if(search_results_menu != null)
+ add(search_results_menu);
+
+ add(view_cds_item);
+ add(feature_filters_menu);
+ addSeparator();
+ add(overview_item);
+ add(forward_overview_item);
+ add(reverse_overview_item);
+ addSeparator();
+ add(view_bases);
+ add(view_aa);
+ addSeparator();
+ add(feature_info_item);
+ add(plot_features_item);
+ }
+
+ /**
+ * Create a new ViewMenu object.
+ * @param entry_edit The EntryEdit that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group The EntryGroup object where new features/entries will
+ * be added.
+ * @param goto_event_source The object that we will call makeBaseVisible()
+ * on.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ public ViewMenu(final JFrame frame,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final EntryGroup entry_group,
+ final BasePlotGroup base_plot_group)
+ {
+ this(frame, selection, goto_event_source, entry_group,
+ base_plot_group, "View");
+ }
+
+ /**
+ * The shortcut for Show Feature Plots.
+ **/
+ final static KeyStroke PLOT_FEATURES_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_W,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ final static public int PLOT_FEATURES_KEY_CODE = KeyEvent.VK_W;
+
+ /**
+ * The shortcut for View Selected Features.
+ **/
+ final static KeyStroke VIEW_FEATURES_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_V,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ final static public int VIEW_FEATURES_KEY_CODE = KeyEvent.VK_V;
+
+ /**
+ * The shortcut for Show Overview.
+ **/
+ final static KeyStroke OVERVIEW_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_O,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ final static public int OVERVIEW_KEY_CODE = KeyEvent.VK_O;
+
+ /**
+ * The shortcut for View FASTA in browser.
+ **/
+ final static KeyStroke FASTA_IN_BROWSER_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_F,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ final static public int FASTA_IN_BROWSER_KEY_CODE = KeyEvent.VK_F;
+
+ /**
+ * The shortcut for View FASTA.
+ **/
+ final static KeyStroke VIEW_FASTA_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_R,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ final static public int VIEW_FASTA_KEY_CODE = KeyEvent.VK_R;
+
+ /**
+ * The shortcut for View BLASTP in browser.
+ **/
+ final static KeyStroke BLASTP_IN_BROWSER_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_B,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ final static public int BLASTP_IN_BROWSER_KEY_CODE = KeyEvent.VK_B;
+
+ /**
+ * The shortcut for View BLASTP.
+ **/
+ final static KeyStroke VIEW_BLASTP_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_BACK_QUOTE ,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ final static public int VIEW_BLASTP_KEY_CODE = KeyEvent.VK_BACK_QUOTE;
+
+ /**
+ * The shortcut for View HTH.
+ **/
+ final static KeyStroke VIEW_HTH_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_H,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); //InputEvent.CTRL_MASK);
+
+ final static public int VIEW_HTH_KEY_CODE = KeyEvent.VK_H;
+
+ /**
+ * Make a JMenuItem for viewing the results of running the given program.
+ * @param send_to_browser if true the results should be sent straight to
+ * the web browser rather than using a SearchResultViewer object.
+ * @param sanger_options true if the sanger_options is set to true in the
+ * options file.
+ **/
+ private JMenuItem makeSearchResultsMenu(final String program_name,
+ final boolean send_to_browser,
+ final boolean sanger_options)
+ {
+ final String suffix;
+ if(send_to_browser)
+ suffix = new String(" results (in browser)");
+ else
+ suffix = new String(" results");
+
+ final JMenuItem new_menu = new JMenuItem(program_name + suffix);
+
+ if ((sanger_options && send_to_browser || !sanger_options)
+ && program_name.equals("fasta"))
+ new_menu.setAccelerator (FASTA_IN_BROWSER_KEY);
+ else
+ {
+ if ((sanger_options && send_to_browser || !sanger_options)
+ && program_name.equals("blastp"))
+ new_menu.setAccelerator (BLASTP_IN_BROWSER_KEY);
+ else
+ {
+ if(program_name.equals("fasta"))
+ new_menu.setAccelerator (VIEW_FASTA_KEY);
+ else if(program_name.equals("blastp"))
+ new_menu.setAccelerator (VIEW_BLASTP_KEY);
+ else if(program_name.equals("hth"))
+ new_menu.setAccelerator (VIEW_HTH_KEY);
+ }
+ }
+
+ new_menu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ viewExternalResults(getParentFrame(), getSelection(),
+ program_name, send_to_browser);
+ }
+ });
+
+ return new_menu;
+ }
+
+ /**
+ * Popup a FeatureListFrame containing the non-pseudo CDS features that
+ * have invalid start codons.
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ protected static void showBadStartCodons (final JFrame parent_frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final FeaturePredicate cds_predicate;
+
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ cds_predicate = new FeatureKeyPredicate(new Key(DatabaseDocument.EXONMODEL));
+ else
+ cds_predicate =
+ new FeaturePredicateConjunction(
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
+ FeaturePredicateConjunction.AND);
+
+ final FeaturePredicate feature_predicate = new FeaturePredicate ()
+ {
+ public boolean testPredicate (final Feature feature)
+ {
+ if(!cds_predicate.testPredicate (feature))
+ return false;
+
+ if(feature.hasValidStartCodon (true))
+ return false;
+ else
+ return true;
+ }
+ };
+
+ final String filter_name;
+
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ filter_name =
+ DatabaseDocument.EXONMODEL+" features with suspicious start codons (filtered from: " +
+ parent_frame.getTitle () + ")";
+ else
+ filter_name =
+ "CDS features with suspicious start codons (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+ /**
+ * Popup a FeatureListFrame containing the non-pseudo CDS features that
+ * have invalid stop codons.
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ protected static void showBadStopCodons (final JFrame parent_frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup
+ base_plot_group)
+ {
+ final FeaturePredicate cds_predicate;
+
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ cds_predicate = new FeatureKeyPredicate(new Key(DatabaseDocument.EXONMODEL));
+ else
+ cds_predicate =
+ new FeaturePredicateConjunction(
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
+ FeaturePredicateConjunction.AND);
+
+ final FeaturePredicate feature_predicate = new FeaturePredicate ()
+ {
+ public boolean testPredicate (final Feature feature)
+ {
+ if(!cds_predicate.testPredicate (feature))
+ return false;
+
+ if(feature.hasValidStopCodon (true))
+ return false;
+ else
+ return true;
+ }
+ };
+
+ final String filter_name;
+
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ filter_name=
+ DatabaseDocument.EXONMODEL+" features with suspicious stop codons (filtered from: " +
+ parent_frame.getTitle () + ")";
+ else
+ filter_name =
+ "CDS features with suspicious stop codons (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+ /**
+ * Popup a FeatureListFrame containing the non-pseudo CDS features that
+ * contain a stop codon in the translation.
+ * @param parent_frame The parent Frame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this Menu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ private static void showStopsInTranslation(final Frame parent_frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup
+ base_plot_group)
+ {
+ final FeaturePredicate cds_predicate;
+
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ cds_predicate = new FeatureKeyPredicate(new Key(DatabaseDocument.EXONMODEL));
+ else
+ cds_predicate =
+ new FeaturePredicateConjunction(
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
+ FeaturePredicateConjunction.AND);
+
+ final FeaturePredicate feature_predicate = new FeaturePredicate ()
+ {
+ public boolean testPredicate (final Feature feature)
+ {
+ if(!cds_predicate.testPredicate (feature))
+ return false;
+
+ final AminoAcidSequence amino_acids = feature.getTranslation ();
+ if(amino_acids.containsStopCodon ())
+ return true;
+ else
+ return false;
+ }
+ };
+
+ final String filter_name;
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ filter_name =
+ DatabaseDocument.EXONMODEL+" features with stop codon(s) in translation (filtered from: " +
+ parent_frame.getTitle () + ")";
+ else
+ filter_name =
+ "CDS features with stop codon(s) in translation (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+ /**
+ * Popup a FeatureListFrame containing the features that contains
+ * introns without GT/GC start and AG end.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ protected static void showIntrons(final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final FeaturePredicate feature_predicate = Selector.getIntronPredicate();
+ final String filter_name = "Contains introns without GT/GC start and AG end";
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+
+ /**
+ * Popup a FeatureListFrame containing the gene models with boundaries that
+ * need fixing.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ protected static void geneBoundaryCheck(
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final FeaturePredicate feature_predicate = new FeaturePredicate() {
+ // return true if boundary need fixing
+ public boolean testPredicate(Feature feature)
+ {
+ if(!GeneUtils.isGFFEntry(entry_group) ||
+ !feature.getKey().equals("gene"))
+ return false;
+ ChadoCanonicalGene chadoGene =
+ ((GFFStreamFeature)feature.getEmblFeature()).getChadoGene();
+
+ if(chadoGene == null)
+ return false;
+ if(GeneUtils.isBoundaryOK(chadoGene) > 0)
+ return true;
+
+ return !GeneUtils.isStrandOK(chadoGene);
+ }
+ };
+
+ final String filter_name = "Gene Model Boundary";
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+ /**
+ * Popup a FeatureListFrame containing the features that have non-EMBL keys.
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ protected static void showNonEMBLKeys(final JFrame parent_frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup
+ base_plot_group)
+ {
+ final FeaturePredicate feature_predicate =
+ new FeaturePredicate () {
+ public boolean testPredicate (final Feature feature) {
+ if (feature.hasValidEMBLKey ()) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ };
+
+ final String filter_name =
+ "features with a non-EMBL key (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+
+ /**
+ * Popup a FeatureListFrame containing the features that have the same key
+ * and location as another features (ie. duplicates).
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ protected static void showDuplicatedFeatures (final JFrame parent_frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup
+ base_plot_group)
+ {
+ final FeaturePredicate feature_predicate =
+ new FeaturePredicate () {
+ public boolean testPredicate (final Feature feature) {
+ final Entry feature_entry = feature.getEntry ();
+
+ final int feature_index = feature_entry.indexOf (feature);
+
+ if (feature_index + 1 == feature_entry.getFeatureCount ()) {
+ // last in the Entry
+ return false;
+ }
+
+ final Feature next_feature =
+ feature_entry.getFeature (feature_index + 1);
+
+ if (feature.getKey ().equals (next_feature.getKey ()) &&
+ feature.getLocation ().equals (next_feature.getLocation ())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+
+ final String filter_name =
+ "duplicated Features (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+ /**
+ * Popup a FeatureListFrame containing those CDS features that overlap with
+ * the next feature.
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ protected static void showOverlappingCDSs(final JFrame parent_frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final Key key;
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ key = new Key(DatabaseDocument.EXONMODEL);
+ else
+ key = Key.CDS;
+
+ final FeatureKeyPredicate cds_predicate = new FeatureKeyPredicate (key);
+
+ final FeaturePredicate feature_predicate =
+ new FeaturePredicate () {
+ public boolean testPredicate (final Feature test_feature) {
+ if (!cds_predicate.testPredicate (test_feature)) {
+ return false;
+ }
+
+ final Range feature_range = test_feature.getMaxRawRange ();
+
+ final FeatureVector overlapping_features;
+
+ try {
+ overlapping_features =
+ entry_group.getFeaturesInRange (feature_range);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ for (int i = 0 ; i < overlapping_features.size () ; ++i) {
+ final Feature current_feature = overlapping_features.elementAt (i);
+
+ if (current_feature != test_feature &&
+ cds_predicate.testPredicate (current_feature)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ };
+
+ final String filter_name =
+ "overlapping "+key.getKeyString()+" features (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+ /**
+ * Popup a FeatureListFrame containing those CDS features that overlap with
+ * the next feature.
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ private static void
+ showFeaturesWithSameStopCodons(final JFrame parent_frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final Key key;
+ if(GeneUtils.isDatabaseEntry(entry_group))
+ key = new Key(DatabaseDocument.EXONMODEL);
+ else
+ key = Key.CDS;
+ final FeatureKeyPredicate cds_predicate = new FeatureKeyPredicate (key);
+
+ final FeaturePredicate feature_predicate =
+ new FeaturePredicate () {
+ public boolean testPredicate (final Feature test_feature) {
+ if (!cds_predicate.testPredicate (test_feature)) {
+ return false;
+ }
+
+ final Range feature_range = test_feature.getMaxRawRange ();
+
+ final FeatureVector overlapping_features;
+
+ try {
+ overlapping_features =
+ entry_group.getFeaturesInRange (feature_range);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ for (int i = 0 ; i < overlapping_features.size () ; ++i) {
+ final Feature current_feature = overlapping_features.elementAt (i);
+
+ if (current_feature == test_feature) {
+ continue;
+ }
+
+ if (current_feature != test_feature &&
+ cds_predicate.testPredicate (current_feature) &&
+ (current_feature.getLastBase () ==
+ test_feature.getLastBase ()) &&
+ (current_feature.getSegments ().lastElement ().getFrameID () ==
+ test_feature.getSegments ().lastElement ().getFrameID ())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ final String filter_name =
+ key.getKeyString()+" features with the same stop codon as another (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+ /**
+ * Popup a FeatureListFrame containing the features that are missing
+ * required EMBL qualifiers.
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ protected static void showMissingQualifierFeatures(final JFrame parent_frame,
+ final Selection selection, final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final FeaturePredicate feature_predicate =
+ new FeaturePredicate () {
+ public boolean testPredicate (final Feature feature) {
+ if (feature.hasRequiredQualifiers ()) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ };
+
+ final String filter_name =
+ "features that are missing a required EMBL " +
+ "qualifier (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+ /**
+ * Popup a FeatureListFrame containing only those features that have the
+ * key choosen by the user.
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ private static void showFilterByKey (final JFrame parent_frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final KeyChooser key_chooser =
+ new KeyChooser (Options.getArtemisEntryInformation (),
+ new Key ("misc_feature"));
+
+ key_chooser.getKeyChoice ().addItemListener (new ItemListener () {
+ public void itemStateChanged (ItemEvent e) {
+ if (e.getStateChange () == ItemEvent.SELECTED) {
+ showFilterByKeyHelper (parent_frame,
+ key_chooser.getKeyChoice ().getSelectedItem (),
+ selection, entry_group, goto_source,
+ base_plot_group);
+ key_chooser.setVisible (false);
+ key_chooser.dispose ();
+ }
+ }
+ });
+
+ key_chooser.getOKButton ().addActionListener (new ActionListener () {
+ public void actionPerformed (ActionEvent _) {
+ showFilterByKeyHelper (parent_frame,
+ key_chooser.getKeyChoice ().getSelectedItem (),
+ selection, entry_group, goto_source,
+ base_plot_group);
+ key_chooser.setVisible (false);
+ key_chooser.dispose ();
+ }
+ });
+
+ key_chooser.setVisible (true);
+ }
+
+ /**
+ * Popup a FeatureListFrame containing only those features that have the
+ * key choosen by the user.
+ * @param parent_frame The parent JFrame.
+ * @param key The key to use in the filter.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ private static void showFilterByKeyHelper (final JFrame parent_frame,
+ final Key key, final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final String filter_name =
+ "features with key: " + key + " (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group,
+ new FeatureKeyPredicate (key), filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+
+ /**
+ * Popup a FeatureListFrame containing the features that have multiple
+ * systematic ID qualifiers (e.g. two or more systematic_id or locus_tag).
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ protected static void showFilterByMultipleID(final JFrame parent_frame,
+ final Selection selection, final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final FeaturePredicate feature_predicate = new FeaturePredicate ()
+ {
+ public boolean testPredicate (final Feature feature)
+ {
+ StringVector names = Options.getOptions().getSystematicQualifierNames();
+
+ for(int i=0; i<names.size(); i++)
+ {
+ try
+ {
+ Qualifier qualifier = feature.getQualifierByName((String) names.get(i));
+ if(qualifier != null && qualifier.getValues().size() > 1)
+ return true;
+ }
+ catch (InvalidRelationException e){}
+ }
+ return false;
+ }
+ };
+
+ final String filter_name =
+ "features that have multiple systematic name qualifiers " +
+ "(filtered from: " + parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, feature_predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+
+
+ /**
+ * Popup a FeatureListFrame containing only those features that are
+ * currently in the Selection.
+ * @param parent_frame The parent JFrame.
+ * @param selection The Selection to pass to the FeatureList and to use to
+ * filter the entry_group.
+ * @param entry_group The EntryGroup to pass to the FilteredEntryGroup.
+ * @param goto_source The GotoEventSource to pass to the FeatureList.
+ * @param base_plot_group The BasePlotGroup associated with this JMenu -
+ * needed to call getCodonUsageAlgorithm()
+ **/
+ private static void showFilterBySelection (final JFrame parent_frame,
+ final Selection selection, final EntryGroup entry_group,
+ final GotoEventSource goto_source,
+ final BasePlotGroup base_plot_group)
+ {
+ final FeaturePredicate predicate =
+ new FeatureFromVectorPredicate (selection.getAllFeatures ());
+
+ final String filter_name =
+ "features from the selection (filtered from: " +
+ parent_frame.getTitle () + ")";
+
+ final FilteredEntryGroup filtered_entry_group =
+ new FilteredEntryGroup (entry_group, predicate, filter_name);
+
+ final FeatureListFrame feature_list_frame =
+ new FeatureListFrame (filter_name,
+ selection, goto_source, filtered_entry_group,
+ base_plot_group);
+
+ feature_list_frame.setVisible (true);
+ }
+
+ /**
+ * viewSelectedFeatures(), plotSelectedFeatures() etc. will only show this
+ * many features.
+ **/
+ private static final int MAXIMUM_SELECTED_FEATURES = 25;
+
+ /**
+ * Open a view window for each of the selected features. The viewer will
+ * listen for feature change events and update itself.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection containing the features to merge.
+ **/
+ protected static void viewSelectedFeatures (final JFrame frame,
+ final Selection selection)
+ {
+ final FeatureVector features_to_view = selection.getAllFeatures ();
+
+ if (features_to_view.size () > MAXIMUM_SELECTED_FEATURES) {
+ new MessageDialog (frame, "warning: only viewing the first " +
+ MAXIMUM_SELECTED_FEATURES + " selected features");
+ }
+
+ for (int i = 0 ;
+ i < features_to_view.size () && i < MAXIMUM_SELECTED_FEATURES ;
+ ++i) {
+ final Feature selection_feature = features_to_view.elementAt (i);
+
+ new FeatureViewer (selection_feature);
+ }
+ }
+
+ /**
+ * Open an plot viewing window for each of the selected features. The plot
+ * viewer will listen for feature change events and update itself.
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection containing the features to plot.
+ **/
+ protected static void plotSelectedFeatures (final JFrame frame,
+ final Selection selection)
+ {
+ final FeatureVector features_to_plot = selection.getAllFeatures ();
+
+ if (features_to_plot.size () > MAXIMUM_SELECTED_FEATURES) {
+ new MessageDialog (frame, "warning: only showing plots for the first " +
+ MAXIMUM_SELECTED_FEATURES + " selected features");
+ }
+
+ for(int i = 0;
+ i < features_to_plot.size () && i < MAXIMUM_SELECTED_FEATURES;
+ ++i)
+ {
+ final Feature selection_feature = features_to_plot.elementAt (i);
+
+ new FeaturePlotGroup (selection_feature);
+ }
+ }
+
+ /**
+ * Show the output file from an external program (like fasta) for the
+ * selected Feature objects. The name of the file to read is stored in a
+ * feature qualifier. The qualifier used is the program name plus "_file".
+ * @param frame The JFrame to use for MessageDialog components.
+ * @param selection The Selection containing the features to merge.
+ * @param send_to_browser if true the results should be sent straight to
+ * the web browser rather than using a SearchResultViewer object.
+ **/
+ protected static void viewExternalResults (final JFrame frame,
+ final Selection selection,
+ final String program_name,
+ final boolean send_to_browser)
+ {
+ final FeatureVector features_to_view = selection.getAllFeatures ();
+
+ if (features_to_view.size () > MAXIMUM_SELECTED_FEATURES) {
+ new MessageDialog (frame, "warning: only viewing results from " +
+ "the first " + MAXIMUM_SELECTED_FEATURES +
+ " selected features");
+ }
+
+ for (int i = 0 ;
+ i < features_to_view.size () && i < MAXIMUM_SELECTED_FEATURES ;
+ ++i) {
+ final Feature this_feature = features_to_view.elementAt (i);
+
+ Qualifier qualifier = null;
+ try
+ {
+ qualifier = this_feature.getQualifierByName(program_name + "_file");
+ }
+ catch(InvalidRelationException e1)
+ {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+
+ if(qualifier == null)
+ continue;
+ StringVector qualifier_values = qualifier.getValues();
+ String qualifier_value;
+ if(qualifier_values == null || qualifier_values.size() == 0) {
+ new MessageDialog (frame,
+ "Message",
+ "No " + program_name + " results for " +
+ this_feature.getIDString ());
+ continue;
+ }
+
+ for(int j=0; j<qualifier_values.size(); j++)
+ {
+ qualifier_value = (String) qualifier_values.get(j);
+
+ // strip off database name
+ int ind;
+ if((ind = qualifier_value.indexOf(':')) > -1)
+ qualifier_value = qualifier_value.substring(ind + 1);
+
+ String file_name = qualifier_value;
+
+ // remove the program name string (if any) from the qualifier, so that
+ // we can try to read the file with and without the prefix.
+ if(file_name.startsWith(program_name + File.separatorChar)||
+ file_name.startsWith(program_name + "/"))
+ {
+ file_name = file_name.substring (program_name.length () + 1);
+ }
+
+
+ try
+ {
+ Document document = getSearchDocument(this_feature, program_name, file_name);
+
+ if(document == null)
+ {
+ final String message_string = "No " + program_name
+ + " results for " + this_feature.getIDString()
+ + " (file not found: " + qualifier_value + ")";
+
+ new MessageDialog(frame, message_string);
+
+ continue;
+ }
+
+ if(send_to_browser)
+ {
+ String fileName = document.toString();
+ if(document instanceof ZipFileDocument)
+ fileName = ((ZipFileDocument)document).writeTmpFile(
+ getDocumentContents(document));
+
+ SearchResultViewer.sendToBrowser(fileName);
+ }
+ else
+ {
+ new SearchResultViewer(program_name + " results for "
+ + this_feature.getIDString() + " from " + document.getName(), document);
+ }
+ }
+ catch(ExternalProgramException e)
+ {
+ new MessageDialog(frame, "error while open results file: " + e);
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(frame, "error while open results file: " + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the corresponding fasta/blast search result. This looks initially
+ * in program zip files, and then local file documents and finally, if a
+ * remote file connection has been made, it looks in the remote file system.
+ * @param this_feature
+ * @param program
+ * @param fileName
+ * @return
+ * @throws IOException
+ */
+ public static Document getSearchDocument(Feature this_feature, String program, String fileName)
+ throws IOException
+ {
+ Entry entry = this_feature.getEntry();
+ Document root_document = entry.getRootDocument();
+
+ if(root_document == null)
+ root_document = new FileDocument(new File("."));
+ Document document = null;
+
+ final File dir_name = new File(program);
+ File rootFile;
+ if(root_document.getLocation() instanceof File)
+ rootFile = (File)root_document.getLocation();
+ else
+ rootFile = new File(".");
+
+ final Document[] possible_documents = new Document[] {
+ new ZipFileDocument(new File(rootFile, program+File.separatorChar+program+".zip"), fileName),
+ new ZipFileDocument(new File(rootFile, program+".zip"), fileName),
+ new ZipFileDocument(new File(program+".zip"), fileName),
+ new ZipFileDocument(new File(program, program+".zip"), fileName),
+ root_document.append(program).append(fileName),
+ root_document.append(fileName),
+ new FileDocument(new File(fileName)),
+ new FileDocument(dir_name).append(fileName),
+ new FileDocument(new File(System.getProperty("user.dir")))
+ .append(program).append(fileName),
+ };
+
+ for(int k = 0; k < possible_documents.length; ++k)
+ {
+ final Document this_document = possible_documents[k];
+ if(this_document.readable())
+ {
+ document = this_document;
+ break;
+ }
+ else
+ {
+ final File gzip_file = new File(this_document.toString() + ".gz");
+ final Document gzip_document = new FileDocument(gzip_file);
+
+ if(gzip_document.readable())
+ {
+ document = gzip_document;
+ break;
+ }
+ }
+ }
+
+ // if not found locally try SSH remote site
+ if(document == null &&
+ ((DocumentEntry) (entry.getEMBLEntry())).getDocument() instanceof RemoteFileDocument)
+ {
+ File fdata = new File(program, fileName);
+ // check on the remote side and scp the file over
+ document = checkRemoteNode(this_feature, program, fileName, fdata.getParentFile());
+ }
+ return document;
+ }
+
+ private static String getDocumentContents(Document document) throws IOException
+ {
+ final BufferedReader buffered_reader = new BufferedReader(document.getReader());
+ String line;
+ final StringBuffer line_buffer = new StringBuffer();
+ while((line = buffered_reader.readLine()) != null)
+ line_buffer.append(line).append('\n');
+
+ buffered_reader.close();
+ return line_buffer.toString();
+ }
+
+ /**
+ * Open a FeatureInfo component for each of the selected features. The
+ * new component will listen for feature change events and update itself.
+ **/
+ private void viewSelectedFeatureInfo ()
+ {
+ final FeatureVector features_to_view = getSelection ().getAllFeatures ();
+
+ if (features_to_view.size () > MAXIMUM_SELECTED_FEATURES) {
+ new MessageDialog (getParentFrame (),
+ "warning: only viewing the statistics for " +
+ "the first " + MAXIMUM_SELECTED_FEATURES +
+ " selected features");
+ }
+
+ for (int i = 0 ;
+ i < features_to_view.size () && i < MAXIMUM_SELECTED_FEATURES ;
+ ++i) {
+ final Feature selection_feature = features_to_view.elementAt (i);
+
+ new FeatureInfo (selection_feature,
+ base_plot_group.getCodonUsageAlgorithm ());
+ }
+ }
+
+ /**
+ * View the bases of the selected features. This creates a
+ * FeatureBaseViewer for each feature.
+ * @param include_numbers If true then the sequence will be numbered
+ * (every second line of the display will be numbers rather than
+ * sequence).
+ **/
+ private void viewSelectedBases (final boolean include_numbers, final boolean selectedExonsOnly) {
+ if (getSelection ().isEmpty ()) {
+ new MessageDialog (getParentFrame (), "Nothing selected");
+ return;
+ }
+
+ if (selection.getMarkerRange () == null) {
+ final FeatureVector fs = getSelection ().getAllFeatures ();
+
+ if(selectedExonsOnly)
+ {
+ if (fs.size () > 1)
+ new MessageDialog (getParentFrame (),
+ "warning: only viewing bases for the selected exons of one feature");
+ new FeatureBaseViewer (fs.elementAt(0), include_numbers,
+ getSelection ().getSelectedSegments());
+ }
+ else
+ {
+ if (fs.size () > MAXIMUM_SELECTED_FEATURES)
+ new MessageDialog (getParentFrame (),
+ "warning: only viewing bases for " +
+ "the first " + MAXIMUM_SELECTED_FEATURES +
+ " selected features");
+ for (int i = 0; i < fs.size () && i < MAXIMUM_SELECTED_FEATURES; ++i)
+ new FeatureBaseViewer (fs.elementAt (i), include_numbers, null);
+ }
+ }
+ else
+ {
+ final SequenceViewer sequence_viewer =
+ new SequenceViewer ("Selected bases", include_numbers);
+
+ final String bases = getSelection ().getSelectionText ();
+ sequence_viewer.setSequence (null, bases);
+ }
+ }
+
+ /**
+ * View the bases of the selected CDS features. This creates a
+ * FeatureBaseViewer for each CDS feature.
+ * @param include_numbers If true then the amino acids will be numbered
+ * (every second line of the display will be numbers rather than
+ * sequence).
+ **/
+ private void viewSelectedAminoAcids (final boolean include_numbers) {
+ if (getSelection ().isEmpty ()) {
+ new MessageDialog (getParentFrame (), "Nothing selected");
+ return;
+ }
+
+ final MarkerRange range = selection.getMarkerRange ();
+
+ if (range == null) {
+ final FeatureVector features_to_view = getSelection ().getAllFeatures ();
+
+ if (features_to_view.size () > MAXIMUM_SELECTED_FEATURES) {
+ new MessageDialog (getParentFrame (),
+ "warning: only viewing amino acids for " +
+ "the first " + MAXIMUM_SELECTED_FEATURES +
+ " selected features");
+ }
+
+ for (int i = 0 ;
+ i < features_to_view.size () && i < MAXIMUM_SELECTED_FEATURES ;
+ ++i) {
+ final Feature this_feature = features_to_view.elementAt (i);
+
+ new FeatureAminoAcidViewer (this_feature, include_numbers);
+ }
+ } else {
+ final SequenceViewer sequence_viewer =
+ new SequenceViewer ("Selected bases (translated)", include_numbers);
+
+ final String bases = getSelection ().getSelectionText ();
+
+ final AminoAcidSequence amino_acids =
+ AminoAcidSequence.getTranslation (bases, true);
+
+ sequence_viewer.setSequence (null, amino_acids.toString ());
+ }
+ }
+
+ /**
+ * Check that the number of features in the EntryGroup is less than the
+ * given number. If it is less return true. If not then popup a
+ * YesNoDialog asking for confirmation and return true if and only if the
+ * user chooses yes.
+ **/
+ private boolean checkEntryGroupSize (final int max_size) {
+ final int feature_count = getEntryGroup ().getAllFeaturesCount ();
+
+ if (feature_count < max_size) {
+ return true;
+ } else {
+ final YesNoDialog dialog =
+ new YesNoDialog (getParentFrame (),
+ "there are " + feature_count + " features in the " +
+ "active entries - continue?");
+
+ return dialog.getResult ();
+ }
+ }
+
+ /**
+ * Return the EntryGroup that was passed to the constructor.
+ **/
+ private EntryGroup getEntryGroup ()
+ {
+ return entry_group;
+ }
+
+
+ /**
+ * Look for the file on the remote side and copy and transfer is
+ * locally.
+ * @param this_feature
+ * @param program_name
+ * @param file_name
+ * @param dir_name
+ * @return
+ */
+ private static Document checkRemoteNode(final Feature this_feature,
+ final String program,
+ String file_name,
+ final File dir_name)
+ {
+ RemoteFileDocument doc = (RemoteFileDocument) (((DocumentEntry) this_feature
+ .getEntry().getEMBLEntry()).getDocument());
+
+ RemoteFileNode node = doc.getRemoteFileNode();
+ Document document = null;
+ FileList flist = new FileList();
+ String path = node.getPathName().concat(
+ "/" + program + "/" + program + ".zip");
+ FileAttributes attr = flist.stat(path);
+
+ if(attr != null && attr.isFile())
+ {
+ File fn = flist.getZipEntryContents(path, file_name, dir_name);
+ if(fn == null)
+ return null;
+ return new FileDocument(fn);
+ }
+
+ path = node.getPathName().concat(
+ "/" + program + "/" + file_name);
+ attr = flist.stat(path);
+
+ if(attr == null || !attr.isFile())
+ {
+ file_name = file_name + ".gz";
+ path = node.getPathName().concat(
+ "/" + program + "/" + file_name);
+ attr = flist.stat(path);
+ }
+
+
+ if(attr != null && attr.isFile())
+ {
+ FileTransferProgressMonitor monitor = new FileTransferProgressMonitor(
+ null);
+ FTProgress progress = monitor.add(node.getFile());
+
+ RemoteFileNode fileNode = new RemoteFileNode("", file_name, null,
+ node.getPathName().concat("/" + program), false);
+ document = new RemoteFileDocument(fileNode);
+ byte contents[] = fileNode.getFileContents(progress);
+ File fn = new File(dir_name.getAbsoluteFile(), file_name);
+
+ writeByteFile(contents, fn);
+ ((RemoteFileDocument)document).setString(fn.getAbsolutePath());
+ monitor.close();
+ }
+ return document;
+ }
+
+ private static boolean writeByteFile(byte[] contents, File fn)
+ {
+ if(fn.exists())
+ {
+ int n = JOptionPane.showConfirmDialog(null,
+ "Overwrite \n"+fn.getName()+"?",
+ "Overwrite File",
+ JOptionPane.YES_NO_OPTION);
+ if(n == JOptionPane.NO_OPTION)
+ return false;
+ }
+ else if(!fn.getParentFile().canWrite())
+ {
+ if(!fn.getParentFile().mkdir())
+ JOptionPane.showMessageDialog(null,"Cannot write "+fn.getName()+" to "+
+ fn.getParentFile().getAbsolutePath(),
+ "Write Permission Denied",
+ JOptionPane.WARNING_MESSAGE);
+ }
+
+ try
+ {
+ FileOutputStream out = new FileOutputStream(fn);
+ out.write(contents);
+ out.close();
+ }
+ catch(FileNotFoundException fnfe) { return false;}
+ catch(IOException ioe) { return false;}
+
+ return true;
+ }
+
+
+}
diff --git a/uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource.java b/uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource.java
new file mode 100644
index 0000000..87ff712
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource.java
@@ -0,0 +1,226 @@
+/* WritableEMBLCorbaEntrySource.java
+ *
+ * created: Mon Jun 12 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000,2002x Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/WritableEMBLCorbaEntrySource.java,v 1.1 2004-06-09 09:47:59 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.LocationParseException;
+import uk.ac.sanger.artemis.io.InvalidKeyException;
+
+import nsdb.ServerInfo;
+import nsdb.EntryStats;
+import nsdb.EmblSeq;
+import nsdb.EmblPackage.Superceded;
+import type.NoResult;
+
+import java.io.*;
+import java.awt.*;
+import java.net.*;
+
+import javax.swing.*;
+
+/**
+ * This is an EntrySource that can get writable Entry objects from an EMBL
+ * CORBA server.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: WritableEMBLCorbaEntrySource.java,v 1.1 2004-06-09 09:47:59 tjc Exp $
+ **/
+
+public class WritableEMBLCorbaEntrySource extends EMBLCorbaEntrySource {
+ /**
+ * Create a new WritableEMBLCorbaEntrySource from the given String.
+ * @param frame The component that created this EntrySource. (Used for
+ * requesters.)
+ * @param ior_url_string A String containing the URL of the IOR for the
+ * server.
+ **/
+ public WritableEMBLCorbaEntrySource (final JFrame frame,
+ final String ior_url_string)
+ throws MalformedURLException {
+ super (frame, ior_url_string);
+ }
+
+ /**
+ * Get an Entry object from the Ensembl CORBA server.
+ * @param bases The Bases object to pass to the Entry constructor.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry (final Bases bases, final boolean show_progress)
+ throws OutOfRangeException, IOException {
+ return makeCorbaDialog (bases, false);
+ }
+
+ /**
+ * Get an Entry object from the Ensembl CORBA server.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ * @exception NoSequenceException Thrown if the entry that we read has no
+ * sequence.
+ * @param show_progress If true a InputStreamProgressDialog will be shown
+ * while loading.
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ **/
+ public Entry getEntry (final boolean show_progress)
+ throws OutOfRangeException, NoSequenceException, IOException {
+ return makeCorbaDialog (null, false);
+ }
+
+ /**
+ * Create a TextRequester, wait for the user to type an accession number
+ * and then read that entry from the EMBL CORBA server.
+ * @param bases If this is null a new Bases object will be created for the
+ * Entry once it has been read from the server. If not null then it will
+ * be passed to the Entry constructor.
+ * @param read_only true if and only if a read-only Entry should be created
+ * (some are always read only).
+ * @return null if and only if the user cancels the read or if the read
+ * fails.
+ * @exception OutOfRangeException Thrown if one of the features in
+ * the Entry is out of range of the Bases object.
+ **/
+ protected Entry makeCorbaDialog (final Bases bases,
+ final boolean read_only)
+ throws OutOfRangeException, IOException {
+ final org.omg.CORBA.ORB orb =
+ org.omg.CORBA.ORB.init (new String [0], new java.util.Properties());
+
+ final nsdb.Embl corba_handle = getServerHandle ();
+
+ final nsdb.EmblWriter embl_writer =
+ nsdb.EmblWriterHelper.narrow (corba_handle);
+
+ if (embl_writer == null) {
+ final String message = "Server reference is not an EmblWriter: " +
+ corba_handle;
+ new MessageDialog (getFrame (), message);
+ return null;
+ }
+
+ final ListDialog list_dialog =
+ new ListDialog (getFrame (), "Select an entry");
+
+ final ServerInfo stats = embl_writer.getServerInfo ();
+
+ final EntryStats [] entry_stats_list = stats.entry_stats_list;
+ final EntryStats [] file_stats_list = stats.file_stats_list;
+
+ list_dialog.getList ().setModel (new AbstractListModel () {
+ public int getSize () {
+ return entry_stats_list.length + file_stats_list.length;
+ }
+ public Object getElementAt (int i) {
+ if (i < entry_stats_list.length) {
+ return makeListString (entry_stats_list[i], false);
+ } else {
+ return makeListString (file_stats_list[i-entry_stats_list.length],
+ true);
+ }
+ }
+ });
+
+ // returns when user hits ok or cancel
+ final String selected_entry_string =
+ (String) list_dialog.getSelectedValue ();
+
+ if (selected_entry_string == null) {
+ // user hit cancel
+ return null;
+ }
+
+ final int end_of_entry_name = selected_entry_string.indexOf (" ");
+
+ final String corba_id =
+ selected_entry_string.substring (0, end_of_entry_name);
+
+ if (corba_id.length () > 0) {
+ final MessageDialog message_frame =
+ new MessageDialog (getFrame (),
+ "reading entry - please wait", false);
+
+ try {
+ return makeFromCorbaID (bases, corba_id, read_only);
+ } finally {
+ message_frame.dispose ();
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Make a ListDialog then let the user choose an entry.
+ **/
+ private String makeListString (final EntryStats this_entry_stats,
+ final boolean is_file_entry) {
+ final StringBuffer buffer = new StringBuffer ();
+
+ buffer.append (this_entry_stats.name).append (" ");
+
+ for (int j = 0 ; j < (30 - this_entry_stats.name.length ()) ; ++j) {
+ buffer.append (" ");
+ }
+
+ final long last_change_time = this_entry_stats.last_change_time;
+
+ if (is_file_entry) {
+ buffer.append ("(file)");
+ } else {
+ if (last_change_time > 0) {
+ buffer.append (new java.util.Date (last_change_time * 1000L));
+ } else {
+ buffer.append ("(not saved)");
+ }
+ }
+
+ return buffer.toString ();
+ }
+
+ /**
+ * Return the name of this source (for display to the user in menus).
+ **/
+ public String getSourceName () {
+ return "Database";
+ }
+
+ /**
+ * Returns true if and only if this EntrySource always returns "full"
+ * entries. ie. entries that contain features and sequence. Entries that
+ * are read from a file may contain just features so in this class this
+ * method returns false.
+ **/
+ public boolean isFullEntrySource () {
+ return false;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/WriteMenu.java b/uk/ac/sanger/artemis/components/WriteMenu.java
new file mode 100644
index 0000000..a02a09d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/WriteMenu.java
@@ -0,0 +1,1254 @@
+/* WriteMenu.java
+ *
+ * created: Mon Jan 11 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/WriteMenu.java,v 1.12 2009-05-06 10:53:51 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.components;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+import uk.ac.sanger.artemis.circular.TextFieldInt;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.StreamSequence;
+import uk.ac.sanger.artemis.io.FastaStreamSequence;
+import uk.ac.sanger.artemis.io.RawStreamSequence;
+import uk.ac.sanger.artemis.io.EmblStreamSequence;
+import uk.ac.sanger.artemis.io.GenbankStreamSequence;
+import uk.ac.sanger.artemis.io.StreamSequenceFactory;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.StringTokenizer;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+/**
+ * A menu of commands for writing out protein and bases.
+ *
+ * @author Kim Rutherford
+ * @version $Id: WriteMenu.java,v 1.12 2009-05-06 10:53:51 tjc Exp $
+ **/
+public class WriteMenu extends SelectionMenu
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new WriteMenu component.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group The EntryGroup object to use when writing a sequence.
+ * @param menu_name The name of the new menu.
+ **/
+ public WriteMenu(final JFrame frame, final Selection selection,
+ final EntryGroup entry_group, final String menu_name)
+ {
+ super(frame, menu_name, selection);
+
+ this.entry_group = entry_group;
+
+ final JMenuItem aa_item = new JMenuItem("Amino Acids Of Selected Features");
+ aa_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeAminoAcids();
+ }
+ });
+
+ add(aa_item);
+
+
+ final JMenuItem aa_to_qualifier_item = new JMenuItem("Amino Acids Of Selected Features to Qualifier");
+ aa_to_qualifier_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ final FeatureVector features_to_write =
+ getSelection().getAllFeatures();
+
+ try
+ {
+ for(int i = 0; i < features_to_write.size(); ++i)
+ {
+ final Feature selection_feature = features_to_write.elementAt(i);
+ final String translation_string =
+ selection_feature.getTranslation().toString().toUpperCase();
+ selection_feature.setQualifier(new Qualifier("translation", translation_string));
+ }
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ });
+
+ add(aa_to_qualifier_item);
+
+ final JMenuItem pir_item =
+ new JMenuItem("PIR Database Of Selected Features");
+ pir_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writePIRDataBase();
+ }
+ });
+
+ add(pir_item);
+ addSeparator();
+
+
+ final JMenu bases_menu = new JMenu("Bases Of Selection");
+ final JMenuItem raw_bases_item = new JMenuItem("Raw Format");
+ raw_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeBasesOfSelection(StreamSequenceFactory.RAW_FORMAT);
+ }
+ });
+
+ bases_menu.add(raw_bases_item);
+
+ final JMenuItem fasta_bases_item = new JMenuItem("FASTA Format");
+ fasta_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeBasesOfSelection(StreamSequenceFactory.FASTA_FORMAT);
+ }
+ });
+
+ bases_menu.add(fasta_bases_item);
+
+ final JMenuItem embl_bases_item = new JMenuItem("EMBL Format");
+ embl_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeBasesOfSelection(StreamSequenceFactory.EMBL_FORMAT);
+ }
+ });
+
+ bases_menu.add(embl_bases_item);
+
+ final JMenuItem genbank_bases_item = new JMenuItem("Genbank Format");
+ genbank_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeBasesOfSelection(StreamSequenceFactory.GENBANK_FORMAT);
+ }
+ });
+
+ bases_menu.add(genbank_bases_item);
+ add(bases_menu);
+
+// final JMenu exons_menu = new JMenu("Exons Of Selection");
+// final JMenuItem fasta_exons_item = new JMenuItem("Multiple FASTA Format");
+// fasta_exons_item.addActionListener(new ActionListener()
+// {
+// public void actionPerformed(ActionEvent event)
+// {
+// writeExonsOfSelection(StreamSequenceFactory.FASTA_FORMAT);
+// }
+// });
+// exons_menu.add(fasta_exons_item);
+// add(exons_menu);
+
+ final JMenu upstream_bases_menu =
+ new JMenu("Upstream Bases Of Selected Features");
+
+ final JMenuItem raw_upstream_bases_item = new JMenuItem("Raw Format");
+ raw_upstream_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeUpstreamBases(StreamSequenceFactory.RAW_FORMAT);
+ }
+ });
+
+ upstream_bases_menu.add(raw_upstream_bases_item);
+
+ final JMenuItem fasta_upstream_bases_item = new JMenuItem("FASTA Format");
+ fasta_upstream_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeUpstreamBases(StreamSequenceFactory.FASTA_FORMAT);
+ }
+ });
+
+ upstream_bases_menu.add(fasta_upstream_bases_item);
+ final JMenuItem embl_upstream_bases_item = new JMenuItem("EMBL Format");
+ embl_upstream_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeUpstreamBases(StreamSequenceFactory.EMBL_FORMAT);
+ }
+ });
+
+ upstream_bases_menu.add(embl_upstream_bases_item);
+
+ final JMenuItem genbank_upstream_bases_item = new JMenuItem("Genbank Format");
+ genbank_upstream_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeUpstreamBases(StreamSequenceFactory.GENBANK_FORMAT);
+ }
+ });
+
+ upstream_bases_menu.add(genbank_upstream_bases_item);
+
+ add(upstream_bases_menu);
+
+ final JMenu downstream_bases_menu =
+ new JMenu("Downstream Bases Of Selected Features");
+
+ final JMenuItem raw_downstream_bases_item = new JMenuItem("Raw Format");
+ raw_downstream_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeDownstreamBases(StreamSequenceFactory.RAW_FORMAT);
+ }
+ });
+
+ downstream_bases_menu.add(raw_downstream_bases_item);
+
+ final JMenuItem fasta_downstream_bases_item = new JMenuItem("FASTA Format");
+ fasta_downstream_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeDownstreamBases(StreamSequenceFactory.FASTA_FORMAT);
+ }
+ });
+
+ downstream_bases_menu.add(fasta_downstream_bases_item);
+
+ final JMenuItem embl_downstream_bases_item = new JMenuItem("EMBL Format");
+ embl_downstream_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeDownstreamBases(StreamSequenceFactory.EMBL_FORMAT);
+ }
+ });
+
+ downstream_bases_menu.add(embl_downstream_bases_item);
+
+ final JMenuItem genbank_downstream_bases_item =
+ new JMenuItem("Genbank Format");
+ genbank_downstream_bases_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeDownstreamBases(StreamSequenceFactory.GENBANK_FORMAT);
+ }
+ });
+
+ downstream_bases_menu.add(genbank_downstream_bases_item);
+
+ add(downstream_bases_menu);
+
+ addSeparator();
+
+ final JMenuItem write_combo_bases = new JMenuItem("Upstream+Feature+Downstream Bases ...");
+ write_combo_bases.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeComboBases();
+ }
+ });
+ add(write_combo_bases);
+
+ addSeparator();
+
+ final JMenu write_all_bases_menu = new JMenu("All Bases");
+
+ final JMenuItem write_raw_item = new JMenuItem("Raw Format");
+ write_raw_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeAllSequence(StreamSequenceFactory.RAW_FORMAT);
+ }
+ });
+
+ write_all_bases_menu.add(write_raw_item);
+
+ final JMenuItem write_fasta_item = new JMenuItem("FASTA Format");
+ write_fasta_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeAllSequence(StreamSequenceFactory.FASTA_FORMAT);
+ }
+ });
+
+ write_all_bases_menu.add(write_fasta_item);
+
+ final JMenuItem write_embl_item = new JMenuItem("EMBL Format");
+ write_embl_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeAllSequence(StreamSequenceFactory.EMBL_FORMAT);
+ }
+ });
+
+ write_all_bases_menu.add(write_embl_item);
+
+ final JMenuItem write_genbank_item = new JMenuItem("Genbank Format");
+ write_genbank_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeAllSequence(StreamSequenceFactory.GENBANK_FORMAT);
+ }
+ });
+
+ write_all_bases_menu.add(write_genbank_item);
+
+ add(write_all_bases_menu);
+
+ addSeparator();
+
+ final JMenuItem codon_usage_item =
+ new JMenuItem("Codon Usage of Selected Features");
+ codon_usage_item.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ writeCodonUsage();
+ }
+ });
+
+ add(codon_usage_item);
+ }
+
+ /**
+ * Create a new WriteMenu component.
+ * @param frame The JFrame that owns this JMenu.
+ * @param selection The Selection that the commands in the menu will
+ * operate on.
+ * @param entry_group The EntryGroup object to use when writing a sequence.
+ **/
+ public WriteMenu(final JFrame frame, final Selection selection,
+ final EntryGroup entry_group)
+ {
+ this(frame, selection, entry_group, "Write");
+ }
+
+ /**
+ * Write a PIR database of the selected features to a file choosen by the
+ * user.
+ **/
+ private void writePIRDataBase()
+ {
+ if(!checkForSelectionFeatures())
+ return;
+
+ final File write_file =
+ getWriteFile("Select a PIR output file name ...", "cosmid.pir", null);
+
+ if(write_file == null)
+ return;
+
+ try
+ {
+ final FileWriter writer = new FileWriter(write_file);
+
+ final FeatureVector features_to_write =
+ getSelection().getAllFeatures();
+
+ for(int i = 0; i < features_to_write.size() ; ++i)
+ {
+ final Feature selection_feature = features_to_write.elementAt(i);
+ selection_feature.writePIROfFeature(writer);
+ }
+
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "error while writing: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Write the amino acid symbols of the selected features to a file choosen
+ * by the user.
+ **/
+ private void writeAminoAcids()
+ {
+ if(!checkForSelectionFeatures())
+ return;
+
+ final File write_file =
+ getWriteFile("Select an output file name..", "amino_acids", null);
+
+ if(write_file == null)
+ return;
+
+ try
+ {
+ final FileWriter writer = new FileWriter(write_file);
+
+ final FeatureVector features_to_write =
+ getSelection().getAllFeatures();
+
+ for(int i = 0; i < features_to_write.size(); ++i)
+ {
+ final Feature selection_feature = features_to_write.elementAt(i);
+ selection_feature.writeAminoAcidsOfFeature(writer);
+ }
+
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "error while writing: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Write the bases of the selection to a file choosen by the user.
+ * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc.
+ **/
+ private void writeBasesOfSelection(final int output_type)
+ {
+ final MarkerRange marker_range = getSelection().getMarkerRange();
+
+ if(marker_range == null)
+ writeFeatureBases (output_type);
+ else
+ {
+ final String selection_bases = Strand.markerRangeBases(marker_range);
+
+ writeBases(selection_bases, "selected bases",
+ getSequenceFileName(output_type),
+ output_type);
+ }
+ }
+
+
+ /**
+ * Write the bases of the selected features to a file choosen by the user
+ * or show a message and return immediately if there are no selected
+ * features.
+ * @param sequence The sequence to be written.
+ * @param header The header line that will be used on those output formats
+ * that need it.
+ * @param default_output_filename The filename that is passed to
+ * getWriteFile()
+ * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc.
+ **/
+ private void writeBases(final String sequence, String header,
+ final String default_output_filename,
+ final int output_type)
+ {
+ JTextField headerField = null;
+
+ if(output_type == StreamSequenceFactory.FASTA_FORMAT ||
+ output_type == StreamSequenceFactory.EMBL_FORMAT)
+ headerField = new JTextField(header);
+
+ final File write_file = getWriteFile("Select an output file name ...",
+ default_output_filename, headerField);
+
+ if(write_file == null)
+ return;
+
+ try
+ {
+ final FileWriter writer = new FileWriter(write_file);
+ final StreamSequence stream_sequence;
+
+ switch(output_type)
+ {
+ case StreamSequenceFactory.FASTA_FORMAT:
+ if(headerField != null && !headerField.getText().equals(""))
+ header = headerField.getText().trim();
+
+ stream_sequence = new FastaStreamSequence(sequence, header);
+ break;
+
+ case StreamSequenceFactory.EMBL_FORMAT:
+ stream_sequence = new EmblStreamSequence(sequence);
+
+ if(headerField != null && !headerField.getText().equals(""))
+ {
+ header = "ID "+headerField.getText().trim();
+ header = header.concat("\nFH Key "+
+ "Location/Qualifiers\nFH\n");
+ ((EmblStreamSequence)stream_sequence).setHeader(header);
+ }
+
+ break;
+
+ case StreamSequenceFactory.GENBANK_FORMAT:
+ stream_sequence = new GenbankStreamSequence(sequence);
+ break;
+
+ case StreamSequenceFactory.RAW_FORMAT:
+ default:
+ stream_sequence = new RawStreamSequence(sequence);
+ break;
+ }
+
+ stream_sequence.writeToStream(writer);
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "error while writing: " + e.getMessage());
+ }
+ }
+
+ /**
+ *
+ * Write selected exons to a mutiple FASTA file.
+ *
+ */
+/*
+ private void writeExonsOfSelection(final int output_type)
+ {
+ if(!checkForSelectionFeatures())
+ return;
+
+ final File write_file =
+ getWriteFile("Select an output file name ...", "exons", null);
+
+ if(write_file == null)
+ return;
+
+ try
+ {
+ final FileWriter writer = new FileWriter(write_file);
+ final FeatureVector features_to_write =
+ getSelection().getAllFeatures();
+ for(int i = 0; i < features_to_write.size(); ++i)
+ {
+ final Feature selection_feature = features_to_write.elementAt(i);
+ final StringBuffer header_buffer = new StringBuffer();
+
+ header_buffer.append(selection_feature.getSystematicName());
+ header_buffer.append(" ");
+ header_buffer.append(selection_feature.getIDString());
+ header_buffer.append(" ");
+
+ final String product = selection_feature.getProductString();
+
+ if(product == null)
+ header_buffer.append("undefined product");
+ else
+ header_buffer.append(product);
+
+ int seg_size = selection_feature.getSegments().size();
+ for(int j = 0; j < seg_size; ++j)
+ {
+ String bases = selection_feature.getSegments().elementAt(j).getBases();
+ Range range = selection_feature.getSegments().elementAt(j).getRawRange();
+
+ String s_range = " "+range.getStart()+":"+range.getEnd();
+ if(selection_feature.isForwardFeature())
+ s_range = s_range+" forward";
+ else
+ s_range = s_range+" reverse";
+
+ final StreamSequence stream_sequence =
+ getStreamSequence(bases,
+ header_buffer.toString()+s_range,
+ output_type);
+
+ stream_sequence.writeToStream(writer);
+ }
+ }
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "error while writing: " + e.getMessage());
+ }
+ }
+*/
+
+ /**
+ * Write the bases of the selected features to a file choosen by the user
+ * or show a message and return immediately if there are no selected
+ * features.
+ * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc.
+ **/
+ private void writeFeatureBases(final int output_type)
+ {
+ if(!checkForSelectionFeatures())
+ return;
+
+
+ final File write_file =
+ getWriteFile("Select an output file name ...", "bases", null);
+
+ if(write_file == null)
+ return;
+
+ try
+ {
+ final FileWriter writer = new FileWriter(write_file);
+ final FeatureVector features_to_write =
+ getSelection().getAllFeatures();
+
+ for(int i = 0; i < features_to_write.size(); ++i)
+ {
+ final Feature selection_feature = features_to_write.elementAt(i);
+ final StringBuffer header_buffer = new StringBuffer();
+
+ header_buffer.append(selection_feature.getSystematicName());
+ header_buffer.append(" ");
+ header_buffer.append(selection_feature.getIDString());
+ header_buffer.append(" ");
+
+ final String product = selection_feature.getProductString();
+
+ if(product == null)
+ header_buffer.append("undefined product");
+ else
+ header_buffer.append(product);
+
+ header_buffer.append(" ").append(selection_feature.getWriteRange());
+
+ final StreamSequence stream_sequence =
+ getStreamSequence(selection_feature.getBases(),
+ header_buffer.toString(),
+ output_type);
+
+ stream_sequence.writeToStream(writer);
+ }
+
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "error while writing: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Return a StreamSequence object for the given sequence string and the
+ * given type.
+ * @param sequence A String containing the sequence.
+ * @param header The header line that will be used on those output formats
+ * that need it.
+ * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc.
+ **/
+ private StreamSequence getStreamSequence(final String sequence,
+ final String header,
+ final int output_type)
+ {
+ switch(output_type)
+ {
+ case StreamSequenceFactory.FASTA_FORMAT:
+ return new FastaStreamSequence (sequence, header);
+
+ case StreamSequenceFactory.EMBL_FORMAT:
+ return new EmblStreamSequence (sequence);
+
+ case StreamSequenceFactory.GENBANK_FORMAT:
+ return new GenbankStreamSequence (sequence);
+
+ case StreamSequenceFactory.RAW_FORMAT:
+ default:
+ return new RawStreamSequence (sequence);
+ }
+ }
+
+ /**
+ * Write the upstream bases of the selected features to a file choosen by
+ * the user. The user can also choose the number of upstream base to
+ * write.
+ * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc.
+ **/
+ private void writeUpstreamBases(final int output_type)
+ {
+ if(!checkForSelectionFeatures())
+ return;
+
+ final TextRequester text_requester =
+ new TextRequester("write how many bases upstream of each feature?",
+ 18, "");
+
+ text_requester.addTextRequesterListener(new TextRequesterListener()
+ {
+ public void actionPerformed(final TextRequesterEvent event)
+ {
+ if(event.getType() == TextRequesterEvent.CANCEL)
+ return;
+
+ final String base_count_string = event.getRequesterText().trim();
+
+ if(base_count_string.length() == 0)
+ {
+ new MessageDialog(getParentFrame(), "no bases written");
+ return;
+ }
+
+ try
+ {
+ final int base_count =
+ Integer.valueOf(base_count_string).intValue();
+
+ final File write_file =
+ getWriteFile("Select an output file name ...",
+ "upstream_" + getSequenceFileName(output_type), null);
+
+ if(write_file == null)
+ return;
+
+ try
+ {
+ final FileWriter writer = new FileWriter(write_file);
+
+ final FeatureVector features_to_write =
+ getSelection().getAllFeatures();
+
+ for(int i = 0; i < features_to_write.size(); ++i)
+ {
+ final Feature selection_feature =
+ features_to_write.elementAt(i);
+
+ final String sequence_string =
+ selection_feature.getUpstreamBases(base_count);
+
+
+ final String header_line =
+ selection_feature.getIDString() + " - " +
+ sequence_string.length() + " bases upstream";
+
+ final StreamSequence stream_sequence =
+ getStreamSequence(sequence_string,
+ header_line,
+ output_type);
+
+ stream_sequence.writeToStream(writer);
+ }
+
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "error while writing: " + e.getMessage());
+ }
+ }
+ catch(NumberFormatException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "this is not a number: " + base_count_string);
+ }
+ }
+ });
+
+ text_requester.setVisible(true);
+ }
+
+ /**
+ * Write a combination of upstream + feature + downstream bases for
+ * selected features.
+ */
+ private void writeComboBases()
+ {
+ TextFieldInt upstreamBases = new TextFieldInt();
+ upstreamBases.setValue(0);
+ Dimension preferredSize = new Dimension(200,
+ upstreamBases.getPreferredSize().height);
+ upstreamBases.setPreferredSize(preferredSize);
+ TextFieldInt downstreamBases = new TextFieldInt();
+ downstreamBases.setValue(0);
+ downstreamBases.setPreferredSize(preferredSize);
+ TextFieldInt featureBases = new TextFieldInt();
+ featureBases.setValue(0);
+ featureBases.setPreferredSize(preferredSize);
+ JCheckBox allFeatureBases = new JCheckBox("All bases of the feature(s)", false);
+
+ int row = 0;
+ JPanel gridPanel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = row;
+ c.anchor = GridBagConstraints.WEST;
+
+ gridPanel.add(new JLabel("Number of bases upstream of each feature to write:"), c);
+ c.gridy = ++row;
+ gridPanel.add(upstreamBases, c);
+ c.gridy = ++row;
+ gridPanel.add(Box.createVerticalStrut(15), c);
+
+ c.gridy = ++row;
+ gridPanel.add(new JLabel("Number of feature bases to write:"), c);
+ c.gridy = ++row;
+ gridPanel.add(featureBases, c);
+ c.gridy = ++row;
+ gridPanel.add(allFeatureBases, c);
+ c.gridy = ++row;
+ gridPanel.add(Box.createVerticalStrut(15), c);
+
+ c.gridy = ++row;
+ gridPanel.add(new JLabel("Number of bases downstream of each feature to write:"),c);
+ c.gridy = ++row;
+ gridPanel.add(downstreamBases, c);
+
+ JOptionPane.showMessageDialog(null, gridPanel,
+ "Bases to write to FASTA file for selected features",
+ JOptionPane.QUESTION_MESSAGE);
+
+ int output_type = StreamSequenceFactory.FASTA_FORMAT;
+ final File write_file =
+ getWriteFile("Select an output file name ...",
+ "bases_" + getSequenceFileName(output_type), null);
+ if(write_file == null)
+ return;
+
+ try
+ {
+ final FileWriter writer = new FileWriter(write_file);
+ final FeatureVector features_to_write =
+ getSelection().getAllFeatures();
+
+ int upstream_base_count = upstreamBases.getValue();
+ int downstream_base_count = downstreamBases.getValue();
+ int feature_base_count = featureBases.getValue();
+
+ for(int i = 0; i < features_to_write.size(); ++i)
+ {
+ final Feature selection_feature =
+ features_to_write.elementAt(i);
+
+ String header_line =
+ selection_feature.getIDString();
+ StringBuffer sequenceBuff = new StringBuffer();
+
+ if(upstream_base_count > 0)
+ {
+ sequenceBuff.append(
+ selection_feature.getUpstreamBases(upstream_base_count));
+ header_line = header_line.concat(" : upstream - "+upstream_base_count);
+ }
+
+ if(allFeatureBases.isSelected())
+ {
+ String bases = selection_feature.getBases();
+ sequenceBuff.append(bases);
+ header_line = header_line.concat(" : complete feature - "+bases.length());
+ }
+ else if (feature_base_count > 0)
+ {
+ sequenceBuff.append(
+ selection_feature.getBases().substring(0, feature_base_count));
+ header_line = header_line.concat(" : feature - "+feature_base_count);
+ }
+
+ if(downstream_base_count > 0)
+ {
+ sequenceBuff.append(
+ selection_feature.getDownstreamBases(downstream_base_count));
+ header_line = header_line.concat(" : downstream - "+downstream_base_count);
+ }
+
+ final StreamSequence stream_sequence =
+ getStreamSequence(sequenceBuff.toString(),
+ header_line,
+ output_type);
+
+ stream_sequence.writeToStream(writer);
+ }
+
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "error while writing: " + e.getMessage());
+ }
+
+ if(write_file == null)
+ return;
+
+ }
+
+ /**
+ * Write the downstream bases of the selected features to a file choosen by
+ * the user. The user can also choose the number of downstream base to
+ * write.
+ * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc.
+ **/
+ private void writeDownstreamBases(final int output_type)
+ {
+ if(!checkForSelectionFeatures())
+ return;
+
+ final TextRequester text_requester =
+ new TextRequester("write how many bases downstream of each feature?",
+ 18, "");
+
+ text_requester.addTextRequesterListener(new TextRequesterListener()
+ {
+ public void actionPerformed(final TextRequesterEvent event)
+ {
+ if(event.getType() == TextRequesterEvent.CANCEL)
+ return;
+
+ final String base_count_string = event.getRequesterText().trim();
+
+ if(base_count_string.length() == 0)
+ {
+ new MessageDialog(getParentFrame(), "no bases written");
+ return;
+ }
+
+ try
+ {
+ final int base_count =
+ Integer.valueOf(base_count_string).intValue();
+
+ final File write_file =
+ getWriteFile("Select an output file name ...",
+ "downstream_" + getSequenceFileName(output_type), null);
+
+ if(write_file == null)
+ return;
+
+ try
+ {
+ final FileWriter writer = new FileWriter(write_file);
+ final FeatureVector features_to_write =
+ getSelection().getAllFeatures();
+
+ for(int i = 0; i < features_to_write.size(); ++i)
+ {
+ final Feature selection_feature =
+ features_to_write.elementAt(i);
+
+ final String sequence_string =
+ selection_feature.getDownstreamBases(base_count);
+
+
+ final String header_line =
+ selection_feature.getIDString() + " - " +
+ sequence_string.length() + " bases downstream ";
+
+ final StreamSequence stream_sequence =
+ getStreamSequence(sequence_string,
+ header_line,
+ output_type);
+
+ stream_sequence.writeToStream(writer);
+ }
+
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "error while writing: " + e.getMessage());
+ }
+ }
+ catch(NumberFormatException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "this is not a number: " + base_count_string);
+ }
+ }
+ });
+
+ text_requester.setVisible(true);
+ }
+
+ /**
+ * Helper method for writeFeatureBases(), writeUpstreamBases() and
+ * writeDownstreamBases().
+ * @return A string of the form "100:200 reverse" or "1:2222 forward".
+ **/
+ /*private static String getWriteRange(final Feature feature)
+ {
+ return (feature.isForwardFeature() ?
+ feature.getFirstCodingBaseMarker().getRawPosition() + ":" +
+ feature.getLastBaseMarker().getRawPosition() +
+ " forward" :
+ feature.getLastBaseMarker().getRawPosition() + ":" +
+ feature.getFirstCodingBaseMarker().getRawPosition() +
+ " reverse");
+ }*/
+
+
+ /**
+ * Return a sensible file name to write sequence of the given type to.
+ * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc.
+ **/
+ private String getSequenceFileName(final int output_type)
+ {
+ switch(output_type)
+ {
+ case StreamSequenceFactory.FASTA_FORMAT:
+ return "sequence.dna";
+
+ case StreamSequenceFactory.EMBL_FORMAT:
+ return "sequence.embl";
+
+ case StreamSequenceFactory.GENBANK_FORMAT:
+ return "sequence.genbank";
+
+ case StreamSequenceFactory.RAW_FORMAT:
+ default:
+ return "sequence.seq";
+ }
+ }
+
+ /**
+ * Write the bases from entry_group as raw sequence.
+ * @param output_type One of EMBL_FORMAT, RAW_FORMAT etc.
+ **/
+ private void writeAllSequence(final int output_type)
+ {
+ final String file_name = getSequenceFileName(output_type);
+
+ String name = "all_bases";
+ try
+ {
+ name = entry_group.getSequenceEntry().getName()+" all_bases";
+ if( entry_group.getSequenceEntry().getHeaderText() != null &&
+ (entry_group.getSequenceEntry().getHeaderText().startsWith("ID ") ||
+ entry_group.getSequenceEntry().getHeaderText().startsWith("LOCUS ")) )
+ {
+ StringTokenizer tok = new StringTokenizer(
+ entry_group.getSequenceEntry().getHeaderText(),
+ " ;");
+ tok.nextElement();
+
+ if(tok.hasMoreTokens())
+ {
+ String ID = tok.nextToken();
+ if(ID.length() > 1 && !(ID.indexOf("\n") > -1) && !(ID.indexOf("\r") > -1))
+ name = ID + " all_bases";
+ }
+ }
+ }
+ catch(Exception e){}
+ writeBases(entry_group.getBases().toString(),
+ name, file_name, output_type);
+ }
+
+ /**
+ * Popup a requester and ask for a file name to write to. If the file
+ * exists ask the user whether to overwrite.
+ * @param title The title of the new FileDialog JFrame.
+ * @param default_name The name to put in the FileDialog as a default.
+ **/
+ private File getWriteFile(final String title, final String default_name,
+ final JTextField idField)
+ {
+ final JFileChooser dialog = new StickyFileChooser();
+
+ JCheckBox header = new JCheckBox("Add Header", false);
+ if(idField != null)
+ dialog.setAccessory(header);
+
+ dialog.setDialogTitle(title);
+ dialog.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ dialog.setSelectedFile(new File(default_name));
+ dialog.setDialogType(JFileChooser.SAVE_DIALOG);
+ final int status = dialog.showSaveDialog(getParentFrame());
+
+ if(status != JFileChooser.APPROVE_OPTION)
+ return null;
+
+ if(dialog.getSelectedFile() == null)
+ return null;
+ else
+ {
+ final File write_file = dialog.getSelectedFile();
+
+ if(write_file.exists())
+ {
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog(getParentFrame(),
+ "this file exists: " + write_file +
+ " overwrite it?");
+
+ if(!yes_no_dialog.getResult())
+ return null;
+ }
+
+ // request user to provide header
+ if(idField != null)
+ {
+ if(header.isSelected())
+ {
+ Box bdown = Box.createVerticalBox();
+ bdown.add(idField);
+
+ int n = JOptionPane.showConfirmDialog(null, bdown,
+ "Enter the entry ID",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(n == JOptionPane.CANCEL_OPTION)
+ idField.setText("");
+ }
+ else
+ idField.setText("");
+ }
+ return write_file;
+ }
+ }
+
+ /**
+ * Write a table of codon usage for the selected features to a file choosen
+ * by the user.
+ **/
+ private void writeCodonUsage()
+ {
+ if(!checkForSelectionFeatures())
+ return;
+
+ final File write_file =
+ getWriteFile("Select a codon usage output file ...", "usage", null);
+
+ if(write_file == null)
+ return;
+
+ try
+ {
+ final PrintWriter writer = new PrintWriter(new FileWriter(write_file));
+
+ final int[][][] codon_counts = new int[4][4][4];
+
+ final FeatureVector features_to_write =
+ getSelection().getAllFeatures();
+
+ int codon_total = 0;
+
+ for(int i = 0; i < features_to_write.size(); ++i)
+ {
+ final Feature selection_feature = features_to_write.elementAt(i);
+
+ for(int base1 = 0 ; base1 < 4 ; ++base1)
+ {
+ for(int base2 = 0 ; base2 < 4 ; ++base2)
+ {
+ for(int base3 = 0 ; base3 < 4 ; ++base3)
+ {
+ codon_counts[base1][base2][base3] +=
+ selection_feature.getCodonCount(base1, base2, base3);
+ }
+ }
+ }
+
+ codon_total += selection_feature.getTranslationBasesLength() / 3;
+ }
+
+ for(int base1 = 0 ; base1 < 4 ; ++base1)
+ {
+ for(int base3 = 0 ; base3 < 4 ; ++base3)
+ {
+ final StringBuffer buffer = new StringBuffer();
+
+ for(int base2 = 0 ; base2 < 4 ; ++base2)
+ {
+ buffer.append(Bases.letter_index[base1]);
+ buffer.append(Bases.letter_index[base2]);
+ buffer.append(Bases.letter_index[base3]);
+ buffer.append(' ');
+
+ final float per_thousand;
+
+ if(codon_total > 0)
+ {
+ per_thousand =
+ 10000 * codon_counts[base1][base2][base3] / codon_total *
+ 1.0F / 10;
+ }
+ else
+ per_thousand = 0.0F;
+
+ buffer.append(per_thousand);
+ buffer.append("( ").append(codon_counts[base1][base2][base3]);
+ buffer.append(") ");
+ }
+
+ writer.println(buffer.toString());
+ }
+ }
+
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(getParentFrame(),
+ "error while writing: " + e.getMessage());
+ }
+ }
+
+ /**
+ * The EntryGroup object that was passed to the constructor.
+ **/
+ private EntryGroup entry_group;
+}
diff --git a/uk/ac/sanger/artemis/components/YesNoDialog.java b/uk/ac/sanger/artemis/components/YesNoDialog.java
new file mode 100644
index 0000000..d76c198
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/YesNoDialog.java
@@ -0,0 +1,86 @@
+/* YesNoDialog.java
+ *
+ * created: Sat Dec 12 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/YesNoDialog.java,v 1.2 2008-10-15 13:45:58 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
+/**
+ * A popup dialog box that displays a message and then waits for the to press
+ * yes or no.
+ *
+ * @author Kim Rutherford
+ * @version $Id: YesNoDialog.java,v 1.2 2008-10-15 13:45:58 tjc Exp $
+ **/
+
+public class YesNoDialog
+{
+ private JFrame parent;
+ private String title;
+ private String message;
+
+ /**
+ * Create a new YesNoDialog component. The constructor does not show ()
+ * the dialog, call getResult () to do that.
+ * @param parent The parent window.
+ * @param title The title of the new dialog JFrame.
+ * @param message The message to display in the JDialog.
+ **/
+ public YesNoDialog (JFrame parent, String title, String message)
+ {
+ this.parent = parent;
+ this.title = title;
+ this.message = message;
+ }
+
+ /**
+ * Create a new YesNoDialog component. The constructor does not show ()
+ * the dialog, call getResult () to do that.
+ * @param parent The parent window.
+ * @param message The message to display in the JDialog and to uise as the
+ * title string.
+ **/
+ public YesNoDialog (JFrame parent, String message)
+ {
+ this (parent, null, message);
+ }
+
+ /**
+ * This method calls show () on this object, and then waits for the user to
+ * press the Yes button or the No button.
+ * @return true is the user pressed Yes, false otherwise.
+ **/
+ protected boolean getResult ()
+ {
+ int result = JOptionPane.showConfirmDialog(
+ parent, message, title,
+ JOptionPane.YES_NO_OPTION);
+ if(result == JOptionPane.YES_OPTION)
+ return true;
+ return false;
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/components/ZoomScrollBar.java b/uk/ac/sanger/artemis/components/ZoomScrollBar.java
new file mode 100644
index 0000000..34b1966
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/ZoomScrollBar.java
@@ -0,0 +1,127 @@
+/* ZoomScrollBar.java
+ *
+ * created: Fri Oct 9 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1998,1999,2000,2001,2002 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/ZoomScrollBar.java,v 1.2 2008-11-27 10:48:57 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components;
+
+import java.awt.Component;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+
+import javax.swing.JScrollBar;
+
+class ZoomScrollBar extends JScrollBar
+{
+ private static final long serialVersionUID = 1L;
+ private FeatureDisplay display;
+ private MouseMotionListener mouseMotionListener = null;
+
+ public ZoomScrollBar(final FeatureDisplay display)
+ {
+ super(JScrollBar.VERTICAL);
+ this.display = display;
+
+ // try to arrange for the scrollbar to have a maximum value big enough
+ // that the whole sequence can be visible at once
+ final int MAX_FACTOR =
+ (int)Math.round(Math.log(display.getSequenceLength()/20) / Math.log(3));
+ setValues(display.getScaleFactor(), 1, 0, MAX_FACTOR);
+ setBlockIncrement(1);
+ setUnitIncrement(1);
+ addAdjustmentListener(new AdjustmentListener()
+ {
+ public void adjustmentValueChanged(AdjustmentEvent e)
+ {
+ display.setScaleFactor(e.getValue());
+ }
+ });
+
+ if(display.getScaleFactor() >= MAX_FACTOR)
+ {
+ display.setScaleFactor(MAX_FACTOR - 1);
+ setValue(display.getScaleFactor());
+ }
+ }
+
+ /**
+ * Add MouseMotionListener to auto hide scroll
+ */
+ protected void addMouseMotionListenerToFeatureDisplay()
+ {
+ if(mouseMotionListener == null)
+ mouseMotionListener = new MouseMotionListener()
+ {
+ public void mouseDragged(MouseEvent e){}
+
+ public void mouseMoved(MouseEvent e)
+ {
+ int thisWidth = WIDTH;
+ if(thisWidth < 5)
+ thisWidth = 15;
+ if(e.getX() > (display.getSize().width - thisWidth))
+ {
+ if(!containsComponent())
+ display.add(ZoomScrollBar.this, "East");
+ }
+ else
+ {
+ if(containsComponent())
+ display.remove(ZoomScrollBar.this);
+ }
+ display.repaint();
+ display.revalidate();
+ }
+ };
+
+ display.addMouseMotionListener(mouseMotionListener);
+ }
+
+ /**
+ * Remove MouseMotionListener
+ */
+ protected void removeMouseMotionListenerFromFeatureDisplay()
+ {
+ if(mouseMotionListener != null)
+ display.removeMouseMotionListener(mouseMotionListener);
+ }
+
+ /**
+ * Check to see if this component is contained by the display
+ * (FeatureDisplay) component.
+ * @return
+ */
+ private boolean containsComponent()
+ {
+ Component[] c = display.getComponents();
+ for(int i=0; i<c.length; i++)
+ {
+ if(c[i].equals(this))
+ return true;
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/AbstractGraphPanel.java b/uk/ac/sanger/artemis/components/alignment/AbstractGraphPanel.java
new file mode 100644
index 0000000..075eb56
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/AbstractGraphPanel.java
@@ -0,0 +1,248 @@
+/* AbstractGraphPanel
+ *
+ * created: 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.text.DecimalFormat;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.io.Range;
+
+public class AbstractGraphPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ protected int start;
+ protected int end;
+ protected float pixPerBase;
+ protected int nBins;
+
+ protected BamView bamView;
+ protected int windowSize;
+ protected int max;
+ protected boolean autoWinSize = true;
+ protected int userWinSize = 1;
+ protected JPopupMenu popup = new JPopupMenu();
+
+ public AbstractGraphPanel()
+ {
+ super();
+ setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.gray));
+ setBackground(Color.white);
+
+ final MouseMotionListener mouseMotionListener = new MouseMotionListener()
+ {
+ public void mouseDragged(MouseEvent e)
+ {
+ if(e.getButton() == MouseEvent.BUTTON3)
+ return;
+
+ if(e.getClickCount() > 1)
+ {
+ bamView.getSelection().clear();
+ repaint();
+ return;
+ }
+ bamView.highlightRange(e,
+ MouseEvent.BUTTON1_DOWN_MASK & MouseEvent.BUTTON2_DOWN_MASK);
+ }
+
+ public void mouseMoved(MouseEvent e){}
+ };
+ addMouseMotionListener(mouseMotionListener);
+ }
+
+ protected void initPopupMenu(JComponent menu)
+ {
+ final JMenuItem setScale = new JMenuItem("Set the Window Size...");
+ setScale.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ GridBagLayout gridbag = new GridBagLayout();
+ GridBagConstraints c = new GridBagConstraints();
+ JPanel pane = new JPanel(gridbag);
+ final JTextField newWinSize = new JTextField(Integer.toString(userWinSize), 10);
+ newWinSize.setEnabled(!autoWinSize);
+ final JLabel lab = new JLabel("Window size:");
+ c.gridy = 0;
+ pane.add(lab, c);
+ pane.add(newWinSize, c);
+
+ final JCheckBox autoSet = new JCheckBox("Automatically set window size", autoWinSize);
+ autoSet.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ lab.setEnabled(!autoSet.isSelected());
+ newWinSize.setEnabled(!autoSet.isSelected());
+ }
+ });
+ c.gridy = 1;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ pane.add(autoSet, c);
+
+ String window_options[] = { "OK", "Cancel" };
+ int select = JOptionPane.showOptionDialog(null, pane, "Window Size",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+ window_options, window_options[0]);
+
+ if (select == 1)
+ return;
+ autoWinSize = autoSet.isSelected();
+ try
+ {
+ userWinSize = Integer.parseInt(newWinSize.getText().trim());
+ }
+ catch (NumberFormatException nfe)
+ {
+ return;
+ }
+ bamView.repaint();
+ }
+ });
+ menu.add(setScale);
+
+ addMouseListener(new PopupListener());
+ }
+
+ /**
+ * Draw maximum average value.
+ * @param g2
+ */
+ protected void drawMax(Graphics2D g2)
+ {
+ DecimalFormat df = new DecimalFormat("0.#");
+ String maxStr = df.format((float)max/(float)windowSize);
+
+ FontMetrics fm = getFontMetrics(getFont());
+ g2.setColor(Color.black);
+
+ int xpos = bamView.getJspView().getVisibleRect().width - fm.stringWidth(maxStr) -
+ bamView.getJspView().getVerticalScrollBar().getWidth();
+ g2.drawString(maxStr, xpos, fm.getHeight());
+ }
+
+ /**
+ * Return the log value if the log scale is selected
+ * @param val
+ * @return
+ */
+ protected float getValue(int val, boolean logScale)
+ {
+ if(val == 0)
+ return 0.f;
+ return (float) (logScale ? Math.log(val) : val);
+ }
+
+ protected void drawSelectionRange(final Graphics2D g2,
+ final float pixPerBase,
+ final int start,
+ final int end,
+ final int hgt,
+ final Color c)
+ {
+ if(bamView != null && bamView.getSelection() != null)
+ {
+ final Range selectedRange = bamView.getSelection().getSelectionRange();
+ if(selectedRange != null)
+ {
+ int rangeStart = selectedRange.getStart();
+ int rangeEnd = selectedRange.getEnd();
+
+ if(end < rangeStart || start > rangeEnd)
+ return;
+
+ int x = (int) (pixPerBase*(rangeStart-bamView.getBaseAtStartOfView()));
+ int width = (int) (pixPerBase*(rangeEnd-rangeStart+1));
+
+ g2.setColor(c);
+ g2.fillRect(x, 0, width, hgt);
+ }
+ }
+ }
+
+ protected void setStartAndEnd(int start, int end)
+ {
+ this.start = start;
+ this.end = end;
+ }
+
+ protected void setPixPerBase(float pixPerBase)
+ {
+ this.pixPerBase = pixPerBase;
+ }
+
+ /**
+ * Popup menu listener
+ */
+ class PopupListener extends MouseAdapter
+ {
+ JMenuItem gotoMateMenuItem;
+ JMenuItem showDetails;
+
+ public void mouseClicked(MouseEvent e)
+ {
+ if(e.getClickCount() > 1)
+ bamView.getSelection().clear();
+ }
+
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ bamView.dragStart = -1;
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ {
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/BamFrame.java b/uk/ac/sanger/artemis/components/alignment/BamFrame.java
new file mode 100644
index 0000000..f9f32ea
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/BamFrame.java
@@ -0,0 +1,75 @@
+package uk.ac.sanger.artemis.components.alignment;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
+ class BamFrame extends JFrame
+ {
+ private static final long serialVersionUID = 1L;
+ private String bamFile = null;
+
+ BamFrame()
+ {
+ super();
+ if(isMac())
+ initMac();
+ }
+
+ private void initMac()
+ {
+ try
+ {
+ // Generate and register the OSXAdapter, passing it a hash of all the
+ // methods we wish to
+ // use as delegates for various com.apple.eawt.ApplicationListener
+ // methods
+ Class<?> bamClass = Class.forName("uk.ac.sanger.artemis.components.alignment.BamFrame");
+ BamOSXAdapter.setQuitHandler(this, bamClass.getDeclaredMethod(
+ "exitApp", (Class[]) null));
+ BamOSXAdapter.setAboutHandler(this, bamClass.getDeclaredMethod("about",
+ (Class[]) null));
+ // OSXAdapter.setPreferencesHandler(this,
+ // splashClass.getDeclaredMethod("preferences", (Class[])null));
+ BamOSXAdapter.setFileHandler(this, bamClass.getDeclaredMethod(
+ "loadFile", new Class[]
+ { String.class }));
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ protected void about()
+ {
+ JOptionPane.showMessageDialog(this,
+ "BamView\nthis is free software and is distributed"
+ + "\nunder the terms of the GNU General Public License.",
+ "About", JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ protected void loadFile(final String bamFile)
+ {
+ this.bamFile = bamFile;
+ }
+
+ protected void exitApp()
+ {
+ int status = JOptionPane.showConfirmDialog(this, "Exit?",
+ "BamView", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+ System.exit(0);
+ }
+
+ protected String getBamFile()
+ {
+ return bamFile;
+ }
+
+ protected static boolean isMac()
+ {
+ return System.getProperty("mrj.version") != null ||
+ System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0;
+ }
+ }
diff --git a/uk/ac/sanger/artemis/components/alignment/BamOSXAdapter.java b/uk/ac/sanger/artemis/components/alignment/BamOSXAdapter.java
new file mode 100644
index 0000000..2aaf011
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/BamOSXAdapter.java
@@ -0,0 +1,209 @@
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.lang.reflect.*;
+
+public class BamOSXAdapter implements InvocationHandler
+
+{
+ protected Object targetObject;
+ protected Method targetMethod;
+ protected String proxySignature;
+ static Object macOSXApplication;
+
+ // Each OSXAdapter has the name of the EAWT method it intends to listen for
+ // (handleAbout, for example),
+ // the Object that will ultimately perform the task, and the Method to be
+ // called on that Object
+ protected BamOSXAdapter(String proxySignature, Object target, Method handler)
+ {
+ this.proxySignature = proxySignature;
+ this.targetObject = target;
+ this.targetMethod = handler;
+ }
+
+ // Pass this method an Object and Method equipped to perform application
+ // shutdown logic
+ // The method passed should return a boolean stating whether or not the quit
+ // should occur
+ public static void setQuitHandler(Object target, Method quitHandler)
+ {
+ setHandler(new BamOSXAdapter("handleQuit", target, quitHandler));
+ }
+
+ // Pass this method an Object and Method equipped to display application info
+ // They will be called when the About menu item is selected from the
+ // application menu
+ public static void setAboutHandler(Object target, Method aboutHandler)
+ {
+ boolean enableAboutMenu = (target != null && aboutHandler != null);
+ if(enableAboutMenu)
+ setHandler(new BamOSXAdapter("handleAbout", target, aboutHandler));
+
+ // If we're setting a handler, enable the About menu item by calling
+ // com.apple.eawt.Application reflectively
+ try
+ {
+ Method enableAboutMethod = macOSXApplication.getClass()
+ .getDeclaredMethod("setEnabledAboutMenu",
+ new Class[] { boolean.class });
+ enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean
+ .valueOf(enableAboutMenu) });
+ }
+ catch(Exception ex)
+ {
+ System.err.println("OSXAdapter could not access the About Menu");
+ ex.printStackTrace();
+ }
+ }
+
+ // Pass this method an Object and a Method equipped to display application
+ // options
+ // They will be called when the Preferences menu item is selected from the
+ // application menu
+ public static void setPreferencesHandler(Object target, Method prefsHandler)
+ {
+ boolean enablePrefsMenu = (target != null && prefsHandler != null);
+ if(enablePrefsMenu)
+ setHandler(new BamOSXAdapter("handlePreferences", target, prefsHandler));
+
+ // If we're setting a handler, enable the Preferences menu item by calling
+ // com.apple.eawt.Application reflectively
+ try
+ {
+ Method enablePrefsMethod = macOSXApplication.getClass()
+ .getDeclaredMethod("setEnabledPreferencesMenu",
+ new Class[] { boolean.class });
+ enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean
+ .valueOf(enablePrefsMenu) });
+ }
+ catch(Exception ex)
+ {
+ System.err.println("OSXAdapter could not access the About Menu");
+ ex.printStackTrace();
+ }
+ }
+
+ // Pass this method an Object and a Method equipped to handle document
+ // events from the Finder. Documents are registered with the Finder via
+ // the CFBundleDocumentTypes dictionary in the application Info.plist
+ public static void setFileHandler(Object target, Method fileHandler)
+ {
+ setHandler(new BamOSXAdapter("handleOpenFile", target, fileHandler)
+ {
+ // Override OSXAdapter.callTarget to send information on the
+ // file to be opened
+ public boolean callTarget(Object appleEvent)
+ {
+ if(appleEvent != null)
+ {
+ try
+ {
+ Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod(
+ "getFilename", (Class[]) null);
+ String filename = (String) getFilenameMethod.invoke(appleEvent,
+ (Object[]) null);
+ this.targetMethod.invoke(this.targetObject,
+ new Object[] { filename });
+ }
+ catch(Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ return true;
+ }
+ });
+ }
+
+ // setHandler creates a Proxy object from the passed OSXAdapter and adds it as
+ // an ApplicationListener
+ public static void setHandler(BamOSXAdapter adapter)
+ {
+ try
+ {
+ Class applicationClass = Class.forName("com.apple.eawt.Application");
+ if(macOSXApplication == null)
+ macOSXApplication = applicationClass.getConstructor((Class[]) null)
+ .newInstance((Object[]) null);
+
+ Class applicationListenerClass = Class
+ .forName("com.apple.eawt.ApplicationListener");
+ Method addListenerMethod = applicationClass.getDeclaredMethod(
+ "addApplicationListener", new Class[] { applicationListenerClass });
+ // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener
+ Object osxAdapterProxy = Proxy.newProxyInstance(BamOSXAdapter.class
+ .getClassLoader(), new Class[] { applicationListenerClass }, adapter);
+ addListenerMethod.invoke(macOSXApplication,
+ new Object[] { osxAdapterProxy });
+ }
+ catch(ClassNotFoundException cnfe)
+ {
+ System.err.println(
+ "This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled ("
+ + cnfe + ")");
+ }
+ catch(Exception ex)
+ { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods
+ System.err.println("Mac OS X Adapter could not talk to EAWT:");
+ ex.printStackTrace();
+ }
+ }
+
+ // Override this method to perform any operations on the event
+ // that comes with the various callbacks
+ // See setFileHandler above for an example
+ public boolean callTarget(Object appleEvent)
+ throws InvocationTargetException, IllegalAccessException
+ {
+ Object result = targetMethod.invoke(targetObject, (Object[]) null);
+ if(result == null)
+ return true;
+
+ return Boolean.valueOf(result.toString()).booleanValue();
+ }
+
+ // InvocationHandler implementation
+ // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable
+ {
+ if(isCorrectMethod(method, args))
+ {
+ boolean handled = callTarget(args[0]);
+ setApplicationEventHandled(args[0], handled);
+ }
+ // All of the ApplicationListener methods are void; return null regardless of what happens
+ return null;
+ }
+
+ // Compare the method that was called to the intended method when the OSXAdapter instance was created
+ // (e.g. handleAbout, handleQuit, handleOpenFile, etc.)
+ protected boolean isCorrectMethod(Method method, Object[] args)
+ {
+ return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1);
+ }
+
+ // It is important to mark the ApplicationEvent as handled and cancel the default behavior
+ // This method checks for a boolean result from the proxy method and sets the event accordingly
+ protected void setApplicationEventHandled(Object event, boolean handled)
+ {
+ if(event != null)
+ {
+ try
+ {
+ Method setHandledMethod = event.getClass().getDeclaredMethod(
+ "setHandled", new Class[] { boolean.class });
+ // If the target method returns a boolean, use that as a hint
+ setHandledMethod.invoke(event,
+ new Object[] { Boolean.valueOf(handled) });
+ }
+ catch(Exception ex)
+ {
+ System.err
+ .println("OSXAdapter was unable to handle an ApplicationEvent: "
+ + event);
+ ex.printStackTrace();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/BamUtils.java b/uk/ac/sanger/artemis/components/alignment/BamUtils.java
new file mode 100644
index 0000000..c064061
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/BamUtils.java
@@ -0,0 +1,230 @@
+/* BamUtils
+ *
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Vector;
+
+import net.sf.samtools.AlignmentBlock;
+import net.sf.samtools.SAMFileReader;
+import net.sf.samtools.SAMRecord;
+import net.sf.samtools.util.CloseableIterator;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.io.Range;
+
+class BamUtils
+{
+
+ protected static float getFeatureLength(Feature f)
+ {
+ FeatureSegmentVector segs = f.getSegments();
+ int len = 0;
+ for(int i=0; i<segs.size(); i++)
+ {
+ Range r = segs.elementAt(i).getRawRange();
+ len += r.getEnd()-r.getStart()+1;
+ }
+ return (float)len;
+ }
+
+ /**
+ * Count the reads in a range.
+ * @param start
+ * @param end
+ * @param bam
+ * @param refName
+ * @param samFileReaderHash
+ * @param seqNames
+ * @param offsetLengths
+ * @param concatSequences
+ * @param seqLengths
+ * @param samRecordFlagPredicate
+ * @param samRecordMapQPredicate
+ * @param contained
+ * @param useStrandTag - strand specific tag
+ * @return
+ */
+ protected static float[] getCount(
+ final int start,
+ final int end,
+ final String bam,
+ final String refName,
+ final Hashtable<String, SAMFileReader> samFileReaderHash,
+ final Vector<String> seqNames,
+ final HashMap<String, Integer> offsetLengths,
+ final boolean concatSequences,
+ final HashMap<String, Integer> seqLengths,
+ final SAMRecordPredicate samRecordFlagPredicate,
+ final SAMRecordMapQPredicate samRecordMapQPredicate,
+ final boolean contained,
+ final boolean useStrandTag)
+ {
+ int cnt[] = new int[2];
+ cnt[0] = 0;
+ cnt[1] = 0;
+ if(concatSequences)
+ {
+ int len = 0;
+ int lastLen = 1;
+ for(String name : seqNames)
+ {
+ int thisLength = seqLengths.get(name);
+ len += thisLength;
+
+ if( (lastLen >= start && lastLen < end) ||
+ (len >= start && len < end) ||
+ (start >= lastLen && start < len) ||
+ (end > lastLen && end < len) )
+ {
+ int offset = offsetLengths.get(name);
+ int thisStart = start - offset;
+ if(thisStart < 1)
+ thisStart = 1;
+ int thisEnd = end - offset;
+ if(thisEnd > thisLength)
+ thisEnd = thisLength;
+
+ cnt = count(bam, samFileReaderHash, name, thisStart, thisEnd,
+ samRecordFlagPredicate, samRecordMapQPredicate, contained, true, useStrandTag);
+
+ }
+ lastLen = len;
+ }
+ }
+ else
+ {
+ cnt = count(bam, samFileReaderHash, refName, start, end,
+ samRecordFlagPredicate, samRecordMapQPredicate, contained, true, useStrandTag);
+ }
+
+ float cntf[] = new float[2];
+ cntf[0] = cnt[0];
+ cntf[1] = cnt[1];
+ return cntf;
+ }
+
+ protected static int[] count(final String bam,
+ final Hashtable<String, SAMFileReader> samFileReaderHash,
+ final String refName,
+ final int start,
+ final int end,
+ final SAMRecordPredicate samRecordFlagPredicate,
+ final SAMRecordPredicate samRecordMapQPredicate,
+ final boolean contained,
+ final boolean byStrand,
+ final boolean useStrandTag)
+ {
+ int cnt[] = new int[2];
+ cnt[0] = 0;
+ cnt[1] = 0;
+
+ SAMFileReader inputSam = samFileReaderHash.get(bam);
+ final CloseableIterator<SAMRecord> it = inputSam.query(refName, start, end, contained);
+
+ while ( it.hasNext() )
+ {
+ SAMRecord samRecord = it.next();
+ if( samRecordFlagPredicate == null ||
+ !samRecordFlagPredicate.testPredicate(samRecord))
+ {
+ if(samRecordMapQPredicate == null ||
+ samRecordMapQPredicate.testPredicate(samRecord))
+ {
+ if(byStrand && BamView.isNegativeStrand(samRecord, useStrandTag))
+ cnt[1]++;
+ else
+ cnt[0]++;
+ }
+ }
+ }
+ it.close();
+ return cnt;
+ }
+
+ /**
+ * Return the coverage for each base in a range for the forward and
+ * reverse strand.
+ * @param bamFile
+ * @param samFileReaderHash
+ * @param refName
+ * @param start
+ * @param end
+ * @param samRecordFlagPredicate
+ * @param samRecordMapQPredicate
+ * @return
+ */
+ protected static int[][] countOverRange(final String bamFile,
+ final Hashtable<String, SAMFileReader> samFileReaderHash,
+ final String refName,
+ final int start,
+ final int end,
+ final int concatShift,
+ final int cnt[][],
+ final SAMRecordPredicate samRecordFlagPredicate,
+ final SAMRecordPredicate samRecordMapQPredicate)
+ {
+ SAMFileReader inputSam = samFileReaderHash.get(bamFile);
+ final CloseableIterator<SAMRecord> it =
+ inputSam.query(refName, start, end, false);
+
+ while (it.hasNext())
+ {
+ SAMRecord samRecord = it.next();
+ if (samRecordFlagPredicate == null
+ || !samRecordFlagPredicate.testPredicate(samRecord))
+ {
+ if (samRecordMapQPredicate == null
+ || samRecordMapQPredicate.testPredicate(samRecord))
+ {
+ List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
+ boolean isFwd = !samRecord.getReadNegativeStrandFlag();
+
+ for(int j=0; j<blocks.size(); j++)
+ {
+ AlignmentBlock block = blocks.get(j);
+ int refStart = block.getReferenceStart();
+ for(int i=0; i<block.getLength(); i++)
+ {
+ int pos = refStart + i + concatShift;
+ int bin = pos - start;
+ if(bin < 0 || bin > cnt.length-1)
+ continue;
+
+ if(isFwd)
+ cnt[bin][0]++;
+ else
+ cnt[bin][1]++;
+ }
+ }
+ }
+ }
+ }
+ it.close();
+ return cnt;
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/components/alignment/BamView.java b/uk/ac/sanger/artemis/components/alignment/BamView.java
new file mode 100644
index 0000000..8dcac22
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/BamView.java
@@ -0,0 +1,4443 @@
+/* BamView
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Composite;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Stroke;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.image.BufferedImage;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollBar;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JSlider;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.apache.log4j.Level;
+
+import net.sf.picard.reference.ReferenceSequenceFile;
+import net.sf.picard.sam.BuildBamIndex;
+import net.sf.samtools.AlignmentBlock;
+import net.sf.samtools.SAMException;
+import net.sf.samtools.SAMFileHeader;
+import net.sf.samtools.SAMFileReader;
+import net.sf.samtools.SAMReadGroupRecord;
+import net.sf.samtools.SAMRecord;
+import net.sf.samtools.SAMSequenceRecord;
+import net.sf.samtools.SAMFileReader.ValidationStringency;
+import net.sf.samtools.util.CloseableIterator;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.SelectionChangeEvent;
+import uk.ac.sanger.artemis.SelectionChangeListener;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.circular.TextFieldInt;
+import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
+import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
+import uk.ac.sanger.artemis.components.EntryEdit;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.components.FeatureDisplay;
+import uk.ac.sanger.artemis.components.FileViewer;
+import uk.ac.sanger.artemis.components.IndexReferenceEvent;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.components.NonModalDialog;
+import uk.ac.sanger.artemis.components.SequenceComboBox;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.FTPSeekableStream;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+public class BamView extends JPanel
+ implements DisplayAdjustmentListener, SelectionChangeListener
+{
+ private static final long serialVersionUID = 1L;
+
+ private List<BamViewRecord> readsInView;
+ private Hashtable<String, SAMFileReader> samFileReaderHash = new Hashtable<String, SAMFileReader>();
+ private List<SAMReadGroupRecord> readGroups = new Vector<SAMReadGroupRecord>();
+
+ private HashMap<String, Integer> seqLengths = new HashMap<String, Integer>();
+ private HashMap<String, Integer> offsetLengths;
+ private Vector<String> seqNames = new Vector<String>();
+ protected List<String> bamList;
+ protected List<Short> hideBamList = new Vector<Short>();
+
+ private SAMRecordPredicate samRecordFlagPredicate;
+ private SAMRecordMapQPredicate samRecordMapQPredicate;
+
+ private SAMRecordFilter filterFrame;
+
+ private Bases bases;
+ private JScrollPane jspView;
+ private JScrollBar scrollBar;
+
+ private SequenceComboBox combo;
+ private boolean isOrientation = false;
+ private boolean isSingle = false;
+ private boolean isSNPs = false;
+
+ private boolean isCoverage = false;
+ private boolean isSNPplot = false;
+
+ private EntryEdit entry_edit;
+ private FeatureDisplay feature_display;
+ private Selection selection;
+ private JPanel mainPanel = new JPanel();
+ private CoveragePanel coveragePanel;
+ private SnpPanel snpPanel;
+
+ private boolean logScale = false;
+ private Ruler ruler;
+ private int nbasesInView;
+
+ private int startBase = -1;
+ private int endBase = -1;
+ private int laststart;
+ private int lastend;
+
+ private boolean asynchronous = true;
+ private boolean showBaseAlignment = false;
+
+ private JMenu bamFilesMenu = new JMenu("BAM files");
+ private JCheckBoxMenuItem logMenuItem = new JCheckBoxMenuItem("Use Log Scale", logScale);
+
+ private JCheckBoxMenuItem cbStackView = new JCheckBoxMenuItem("Stack", true);
+ private JCheckBoxMenuItem cbPairedStackView = new JCheckBoxMenuItem("Paired Stack");
+ private JCheckBoxMenuItem cbStrandStackView = new JCheckBoxMenuItem("Strand Stack");
+ private JCheckBoxMenuItem cbIsizeStackView = new JCheckBoxMenuItem("Inferred Size", false);
+ private JCheckBoxMenuItem cbCoverageView = new JCheckBoxMenuItem("Coverage", false);
+ private JCheckBoxMenuItem cbCoverageStrandView = new JCheckBoxMenuItem("Coverage by Strand", false);
+ private JCheckBoxMenuItem cbCoverageHeatMap = new JCheckBoxMenuItem("Coverage Heat Map", false);
+ private JCheckBoxMenuItem cbLastSelected;
+
+ private ButtonGroup buttonGroup = new ButtonGroup();
+
+ private JCheckBoxMenuItem colourByReadGrp = new JCheckBoxMenuItem("Read Group");
+ private JCheckBoxMenuItem colourByStrandTag = new JCheckBoxMenuItem("RNASeq Strand Specific Tag (XS)");
+ private JCheckBoxMenuItem colourByCoverageColour = new JCheckBoxMenuItem("Coverage Plot Colours");
+ private JCheckBoxMenuItem baseQualityColour = new JCheckBoxMenuItem("Base Quality");
+ private JCheckBoxMenuItem markInsertions = new JCheckBoxMenuItem("Mark Insertions", true);
+ private AlphaComposite translucent =
+ AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f);
+
+ private ReadGroupsFrame readGrpFrame;
+ private GroupBamFrame groupsFrame = new GroupBamFrame(this, bamFilesMenu);
+ private CoveragePanel coverageView = new CoveragePanel();
+
+ protected static String BAM_SUFFIX = ".*\\.(bam|cram)$";
+ /** Used to colour the frames. */
+ private static Color LIGHT_GREY = new Color(200, 200, 200);
+ private static Color DARK_GREEN = new Color(0, 150, 0);
+ private static Color DARK_ORANGE = new Color(255,140,0);
+ private static Color DEEP_PINK = new Color(139,10,80);
+
+ private Point lastMousePoint = null;
+ private BamViewRecord mouseOverSAMRecord = null;
+ private BamViewRecord highlightSAMRecord = null;
+ private String mouseOverInsertion;
+ // record of where a mouse drag starts
+ protected int dragStart = -1;
+
+ private static int MAX_BASES = 26000;
+ private int maxHeight = 800;
+
+ private boolean concatSequences = false;
+ private int ALIGNMENT_PIX_PER_BASE;
+ private int BASE_HEIGHT;
+
+ private JPopupMenu popup;
+ private PopupMessageFrame popFrame = new PopupMessageFrame();
+ private PopupMessageFrame waitingFrame = new PopupMessageFrame("waiting...");
+ private ExecutorService bamReadTaskExecutor;
+ private int MAX_COVERAGE = Integer.MAX_VALUE;
+
+ private float readLnHgt = 2.0f;
+
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(BamView.class);
+
+ public BamView(List<String> bamList,
+ String reference,
+ int nbasesInView,
+ final EntryEdit entry_edit,
+ final FeatureDisplay feature_display,
+ final Bases bases,
+ final JPanel containerPanel,
+ final JFrame frame)
+ {
+ this(bamList, reference, nbasesInView, feature_display, bases, containerPanel, frame);
+ this.entry_edit = entry_edit;
+ }
+
+ public BamView(List<String> bamList,
+ String reference,
+ int nbasesInView,
+ final FeatureDisplay feature_display,
+ final Bases bases,
+ final JPanel containerPanel,
+ final JFrame frame)
+ {
+ super();
+ setBackground(Color.white);
+ this.bamList = bamList;
+ this.nbasesInView = nbasesInView;
+ this.feature_display = feature_display;
+ this.bases = bases;
+
+ containerPanel.setLayout(new BoxLayout(containerPanel, BoxLayout.Y_AXIS));
+ containerPanel.add(mainPanel);
+
+ // filter out unmapped reads by default
+ setSamRecordFlagPredicate(
+ new SAMRecordFlagPredicate(SAMRecordFlagPredicate.READ_UNMAPPED_FLAG));
+
+ if(reference != null)
+ {
+ System.setProperty("reference", reference); // for CRAM
+ EntryGroup entryGroup = new SimpleEntryGroup();
+ try
+ {
+ getEntry(reference,entryGroup);
+ }
+ catch (NoSequenceException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ if(Options.getOptions().getIntegerProperty("bam_read_thread") != null)
+ {
+ logger4j.debug("BAM READ THREADS="+Options.getOptions().getIntegerProperty("bam_read_thread"));
+ bamReadTaskExecutor = Executors.newFixedThreadPool(
+ Options.getOptions().getIntegerProperty("bam_read_thread"));
+ }
+ else
+ bamReadTaskExecutor = Executors.newFixedThreadPool(1);
+
+
+ if(Options.getOptions().getIntegerProperty("bam_max_coverage") != null)
+ {
+ logger4j.debug("BAM MAX COVERAGE="+Options.getOptions().getIntegerProperty("bam_max_coverage"));
+ MAX_COVERAGE = Options.getOptions().getIntegerProperty("bam_max_coverage");
+ }
+
+ try
+ {
+ readHeaderPicard();
+ }
+ catch(java.lang.UnsupportedClassVersionError err)
+ {
+ JOptionPane.showMessageDialog(null,
+ "This requires Java 1.6 or higher.",
+ "Check Java Version", JOptionPane.WARNING_MESSAGE);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ final javax.swing.plaf.FontUIResource font_ui_resource =
+ Options.getOptions().getFontUIResource();
+
+ Enumeration<Object> keys = UIManager.getDefaults().keys();
+ while(keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ Object value = UIManager.get(key);
+ if(value instanceof javax.swing.plaf.FontUIResource)
+ UIManager.put(key, font_ui_resource);
+ }
+
+ setFont(Options.getOptions().getFont());
+ FontMetrics fm = getFontMetrics(getFont());
+ ALIGNMENT_PIX_PER_BASE = fm.charWidth('M');
+ BASE_HEIGHT = fm.getMaxAscent();
+ selection = new Selection(null);
+
+ MultiLineToolTipUI.initialize();
+ setToolTipText("");
+
+ buttonGroup.add(cbStackView);
+ buttonGroup.add(cbPairedStackView);
+ buttonGroup.add(cbStrandStackView);
+ buttonGroup.add(cbIsizeStackView);
+ buttonGroup.add(cbCoverageView);
+ buttonGroup.add(cbCoverageStrandView);
+ buttonGroup.add(cbCoverageHeatMap);
+ addMouseListener(new PopupListener());
+
+ jspView = new JScrollPane(this,
+ JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+ JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+
+ jspView.setViewportBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.DARK_GRAY));
+ Border empty = new EmptyBorder(0,0,0,0);
+ jspView.setBorder(empty);
+ jspView.getVerticalScrollBar().setUnitIncrement(8);
+
+ addBamToPanel(frame);
+
+ // apply command line options
+ if(System.getProperty("show_snps") != null)
+ isSNPs = true;
+ if(System.getProperty("show_snp_plot") != null)
+ {
+ isSNPplot = true;
+ snpPanel.setVisible(isSNPplot);
+ }
+ if(System.getProperty("show_cov_plot") != null)
+ {
+ isCoverage = true;
+ coveragePanel.setVisible(isCoverage);
+ }
+ }
+
+ public String getToolTipText()
+ {
+ if(isCoverageView(getPixPerBaseByWidth()) && lastMousePoint != null)
+ return coverageView.getToolTipText(
+ lastMousePoint.y-getJspView().getViewport().getViewPosition().y);
+
+ if(mouseOverSAMRecord == null)
+ return null;
+
+ String msg =
+ mouseOverSAMRecord.sam.getReadName() + "\n" +
+ mouseOverSAMRecord.sam.getAlignmentStart() + ".." +
+ mouseOverSAMRecord.sam.getAlignmentEnd() +
+ (mouseOverSAMRecord.sam.getReadGroup() != null ? "\nRG="+mouseOverSAMRecord.sam.getReadGroup().getId() : "") +
+ "\nisize=" + mouseOverSAMRecord.sam.getInferredInsertSize() +
+ "\nmapq=" + mouseOverSAMRecord.sam.getMappingQuality()+
+ "\nrname="+ mouseOverSAMRecord.sam.getReferenceName();
+
+ if( mouseOverSAMRecord.sam.getReadPairedFlag() &&
+ mouseOverSAMRecord.sam.getProperPairFlag() &&
+ !mouseOverSAMRecord.sam.getMateUnmappedFlag())
+ {
+ msg = msg +
+ "\nstrand (read/mate): "+
+ (mouseOverSAMRecord.sam.getReadNegativeStrandFlag() ? "-" : "+")+" / "+
+ (mouseOverSAMRecord.sam.getMateNegativeStrandFlag() ? "-" : "+");
+ }
+ else
+ msg = msg +
+ "\nstrand (read/mate): "+
+ (mouseOverSAMRecord.sam.getReadNegativeStrandFlag() ? "-" : "+");
+
+ if(msg != null && mouseOverInsertion != null)
+ msg = msg + "\nInsertion at:" +mouseOverInsertion;
+
+ return msg;
+ }
+
+ /**
+ * Get the BAM index file from the list
+ * @param bam
+ * @return
+ * @throws IOException
+ */
+ private File getBamIndexFile(String bam) throws IOException
+ {
+ File bamIndexFile = null;
+ if (bam.startsWith("http") || bam.startsWith("ftp"))
+ {
+ final URL urlBamIndexFile = new URL(bam + ".bai");
+ InputStream is = urlBamIndexFile.openStream();
+
+ // Create temp file.
+ bamIndexFile = File.createTempFile(urlBamIndexFile.getFile().replaceAll(
+ "[\\/\\s]", "_"), ".bai");
+ bamIndexFile.deleteOnExit();
+
+ FileOutputStream out = new FileOutputStream(bamIndexFile);
+ int c;
+ while ((c = is.read()) != -1)
+ out.write(c);
+ out.flush();
+ out.close();
+ is.close();
+
+ logger4j.debug("create... " + bamIndexFile.getAbsolutePath());
+ }
+ else
+ {
+ bamIndexFile = new File(bam + ".bai");
+ if(!bamIndexFile.exists())
+ {
+ final File cramIndexFile = new File(bam + ".crai");
+ if(cramIndexFile.exists())
+ {
+ logger4j.debug(
+ "ERROR: CRAM INDEX FILE ("+cramIndexFile.getName()+
+ ") EXPECTING A BAM INDEX FILE (USE THIS OPTION --bam-style-index) ");
+ return cramIndexFile;
+ }
+ }
+ }
+
+ return bamIndexFile;
+ }
+
+ /**
+ * Get the SAM file reader.
+ * @param bam
+ * @return
+ * @throws IOException
+ */
+ private SAMFileReader getSAMFileReader(final String bam) throws IOException
+ {
+ // parsing of the header happens during SAMFileReader construction,
+ // so need to set the default stringency
+ SAMFileReader.setDefaultValidationStringency(ValidationStringency.LENIENT);
+
+ if(samFileReaderHash.containsKey(bam))
+ return samFileReaderHash.get(bam);
+
+ File bamIndexFile = getBamIndexFile(bam);
+ if(!bamIndexFile.exists())
+ {
+ try
+ {
+ logger4j.warn("Index file not found so using picard to index the BAM.");
+ // Use Picard to index the file
+ // requires reads to be sorted by coordinate
+ new BuildBamIndex().instanceMain(
+ new String[]{ "I="+bam, "O="+bamIndexFile.getAbsolutePath(), "MAX_RECORDS_IN_RAM=50000", "VALIDATION_STRINGENCY=SILENT" });
+ }
+ catch(SAMException e)
+ {
+ String ls = System.getProperty("line.separator");
+ String msg =
+ "BAM index file is missing. The BAM file needs to be sorted and indexed"+ls+
+ "This can be done using samtools (http://samtools.sf.net/):"+ls+ls+
+ "samtools sort <in.bam> <out.prefix>"+ls+
+ "samtools index <sorted.bam>";
+
+ throw new SAMException(msg);
+ }
+ }
+
+ final SAMFileReader samFileReader;
+
+ if(feature_display != null && bam.endsWith("cram"))
+ {
+ // set log level
+ net.sf.picard.util.Log.setGlobalLogLevel(
+ net.sf.picard.util.Log.LogLevel.ERROR);
+ final CRAMReferenceSequenceFile ref = new CRAMReferenceSequenceFile(
+ feature_display.getEntryGroup().getSequenceEntry(), this);
+
+ final Map<Object, ReferenceSequenceFile> referenceFactory =
+ new HashMap<Object, ReferenceSequenceFile>();
+ referenceFactory.put(bamIndexFile, ref);
+
+ try
+ {
+ Class<?> cls = getClass().getClassLoader().loadClass("net.sf.samtools.ReferenceDiscovery");
+ Field f = cls.getDeclaredField("referenceFactory");
+ f.set(null, referenceFactory);
+ }
+ catch (ClassNotFoundException e)
+ {
+ System.err.println("Check cramtools.jar is in the CLASSPATH. "+e.getMessage());
+ }
+ catch (SecurityException e)
+ {
+ e.printStackTrace();
+ }
+ catch (NoSuchFieldException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IllegalArgumentException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IllegalAccessException e)
+ {
+ e.printStackTrace();
+ }
+
+
+ //net.sf.samtools.ReferenceDiscovery.referenceFactory.put(bamIndexFile, ref);
+ }
+
+ if(bam.startsWith("ftp"))
+ {
+ FTPSeekableStream fss = new FTPSeekableStream(new URL(bam));
+ samFileReader = new SAMFileReader(fss, bamIndexFile, false);
+ }
+ else if(!bam.startsWith("http"))
+ {
+ File bamFile = new File(bam);
+ samFileReader = new SAMFileReader(bamFile, bamIndexFile);
+ }
+ else
+ {
+ final URL urlBamFile = new URL(bam);
+ samFileReader = new SAMFileReader(urlBamFile, bamIndexFile, false);
+ }
+ samFileReader.setValidationStringency(ValidationStringency.SILENT);
+ samFileReaderHash.put(bam, samFileReader);
+
+ readGroups.addAll(samFileReader.getFileHeader().getReadGroups());
+ return samFileReader;
+ }
+
+ private void readHeaderPicard() throws IOException
+ {
+ final SAMFileReader inputSam = getSAMFileReader(bamList.get(0));
+ final SAMFileHeader header = inputSam.getFileHeader();
+
+ for(SAMSequenceRecord seq: header.getSequenceDictionary().getSequences())
+ {
+ seqLengths.put(seq.getSequenceName(),
+ seq.getSequenceLength());
+ seqNames.add(seq.getSequenceName());
+ }
+ }
+
+ class BamReadTask implements Runnable
+ {
+ private int start;
+ private int end;
+ private short bamIndex;
+ private float pixPerBase;
+ private CountDownLatch latch;
+ BamReadTask(int start, int end, short bamIndex, float pixPerBase, CountDownLatch latch)
+ {
+ this.start = start;
+ this.end = end;
+ this.bamIndex = bamIndex;
+ this.pixPerBase = pixPerBase;
+ this.latch = latch;
+ }
+
+ public void run()
+ {
+ try
+ {
+ readFromBamPicard(start, end, bamIndex, pixPerBase) ;
+ }
+ catch (OutOfMemoryError ome)
+ {
+ throw ome;
+ }
+ catch(IOException me)
+ {
+ me.printStackTrace();
+ }
+ finally
+ {
+ latch.countDown();
+ }
+ }
+ }
+
+ /**
+ * Read a SAM or BAM file.
+ * @throws IOException
+ */
+ private void readFromBamPicard(int start, int end, short bamIndex, float pixPerBase)
+ throws IOException
+ {
+ // Open the input file. Automatically detects whether input is SAM or BAM
+ // and delegates to a reader implementation for the appropriate format.
+ final String bam = bamList.get(bamIndex);
+ final SAMFileReader inputSam = getSAMFileReader(bam);
+
+ //final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
+ if(isConcatSequences())
+ {
+ for(String seq: seqNames)
+ {
+ int sLen = seqLengths.get(seq);
+ int offset = getSequenceOffset(seq);
+ int sBeg = offset+1;
+ int sEnd = sBeg+sLen-1;
+
+ if( (sBeg >= start && sBeg <= end) ||
+ (sBeg <= start && sEnd >= start) )
+ {
+ int thisStart = start - offset;
+ if(thisStart < 1)
+ thisStart = 1;
+ int thisEnd = end - offset;
+ if(thisEnd > sLen)
+ thisEnd = sLen;
+
+ iterateOverBam(inputSam, seq, thisStart, thisEnd, bamIndex, pixPerBase, bam);
+ //System.out.println("READ "+seq+" "+thisStart+".."+thisEnd+" "+start+" --- "+offset);
+ }
+ }
+ }
+ else
+ {
+ String refName = (String) combo.getSelectedItem();
+ iterateOverBam(inputSam, refName, start, end, bamIndex, pixPerBase, bam);
+ }
+ //inputSam.close();
+ }
+
+ /**
+ * Iterate over BAM file and load into the <code>List</code> of
+ * <code>SAMRecord</code>.
+ * @param inputSam
+ * @param refName
+ * @param start
+ * @param end
+ */
+ private void iterateOverBam(final SAMFileReader inputSam,
+ final String refName, final int start, final int end,
+ final short bamIndex, final float pixPerBase,
+ final String bam)
+ {
+ final MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
+ final int checkMemAfter = 8000;
+ final int seqOffset = getSequenceOffset(refName);
+ final int offset = seqOffset- getBaseAtStartOfView();
+ final boolean isCoverageView = isCoverageView(pixPerBase);
+
+ int cnt = 0;
+
+ int nbins = 800;
+ int binSize = ((end-start)/nbins)+1;
+ if(binSize < 1)
+ {
+ binSize = 1;
+ nbins = end-start+1;
+ }
+ int max = MAX_COVERAGE*binSize;
+ if(max < 1)
+ max = Integer.MAX_VALUE;
+ final int cov[] = new int[nbins];
+ for(int i=0; i<nbins; i++)
+ cov[i] = 0;
+
+ final CloseableIterator<SAMRecord> it = inputSam.queryOverlapping(refName, start, end);
+ try
+ {
+ while ( it.hasNext() )
+ {
+ try
+ {
+ cnt++;
+ SAMRecord samRecord = it.next();
+
+ if(readGrpFrame != null && !readGrpFrame.isReadGroupVisible(samRecord.getReadGroup()))
+ continue;
+
+ if( samRecordFlagPredicate == null ||
+ !samRecordFlagPredicate.testPredicate(samRecord))
+ {
+ if(samRecordMapQPredicate == null ||
+ samRecordMapQPredicate.testPredicate(samRecord))
+ {
+ int abeg = samRecord.getAlignmentStart();
+ int aend = samRecord.getAlignmentEnd();
+ boolean over = false;
+
+ for(int i=abeg; i<aend; i++)
+ {
+ int bin = ((i-start)/binSize)-1;
+ if(bin < 0)
+ continue;
+ else if(bin > nbins-1)
+ bin = nbins-1;
+ cov[bin]++;
+ if(cov[bin] > max)
+ {
+ over = true;
+ break;
+ }
+ }
+
+ if(over)
+ continue;
+
+ if(isCoverageView)
+ coverageView.addRecord(samRecord, offset, bam, colourByStrandTag.isSelected());
+ if(isCoverage)
+ coveragePanel.addRecord(samRecord, offset, bam, colourByStrandTag.isSelected());
+ if(isSNPplot)
+ snpPanel.addRecord(samRecord, seqOffset);
+ if(!isCoverageView)
+ readsInView.add(new BamViewRecord(samRecord, bamIndex));
+ }
+ }
+
+ if(cnt > checkMemAfter)
+ {
+ cnt = 0;
+ float heapFraction =
+ (float)((float)memory.getHeapMemoryUsage().getUsed()/
+ (float)memory.getHeapMemoryUsage().getMax());
+ logger4j.debug("Heap memory usage (used/max): "+heapFraction);
+
+ if(readsInView.size() > checkMemAfter*2 && !waitingFrame.isVisible())
+ waitingFrame.showWaiting("loading...", mainPanel);
+
+ if(heapFraction > 0.90)
+ {
+ popFrame.show(
+ "Using > 90 % of the maximum memory limit:"+
+ (memory.getHeapMemoryUsage().getMax()/1000000.f)+" Mb.\n"+
+ "Not all reads in this range have been read in. Zoom in or\n"+
+ "consider increasing the memory for this application.",
+ mainPanel,
+ 15000);
+ break;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+ finally
+ {
+ it.close();
+ }
+ }
+
+ private int getSequenceLength()
+ {
+ if(isConcatSequences())
+ {
+ int len = 0;
+ for(String seq: seqNames)
+ len += seqLengths.get(seq);
+ return len;
+ }
+ else
+ return seqLengths.get((String) combo.getSelectedItem());
+ }
+
+ /**
+ * For BAM files with multiple references sequences, calculate
+ * the offset from the start of the concatenated sequence for
+ * a given reference.
+ * @param refName
+ * @return
+ */
+ protected int getSequenceOffset(String refName)
+ {
+ if(!isConcatSequences())
+ return 0;
+
+ if(offsetLengths == null)
+ {
+ if(feature_display == null)
+ {
+ offsetLengths = new HashMap<String, Integer>(combo.getItemCount());
+ int offset = 0;
+ for(int i=0; i<combo.getItemCount(); i++)
+ {
+ String thisSeqName = (String) combo.getItemAt(i);
+ offsetLengths.put(thisSeqName, offset);
+ offset += seqLengths.get(combo.getItemAt(i));
+ }
+ return offsetLengths.get(refName);
+ }
+
+ final FeatureVector features = feature_display.getEntryGroup().getAllFeatures();
+ final HashMap<String, Integer> lookup = new HashMap<String, Integer>();
+ for(int i=0; i<features.size(); i++)
+ lookup.put(features.elementAt(i).getIDString(), features.elementAt(i).getFirstBase());
+
+ offsetLengths = new HashMap<String, Integer>(seqNames.size());
+ for(int i=0; i<seqNames.size(); i++)
+ {
+ final Integer pos = lookup.get(seqNames.get(i));
+ if(pos != null)
+ offsetLengths.put(seqNames.get(i), pos-1);
+ /*final FeatureContigPredicate predicate = new FeatureContigPredicate(seqNames.get(i).trim());
+ for(int j=0; j<features.size(); j++)
+ {
+ if(predicate.testPredicate(features.elementAt(j)))
+ {
+ offsetLengths.put(seqNames.get(i), features.elementAt(j).getFirstBase()-1);
+ break;
+ }
+ }*/
+ }
+
+ if(offsetLengths.size() != seqNames.size())
+ {
+ System.err.println("Found: "+offsetLengths.size() +" of "+ seqNames.size());
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ JOptionPane.showMessageDialog(BamView.this,
+ "There is a problem matching the reference sequences\n"+
+ "to the names in the BAM file. This may mean the labels\n"+
+ "on the reference features do not match those in the in\n"+
+ "the BAM file.",
+ "Problem Found", JOptionPane.WARNING_MESSAGE);
+ }
+ });
+
+ //concatSequences = false;
+ int offset = 0;
+ for(int i=0; i<combo.getItemCount(); i++)
+ {
+ String thisSeqName = (String) combo.getItemAt(i);
+ offsetLengths.put(thisSeqName, offset);
+ offset += seqLengths.get(combo.getItemAt(i));
+ }
+ //return 0;
+ }
+ }
+ return offsetLengths.get(refName);
+ }
+
+ /**
+ * Override
+ */
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+
+ mouseOverSAMRecord = null;
+ final int seqLength = getSequenceLength();
+ int start;
+ int end;
+
+ if(startBase > 0)
+ start = startBase;
+ else
+ start = getBaseAtStartOfView();
+
+ if(endBase > 0)
+ end = endBase;
+ else
+ {
+ end = start + nbasesInView - 1;
+ if(end > seqLength)
+ end = seqLength;
+
+ if(feature_display != null && nbasesInView < feature_display.getMaxVisibleBases())
+ nbasesInView = feature_display.getMaxVisibleBases();
+ }
+
+ final float pixPerBase = getPixPerBaseByWidth();
+ boolean changeToStackView = false;
+ MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
+ if(laststart != start ||
+ lastend != end ||
+ CoveragePanel.isRedraw())
+ {
+ if(isCoverageView(pixPerBase))
+ coverageView.init(this, pixPerBase, start, end);
+ if(isCoverage)
+ coveragePanel.init(this, pixPerBase, start, end);
+ if(isSNPplot)
+ snpPanel.init(this, pixPerBase, start, end);
+
+ synchronized (this)
+ {
+ try
+ {
+ float heapFractionUsedBefore = (float) ((float) memory.getHeapMemoryUsage().getUsed() /
+ (float) memory.getHeapMemoryUsage().getMax());
+ if(readsInView == null)
+ readsInView = new Vector<BamViewRecord>();
+ else
+ readsInView.clear();
+
+ final CountDownLatch latch = new CountDownLatch(bamList.size()-hideBamList.size());
+ //long ms = System.currentTimeMillis();
+ for(short i=0; i<bamList.size(); i++)
+ {
+ if(!hideBamList.contains(i))
+ bamReadTaskExecutor.execute(
+ new BamReadTask(start, end, i, pixPerBase, latch));
+ }
+
+ try
+ {
+ latch.await();
+ }
+ catch (InterruptedException e) {} // TODO
+
+ //System.out.println("===== NO. THREADS="+
+ // ((java.util.concurrent.ThreadPoolExecutor)bamReadTaskExecutor).getPoolSize()+" TIME="+(System.currentTimeMillis()-ms));
+
+ float heapFractionUsedAfter = (float) ((float) memory.getHeapMemoryUsage().getUsed() /
+ (float) memory.getHeapMemoryUsage().getMax());
+
+ // System.out.println("Heap Max : "+memory.getHeapMemoryUsage().getMax());
+ // System.out.println("Heap Used : "+memory.getHeapMemoryUsage().getUsed());
+ // System.out.println("Heap memory used "+heapFractionUsedAfter);
+
+ if ((heapFractionUsedAfter - heapFractionUsedBefore) > 0.06
+ && !isStackView() && heapFractionUsedAfter > 0.8)
+ {
+ cbStackView.setSelected(true);
+ changeToStackView = true;
+ }
+
+ if((!isStackView() && !isStrandStackView()) || isBaseAlignmentView(pixPerBase))
+ {
+ Collections.sort(readsInView, new SAMRecordComparator());
+ }
+ else if( (isStackView() || isStrandStackView()) &&
+ bamList.size() > 1)
+ {
+ // merge multiple BAM files
+ Collections.sort(readsInView, new SAMRecordPositionComparator(BamView.this));
+ }
+ }
+ catch (OutOfMemoryError ome)
+ {
+ JOptionPane.showMessageDialog(this, "Out of Memory");
+ readsInView.clear();
+ return;
+ }
+ catch(net.sf.samtools.util.RuntimeIOException re)
+ {
+ JOptionPane.showMessageDialog(this, re.getMessage());
+ }
+ }
+ }
+
+ laststart = start;
+ lastend = end;
+
+ // this needs to be synchronized when cloning BAM window
+ synchronized(this)
+ {
+ if(showBaseAlignment)
+ drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
+ else
+ {
+ if(isCoverageView(pixPerBase))
+ drawCoverage(g2,start, end, pixPerBase);
+ else if(isStackView())
+ drawStackView(g2, seqLength, pixPerBase, start, end);
+ else if(isPairedStackView())
+ drawPairedStackView(g2, seqLength, pixPerBase, start, end);
+ else if(isStrandStackView())
+ drawStrandStackView(g2, seqLength, pixPerBase, start, end);
+ else
+ drawLineView(g2, seqLength, pixPerBase, start, end);
+ }
+ }
+
+ if(isCoverage)
+ coveragePanel.repaint();
+ if(isSNPplot)
+ snpPanel.repaint();
+
+ if(waitingFrame.isVisible())
+ waitingFrame.hideFrame();
+ if(changeToStackView)
+ {
+ popFrame.show(
+ "Note :: Changed to the stack view to save memory.\n"+
+ "Currently this is using "+
+ (memory.getHeapMemoryUsage().getUsed()/1000000.f)+" Mb "+
+ "and the maximum\nmemory limit is "+
+ (memory.getHeapMemoryUsage().getMax()/1000000.f)+" Mb.",
+ mainPanel,
+ 15000);
+ }
+ }
+
+ protected void repaintBamView()
+ {
+ laststart = -1;
+ repaint();
+ }
+
+ private float getPixPerBaseByWidth()
+ {
+ return (float)mainPanel.getWidth() / (float)nbasesInView;
+ }
+
+
+ private int getMaxBasesInPanel(int seqLength)
+ {
+ if(feature_display == null)
+ return seqLength+nbasesInView/3;
+ else
+ return seqLength+nbasesInView;
+ }
+
+ /**
+ * Draw the zoomed-in base view.
+ * @param g2
+ * @param seqLength
+ * @param pixPerBase
+ * @param start
+ * @param end
+ */
+ private void drawBaseAlignment(Graphics2D g2,
+ int seqLength,
+ float pixPerBase,
+ final int start,
+ int end)
+ {
+ ruler.start = start;
+ ruler.end = end;
+
+ int ypos = 0;
+ String refSeq = null;
+ int refSeqStart = start;
+
+ end = start + ( mainPanel.getWidth() * ALIGNMENT_PIX_PER_BASE );
+ if(bases != null)
+ {
+ try
+ {
+ int seqEnd = end+2;
+ if(seqEnd > bases.getLength())
+ seqEnd = bases.getLength();
+
+ if(refSeqStart < 1)
+ refSeqStart = 1;
+ refSeq =
+ bases.getSubSequence(new Range(refSeqStart, seqEnd), Bases.FORWARD).toUpperCase();
+
+ ruler.refSeq = refSeq;
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ ruler.repaint();
+ drawSelectionRange(g2, ALIGNMENT_PIX_PER_BASE, start, end, Color.PINK);
+
+ g2.setStroke(new BasicStroke (2.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
+
+ boolean drawn[] = new boolean[readsInView.size()];
+ for(int i=0; i<readsInView.size(); i++)
+ drawn[i] = false;
+
+ Rectangle r = jspView.getViewport().getViewRect();
+ int nreads = readsInView.size();
+
+ for (int i = 0; i < nreads; i++)
+ {
+ try
+ {
+ if (!drawn[i])
+ {
+ ypos += 11;
+
+ BamViewRecord thisRead = readsInView.get(i);
+ if (ypos < r.getMaxY() || ypos > r.getMinY())
+ drawSequence(g2, thisRead, ypos, refSeq, refSeqStart);
+ drawn[i] = true;
+
+ int thisEnd = thisRead.sam.getAlignmentEnd();
+ if (thisEnd == 0)
+ thisEnd = thisRead.sam.getAlignmentStart() + thisRead.sam.getReadLength();
+
+ for (int j = i + 1; j < nreads; j++)
+ {
+ if (!drawn[j])
+ {
+ BamViewRecord nextRead = readsInView.get(j);
+ int nextStart = nextRead.sam.getAlignmentStart();
+ if (nextStart > thisEnd + 1)
+ {
+ if (ypos < r.getMaxY() || ypos > r.getMinY())
+ drawSequence(g2, nextRead, ypos, refSeq, refSeqStart);
+
+ drawn[j] = true;
+ thisEnd = nextRead.sam.getAlignmentEnd();
+ if (thisEnd == 0)
+ thisEnd = nextStart + nextRead.sam.getReadLength();
+ }
+ else if (ypos > r.getMaxY() || ypos < r.getMinY())
+ break;
+ }
+ }
+ }
+ }
+ catch (ArrayIndexOutOfBoundsException ae)
+ {
+ System.err.println(readsInView.size()+" "+nreads);
+ ae.printStackTrace();
+ }
+ }
+
+ if(ypos > getHeight())
+ {
+ Dimension d = getPreferredSize();
+ d.setSize(getPreferredSize().getWidth(), ypos);
+ setPreferredSize(d);
+ revalidate();
+ }
+ }
+
+
+ /**
+ * Draw the query sequence
+ * @param g2
+ * @param read
+ * @param pixPerBase
+ * @param ypos
+ */
+ private void drawSequence(final Graphics2D g2, final BamViewRecord bamViewRecord,
+ int ypos, String refSeq, int refSeqStart)
+ {
+ SAMRecord samRecord = bamViewRecord.sam;
+ if (!samRecord.getReadPairedFlag() || // read is not paired in sequencing
+ samRecord.getMateUnmappedFlag() ) // mate is unmapped ) // mate is unmapped
+ g2.setColor(Color.black);
+ else
+ g2.setColor(Color.blue);
+
+ final Color col = g2.getColor();
+ int xpos;
+ int len = 0;
+ int refPos = 0;
+ final String readSeq = samRecord.getReadString();
+ final int offset = getSequenceOffset(samRecord.getReferenceName());
+
+ byte[] phredQuality = null;
+ if(baseQualityColour.isSelected())
+ phredQuality = samRecord.getBaseQualities();
+
+ Hashtable<Integer, String> insertions = null;
+ List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
+ for(int i=0; i<blocks.size(); i++)
+ {
+ AlignmentBlock block = blocks.get(i);
+ int blockStart = block.getReadStart();
+ len += block.getLength();
+ for(int j=0; j<block.getLength(); j++)
+ {
+ int readPos = blockStart-1+j;
+ xpos = block.getReferenceStart() - 1 + j + offset;
+ refPos = xpos - refSeqStart + 1;
+
+ if(phredQuality != null)
+ setColourByBaseQuality(g2, phredQuality[readPos]);
+
+ if(isSNPs && refSeq != null && refPos > 0 && refPos < refSeq.length())
+ {
+ if(Character.toUpperCase(readSeq.charAt(readPos)) != refSeq.charAt(refPos))
+ g2.setColor(Color.red);
+ else
+ g2.setColor(col);
+ }
+
+ g2.drawString(readSeq.substring(readPos, readPos+1),
+ refPos*ALIGNMENT_PIX_PER_BASE, ypos);
+
+ if(isSNPs)
+ g2.setColor(col);
+ }
+
+ // look for insertions
+ if(markInsertions.isSelected() && i < blocks.size()-1)
+ {
+ int blockEnd = blockStart+block.getLength();
+ int nextBlockStart = blocks.get(i+1).getReadStart();
+ int insertSize = nextBlockStart - blockEnd;
+ if(insertSize > 0)
+ {
+ if(insertions == null)
+ insertions = new Hashtable<Integer, String>();
+
+ g2.setColor(DEEP_PINK);
+
+ int xscreen = (refPos+1)*ALIGNMENT_PIX_PER_BASE;
+ insertions.put(xscreen,
+ (refPos+refSeqStart+1)+" "+
+ readSeq.substring(blockEnd-1, nextBlockStart-1));
+ g2.drawLine(xscreen, ypos, xscreen, ypos-BASE_HEIGHT);
+
+ // mark on reference sequence as well
+ if(bases != null)
+ g2.drawLine(xscreen, 11, xscreen, 11-BASE_HEIGHT);
+ g2.setColor(col);
+ }
+ }
+
+ // highlight
+ if(highlightSAMRecord != null &&
+ highlightSAMRecord.sam.getReadName().equals(samRecord.getReadName()))
+ {
+ refPos = block.getReferenceStart() + offset - refSeqStart;
+ int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
+ int width = block.getLength()*ALIGNMENT_PIX_PER_BASE;
+ Color col1 = g2.getColor();
+ g2.setColor(Color.red);
+ g2.drawRect(xstart, ypos-BASE_HEIGHT, width, BASE_HEIGHT);
+ if(i < blocks.size()-1)
+ {
+ int nextStart =
+ (blocks.get(i+1).getReferenceStart() + offset - refSeqStart)*ALIGNMENT_PIX_PER_BASE;
+ g2.drawLine(xstart+width, ypos-(BASE_HEIGHT/2), nextStart, ypos-(BASE_HEIGHT/2));
+ }
+
+ g2.setColor(col1);
+ }
+ else if(i < blocks.size()-1)
+ {
+ refPos = block.getReferenceStart() + offset - refSeqStart;
+ int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
+ int width = block.getLength()*ALIGNMENT_PIX_PER_BASE;
+ int nextStart =
+ (blocks.get(i+1).getReferenceStart() + offset - refSeqStart)*ALIGNMENT_PIX_PER_BASE;
+ g2.drawLine(xstart+width, ypos-(BASE_HEIGHT/2), nextStart, ypos-(BASE_HEIGHT/2));
+ }
+ }
+
+ if(lastMousePoint != null && blocks.size() > 0)
+ {
+ refPos = blocks.get(0).getReferenceStart()+offset-refSeqStart;
+ int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
+
+ refPos = blocks.get(blocks.size()-1).getReferenceStart()+
+ blocks.get(blocks.size()-1).getLength()+offset-refSeqStart;
+ int xend = (refPos+len)*ALIGNMENT_PIX_PER_BASE;
+
+ if(lastMousePoint.getY() > ypos-11 && lastMousePoint.getY() < ypos)
+ if(lastMousePoint.getX() > xstart &&
+ lastMousePoint.getX() < xend)
+ {
+ mouseOverSAMRecord = bamViewRecord;
+
+ if(insertions != null)
+ mouseOverInsertion = insertions.get((int)lastMousePoint.getX());
+ }
+ }
+ }
+
+ /**
+ * Colour bases on their mapping quality.
+ * @param g2
+ * @param baseQuality
+ */
+ private void setColourByBaseQuality(Graphics2D g2, byte baseQuality)
+ {
+ if (baseQuality < 10)
+ g2.setColor(Color.blue);
+ else if (baseQuality < 20)
+ g2.setColor(DARK_GREEN);
+ else if (baseQuality < 30)
+ g2.setColor(DARK_ORANGE);
+ else
+ g2.setColor(Color.black);
+ }
+
+ /**
+ * Draw inferred size view.
+ * @param g2
+ * @param seqLength
+ * @param pixPerBase
+ * @param start
+ * @param end
+ */
+ private void drawLineView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end)
+ {
+ drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
+ if(isShowScale())
+ drawScale(g2, start, end, pixPerBase, getHeight());
+
+ final Stroke stroke =
+ new BasicStroke (readLnHgt, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
+ g2.setStroke(stroke);
+ int ydiff = (int) Math.round(1.5*readLnHgt);
+
+ final int scaleHeight;
+ if(isShowScale())
+ scaleHeight = 15;
+ else
+ scaleHeight = 0;
+
+ int baseAtStartOfView = getBaseAtStartOfView();
+ Rectangle r = jspView.getViewport().getViewRect();
+
+ for(int i=0; i<readsInView.size(); i++)
+ {
+ BamViewRecord bamViewRecord = readsInView.get(i);
+ SAMRecord samRecord = bamViewRecord.sam;
+ BamViewRecord bamViewNextRecord = null;
+ SAMRecord samNextRecord = null;
+
+ List<Integer> snps = getSNPs(samRecord);
+
+ if( !samRecord.getReadPairedFlag() || // read is not paired in sequencing
+ samRecord.getMateUnmappedFlag() ) // mate is unmapped
+ {
+ if(isSingle)
+ {
+ int ypos = getYPos(scaleHeight, samRecord.getReadString().length()); // (getHeight() - scaleHeight) - samRecord.getReadString().length();
+ if(ypos > r.getMaxY() || ypos < r.getMinY())
+ continue;
+
+ g2.setColor(Color.black);
+ drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
+ }
+ continue;
+ }
+
+ int ypos = getYPos(scaleHeight, Math.abs(samRecord.getInferredInsertSize()));
+ if( (ypos > r.getMaxY() || ypos < r.getMinY()) && ypos > 0 )
+ continue;
+
+ if(i < readsInView.size()-1)
+ {
+ bamViewNextRecord = readsInView.get(++i);
+ samNextRecord = bamViewNextRecord.sam;
+
+ if(samRecord.getReadName().equals(samNextRecord.getReadName()))
+ {
+ // draw connection between paired reads
+ if(samRecord.getAlignmentEnd() < samNextRecord.getAlignmentStart() &&
+ (samNextRecord.getAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
+ {
+ g2.setColor(Color.LIGHT_GRAY);
+
+ int offset1 = getSequenceOffset(samRecord.getReferenceName());
+ int end1 = samRecord.getAlignmentEnd()+offset1-baseAtStartOfView;
+
+ int offset2 = getSequenceOffset(samNextRecord.getReferenceName());
+ int start2 = samNextRecord.getAlignmentStart()+offset2-baseAtStartOfView;
+
+ drawTranslucentLine(g2,
+ (int)(end1*pixPerBase), (int)(start2*pixPerBase), ypos);
+ }
+
+ if(colourByCoverageColour.isSelected())
+ g2.setColor(getColourByCoverageColour(bamViewRecord));
+ else if( (samRecord.getReadNegativeStrandFlag() && // strand of the query (1 for reverse)
+ samNextRecord.getReadNegativeStrandFlag()) ||
+ (!samRecord.getReadNegativeStrandFlag() &&
+ !samNextRecord.getReadNegativeStrandFlag()))
+ g2.setColor(Color.red);
+ else
+ g2.setColor(Color.blue);
+
+ drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
+ drawRead(g2, bamViewNextRecord, pixPerBase, ypos, baseAtStartOfView, getSNPs(samNextRecord), ydiff);
+ }
+ else
+ {
+ drawLoneRead(g2, bamViewRecord, ypos, pixPerBase, baseAtStartOfView, scaleHeight, snps, ydiff);
+ i--;
+ }
+ }
+ else
+ {
+ drawLoneRead(g2, bamViewRecord, ypos, pixPerBase, baseAtStartOfView, scaleHeight, snps, ydiff);
+ }
+ }
+
+ drawYScale(g2, scaleHeight);
+ }
+
+ private int getYPos(int scaleHeight, int size)
+ {
+ if(!logScale)
+ return (getHeight() - scaleHeight) - size;
+ else
+ {
+ int logInfSize = (int)( Math.log(size) * 100);
+ return (getHeight() - scaleHeight) - logInfSize;
+ }
+ }
+
+ /**
+ * Draw the reads as lines in vertical stacks. The reads are colour
+ * coded as follows:
+ *
+ * blue - reads are unique and are paired with a mapped mate
+ * black - reads are unique and are not paired or have an unmapped mate
+ * green - reads are duplicates
+ *
+ * @param g2
+ * @param seqLength
+ * @param pixPerBase
+ * @param start
+ * @param end
+ */
+ private void drawStackView(Graphics2D g2,
+ final int seqLength,
+ final float pixPerBase,
+ final int start,
+ final int end)
+ {
+ drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
+ if(isShowScale())
+ drawScale(g2, start, end, pixPerBase, getHeight());
+
+ final BasicStroke stroke = new BasicStroke(
+ readLnHgt,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+ g2.setStroke(stroke);
+
+ final int scaleHeight;
+ if(isShowScale())
+ scaleHeight = 15;
+ else
+ scaleHeight = 0;
+
+ int ypos = (getHeight() - scaleHeight);
+ int ydiff = (int) Math.round(1.5*readLnHgt);
+
+ if(isOrientation)
+ ydiff= 2*ydiff;
+ int maxEnd = 0;
+ int lstStart = 0;
+ int lstEnd = 0;
+ final int baseAtStartOfView = getBaseAtStartOfView();
+ g2.setColor(Color.blue);
+ final Rectangle r = jspView.getViewport().getViewRect();
+
+ for(BamViewRecord bamViewRecord: readsInView)
+ {
+ SAMRecord samRecord = bamViewRecord.sam;
+ int offset = getSequenceOffset(samRecord.getReferenceName());
+
+ int recordStart = samRecord.getAlignmentStart()+offset;
+ int recordEnd = samRecord.getAlignmentEnd()+offset;
+
+ List<Integer> snps = getSNPs(samRecord);
+
+ if(colourByCoverageColour.isSelected() ||
+ colourByStrandTag.isSelected() ||
+ colourByReadGrp.isSelected() ||
+ lstStart != recordStart || lstEnd != recordEnd || snps != null)
+ {
+ if(colourByStrandTag.isSelected())
+ {
+ if(samRecord.getAttribute("XS") == null)
+ g2.setColor(Color.BLACK);
+ else if( ((Character)samRecord.getAttribute("XS")).equals('+') )
+ g2.setColor(Color.BLUE);
+ else if( ((Character)samRecord.getAttribute("XS")).equals('-') )
+ g2.setColor(Color.RED);
+ else
+ g2.setColor(Color.BLACK);
+ }
+ else if(colourByCoverageColour.isSelected())
+ g2.setColor(getColourByCoverageColour(bamViewRecord));
+ else if(colourByReadGrp.isSelected())
+ g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, samRecord.getReadGroup()));
+ else if (!samRecord.getReadPairedFlag() || // read is not paired in sequencing
+ samRecord.getMateUnmappedFlag() ) // mate is unmapped ) // mate is unmapped
+ g2.setColor(Color.black);
+ else
+ g2.setColor(Color.blue);
+
+ if(maxEnd < recordStart || ypos < 0)
+ {
+ ypos = (getHeight() - scaleHeight)-ydiff;
+ maxEnd = recordEnd+2;
+ }
+ else
+ ypos = ypos-ydiff;
+ }
+ else
+ g2.setColor(DARK_GREEN);
+
+ if(snps != null)
+ lstStart = -1;
+ else
+ {
+ lstStart = recordStart;
+ lstEnd = recordEnd;
+ }
+
+ if(ypos > r.getMaxY() || ypos < r.getMinY())
+ continue;
+ drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
+ }
+ }
+
+ /**
+ * Draw the reads as lines in vertical stacks. The reads are colour
+ * coded as follows:
+ *
+ * blue - reads are unique and are paired with a mapped mate
+ * black - reads are unique and are not paired or have an unmapped mate
+ * green - reads are duplicates
+ *
+ * @param g2
+ * @param seqLength
+ * @param pixPerBase
+ * @param start
+ * @param end
+ */
+ private void drawStrandStackView(Graphics2D g2,
+ int seqLength,
+ float pixPerBase,
+ int start,
+ int end)
+ {
+ drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
+ final BasicStroke stroke = new BasicStroke(
+ readLnHgt,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+
+ final int scaleHeight = 15;
+ drawScale(g2, start, end, pixPerBase, ((getHeight()+scaleHeight)/2));
+
+ int ymid = (getHeight()/ 2);
+ int ydiff = (int) Math.round(1.5*readLnHgt);
+ if(isOrientation)
+ ydiff= 2*ydiff;
+
+ g2.setStroke(stroke);
+ // positive strand
+ drawStrand(g2, false, scaleHeight, ymid-(scaleHeight/2), -ydiff, pixPerBase);
+
+ // negative strand
+ drawStrand(g2, true, scaleHeight, ymid+(scaleHeight/2), ydiff, pixPerBase);
+ }
+
+
+ private void drawStrand(Graphics2D g2,
+ boolean isStrandNegative,
+ int scaleHeight,
+ int ymid,
+ int ystep,
+ float pixPerBase)
+ {
+ int hgt = getHeight();
+ int ypos = (hgt - scaleHeight);
+ int maxEnd = 0;
+ int lstStart = 0;
+ int lstEnd = 0;
+ int baseAtStartOfView = getBaseAtStartOfView();
+ g2.setColor(Color.blue);
+ Rectangle r = jspView.getViewport().getViewRect();
+
+ for(BamViewRecord bamViewRecord: readsInView)
+ {
+ SAMRecord samRecord = bamViewRecord.sam;
+ if( isNegativeStrand(samRecord, colourByStrandTag.isSelected()) == isStrandNegative )
+ {
+ final int offset = getSequenceOffset(samRecord.getReferenceName());
+ final int recordStart = samRecord.getAlignmentStart()+offset;
+ final int recordEnd = samRecord.getAlignmentEnd()+offset;
+ List<Integer> snps = getSNPs(samRecord);
+
+ if(colourByCoverageColour.isSelected() ||
+ colourByStrandTag.isSelected() ||
+ colourByReadGrp.isSelected() ||
+ lstStart != recordStart || lstEnd != recordEnd || snps != null)
+ {
+ if(colourByStrandTag.isSelected())
+ {
+ if(samRecord.getAttribute("XS") == null)
+ g2.setColor(Color.BLACK);
+ else if( ((Character)samRecord.getAttribute("XS")).equals('+') )
+ g2.setColor(Color.BLUE);
+ else if( ((Character)samRecord.getAttribute("XS")).equals('-') )
+ g2.setColor(Color.RED);
+ else
+ g2.setColor(Color.BLACK);
+ }
+ else if(colourByCoverageColour.isSelected())
+ g2.setColor(getColourByCoverageColour(bamViewRecord));
+ else if(colourByReadGrp.isSelected())
+ g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, samRecord.getReadGroup()));
+ else if (!samRecord.getReadPairedFlag() || // read is not paired in sequencing
+ samRecord.getMateUnmappedFlag() ) // mate is unmapped
+ g2.setColor(Color.black);
+ else
+ g2.setColor(Color.blue);
+
+ if(maxEnd < recordStart || ypos < 0 || ypos > hgt)
+ {
+ ypos = ymid + ystep;
+ maxEnd = recordEnd+2;
+ }
+ else
+ ypos = ypos + ystep;
+ }
+ else
+ g2.setColor(DARK_GREEN);
+
+ if(snps != null)
+ lstStart = -1;
+ else
+ {
+ lstStart = recordStart;
+ lstEnd = recordEnd;
+ }
+
+ if(ypos > r.getMaxY() || ypos < r.getMinY())
+ continue;
+
+ drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ystep);
+ }
+ }
+ }
+
+ /**
+ * Draw paired reads as lines in a vertical stacks.
+ * @param g2
+ * @param seqLength
+ * @param pixPerBase
+ * @param start
+ * @param end
+ */
+ private void drawPairedStackView(Graphics2D g2,
+ final int seqLength,
+ final float pixPerBase,
+ final int start,
+ final int end)
+ {
+ drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
+ if(isShowScale())
+ drawScale(g2, start, end, pixPerBase, getHeight());
+
+ final Vector<PairedRead> pairedReads = new Vector<PairedRead>();
+ for(int i=0; i<readsInView.size(); i++)
+ {
+ BamViewRecord bamViewRecord = readsInView.get(i);
+ SAMRecord samRecord = bamViewRecord.sam;
+ if( !samRecord.getReadPairedFlag() || // read is not paired in sequencing
+ samRecord.getMateUnmappedFlag() ) // mate is unmapped
+ continue;
+
+ BamViewRecord bamViewNextRecord = null;
+ if(i < readsInView.size()-1)
+ {
+ bamViewNextRecord = readsInView.get(++i);
+ SAMRecord samNextRecord = bamViewNextRecord.sam;
+
+ final PairedRead pr = new PairedRead();
+ if(samRecord.getReadName().equals(samNextRecord.getReadName()) &&
+ isFromSameBamFile(bamViewRecord, bamViewNextRecord, bamList))
+ {
+ if(samRecord.getAlignmentStart() < samNextRecord.getAlignmentStart())
+ {
+ pr.sam1 = bamViewRecord;
+ pr.sam2 = bamViewNextRecord;
+ }
+ else
+ {
+ pr.sam2 = bamViewRecord;
+ pr.sam1 = bamViewNextRecord;
+ }
+ }
+ else
+ {
+ --i;
+ pr.sam1 = bamViewRecord;
+ pr.sam2 = null;
+ }
+
+ pairedReads.add(pr);
+ }
+ }
+ Collections.sort(pairedReads, new PairedReadComparator());
+
+ Stroke originalStroke = new BasicStroke (readLnHgt, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
+
+ g2.setStroke( new BasicStroke (1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
+
+ final int scaleHeight;
+ if(isShowScale())
+ scaleHeight = 15;
+ else
+ scaleHeight = 0;
+
+ int ydiff = (int) Math.round(2.3*readLnHgt);
+ if(isOrientation)
+ ydiff= 2*ydiff;
+ int ypos = getHeight() - scaleHeight - ydiff;
+ int lastEnd = 0;
+ int baseAtStartOfView = getBaseAtStartOfView();
+ Rectangle r = jspView.getViewport().getViewRect();
+
+ for(PairedRead pr: pairedReads)
+ {
+ if(pr.sam1.sam.getAlignmentStart() > lastEnd)
+ {
+ ypos = getHeight() - scaleHeight - ydiff;
+ if(pr.sam2 != null)
+ lastEnd = pr.sam2.sam.getAlignmentEnd();
+ else
+ lastEnd = pr.sam1.sam.getAlignmentEnd();
+ }
+ else
+ ypos = ypos - ydiff;
+
+ if(ypos > r.getMaxY() || ypos < r.getMinY())
+ continue;
+
+ g2.setStroke(originalStroke);
+
+ if(highlightSAMRecord != null &&
+ highlightSAMRecord.sam.getReadName().equals(pr.sam1.sam.getReadName()))
+ g2.setColor(Color.black);
+ else
+ g2.setColor(Color.gray);
+
+ if(pr.sam2 != null)
+ {
+ if(!readsOverlap(pr.sam1.sam, pr.sam2.sam))
+ {
+ int offset1 = getSequenceOffset(pr.sam1.sam.getReferenceName());
+ int offset2 = getSequenceOffset(pr.sam2.sam.getReferenceName());
+ drawTranslucentJointedLine(g2,
+ (int)((pr.sam1.sam.getAlignmentEnd()+offset1-getBaseAtStartOfView())*pixPerBase),
+ (int)((pr.sam2.sam.getAlignmentStart()+offset2-getBaseAtStartOfView())*pixPerBase), ypos);
+ }
+ }
+ else if(!pr.sam1.sam.getMateUnmappedFlag() &&
+ pr.sam1.sam.getProperPairFlag() &&
+ pr.sam1.sam.getMateReferenceName().equals(pr.sam1.sam.getReferenceName()))
+ {
+ final int prStart, prEnd;
+ if(pr.sam1.sam.getAlignmentStart() > pr.sam1.sam.getMateAlignmentStart())
+ {
+ prStart = pr.sam1.sam.getMateAlignmentStart();
+ prEnd = pr.sam1.sam.getAlignmentStart();
+ }
+ else
+ {
+ prStart = pr.sam1.sam.getAlignmentEnd();
+ prEnd = pr.sam1.sam.getMateAlignmentStart();
+ }
+
+ int offset = getSequenceOffset(pr.sam1.sam.getReferenceName());
+ drawTranslucentJointedLine(g2,
+ (int)( (prStart+offset-getBaseAtStartOfView())*pixPerBase),
+ (int)( (prEnd +offset-getBaseAtStartOfView())*pixPerBase), ypos);
+ }
+
+ if(colourByCoverageColour.isSelected())
+ g2.setColor(getColourByCoverageColour(pr.sam1));
+ else if(colourByStrandTag.isSelected())
+ {
+ if( ((Character)pr.sam1.sam.getAttribute("XS")).equals('+') )
+ g2.setColor(Color.BLUE);
+ else if( ((Character)pr.sam1.sam.getAttribute("XS")).equals('-') )
+ g2.setColor(Color.RED);
+ else
+ g2.setColor(Color.BLACK);
+ }
+ else if(colourByReadGrp.isSelected())
+ g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, pr.sam1.sam.getReadGroup()));
+ else if( pr.sam2 != null &&
+ !( pr.sam1.sam.getReadNegativeStrandFlag() ^ pr.sam2.sam.getReadNegativeStrandFlag() ) )
+ g2.setColor(Color.red);
+ else
+ g2.setColor(Color.blue);
+
+ drawRead(g2, pr.sam1, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam1.sam), ydiff);
+ if(pr.sam2 != null)
+ drawRead(g2, pr.sam2, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam2.sam), ydiff);
+ }
+ }
+
+ /**
+ * Check if a record is on the negative strand. If the RNA strand specific
+ * checkbox is set then use the RNA strand.
+ * @param samRecord
+ * @param useStrandTag - strand specific tag
+ * @return
+ */
+ protected static boolean isNegativeStrand(final SAMRecord samRecord,
+ final boolean useStrandTag)
+ {
+ if(useStrandTag)
+ {
+ if(samRecord.getAttribute("XS") == null)
+ return samRecord.getReadNegativeStrandFlag();
+ if( ((Character)samRecord.getAttribute("XS")).equals('+') )
+ return false;
+ else
+ return true;
+ }
+ return samRecord.getReadNegativeStrandFlag();
+ }
+
+ /**
+ * Check if two records are from the same BAM file
+ * @param sam1
+ * @param sam2
+ * @param bamList
+ * @return
+ */
+ private boolean isFromSameBamFile(final BamViewRecord sam1,
+ final BamViewRecord sam2,
+ final List<String> bamList)
+ {
+ if(bamList == null || bamList.size()<2)
+ return true;
+
+ final short o1 = sam1.bamIndex;
+ final short o2 = sam2.bamIndex;
+ if(o1 != -1 && o2 != -1)
+ if( o1 != o2 )
+ return false;
+
+ return true;
+ }
+
+
+ /**
+ * Check if two records overlap
+ * @param s1
+ * @param s2
+ * @return true id the two reads overlap
+ */
+ private boolean readsOverlap(final SAMRecord s1,
+ final SAMRecord s2)
+ {
+ if( (s2.getAlignmentStart() >= s1.getAlignmentStart() &&
+ s2.getAlignmentStart() <= s1.getAlignmentEnd()) ||
+ (s2.getAlignmentEnd() >= s1.getAlignmentStart() &&
+ s2.getAlignmentEnd() <= s1.getAlignmentEnd()) )
+ return true;
+
+ if( (s1.getAlignmentStart() >= s2.getAlignmentStart() &&
+ s1.getAlignmentStart() <= s2.getAlignmentEnd()) ||
+ (s1.getAlignmentEnd() >= s2.getAlignmentStart() &&
+ s1.getAlignmentEnd() <= s2.getAlignmentEnd()) )
+ return true;
+ return false;
+ }
+
+ /**
+ * Draw the read coverage.
+ * @param g2
+ * @param start
+ * @param end
+ * @param pixPerBase
+ */
+ private void drawCoverage(Graphics2D g2, int start, int end, float pixPerBase)
+ {
+ int scaleHeight = 0;
+ if(isShowScale())
+ {
+ drawScale(g2, start, end, pixPerBase, getHeight());
+ scaleHeight = 15;
+ }
+
+ int hgt = jspView.getVisibleRect().height-scaleHeight;
+ if(!cbCoverageStrandView.isSelected() && !coverageView.isPlotHeatMap())
+ {
+ try
+ {
+ int y = getHeight()-6-( (hgt* MAX_COVERAGE)/(coverageView.max/coverageView.windowSize) );
+ g2.setColor(Color.YELLOW);
+ // draw the threshold for the coverage max read cut-off
+ g2.fillRect(0, y, getWidth(), 8);
+ }
+ catch(Exception e){}
+ }
+
+ g2.translate(0, getJspView().getViewport().getViewPosition().y);
+
+ coverageView.drawSelectionRange(g2, pixPerBase, start, end, getHeight(), Color.PINK);
+ coverageView.draw(g2, getWidth(), hgt, hideBamList);
+ if(!coverageView.isPlotHeatMap())
+ coverageView.drawMax(g2);
+ }
+
+ /**
+ * Draw a read that apparently has a read mate that is not in view.
+ * @param g2
+ * @param thisRead
+ * @param ypos
+ * @param pixPerBase
+ * @param originalStroke
+ * @param stroke
+ */
+ private void drawLoneRead(Graphics2D g2, BamViewRecord bamViewRecord, int ypos,
+ float pixPerBase, int baseAtStartOfView, int scaleHeight, List<Integer> snps, int ydiff)
+ {
+ SAMRecord samRecord = bamViewRecord.sam;
+ boolean offTheTop = false;
+ int offset = getSequenceOffset(samRecord.getReferenceName());
+ int thisStart = samRecord.getAlignmentStart()+offset;
+ int thisEnd = thisStart + samRecord.getReadString().length() -1;
+
+ if(ypos <= 0)
+ {
+ offTheTop = true;
+ ypos = samRecord.getReadString().length();
+ }
+
+ if(samRecord.getInferredInsertSize() == 0)
+ {
+ offTheTop = true;
+ ypos = getHeight() - scaleHeight - 5;
+ }
+
+ if(samRecord.getInferredInsertSize() != 0 &&
+ Math.abs(samRecord.getMateAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
+ {
+ g2.setColor(Color.LIGHT_GRAY);
+
+ if(samRecord.getAlignmentEnd() < samRecord.getMateAlignmentStart())
+ {
+ int nextStart =
+ (int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase);
+ drawTranslucentLine(g2,
+ (int)((thisEnd-getBaseAtStartOfView())*pixPerBase), nextStart, ypos);
+ }
+ else
+ {
+ int nextStart =
+ (int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase);
+ drawTranslucentLine(g2,
+ (int)((thisStart-getBaseAtStartOfView())*pixPerBase), nextStart, ypos);
+ }
+ }
+
+ if(colourByCoverageColour.isSelected())
+ g2.setColor(getColourByCoverageColour(bamViewRecord));
+ else if(offTheTop)
+ g2.setColor(DARK_ORANGE);
+ else if(samRecord.getReadNegativeStrandFlag() &&
+ samRecord.getMateNegativeStrandFlag()) // strand of the query (1 for reverse)
+ g2.setColor(Color.red);
+ else
+ g2.setColor(Color.blue);
+
+ drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
+
+ /*if (isSNPs)
+ showSNPsOnReads(g2, samRecord, pixPerBase, ypos, offset);*/
+ }
+
+
+ private void drawScale(Graphics2D g2, int start, int end, float pixPerBase, int ypos)
+ {
+ g2.setColor(Color.black);
+ g2.drawLine( 0, ypos-14,
+ (int)((end - getBaseAtStartOfView())*pixPerBase), ypos-14);
+ int interval = end-start;
+
+ if(interval > 256000)
+ drawTicks(g2, start, end, pixPerBase, 512000, ypos);
+ else if(interval > 64000)
+ drawTicks(g2, start, end, pixPerBase, 12800, ypos);
+ else if(interval > 16000)
+ drawTicks(g2, start, end, pixPerBase, 3200, ypos);
+ else if(interval > 4000)
+ drawTicks(g2, start, end, pixPerBase, 800, ypos);
+ else if(interval > 1000)
+ drawTicks(g2, start, end, pixPerBase, 400, ypos);
+ else
+ drawTicks(g2, start, end, pixPerBase, 100, ypos);
+ }
+
+ private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division, int ypos)
+ {
+ int markStart = (Math.round(start/division)*division);
+
+ if(markStart < 1)
+ markStart = 1;
+
+ int sm = markStart-(division/2);
+ float x;
+ if(sm > start)
+ {
+ x = (sm-getBaseAtStartOfView())*pixPerBase;
+ g2.drawLine((int)x, ypos-14,(int)x, ypos-12);
+ }
+
+ for(int m=markStart; m<end; m+=division)
+ {
+ x = (m-getBaseAtStartOfView())*pixPerBase;
+ g2.drawString(Integer.toString(m), x, ypos-1);
+ g2.drawLine((int)x, ypos-14,(int)x, ypos-11);
+
+ sm = m+(division/2);
+
+ if(sm < end)
+ {
+ x = (sm-getBaseAtStartOfView())*pixPerBase;
+ g2.drawLine((int)x, ypos-14,(int)x, ypos-12);
+ }
+
+ if(m == 1)
+ m = 0;
+ }
+ }
+
+ /**
+ * Draw a y-scale for inferred size (isize) of reads.
+ * @param g2
+ * @param xScaleHeight
+ */
+ private void drawYScale(Graphics2D g2, int xScaleHeight)
+ {
+ g2.setColor(Color.black);
+ int maxY = getPreferredSize().height-xScaleHeight;
+
+ if(logScale)
+ {
+ int start = 10;
+ int count = 0;
+ int ypos = getYPos(xScaleHeight, start);
+
+ while(ypos > 0 && count < 15 && start > 1)
+ {
+ g2.drawLine(0, ypos, 2, ypos);
+ g2.drawString(Integer.toString(start), 3, ypos);
+ start = start*5;
+ ypos = getYPos(xScaleHeight, start);
+ count++;
+ }
+ return;
+ }
+
+ for(int i=100; i<maxY; i+=100)
+ {
+ int ypos = getHeight()-i-xScaleHeight;
+ g2.drawLine(0, ypos, 2, ypos);
+ g2.drawString(Integer.toString(i), 3, ypos);
+ }
+ }
+
+ /**
+ * Draw a given read.
+ * @param g2
+ * @param thisRead
+ * @param pixPerBase
+ * @param ypos
+ * @param baseAtStartOfView
+ * @param snps
+ */
+ private void drawRead(Graphics2D g2,
+ final BamViewRecord bamViewRecord,
+ final float pixPerBase,
+ final int ypos,
+ final int baseAtStartOfView,
+ final List<Integer> snps,
+ final int ydiff)
+ {
+ SAMRecord thisRead = bamViewRecord.sam;
+ int offset = getSequenceOffset(thisRead.getReferenceName());
+
+ int thisStart = thisRead.getAlignmentStart()+offset-baseAtStartOfView;
+ int thisEnd = thisRead.getAlignmentEnd()+offset-baseAtStartOfView;
+
+ if(highlightSAMRecord != null &&
+ highlightSAMRecord.sam.getReadName().equals(thisRead.getReadName()))
+ {
+ Stroke originalStroke = g2.getStroke();
+ Stroke stroke =
+ new BasicStroke (readLnHgt*1.6f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
+ g2.setStroke(stroke);
+ Color c = g2.getColor();
+ g2.setColor(Color.black);
+ g2.drawLine((int)( thisStart * pixPerBase), ypos,
+ (int)( thisEnd * pixPerBase), ypos);
+ g2.setColor(c);
+ g2.setStroke(originalStroke);
+ }
+
+ if(thisRead.getCigar().getCigarElements().size() == 1)
+ g2.drawLine((int)( thisStart * pixPerBase), ypos,
+ (int)( thisEnd * pixPerBase), ypos);
+ else
+ {
+ List<AlignmentBlock> blocks = thisRead.getAlignmentBlocks();
+ Color c = g2.getColor();
+ int lastEnd = 0;
+ for(int i=0; i<blocks.size(); i++)
+ {
+ AlignmentBlock block = blocks.get(i);
+ int blockStart = block.getReferenceStart()+offset-baseAtStartOfView;
+ int blockEnd = blockStart + block.getLength() - 1;
+
+ g2.drawLine((int)( blockStart * pixPerBase), ypos,
+ (int)( blockEnd * pixPerBase), ypos);
+ if(i > 0 && blockStart != lastEnd)
+ {
+ g2.setColor(Color.gray);
+ g2.drawLine((int)( blockStart * pixPerBase), ypos,
+ (int)( lastEnd * pixPerBase), ypos);
+ g2.setColor(c);
+ }
+ lastEnd = blockEnd;
+ }
+ }
+
+ if(isOrientation)
+ drawArrow(g2, thisRead, thisStart, thisEnd, pixPerBase, ypos, ydiff);
+
+ // test if the mouse is over this read
+ if(lastMousePoint != null)
+ {
+ if(lastMousePoint.getY()+2 > ypos && lastMousePoint.getY()-2 < ypos)
+ if(lastMousePoint.getX() > thisStart * pixPerBase &&
+ lastMousePoint.getX() < thisEnd * pixPerBase)
+ {
+ mouseOverSAMRecord = bamViewRecord;
+ }
+ }
+
+ if (isSNPs && snps != null)
+ showSNPsOnReads(snps, g2, pixPerBase, ypos);
+ }
+
+ /**
+ * Draw arrow on the read to indicate orientation.
+ * @param g2
+ * @param thisRead
+ * @param thisStart
+ * @param thisEnd
+ * @param pixPerBase
+ * @param ypos
+ */
+ private void drawArrow(final Graphics2D g2,
+ final SAMRecord thisRead,
+ final int thisStart,
+ final int thisEnd,
+ final float pixPerBase,
+ int ypos,
+ int ydiff)
+ {
+ if(ydiff < 0)
+ ydiff = -ydiff;
+
+ if(thisRead.getReadNegativeStrandFlag())
+ {
+ ypos-=readLnHgt/2;
+ int apos = ypos + ydiff - 1;
+ g2.drawLine((int)( (thisStart+5) * pixPerBase), apos,
+ (int)( thisStart * pixPerBase), ypos);
+ }
+ else
+ {
+ ypos+=readLnHgt/2;
+ int apos = ypos - ydiff + 1;
+ g2.drawLine((int)( (thisEnd-5) * pixPerBase), apos,
+ (int)( thisEnd * pixPerBase), ypos);
+ }
+ }
+
+ /**
+ * Highlight a selected range
+ * @param g2
+ * @param pixPerBase
+ * @param start
+ * @param end
+ */
+ private void drawSelectionRange(Graphics2D g2, float pixPerBase, int start, int end, Color c)
+ {
+ if(getSelection() != null)
+ {
+ Range selectedRange = getSelection().getSelectionRange();
+
+ if(selectedRange != null)
+ {
+ int rangeStart = selectedRange.getStart();
+ int rangeEnd = selectedRange.getEnd();
+
+ if(end < rangeStart || start > rangeEnd)
+ return;
+
+ int x = (int) (pixPerBase*(rangeStart-getBaseAtStartOfView()));
+ int width = (int) (pixPerBase*(rangeEnd-rangeStart+1));
+
+ g2.setColor(c);
+ g2.fillRect(x, 0, width, getHeight());
+ }
+ }
+ }
+
+ /**
+ * Draw a translucent line
+ * @param g2
+ * @param start
+ * @param end
+ * @param ypos
+ */
+ private void drawTranslucentLine(Graphics2D g2, int start, int end, int ypos)
+ {
+ Composite origComposite = g2.getComposite();
+ g2.setComposite(translucent);
+ g2.drawLine(start, ypos, end, ypos);
+ g2.setComposite(origComposite);
+ }
+
+ /**
+ * Draw a translucent line
+ * @param g2
+ * @param start
+ * @param end
+ * @param ypos
+ */
+ private void drawTranslucentJointedLine(Graphics2D g2, int start, int end, int ypos)
+ {
+ Composite origComposite = g2.getComposite();
+ g2.setComposite(translucent);
+
+ int mid = (int) ((end-start)/2.f)+start;
+ //g2.drawLine(start, ypos, end, ypos);
+ g2.drawLine(start, ypos, mid, ypos-5);
+ g2.drawLine(mid, ypos-5, end, ypos);
+ g2.setComposite(origComposite);
+ }
+
+ /**
+ * Display the SNPs for the given read.
+ * @param snps
+ * @param g2
+ * @param pixPerBase
+ * @param ypos
+ */
+ private void showSNPsOnReads(final List<Integer> snps,
+ final Graphics2D g2,
+ float pixPerBase, final int ypos)
+ {
+ final Stroke originalStroke = g2.getStroke();
+ final BasicStroke stroke = new BasicStroke(
+ 1.3f,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+ g2.setStroke(stroke);
+
+ g2.setColor(Color.red);
+ for(int pos: snps)
+ g2.drawLine((int) (pos * pixPerBase), ypos + 2,
+ (int) (pos * pixPerBase), ypos - 2);
+ g2.setStroke(originalStroke);
+ }
+
+
+ /**
+ * Get the SNP positions
+ * @param samRecord
+ */
+ private List<Integer> getSNPs(final SAMRecord samRecord)
+ {
+ if(!isSNPs) // return null if not displaying SNPs
+ return null;
+ int rbeg = samRecord.getAlignmentStart();
+ int rend = samRecord.getAlignmentEnd();
+ int offset = getSequenceOffset(samRecord.getReferenceName());
+ ArrayList<Integer> snps = null;
+
+ // use alignment blocks of the contiguous alignment of
+ // subsets of read bases to a reference sequence
+ try
+ {
+ final char[] refSeq = bases.getSubSequenceC(
+ new Range(rbeg+offset, rend+offset), Bases.FORWARD);
+ final byte[] readSeq = samRecord.getReadBases();
+
+ offset = offset - getBaseAtStartOfView();
+ final List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
+ for(AlignmentBlock block: blocks)
+ {
+ int readStart = block.getReadStart();
+ int refStart = block.getReferenceStart();
+ int len = block.getLength();
+ for(int j=0; j<len; j++)
+ {
+ int readPos = readStart-1+j;
+ int refPos = refStart+j;
+ if (Character.toUpperCase(refSeq[refPos-rbeg]) != Character.toUpperCase( (char)readSeq[readPos]) )
+ {
+ if(snps == null)
+ snps = new ArrayList<Integer>();
+ snps.add(refPos+offset);
+ }
+ }
+ }
+ }
+ catch (OutOfRangeException e)
+ {
+ System.err.println(samRecord.getReadName()+" "+e.getMessage());
+ }
+ return snps;
+ }
+
+
+ /**
+ * Add the alignment view to the supplied <code>JPanel</code> in
+ * a <code>JScrollPane</code>.
+ * @param mainPanel panel to add the alignment to
+ * @param frame
+ * @param autohide automatically hide the top panel containing the buttons
+ * @param feature_display
+ */
+ private void addBamToPanel(final JFrame frame)
+ {
+ final JComponent topPanel = bamTopPanel(frame);
+ mainPanel.setPreferredSize(new Dimension(900, 400));
+
+ setDisplay(1, nbasesInView, null);
+ mainPanel.setLayout(new BorderLayout());
+
+ if(topPanel instanceof JPanel)
+ mainPanel.add(topPanel, BorderLayout.NORTH);
+ mainPanel.add(jspView, BorderLayout.CENTER);
+
+ JPanel bottomPanel = new JPanel(new BorderLayout());
+ coveragePanel = new CoveragePanel(this);
+ bottomPanel.add(coveragePanel, BorderLayout.CENTER);
+
+ //
+ snpPanel = new SnpPanel(this, bases);
+ bottomPanel.add(snpPanel, BorderLayout.NORTH);
+
+ if(feature_display == null)
+ {
+ scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 1, nbasesInView, 1,
+ getMaxBasesInPanel(getSequenceLength()));
+ scrollBar.setUnitIncrement(nbasesInView/20);
+ scrollBar.addAdjustmentListener(new AdjustmentListener()
+ {
+ public void adjustmentValueChanged(AdjustmentEvent e)
+ {
+ repaint();
+
+ if(isSNPplot)
+ snpPanel.repaint();
+ if(isCoverage)
+ coveragePanel.repaint();
+ }
+ });
+ bottomPanel.add(scrollBar, BorderLayout.SOUTH);
+ }
+ else
+ {
+ if(!isConcatSequences())
+ {
+ int seqLen = seqLengths.get((String) combo.getSelectedItem());
+ int artemisSeqLen = feature_display.getSequenceLength();
+ if(seqLen != artemisSeqLen)
+ {
+ int newIndex = -1;
+ for(int i=0; i<seqNames.size(); i++)
+ {
+ if(seqLengths.get(seqNames.get(i)) == artemisSeqLen)
+ {
+ // this looks like the correct sequence
+ combo.setSelectedIndex(i);
+ newIndex = i;
+ }
+ }
+
+ if(!Options.isBlackBeltMode())
+ {
+ String label[] = {
+ "The length of the sequence loaded does not match the length of",
+ "the default reference sequence in the BAM ("+seqNames.get(0)+").",
+ (newIndex == -1 ? "" : "The length does match the reference "+
+ seqNames.get(newIndex)+" so this has been set as the default.")
+ };
+ new NonModalDialog(frame, label);
+ }
+ }
+ }
+ }
+
+ mainPanel.add(bottomPanel, BorderLayout.SOUTH);
+ coveragePanel.setPreferredSize(new Dimension(900, 100));
+ coveragePanel.setVisible(false);
+ snpPanel.setPreferredSize(new Dimension(900, 100));
+ snpPanel.setVisible(false);
+
+ mainPanel.revalidate();
+ jspView.getVerticalScrollBar().setValue(
+ jspView.getVerticalScrollBar().getMaximum());
+ }
+
+ private void addToViewMenu(final short thisBamIndex)
+ {
+ final File f = new File(bamList.get(thisBamIndex));
+ final JCheckBoxMenuItem cbBam = new JCheckBoxMenuItem(
+ f.getName(),
+ getImageIcon(getColourByCoverageColour(thisBamIndex)),
+ true);
+ bamFilesMenu.add(cbBam);
+ cbBam.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ if(cbBam.isSelected())
+ hideBamList.remove(new Short(thisBamIndex));
+ else
+ hideBamList.add(new Short(thisBamIndex));
+ laststart = -1;
+ repaint();
+ }
+ });
+ }
+
+ /**
+ * Refresh the colour of the icons used to identify the
+ * BAM files.
+ */
+ protected void refreshColourOfBamMenu()
+ {
+ final Component cs[] = bamFilesMenu.getMenuComponents();
+ for(Component c : cs)
+ {
+ if(c instanceof JCheckBoxMenuItem)
+ {
+ final JCheckBoxMenuItem cbBam = (JCheckBoxMenuItem) c;
+ final Color col = getColorByJCheckBoxMenuItem(cbBam);
+ if(col != null)
+ cbBam.setIcon(getImageIcon(col));
+ }
+ }
+ }
+
+ protected Color getColorByJCheckBoxMenuItem(JCheckBoxMenuItem cbBam)
+ {
+ final String bam = cbBam.getText();
+ for(short i=0; i<bamList.size(); i++)
+ {
+ final File f = new File(bamList.get(i));
+ if(f.getName().equals(bam))
+ return getColourByCoverageColour(i);
+ }
+ return null;
+ }
+
+ /**
+ * Create an icon of a box using the given colour.
+ * @param c
+ * @return
+ */
+ ImageIcon getImageIcon(Color c)
+ {
+ BufferedImage image = (BufferedImage)this.createImage(10, 10);
+ Graphics2D g2 = image.createGraphics();
+ g2.setColor(c);
+ g2.fillRect(0, 0, 10, 10);
+ return new ImageIcon(image);
+ }
+
+ private void createMenus(JComponent menu)
+ {
+ final JMenuItem addBam = new JMenuItem("Add BAM ...");
+ menu.add(addBam);
+ addBam.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ FileSelectionDialog bamFileSelection = new FileSelectionDialog(
+ null, false, "BamView", "BAM");
+ List<String> bamFiles = bamFileSelection.getFiles(BAM_SUFFIX);
+ short count = (short) bamList.size();
+
+ bamList.addAll(bamFileSelection.getFiles(BAM_SUFFIX));
+
+ for(short i=0; i<bamFiles.size(); i++)
+ addToViewMenu((short) (i+count));
+ laststart = -1;
+ repaint();
+ }
+ });
+
+ bamFilesMenu.setFont(addBam.getFont());
+
+ final JMenuItem groupBams = new JMenuItem("Group BAMs ...");
+ groupBams.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ groupsFrame.updateAndDisplay();
+ }
+ });
+ bamFilesMenu.add(groupBams);
+ bamFilesMenu.addSeparator();
+ menu.add(bamFilesMenu);
+
+
+ final JMenu analyse = new JMenu("Analyse");
+ menu.add(analyse);
+ final JMenuItem readCount = new JMenuItem("Read count of selected features ...");
+ analyse.add(readCount);
+ if(feature_display == null)
+ readCount.setEnabled(false);
+ readCount.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ FeatureVector features = feature_display.getSelection().getAllFeatures();
+
+ JCheckBox overlap = new JCheckBox("Include all overlapping reads", true);
+ overlap.setToolTipText("Include reads that partially overlap the feature");
+ JCheckBox spliced = new JCheckBox("Introns included", true);
+ Box yBox = Box.createVerticalBox();
+ yBox.add(overlap);
+ yBox.add(spliced);
+
+ final ReadCountDialog opts = new ReadCountDialog(new JFrame(),
+ "Read Count Options", feature_display, yBox);
+ if(opts.getStatus() == -1)
+ return;
+ //JOptionPane.showMessageDialog(null, yBox, "Read Count Option", JOptionPane.INFORMATION_MESSAGE);
+
+ new MappedReads(features, (String)combo.getSelectedItem(), samFileReaderHash, bamList,
+ seqNames, offsetLengths, concatSequences, seqLengths,
+ samRecordFlagPredicate, samRecordMapQPredicate,
+ !overlap.isSelected(), spliced.isSelected(), colourByStrandTag.isSelected());
+ }
+ });
+
+ final JMenuItem rpkm = new JMenuItem("RPKM value of selected features ...");
+ analyse.add(rpkm);
+ if(feature_display == null)
+ rpkm.setEnabled(false);
+ rpkm.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final FeatureVector features = feature_display.getSelection().getAllFeatures();
+
+ JCheckBox overlap = new JCheckBox("Include all overlapping reads", true);
+ overlap.setToolTipText("Include reads that partially overlap the feature");
+ JCheckBox spliced = new JCheckBox("Introns included", true);
+ JCheckBox allRefSeqs = new JCheckBox("Use reads mapped to all reference sequences", false);
+
+ Box yBox = Box.createVerticalBox();
+ yBox.add(overlap);
+ yBox.add(spliced);
+
+ if(seqLengths.size() > 1)
+ yBox.add(allRefSeqs);
+
+ final ReadCountDialog opts = new ReadCountDialog(new JFrame(),
+ "RPKM Options", feature_display, yBox);
+ if(opts.getStatus() == -1)
+ return;
+
+ int seqlen = 0;
+ if(feature_display != null)
+ seqlen = feature_display.getSequenceLength();
+ else if(bases != null)
+ seqlen = bases.getLength();
+
+ new MappedReads(features, (String)combo.getSelectedItem(),
+ samFileReaderHash, bamList, seqNames, offsetLengths, concatSequences,
+ seqLengths, seqlen, samRecordFlagPredicate, samRecordMapQPredicate,
+ !overlap.isSelected(), spliced.isSelected(), allRefSeqs.isSelected(),
+ colourByStrandTag.isSelected());
+ }
+ });
+
+
+
+ final JMenuItem createFeatures = new JMenuItem("Create features from coverage peaks ...");
+ analyse.add(createFeatures);
+ if(feature_display == null)
+ createFeatures.setEnabled(false);
+ createFeatures.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(feature_display == null)
+ return;
+ new CreateFeatures(groupsFrame);
+ }
+ });
+
+ for(short i=0; i<bamList.size(); i++)
+ addToViewMenu(i);
+
+ menu.add(new JSeparator());
+
+ JMenu viewMenu = new JMenu("Views");
+ cbStackView.setFont(viewMenu.getFont());
+ cbIsizeStackView.setFont(viewMenu.getFont());
+ cbPairedStackView.setFont(viewMenu.getFont());
+ cbStrandStackView.setFont(viewMenu.getFont());
+ cbCoverageView.setFont(viewMenu.getFont());
+ cbCoverageStrandView.setFont(viewMenu.getFont());
+ cbCoverageHeatMap.setFont(viewMenu.getFont());
+
+ baseQualityColour.setFont(viewMenu.getFont());
+ colourByReadGrp.setFont(viewMenu.getFont());
+ colourByCoverageColour.setFont(viewMenu.getFont());
+ colourByStrandTag.setFont(viewMenu.getFont());
+ markInsertions.setFont(viewMenu.getFont());
+
+ cbIsizeStackView.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ laststart = -1;
+ logMenuItem.setEnabled(isIsizeStackView());
+ getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+ repaint();
+ }
+ });
+ viewMenu.add(cbIsizeStackView);
+
+
+ cbStackView.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ laststart = -1;
+ if(cbStackView.isSelected())
+ logMenuItem.setEnabled(false);
+ getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+ repaint();
+ }
+ });
+ viewMenu.add(cbStackView);
+
+
+ cbPairedStackView.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ laststart = -1;
+ if(cbPairedStackView.isSelected())
+ logMenuItem.setEnabled(false);
+ getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+ repaint();
+ }
+ });
+ viewMenu.add(cbPairedStackView);
+
+ cbStrandStackView.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ laststart = -1;
+ if(isStrandStackView())
+ setViewportMidPoint();
+
+ if(cbStrandStackView.isSelected())
+ logMenuItem.setEnabled(false);
+ getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+ repaint();
+ }
+ });
+ viewMenu.add(cbStrandStackView);
+
+ cbCoverageView.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ laststart = -1;
+ if(cbCoverageView.isSelected())
+ {
+ logMenuItem.setEnabled(false);
+ coverageView.setPlotHeatMap(false);
+ coverageView.setPlotByStrand(false);
+ setViewportBtm();
+ getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ repaint();
+ }
+ });
+ viewMenu.add(cbCoverageView);
+
+ cbCoverageStrandView.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ laststart = -1;
+ if(cbCoverageStrandView.isSelected())
+ {
+ logMenuItem.setEnabled(true);
+ coverageView.setPlotHeatMap(false);
+ coverageView.setPlotByStrand(true);
+ setViewportBtm();
+ getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+
+ repaint();
+ }
+ });
+ viewMenu.add(cbCoverageStrandView);
+
+
+ cbCoverageHeatMap.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ laststart = -1;
+ if(cbCoverageHeatMap.isSelected())
+ {
+ logMenuItem.setEnabled(true);
+ coverageView.setPlotHeatMap(true);
+ setViewportBtm();
+ getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+
+ repaint();
+ }
+ });
+ viewMenu.add(cbCoverageHeatMap);
+
+ menu.add(viewMenu);
+
+ final JCheckBoxMenuItem checkBoxSNPs = new JCheckBoxMenuItem("SNP marks", isSNPs);
+ //
+ final JMenu colourMenu = new JMenu("Colour By");
+
+ final JCheckBoxMenuItem colourDefault = new JCheckBoxMenuItem ("Default", true);
+ final ButtonGroup grp = new ButtonGroup();
+ grp.add(colourByReadGrp);
+ grp.add(colourByCoverageColour);
+ grp.add(colourByStrandTag);
+ grp.add(colourDefault);
+
+ colourMenu.add(colourDefault);
+ colourDefault.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ repaintBamView();
+ }
+ });
+
+ colourMenu.add(colourByReadGrp);
+ colourByReadGrp.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ repaintBamView();
+ }
+ });
+
+ colourMenu.add(colourByCoverageColour);
+ colourByCoverageColour.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ repaintBamView();
+ }
+ });
+
+ colourMenu.add(colourByStrandTag);
+ colourByStrandTag.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ repaintBamView();
+ }
+ });
+
+
+ baseQualityColour.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(baseQualityColour.isSelected())
+ {
+ checkBoxSNPs.setSelected(false);
+ isSNPs = false;
+ }
+ repaint();
+ }
+ });
+ colourMenu.addSeparator();
+ colourMenu.add(baseQualityColour);
+ menu.add(colourMenu);
+
+ //
+ JMenu showMenu = new JMenu("Show");
+ JCheckBoxMenuItem checkBoxOrientation = new JCheckBoxMenuItem("Orientation");
+ checkBoxOrientation.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ isOrientation = !isOrientation;
+ repaint();
+ }
+ });
+ showMenu.add(checkBoxOrientation);
+
+ JCheckBoxMenuItem checkBoxSingle = new JCheckBoxMenuItem("Single Reads");
+ checkBoxSingle.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ repaint();
+ isSingle = !isSingle;
+ }
+ });
+ showMenu.add(checkBoxSingle);
+
+ checkBoxSNPs.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if (isSNPs && bases == null)
+ {
+ JOptionPane.showMessageDialog(null,
+ "No reference sequence supplied to identify SNPs.", "SNPs",
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ isSNPs = !isSNPs;
+
+ if(isSNPs)
+ baseQualityColour.setSelected(false);
+ repaint();
+ }
+ });
+ showMenu.add(checkBoxSNPs);
+
+ markInsertions.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ repaint();
+ }
+ });
+ showMenu.add(markInsertions);
+ menu.add(showMenu);
+
+ //
+ JMenu graphMenu = new JMenu("Graph");
+ JCheckBoxMenuItem checkBoxCoverage = new JCheckBoxMenuItem("Coverage", isCoverage);
+ checkBoxCoverage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ isCoverage = !isCoverage;
+ coveragePanel.setVisible(isCoverage);
+
+ if( isCoverage &&
+ !cbCoverageView.isSelected() &&
+ !cbCoverageStrandView.isSelected() &&
+ !cbCoverageHeatMap.isSelected())
+ laststart = -1;
+ repaint();
+ }
+ });
+ graphMenu.add(checkBoxCoverage);
+
+ JCheckBoxMenuItem checkBoxSNP = new JCheckBoxMenuItem("SNP", isSNPplot);
+ checkBoxSNP.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ isSNPplot = !isSNPplot;
+ snpPanel.setVisible(isSNPplot);
+ laststart = -1;
+ repaint();
+ }
+ });
+ graphMenu.add(checkBoxSNP);
+ menu.add(graphMenu);
+
+
+ if(feature_display != null)
+ {
+ final JCheckBoxMenuItem checkBoxSync =
+ new JCheckBoxMenuItem("Asynchronous", asynchronous);
+ checkBoxSync.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ asynchronous = checkBoxSync.isSelected();
+ }
+ });
+ menu.add(checkBoxSync);
+ }
+
+ menu.add(new JSeparator());
+
+ JMenu maxHeightMenu = new JMenu("BamView Height");
+ menu.add(maxHeightMenu);
+
+ final String hgts[] =
+ {"500", "800", "1000", "1500", "2500", "5000", "50000"};
+
+ ButtonGroup bgroup = new ButtonGroup();
+ for(int i=0; i<hgts.length; i++)
+ {
+ final String hgt = hgts[i];
+ final JCheckBoxMenuItem maxHeightMenuItem = new JCheckBoxMenuItem(hgt);
+ bgroup.add(maxHeightMenuItem);
+ maxHeightMenuItem.setSelected(hgts[i].equals(Integer.toString(maxHeight)));
+ maxHeightMenu.add(maxHeightMenuItem);
+ maxHeightMenuItem.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(maxHeightMenuItem.isSelected())
+ maxHeight = Integer.parseInt(hgt);
+ int start = getBaseAtStartOfView();
+ setDisplay(start, nbasesInView+start, null);
+ }
+ });
+ }
+
+ menu.add(new JSeparator());
+ logMenuItem.setFont(menu.getFont());
+ menu.add(logMenuItem);
+ logMenuItem.setEnabled(isIsizeStackView());
+
+ logMenuItem.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ logScale = logMenuItem.isSelected();
+ repaint();
+ }
+ });
+
+ final JMenuItem readGroupsMenu = new JMenuItem("Read Groups ...");
+ readGroupsMenu.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ ReadGroupsFrame f = getReadGroupFrame();
+ f.setVisible(true);
+ }
+ });
+ menu.add(readGroupsMenu);
+
+ JMenuItem filter = new JMenuItem("Filter Reads ...");
+ menu.add(filter);
+ filter.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(filterFrame == null)
+ filterFrame = new SAMRecordFilter(BamView.this);
+ else
+ filterFrame.setVisible(true);
+ }
+ });
+
+ JMenuItem maxReadCoverage = new JMenuItem("Read Coverage Threshold ...");
+ menu.add(maxReadCoverage);
+ maxReadCoverage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final TextFieldInt maxRead = new TextFieldInt();
+ maxRead.setValue(MAX_COVERAGE);
+ int status = JOptionPane.showConfirmDialog(null, maxRead,
+ "Read Coverage Threshold", JOptionPane.OK_CANCEL_OPTION);
+ if(status == JOptionPane.OK_OPTION &&
+ maxRead.getValue() != MAX_COVERAGE)
+ {
+ MAX_COVERAGE = maxRead.getValue();
+ if(MAX_COVERAGE < 1)
+ MAX_COVERAGE = Integer.MAX_VALUE;
+ laststart = -1;
+ repaint();
+ }
+ }
+ });
+
+ JMenuItem readList = new JMenuItem("List Reads ...");
+ menu.add(readList);
+ readList.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ new SAMRecordList(BamView.this);
+ }
+ });
+
+ final JMenuItem bamSplitter = new JMenuItem("Clone window");
+ bamSplitter.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ openBamView(new Vector<String>(bamList));
+ }
+ });
+ menu.add(new JSeparator());
+ menu.add(bamSplitter);
+
+ //
+ JMenu coverageMenu = new JMenu("Coverage Options");
+ coverageView.init(this, 0.f, 0, 0);
+ coverageView.createMenus(coverageMenu);
+ viewMenu.add(new JSeparator());
+ viewMenu.add(coverageMenu);
+ }
+
+ private ReadGroupsFrame getReadGroupFrame()
+ {
+ if(readGrpFrame == null)
+ readGrpFrame = new ReadGroupsFrame(readGroups, BamView.this);
+ return readGrpFrame;
+ }
+
+ private JComponent bamTopPanel(final JFrame frame)
+ {
+ final JComponent topPanel;
+ if(frame == null)
+ {
+ topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
+ if(feature_display != null)
+ this.selection = feature_display.getSelection();
+ }
+ else
+ {
+ topPanel = new JMenuBar();
+ frame.setJMenuBar((JMenuBar)topPanel);
+
+ JMenu fileMenu = new JMenu("File");
+ topPanel.add(fileMenu);
+
+ JMenuItem readBam = new JMenuItem("Open new BamView ...");
+ fileMenu.add(readBam);
+ readBam.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ String[] s = { "NEW-BAMVIEW" };
+ BamView.main(s);
+ }
+ });
+
+
+ JMenuItem saveAs = new JMenuItem("Save As Image File (png/jpeg/svg) ...");
+ fileMenu.add(saveAs);
+ saveAs.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintBamView.print((JPanel)mainPanel.getParent());
+ }
+ });
+
+
+ JMenuItem close = new JMenuItem("Close");
+ fileMenu.add(close);
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ BamView.this.setVisible(false);
+ Component comp = BamView.this;
+
+ while( !(comp instanceof JFrame) )
+ comp = comp.getParent();
+ ((JFrame)comp).dispose();
+ }
+ });
+
+ JMenuItem exit = new JMenuItem("Exit");
+ fileMenu.add(new JSeparator());
+ fileMenu.add(exit);
+ exit.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int status = JOptionPane.showConfirmDialog(BamView.this,
+ "Exit BamView?", "Exit",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(status != JOptionPane.OK_OPTION)
+ return;
+ System.exit(0);
+ }
+ });
+
+ addKeyListener(new KeyAdapter()
+ {
+ public void keyPressed(final KeyEvent event)
+ {
+ switch (event.getKeyCode())
+ {
+ case KeyEvent.VK_UP:
+ setZoomLevel((int) (BamView.this.nbasesInView * 1.1));
+ break;
+ case KeyEvent.VK_DOWN:
+ if (showBaseAlignment)
+ break;
+ setZoomLevel((int) (BamView.this.nbasesInView * .9));
+ break;
+ default:
+ break;
+ }
+ }
+ });
+ }
+
+ if(seqNames.size() > 1)
+ {
+ int len = 0;
+ for(int i=0; i<seqNames.size(); i++)
+ len += seqLengths.get(seqNames.get(i));
+
+ if(feature_display != null &&
+ len == feature_display.getSequenceLength())
+ concatSequences = true;
+ else if(bases != null &&
+ len == bases.getLength() )
+ concatSequences = true;
+ }
+
+ // auto hide top panel
+ final JCheckBox buttonAutoHide = new JCheckBox("Hide", (frame == null));
+ buttonAutoHide.setToolTipText("Auto-Hide");
+ final MouseMotionListener mouseMotionListener = new MouseMotionListener()
+ {
+ public void mouseDragged(MouseEvent event)
+ {
+ handleCanvasMouseDrag(event);
+ }
+
+ public void mouseMoved(MouseEvent e)
+ {
+ lastMousePoint = e.getPoint();
+
+ int thisHgt = HEIGHT-2;
+ if (thisHgt < 5)
+ thisHgt = 15;
+
+ int y = (int) (e.getY() - jspView.getViewport().getViewRect().getY());
+ Point p = jspView.getViewport().getViewPosition();
+ boolean isVis = topPanel.isVisible();
+
+ if (y < thisHgt)
+ {
+ topPanel.setVisible(true);
+ if(!isVis)
+ p.y += topPanel.getHeight();
+ }
+ else
+ {
+ if (buttonAutoHide.isSelected())
+ topPanel.setVisible(false);
+ }
+
+ if(!showBaseAlignment && topPanel.isVisible())
+ jspView.getViewport().setViewPosition(p);
+ mainPanel.repaint();
+ //mainPanel.revalidate();
+ }
+ };
+ addMouseMotionListener(mouseMotionListener);
+
+
+ combo = new SequenceComboBox(seqNames){
+ private static final long serialVersionUID = 1L;
+ public void update(IndexReferenceEvent event)
+ {
+ laststart = -1;
+ if(feature_display != null)
+ setZoomLevel(feature_display.getMaxVisibleBases());
+ else
+ setZoomLevel(BamView.this.nbasesInView);
+ }
+ };
+ topPanel.add(combo);
+
+ if(feature_display == null)
+ {
+ final JTextField baseText = new JTextField(8);
+ JButton goTo = new JButton("GoTo:");
+ goTo.setToolTipText("Go to base position");
+ goTo.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ try
+ {
+ int basePosition = Integer.parseInt(baseText.getText());
+ scrollBar.setValue(basePosition);
+ }
+ catch (NumberFormatException nfe)
+ {
+ JOptionPane.showMessageDialog(BamView.this,
+ "Expecting a base number!", "Number Format",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ });
+ topPanel.add(goTo);
+ topPanel.add(baseText);
+
+ JButton zoomIn = new JButton("-");
+ zoomIn.setToolTipText("Zoom out (up arrow)");
+ Insets ins = new Insets(1,1,1,1);
+ zoomIn.setMargin(ins);
+ zoomIn.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setZoomLevel((int) (BamView.this.nbasesInView * 1.1));
+ }
+ });
+ topPanel.add(zoomIn);
+
+ JButton zoomOut = new JButton("+");
+ zoomOut.setToolTipText("Zoom in (down arrow)");
+ zoomOut.setMargin(ins);
+ zoomOut.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if (showBaseAlignment)
+ return;
+ setZoomLevel((int) (BamView.this.nbasesInView * .9));
+ }
+ });
+ topPanel.add(zoomOut);
+ }
+
+ topPanel.add(buttonAutoHide);
+
+
+ final JSlider slider = new JSlider(13, 52, (int) (readLnHgt*10));
+ slider.addChangeListener(new ChangeListener(){
+ public void stateChanged(ChangeEvent arg0)
+ {
+ readLnHgt = (slider.getValue()/10.f);
+ repaintBamView();
+ }
+ });
+ topPanel.add(new JLabel(" Read Height:"));
+ topPanel.add(slider);
+
+ if(feature_display != null)
+ {
+ JButton close = new JButton("Close");
+ topPanel.add(close);
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int status = JOptionPane.showConfirmDialog(frame,
+ "Close the BAM panel?", "Close",
+ JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+
+ final JPanel containerPanel = (JPanel) mainPanel.getParent();
+ feature_display.removeDisplayAdjustmentListener(BamView.this);
+ feature_display.getSelection().removeSelectionChangeListener(BamView.this);
+ containerPanel.remove(mainPanel);
+
+ if(containerPanel.getComponentCount() > 0)
+ containerPanel.revalidate();
+ else
+ {
+ if(entry_edit != null)
+ entry_edit.setNGDivider();
+ else
+ containerPanel.setVisible(false);
+ }
+ }
+ });
+ }
+ return topPanel;
+ }
+
+ public void setVisible(boolean visible)
+ {
+ super.setVisible(visible);
+ mainPanel.setVisible(visible);
+ }
+
+ private void setViewportMidPoint()
+ {
+ Point p = jspView.getViewport().getLocation();
+ p.y = (getHeight() - jspView.getViewport().getViewRect().height)/2;
+ jspView.getViewport().setViewPosition(p);
+ }
+
+ private void setViewportBtm()
+ {
+ jspView.getVerticalScrollBar().setValue(
+ jspView.getVerticalScrollBar().getMaximum());
+ }
+
+ protected int getBaseAtStartOfView()
+ {
+ if(feature_display != null)
+ return feature_display.getForwardBaseAtLeftEdge();
+ else
+ return scrollBar.getValue();
+ }
+
+ /**
+ * Set the panel size based on the number of bases visible
+ * and repaint.
+ * @param nbasesInView
+ */
+ private void setZoomLevel(final int nbasesInView)
+ {
+ int startValue = getBaseAtStartOfView();
+ this.nbasesInView = nbasesInView;
+ float pixPerBase = getPixPerBaseByWidth();
+
+ if(isBaseAlignmentView(pixPerBase))
+ {
+ pixPerBase = ALIGNMENT_PIX_PER_BASE;
+ this.nbasesInView = (int)(mainPanel.getWidth()/pixPerBase);
+ jspView.getVerticalScrollBar().setValue(0);
+
+ if(ruler == null)
+ ruler = new Ruler();
+ jspView.setColumnHeaderView(ruler);
+ showBaseAlignment = true;
+ baseQualityColour.setEnabled(true);
+ markInsertions.setEnabled(true);
+ }
+ else if(jspView != null)
+ {
+ if(!isCoverageView(pixPerBase) && nbasesInView >= MAX_BASES)
+ {
+ cbLastSelected = getSelectedCheckBoxMenuItem();
+ cbCoverageView.setSelected(true);
+ coverageView.setPlotByStrand(false);
+ }
+ else if(isCoverageView(pixPerBase) && nbasesInView < MAX_BASES && cbLastSelected != null)
+ {
+ cbLastSelected.setSelected(true);
+ cbLastSelected = null;
+ }
+
+ jspView.setColumnHeaderView(null);
+
+ if(!isStrandStackView())
+ setViewportBtm();
+ else
+ setViewportMidPoint();
+ showBaseAlignment = false;
+ baseQualityColour.setEnabled(false);
+ markInsertions.setEnabled(false);
+ }
+
+ if(scrollBar != null)
+ {
+ scrollBar.setValues(startValue, nbasesInView, 1,
+ getMaxBasesInPanel(getSequenceLength()));
+ scrollBar.setUnitIncrement(nbasesInView/20);
+ scrollBar.setBlockIncrement(nbasesInView);
+ }
+ }
+
+
+ /**
+ * Set the start and end base positions to display.
+ * @param start
+ * @param end
+ * @param event
+ */
+ public void setDisplay(int start,
+ int end,
+ DisplayAdjustmentEvent event)
+ {
+ this.startBase = start;
+ this.endBase = end;
+ this.nbasesInView = end-start+1;
+ lastMousePoint = null;
+
+ float pixPerBase;
+ if(jspView.getViewport().getViewRect().width > 0)
+ pixPerBase = getPixPerBaseByWidth();
+ else
+ {
+ if(feature_display == null)
+ pixPerBase = 1000.f/(float)(end-start+1);
+ else
+ pixPerBase = feature_display.getWidth()/(float)(end-start+1);
+ }
+
+ Dimension d = new Dimension();
+ d.setSize(nbasesInView*pixPerBase, maxHeight);
+ setPreferredSize(d);
+
+ if(event == null)
+ {
+ this.startBase = -1;
+ this.endBase = -1;
+ }
+ }
+
+ /**
+ * Return an Artemis entry from a file
+ * @param entryFileName
+ * @param entryGroup
+ * @return
+ * @throws NoSequenceException
+ */
+ private Entry getEntry(final String entryFileName, final EntryGroup entryGroup)
+ throws NoSequenceException
+ {
+ final Document entry_document = DocumentFactory.makeDocument(entryFileName);
+ final EntryInformation artemis_entry_information =
+ Options.getArtemisEntryInformation();
+
+ //System.out.println(entryFileName);
+ final uk.ac.sanger.artemis.io.Entry new_embl_entry =
+ EntryFileDialog.getEntryFromFile(null, entry_document,
+ artemis_entry_information,
+ false);
+
+ if(new_embl_entry == null) // the read failed
+ return null;
+
+ Entry entry = null;
+ try
+ {
+ if(entryGroup.getSequenceEntry() != null)
+ bases = entryGroup.getSequenceEntry().getBases();
+
+ if(bases == null)
+ {
+ entry = new Entry(new_embl_entry);
+ bases = entry.getBases();
+ }
+ else
+ entry = new Entry(bases,new_embl_entry);
+
+ entryGroup.add(entry);
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(null, "read failed: one of the features in " +
+ entryFileName + " has an out of range " +
+ "location: " + e.getMessage());
+ }
+ return entry;
+ }
+
+ private boolean isShowScale()
+ {
+ return (feature_display == null ? true : false);
+ }
+
+ public JScrollPane getJspView()
+ {
+ return jspView;
+ }
+
+ /**
+ * Handle a mouse drag event on the drawing canvas.
+ **/
+ private void handleCanvasMouseDrag(final MouseEvent event)
+ {
+ if(event.getButton() == MouseEvent.BUTTON3 || bases == null)
+ return;
+
+ highlightSAMRecord = null;
+ if(event.getClickCount() > 1)
+ {
+ getSelection().clear();
+ repaint();
+ return;
+ }
+
+ highlightRange(event,
+ MouseEvent.BUTTON1_DOWN_MASK & MouseEvent.BUTTON2_DOWN_MASK);
+ }
+
+ /**
+ *
+ * @param event
+ * @param onmask
+ */
+ protected void highlightRange(MouseEvent event, int onmask)
+ {
+ int seqLength = getSequenceLength();
+ float pixPerBase = getPixPerBaseByWidth();
+ int start = (int) ( ( (event.getPoint().getX())/pixPerBase) + getBaseAtStartOfView() );
+
+ if(start < 1)
+ start = 1;
+ if(start > seqLength)
+ start = seqLength;
+
+ if (dragStart < 0 && (event.getModifiersEx() & onmask) == onmask)
+ dragStart = start;
+ else if((event.getModifiersEx() & onmask) != onmask)
+ dragStart = -1;
+
+ MarkerRange drag_range;
+ try
+ {
+ if(dragStart < 0)
+ drag_range = new MarkerRange (bases.getForwardStrand(), start, start);
+ else
+ drag_range = new MarkerRange (bases.getForwardStrand(), dragStart, start);
+ getSelection().setMarkerRange(drag_range);
+ repaint();
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Get the colour for the given read given to it by the coverage plot.
+ * @param samRecord
+ * @return
+ */
+ private Color getColourByCoverageColour(BamViewRecord samRecord)
+ {
+ short fileIndex = 0;
+ if(bamList.size()>1)
+ fileIndex = samRecord.bamIndex;
+ return getColourByCoverageColour(fileIndex);
+ }
+
+ private Color getColourByCoverageColour(final short fileIndex)
+ {
+ LineAttributes lines[] = CoveragePanel.getLineAttributes(bamList.size());
+ return lines[fileIndex].getLineColour();
+ }
+
+
+ protected int getMaxBases()
+ {
+ return MAX_BASES;
+ }
+
+ protected void setMaxBases(int max)
+ {
+ MAX_BASES = max;
+ }
+
+ private boolean isStackView()
+ {
+ return cbStackView.isSelected();
+ }
+
+ private boolean isPairedStackView()
+ {
+ return cbPairedStackView.isSelected();
+ }
+
+ private boolean isStrandStackView()
+ {
+ return cbStrandStackView.isSelected();
+ }
+
+ private boolean isCoverageView(float pixPerBase)
+ {
+ if(isBaseAlignmentView(pixPerBase))
+ return false;
+ return cbCoverageView.isSelected() || cbCoverageStrandView.isSelected() || cbCoverageHeatMap.isSelected();
+ }
+
+ private boolean isIsizeStackView()
+ {
+ return cbIsizeStackView.isSelected();
+ }
+
+ private boolean isBaseAlignmentView(float pixPerBase)
+ {
+ if(pixPerBase*1.08f >= ALIGNMENT_PIX_PER_BASE)
+ return true;
+ return false;
+ }
+
+ private JCheckBoxMenuItem getSelectedCheckBoxMenuItem()
+ {
+ if(isStackView())
+ return cbStackView;
+ if(isPairedStackView())
+ return cbPairedStackView;
+ if(isStrandStackView())
+ return cbStrandStackView;
+ if(isIsizeStackView())
+ return cbIsizeStackView;
+ if(cbCoverageView.isSelected())
+ return cbCoverageView;
+ if(cbCoverageHeatMap.isSelected())
+ return cbCoverageHeatMap;
+ return cbCoverageStrandView;
+ }
+
+ protected Selection getSelection()
+ {
+ return selection;
+ }
+
+ protected List<BamViewRecord> getReadsInView()
+ {
+ return readsInView;
+ }
+
+ protected int getBasesInView()
+ {
+ return nbasesInView;
+ }
+
+ protected void setHighlightSAMRecord(BamViewRecord highlightSAMRecord)
+ {
+ this.highlightSAMRecord = highlightSAMRecord;
+ }
+
+ protected BamViewRecord getHighlightSAMRecord()
+ {
+ return highlightSAMRecord;
+ }
+
+ protected FeatureDisplay getFeatureDisplay()
+ {
+ return feature_display;
+ }
+
+ /**
+ * @return the combo
+ */
+ public SequenceComboBox getCombo()
+ {
+ return combo;
+ }
+
+ private String getVersion()
+ {
+ final ClassLoader cl = this.getClass().getClassLoader();
+ try
+ {
+ String line;
+ InputStream in = cl.getResourceAsStream("etc/versions");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ while((line = reader.readLine()) != null)
+ {
+ if(line.startsWith("BamView"))
+ return line.substring( "BamView".length() ).trim();
+ }
+ reader.close();
+ in.close();
+ }
+ catch (Exception ex)
+ {
+ }
+ return null;
+ }
+
+ /**
+ * Open another BamView window
+ */
+ public void openBamView(final List<String> bamsList)
+ {
+ BamView bamView = new BamView(bamsList,
+ null, nbasesInView, entry_edit,
+ feature_display, bases, (JPanel) mainPanel.getParent(), null);
+ bamView.getJspView().getVerticalScrollBar().setValue(
+ bamView.getJspView().getVerticalScrollBar().getMaximum());
+ getJspView().getVerticalScrollBar().setValue(
+ bamView.getJspView().getVerticalScrollBar().getMaximum());
+
+ int start = getBaseAtStartOfView();
+ setDisplay(start, nbasesInView+start, null);
+ if(feature_display != null)
+ {
+ feature_display.addDisplayAdjustmentListener(bamView);
+ feature_display.getSelection().addSelectionChangeListener(bamView);
+
+ if(entry_edit != null)
+ entry_edit.getOneLinePerEntryDisplay().addDisplayAdjustmentListener(bamView);
+ if(feature_display.getEntryGroup().getSequenceEntry().getEMBLEntry().getSequence()
+ instanceof uk.ac.sanger.artemis.io.IndexFastaStream)
+ {
+ if(entry_edit != null)
+ {
+ // add reference sequence selection listeners
+ entry_edit.getEntryGroupDisplay().getIndexFastaCombo().addIndexReferenceListener(bamView.getCombo());
+ bamView.getCombo().addIndexReferenceListener(entry_edit.getEntryGroupDisplay().getIndexFastaCombo());
+ }
+ }
+ }
+ }
+
+ /**
+ * Artemis event notification
+ */
+ public void displayAdjustmentValueChanged(final DisplayAdjustmentEvent event)
+ {
+ if(event.getType() == DisplayAdjustmentEvent.REV_COMP_EVENT &&
+ event.isRevCompDisplay())
+ JOptionPane.showMessageDialog(this,
+ "Flipping the display is not supported by BamView.", "Warning",
+ JOptionPane.WARNING_MESSAGE);
+
+ if(!asynchronous)
+ {
+ // if not asynchronous
+ displayAdjustmentWork(event);
+ return;
+ }
+
+ SwingWorker worker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ try
+ {
+ Thread.sleep(500);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+
+ if(event.getStart() != ((FeatureDisplay)event.getSource()).getForwardBaseAtLeftEdge())
+ {
+ waitingFrame.showWaiting("waiting...", mainPanel);
+ return null;
+ }
+
+ displayAdjustmentWork(event);
+ waitingFrame.setVisible(false);
+ return null;
+ }
+ };
+ worker.start();
+ }
+
+ /**
+ * Carry out the display agjustment event action.
+ * @param event
+ */
+ private void displayAdjustmentWork(final DisplayAdjustmentEvent event)
+ {
+ if(event.getType() == DisplayAdjustmentEvent.SCALE_ADJUST_EVENT)
+ {
+ laststart = -1;
+
+ BamView.this.startBase = event.getStart();
+ BamView.this.endBase = event.getEnd();
+
+ int width = feature_display.getMaxVisibleBases();
+ setZoomLevel(width);
+ repaint();
+ }
+ else
+ {
+ setDisplay(event.getStart(),
+ event.getStart()+feature_display.getMaxVisibleBases(), event);
+ repaint();
+ }
+ }
+
+ public void selectionChanged(SelectionChangeEvent event)
+ {
+ repaint();
+ }
+
+ private class Ruler extends JPanel
+ {
+ private static final long serialVersionUID = 1L;
+ protected int start;
+ protected int end;
+ protected String refSeq;
+
+ public Ruler()
+ {
+ super();
+ setPreferredSize(new Dimension(mainPanel.getWidth(), 26));
+ setBackground(Color.white);
+ }
+
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+ drawBaseScale(g2, start, end, 12);
+ }
+
+ private void drawBaseScale(Graphics2D g2, int start, int end, int ypos)
+ {
+ int startMark = (((int)(start/10))*10)+1;
+ if(end > getSequenceLength())
+ end = getSequenceLength();
+
+ for(int i=startMark; i<end; i+=20)
+ {
+ int xpos = (i-start)*ALIGNMENT_PIX_PER_BASE;
+ g2.drawString(Integer.toString(i), xpos, ypos);
+ }
+
+ for(int i=startMark; i<end; i+=10)
+ {
+ int xpos = (i-start)*ALIGNMENT_PIX_PER_BASE;
+ xpos+=(ALIGNMENT_PIX_PER_BASE/2);
+ g2.drawLine(xpos, ypos+1, xpos, ypos+5);
+ }
+
+ if(refSeq != null)
+ {
+ ypos+=15;
+ g2.setColor(LIGHT_GREY);
+ g2.fillRect(0, ypos-11, getWidth(), 11);
+
+ g2.translate(0, 16);
+ drawSelectionRange(g2, ALIGNMENT_PIX_PER_BASE, start, end, Color.yellow);
+ g2.translate(0, -16);
+ g2.setColor(Color.black);
+ g2.drawString(refSeq, 0, ypos-2);
+ }
+ }
+ }
+
+ /**
+ * Popup menu listener
+ */
+ class PopupListener extends MouseAdapter
+ {
+ private JMenuItem gotoMateMenuItem;
+ private JMenuItem showDetails;
+ private JMenu coverageMenu;
+ private JMenuItem createGroup;
+
+ public void mouseClicked(MouseEvent e)
+ {
+ if(e.isPopupTrigger() ||
+ e.getButton() == MouseEvent.BUTTON3)
+ return;
+
+ BamView.this.requestFocus();
+
+ if(e.getClickCount() > 1)
+ getSelection().clear();
+ else if(e.getButton() == MouseEvent.BUTTON1)
+ {
+ if(isCoverageView(getPixPerBaseByWidth()))
+ coverageView.singleClick(e.isShiftDown(),
+ e.getPoint().y-getJspView().getViewport().getViewPosition().y);
+ else
+ highlightSAMRecord = mouseOverSAMRecord;
+ }
+ else
+ highlightRange(e, MouseEvent.BUTTON2_DOWN_MASK);
+ repaint();
+ }
+
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ dragStart = -1;
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ {
+ //
+ // main menu options
+ if(popup == null)
+ {
+ popup = new JPopupMenu();
+ createMenus(popup);
+ }
+
+ //
+ // coverage heatmap menu options
+ if(coverageMenu != null)
+ popup.remove(coverageMenu);
+ if(isCoverageView(getPixPerBaseByWidth()) && coverageView.isPlotHeatMap())
+ {
+ if(coverageMenu == null)
+ {
+ coverageMenu = new JMenu("Coverage HeatMap");
+ coverageView.createMenus(coverageMenu);
+
+ final JCheckBoxMenuItem coverageGrid = new JCheckBoxMenuItem("Show heatmap grid", false);
+ coverageGrid.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ coverageView.showLabels(coverageGrid.isSelected());
+ }
+ });
+ coverageMenu.add(coverageGrid);
+
+ createGroup = new JMenuItem("Create group from selected BAMs");
+ createGroup.addActionListener(new ActionListener()
+ {
+ private int n = 1;
+ public void actionPerformed(ActionEvent e)
+ {
+ String groupName = "group_"+n;
+ groupsFrame.addGroup(groupName);
+ final List<String> selected = coverageView.getSelected();
+ for(String sel: selected)
+ groupsFrame.addToGroup((new File(sel)).getName(), groupName);
+ groupsFrame.updateAndDisplay();
+ n++;
+ }
+ });
+ coverageMenu.add(createGroup);
+ }
+ createGroup.setEnabled(coverageView.hasSelectedBams());
+ popup.add(coverageMenu);
+ }
+
+ if(gotoMateMenuItem != null)
+ popup.remove(gotoMateMenuItem);
+ if(showDetails != null)
+ popup.remove(showDetails);
+
+ if( mouseOverSAMRecord != null &&
+ mouseOverSAMRecord.sam.getReadPairedFlag() &&
+ !mouseOverSAMRecord.sam.getMateUnmappedFlag() )
+ {
+ final BamViewRecord thisSAMRecord = mouseOverSAMRecord;
+ gotoMateMenuItem = new JMenuItem("Go to mate of : "+
+ thisSAMRecord.sam.getReadName());
+ gotoMateMenuItem.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ String name = thisSAMRecord.sam.getMateReferenceName();
+ if(name.equals("="))
+ name = thisSAMRecord.sam.getReferenceName();
+ int offset = getSequenceOffset(name);
+ if(feature_display != null)
+ feature_display.makeBaseVisible(
+ thisSAMRecord.sam.getMateAlignmentStart()+offset);
+ else
+ scrollBar.setValue(
+ thisSAMRecord.sam.getMateAlignmentStart()+offset-
+ (nbasesInView/2));
+
+ highlightSAMRecord = thisSAMRecord;
+ }
+ });
+ popup.add(gotoMateMenuItem);
+ }
+
+ if( mouseOverSAMRecord != null)
+ {
+ final BamViewRecord thisSAMRecord = mouseOverSAMRecord;
+ showDetails = new JMenuItem("Show details of : "+
+ thisSAMRecord.sam.getReadName());
+ showDetails.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ openFileViewer(thisSAMRecord.sam, getMate(thisSAMRecord), bamList);
+ }
+ });
+ popup.add(showDetails);
+ }
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ }
+ }
+
+ protected static void openFileViewer(SAMRecord readRecord, SAMRecord mateRecord, List<String> bamList)
+ {
+ FileViewer viewDetail = new FileViewer(readRecord.getReadName(), true, false, false);
+ appendToDetailView(readRecord, mateRecord, viewDetail, bamList);
+ }
+
+ private static void appendToDetailView(final SAMRecord sam,
+ final SAMRecord mate,
+ final FileViewer viewer,
+ final List<String> bamList)
+ {
+ if(bamList.size() > 1 && sam.getAttribute("FL") != null)
+ {
+ int bamIdx = (Integer)sam.getAttribute("FL");
+ if(bamIdx < bamList.size())
+ viewer.appendString("File "+bamList.get(bamIdx)+"\n\n", Level.INFO);
+ }
+
+ viewer.appendString("Read Name "+sam.getReadName()+"\n", Level.INFO);
+ viewer.appendString("Coordinates "+sam.getAlignmentStart()+".."+
+ sam.getAlignmentEnd()+"\n", Level.DEBUG);
+ if(sam.getReadGroup() != null)
+ viewer.appendString("Read Group "+sam.getReadGroup().getId()+"\n", Level.DEBUG);
+ viewer.appendString("Length "+sam.getReadLength()+"\n", Level.DEBUG);
+ viewer.appendString("Reference Name "+sam.getReferenceName()+"\n", Level.DEBUG);
+ viewer.appendString("Inferred Size "+sam.getInferredInsertSize()+"\n", Level.DEBUG);
+ viewer.appendString("Mapping Quality "+sam.getMappingQuality()+"\n", Level.DEBUG);
+ viewer.appendString("Cigar String "+sam.getCigarString()+"\n", Level.DEBUG);
+ viewer.appendString("Strand "+
+ (sam.getReadNegativeStrandFlag() ? "-\n\n" : "+\n\n"), Level.DEBUG);
+
+ if(sam.getReadPairedFlag())
+ {
+ if(mate != null)
+ {
+ viewer.appendString("Mate Coordinates "+mate.getAlignmentStart()+".."+
+ mate.getAlignmentEnd()+"\n", Level.DEBUG);
+ viewer.appendString("Mate Length "+mate.getReadLength()+"\n", Level.DEBUG);
+ viewer.appendString("Mate Reference Name "+mate.getReferenceName()+"\n", Level.DEBUG);
+ viewer.appendString("Mate Inferred Size "+mate.getInferredInsertSize()+"\n", Level.DEBUG);
+ viewer.appendString("Mate Mapping Quality "+mate.getMappingQuality()+"\n", Level.DEBUG);
+ viewer.appendString("Mate Cigar String "+mate.getCigarString()+"\n", Level.DEBUG);
+ }
+ else
+ {
+ viewer.appendString("Mate Start Coordinate "+sam.getMateAlignmentStart()+"\n", Level.DEBUG);
+ viewer.appendString("Mate Reference Name "+sam.getMateReferenceName()+"\n", Level.DEBUG);
+ }
+ viewer.appendString("Mate Strand "+
+ (sam.getMateNegativeStrandFlag() ? "-" : "+"), Level.DEBUG);
+ }
+
+ viewer.appendString("\n\nFlags:", Level.INFO);
+ viewer.appendString("\nDuplicate Read "+
+ (sam.getDuplicateReadFlag() ? "yes" : "no"), Level.DEBUG);
+
+ viewer.appendString("\nRead Paired "+
+ (sam.getReadPairedFlag() ? "yes" : "no"), Level.DEBUG);
+ if(sam.getReadPairedFlag())
+ {
+ viewer.appendString("\nFirst of Pair "+
+ (sam.getFirstOfPairFlag() ? "yes" : "no"), Level.DEBUG);
+ viewer.appendString("\nMate Unmapped "+
+ (sam.getMateUnmappedFlag() ? "yes" : "no"), Level.DEBUG);
+ viewer.appendString("\nProper Pair "+
+ (sam.getProperPairFlag() ? "yes" : "no"), Level.DEBUG);
+ }
+ viewer.appendString("\nRead Fails Vendor\nQuality Check "+
+ (sam.getReadFailsVendorQualityCheckFlag() ? "yes" : "no"), Level.DEBUG);
+ viewer.appendString("\nRead Unmapped "+
+ (sam.getReadUnmappedFlag() ? "yes" : "no"), Level.DEBUG);
+
+ if(sam.getReadPairedFlag())
+ viewer.appendString("\nSecond Of Pair "+
+ (sam.getSecondOfPairFlag() ? "yes" : "no"), Level.DEBUG);
+
+ viewer.appendString("\n\nRead Bases:\n", Level.INFO);
+ wrapReadBases(sam, viewer);
+
+ if(sam.getReadPairedFlag() && mate != null)
+ {
+ viewer.appendString("\nMate Read Bases:\n", Level.INFO);
+ wrapReadBases(mate, viewer);
+ }
+ }
+
+ private static void wrapReadBases(final SAMRecord sam,
+ final FileViewer viewer)
+ {
+ final String seq = new String(sam.getReadBases());
+ final int MAX_SEQ_LINE_LENGTH = 100;
+ for(int i=0; i<=seq.length(); i+=MAX_SEQ_LINE_LENGTH)
+ {
+ int iend = i+MAX_SEQ_LINE_LENGTH;
+ if(iend > seq.length())
+ iend = seq.length();
+ viewer.appendString(seq.substring(i, iend)+"\n", Level.DEBUG);
+ }
+ }
+
+ /**
+ * Query for the mate of a read
+ * @param mate
+ * @return
+ */
+ protected SAMRecord getMate(BamViewRecord thisSAMRecord)
+ {
+ if(!thisSAMRecord.sam.getReadPairedFlag()) // read is not paired in sequencing
+ return null;
+
+ SAMRecord mate = null;
+ try
+ {
+ short fileIndex = 0;
+ if(bamList.size()>1 && thisSAMRecord.bamIndex > 0)
+ fileIndex = thisSAMRecord.bamIndex;
+ String bam = bamList.get(fileIndex);
+ final SAMFileReader inputSam = getSAMFileReader(bam);
+ mate = inputSam.queryMate(thisSAMRecord.sam);
+ }
+ catch (Exception e)
+ {
+ logger4j.warn("WARN: BamView.getMate() :: "+e.getMessage());
+ e.printStackTrace();
+ }
+ return mate;
+ }
+
+ protected SAMRecordPredicate getSamRecordFlagPredicate()
+ {
+ return samRecordFlagPredicate;
+ }
+
+ protected void setSamRecordFlagPredicate(
+ SAMRecordPredicate samRecordFlagPredicate)
+ {
+ laststart = -1;
+ lastend = -1;
+ this.samRecordFlagPredicate = samRecordFlagPredicate;
+ }
+
+ protected SAMRecordMapQPredicate getSamRecordMapQPredicate()
+ {
+ return samRecordMapQPredicate;
+ }
+
+ protected void setSamRecordMapQPredicate(
+ SAMRecordMapQPredicate samRecordMapQPredicate)
+ {
+ laststart = -1;
+ lastend = -1;
+ this.samRecordMapQPredicate = samRecordMapQPredicate;
+ }
+
+ /**
+ * @return the concatSequences
+ */
+ protected boolean isConcatSequences()
+ {
+ return concatSequences;
+ }
+
+ class PairedRead
+ {
+ BamViewRecord sam1;
+ BamViewRecord sam2;
+ }
+
+ class CreateFeatures
+ {
+ CreateFeatures(final GroupBamFrame groupsFrame)
+ {
+ final TextFieldInt threshold = new TextFieldInt();
+ final TextFieldInt minSize = new TextFieldInt();
+ final TextFieldInt minBams = new TextFieldInt();
+
+ threshold.setValue(6);
+ minSize.setValue(6);
+ minBams.setValue( (groupsFrame.getNumberOfGroups() == 1 ?
+ bamList.size() : groupsFrame.getMaximumBamsInGroup()) );
+
+ final JPanel gridPanel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 0;
+ c.gridy = 0;
+
+ gridPanel.add(new JLabel("Minimum number of reads:"), c);
+ c.gridy++;
+ gridPanel.add(threshold, c);
+
+ c.gridy++;
+ gridPanel.add(new JSeparator(), c);
+ c.gridy++;
+ gridPanel.add(new JLabel("Minimum number of BAMs for reads to be present in:"), c);
+ c.gridy++;
+ gridPanel.add(minBams, c);
+
+ JRadioButton useAllBams = new JRadioButton("out of all BAMs", (groupsFrame.getNumberOfGroups() == 1));
+ JRadioButton useGroup = new JRadioButton("within a group", (groupsFrame.getNumberOfGroups() != 1));
+
+ if(groupsFrame.getNumberOfGroups() == 1)
+ useGroup.setEnabled(false);
+
+ final ButtonGroup group = new ButtonGroup();
+ group.add(useAllBams);
+ group.add(useGroup);
+
+ final Box xBox = Box.createHorizontalBox();
+ xBox.add(useAllBams);
+ xBox.add(useGroup);
+ xBox.add(Box.createHorizontalGlue());
+ c.gridy++;
+ gridPanel.add(xBox, c);
+
+ c.gridy++;
+ gridPanel.add(new JSeparator(), c);
+ c.gridy++;
+ gridPanel.add(new JLabel("Minimum feature size:"), c);
+ c.gridy++;
+ gridPanel.add(minSize, c);
+
+ final JCheckBox cbOpposite = new JCheckBox("Assume reads on opposite strand", false);
+ cbOpposite.setToolTipText("for cDNA experiments when the reads are on the opposite strand");
+ c.gridy++;
+ gridPanel.add(cbOpposite, c);
+
+ int status =
+ JOptionPane.showConfirmDialog(feature_display, gridPanel,
+ "Options", JOptionPane.OK_CANCEL_OPTION);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+
+ if(!useGroup.isSelected() && minBams.getValue() > bamList.size())
+ {
+ status =
+ JOptionPane.showConfirmDialog(feature_display,
+ "The minimum number of BAMs setting can not be\n"+
+ "greater than the total number of BAM files.\n"+
+ "Set this to the number of BAMs (i.e. "+bamList.size()+").",
+ "Options", JOptionPane.OK_CANCEL_OPTION);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+ minBams.setValue(bamList.size());
+ }
+ else if(useGroup.isSelected() && minBams.getValue() > groupsFrame.getMaximumBamsInGroup())
+ {
+ status =
+ JOptionPane.showConfirmDialog(feature_display,
+ "Minimum number of BAMs setting can not be greater than\n"+
+ "the total number of BAM files found in any of the groups.\n"+
+ "Set this to the greatest number of BAM files in any\n"+
+ "group (i.e. "+groupsFrame.getMaximumBamsInGroup()+").",
+ "Options", JOptionPane.OK_CANCEL_OPTION);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+ minBams.setValue(groupsFrame.getMaximumBamsInGroup());
+ }
+
+ new MappedReads((String)combo.getSelectedItem(),BamView.this, samFileReaderHash,
+ seqNames, offsetLengths, concatSequences, seqLengths,
+ (useGroup.isSelected() ? groupsFrame : null), threshold.getValue(),
+ minSize.getValue(), minBams.getValue(), cbOpposite.isSelected(), true);
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ BamFrame frame = new BamFrame();
+ if(args.length == 0 && BamFrame.isMac())
+ {
+ try
+ {
+ Thread.sleep(1000);
+ }
+ catch (InterruptedException e1) {}
+ if(frame.getBamFile() != null)
+ args = new String[]{ frame.getBamFile() };
+ }
+
+ List<String> bam = new Vector<String>();
+ String reference = null;
+ if(args.length == 0 || args[0].equals("NEW-BAMVIEW"))
+ {
+ System.setProperty("default_directory", System.getProperty("user.dir"));
+ FileSelectionDialog fileSelection = new FileSelectionDialog(
+ null, true, "BamView", "BAM");
+ bam = fileSelection.getFiles(BAM_SUFFIX);
+ reference = fileSelection.getReferenceFile();
+ if(reference == null || reference.equals(""))
+ reference = null;
+
+ if(bam == null || bam.size() < 1)
+ {
+ if(args.length > 0 && args[0].equals("NEW-BAMVIEW"))
+ return;
+ System.err.println("No files found.");
+ System.exit(0);
+ }
+ }
+ else if(!args[0].startsWith("-"))
+ {
+ for(int i=0; i< args.length; i++)
+ bam.add(args[i]);
+ }
+ int nbasesInView = 2000;
+ String chr = null;
+ String vw = null;
+ boolean orientation = false;
+ boolean covPlot = false;
+ boolean snpPlot = false;
+ int base = 0;
+
+ for(int i=0;i<args.length; i++)
+ {
+ if(args[i].equals("-a"))
+ {
+ while(i < args.length-1 && !args[++i].startsWith("-"))
+ {
+ String filename = args[i];
+ if(FileSelectionDialog.isListOfFiles(filename))
+ bam.addAll(FileSelectionDialog.getListOfFiles(filename));
+ else
+ bam.add(filename);
+ }
+ --i;
+ }
+ else if(args[i].equals("-r"))
+ reference = args[++i];
+ else if(args[i].equals("-n"))
+ nbasesInView = Integer.parseInt(args[++i]);
+ else if(args[i].equals("-s"))
+ System.setProperty("samtoolDir", args[++i]);
+ else if(args[i].equals("-c"))
+ chr = args[++i].trim();
+ else if(args[i].equals("-b"))
+ base = Integer.parseInt(args[++i].trim());
+ else if(args[i].equals("-v"))
+ vw = args[++i].trim();
+ else if(args[i].equals("-o"))
+ orientation = true;
+ else if(args[i].equals("-pc"))
+ covPlot = true;
+ else if(args[i].equals("-ps"))
+ snpPlot = true;
+ else if(args[i].startsWith("-h"))
+ {
+ System.out.println("-h\t show help");
+
+ System.out.println("-a\t BAM/SAM file to display");
+ System.out.println("-r\t reference file (optional)");
+ System.out.println("-n\t number of bases to display in the view (optional)");
+ System.out.println("-c\t chromosome name (optional)");
+ System.out.println("-v\t view (optional - IS (inferred size), S (stack, default), PS (paired stack), ST (strand), C (coverage))");
+ System.out.println("-b\t base position (optional)");
+ System.out.println("-o\t show orientation (optional)");
+ System.out.println("-pc\t plot coverage (optional)");
+ System.out.println("-ps\t plot SNP (optional and only with -r)");
+ System.exit(0);
+ }
+ }
+
+ final BamView view = new BamView(bam, reference, nbasesInView, null, null,
+ (JPanel)frame.getContentPane(), frame);
+ frame.setTitle("BamView v"+view.getVersion());
+
+ if(chr != null)
+ view.combo.setSelectedItem(chr);
+ if(vw != null)
+ {
+ if(vw.equalsIgnoreCase("IS"))
+ view.cbIsizeStackView.setSelected(true);
+ if(vw.equalsIgnoreCase("PS"))
+ view.cbPairedStackView.setSelected(true);
+ if(vw.equalsIgnoreCase("ST"))
+ view.cbStrandStackView.setSelected(true);
+ if(vw.equalsIgnoreCase("C"))
+ view.cbCoverageView.setSelected(true);
+ }
+ if(base > 0)
+ view.scrollBar.setValue(base);
+ if(orientation)
+ view.isOrientation = true;
+ if(covPlot)
+ {
+ view.isCoverage = true;
+ view.coveragePanel.setVisible(true);
+ }
+ if(snpPlot)
+ {
+ view.isSNPplot = true;
+ view.snpPanel.setVisible(true);
+ }
+
+ // translucent
+ //frame.getRootPane().putClientProperty("Window.alpha", new Float(0.9f));
+ /*frame.addWindowFocusListener(new WindowFocusListener()
+ {
+ public void windowGainedFocus(WindowEvent e)
+ {
+ view.requestFocus();
+ }
+ public void windowLostFocus(WindowEvent e){}
+ });*/
+
+ frame.pack();
+
+ view.jspView.getVerticalScrollBar().setValue(
+ view.jspView.getVerticalScrollBar().getMaximum());
+ frame.setVisible(true);
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/alignment/BamViewRecord.java b/uk/ac/sanger/artemis/components/alignment/BamViewRecord.java
new file mode 100644
index 0000000..51ae898
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/BamViewRecord.java
@@ -0,0 +1,15 @@
+package uk.ac.sanger.artemis.components.alignment;
+
+import net.sf.samtools.SAMRecord;
+
+
+class BamViewRecord
+{
+ protected SAMRecord sam;
+ protected short bamIndex = -1;
+ BamViewRecord(final SAMRecord sam, final short bamIndex)
+ {
+ this.sam = sam;
+ this.bamIndex = bamIndex;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/CRAMReferenceSequenceFile.java b/uk/ac/sanger/artemis/components/alignment/CRAMReferenceSequenceFile.java
new file mode 100644
index 0000000..ae2fb2d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/CRAMReferenceSequenceFile.java
@@ -0,0 +1,111 @@
+/* BamView
+ *
+ * created: 2012
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2012 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+import net.sf.picard.reference.ReferenceSequence;
+import net.sf.picard.reference.ReferenceSequenceFile;
+import net.sf.samtools.SAMSequenceDictionary;
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+
+ class CRAMReferenceSequenceFile implements ReferenceSequenceFile
+ {
+ private Entry sequence;
+ private BamView bamView;
+
+ CRAMReferenceSequenceFile(final Entry sequence, final BamView bamView)
+ {
+ this.sequence = sequence;
+ this.bamView = bamView;
+ }
+
+ /**
+ * Retrieves the complete sequence described by this contig.
+ * @param contig contig whose data should be returned.
+ * @return The full sequence associated with this contig.
+ * @throws UnsupportedOperationException if !sIndexed.
+ */
+ public ReferenceSequence getSequence(String contig)
+ {
+ return getSubsequenceAt(contig, 1L, sequence.getBases().getLength());
+
+ //return new ReferenceSequence(sequence.getName(), 0,
+ // sequence.getBases().getForwardStrand().getStrandBases().getBytes());
+ }
+
+ public SAMSequenceDictionary getSequenceDictionary()
+ {
+ return null;
+ }
+
+ /**
+ * Gets the subsequence of the contig in the range [start,stop]
+ * @param contig Contig whose subsequence to retrieve.
+ * @param start inclusive, 1-based start of region.
+ * @param stop inclusive, 1-based stop of region.
+ * @return The partial reference sequence associated with this range.
+ * @throws UnsupportedOperationException if !sIndexed.
+ */
+ public ReferenceSequence getSubsequenceAt(String contig, long start, long stop )
+ {
+ try
+ {
+ if(bamView.isConcatSequences())
+ {
+ int offset = bamView.getSequenceOffset(contig);
+ start += offset;
+ stop += offset;
+ }
+
+ return new ReferenceSequence(sequence.getName(), 0,
+ sequence.getBases().getSubSequence(new Range((int)start, (int)stop), Bases.FORWARD).getBytes());
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * @return true if getSequence and getSubsequenceAt methods are allowed.
+ */
+ public boolean isIndexed()
+ {
+ return true;
+ }
+
+ public ReferenceSequence nextSequence()
+ {
+ return null;
+ }
+
+ public void reset()
+ {
+ }
+
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java b/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java
new file mode 100644
index 0000000..64dbaa5
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java
@@ -0,0 +1,675 @@
+/* CoveragePanel
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Stroke;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.GeneralPath;
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.components.Plot;
+
+import net.sf.samtools.AlignmentBlock;
+import net.sf.samtools.SAMRecord;
+
+ public class CoveragePanel extends AbstractGraphPanel
+ {
+ private static final long serialVersionUID = 1L;
+ private static LineAttributes lines[];
+ private boolean includeCombined = false;
+ private Hashtable<String, int[][]> plots;
+ private int combinedCoverage[][];
+
+ private static boolean redraw = false;
+ private boolean setMaxBases = false;
+
+ private boolean plotByStrand = false;
+ private boolean plotHeatMap = false;
+ private List<HeatMapLn> heatPlots;
+ private List<String> selected = new Vector<String>();
+ private boolean showGrid = false;
+ private boolean logScale = false;
+
+ protected CoveragePanel(final BamView bamView)
+ {
+ this();
+ this.bamView = bamView;
+ createMenus(popup);
+ addMouseListener(new PopupListener());
+ }
+
+ protected CoveragePanel()
+ {
+ super();
+ setMaxBases = true;
+ }
+
+ protected void createMenus(JComponent menu)
+ {
+ final JMenuItem configure = new JMenuItem("Configure Line(s)...");
+ configure.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int size = bamView.bamList.size();
+ if(includeCombined)
+ size++;
+ lines =
+ LineAttributes.configurePlots(bamView.bamList,
+ getLineAttributes(size), CoveragePanel.this);
+ bamView.refreshColourOfBamMenu();
+ }
+ });
+ menu.add(configure);
+
+ final JMenuItem optMenu = new JMenuItem("Options...");
+ optMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ defineOpts();
+ bamView.repaint();
+ }
+ });
+ menu.add(optMenu);
+ }
+
+ /**
+ * Override
+ */
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+ if(plots == null)
+ return;
+
+ if(!plotHeatMap)
+ drawSelectionRange(g2, pixPerBase, start, end, getHeight(), Color.PINK);
+ drawPlot(g2);
+ drawMax(g2);
+ }
+
+ protected void init(BamView bamView, float pixPerBase, int start, int end)
+ {
+ this.bamView = bamView;
+ setPixPerBase(pixPerBase);
+ setStartAndEnd(start, end);
+ init();
+ }
+
+ private void init()
+ {
+ if(autoWinSize)
+ {
+ windowSize = (bamView.getBasesInView()/300);
+ userWinSize = windowSize;
+ }
+ else
+ windowSize = userWinSize;
+
+ if(windowSize < 1)
+ windowSize = 1;
+ nBins = Math.round((end-start+1.f)/windowSize);
+
+ plots = new Hashtable<String, int[][]>();
+ combinedCoverage = null;
+ if(includeCombined)
+ {
+ combinedCoverage = new int[nBins][2];
+ for(int k=0; k<combinedCoverage.length; k++)
+ for(int l=0; l<2; l++)
+ combinedCoverage[k][l] = 0;
+ plots.put("-1", combinedCoverage);
+ }
+ max = 0;
+ }
+
+ private void drawPlot(Graphics2D g2)
+ {
+ max = 0;
+ Enumeration<String> plotEum = plots.keys();
+ while(plotEum.hasMoreElements())
+ {
+ String fileName = plotEum.nextElement();
+ int[][] thisPlot = plots.get(fileName);
+ for(int i=1; i<thisPlot.length; i++)
+ {
+ if(plotByStrand)
+ {
+ for(int j=0; j<2; j++)
+ if(max < thisPlot[i][j])
+ max = thisPlot[i][j];
+ }
+ else if(max < thisPlot[i][0])
+ max = thisPlot[i][0];
+ }
+ }
+
+ draw(g2, getWidth(), getHeight(), null);
+ }
+
+ /**
+ * Return true is the read is on the negative strand or if using RNASeq strand
+ * specific then return true if the XS tag is set to '-'
+ * @param thisRead
+ * @param useRNASeqStrand
+ * @return
+ */
+ private boolean isNegativeStrand(final SAMRecord thisRead, final boolean useRNASeqStrand)
+ {
+ if(useRNASeqStrand)
+ {
+ if( ((Character)thisRead.getAttribute("XS")).equals('-') )
+ return true;
+ }
+ else if(thisRead.getReadNegativeStrandFlag())
+ return true;
+ return false;
+ }
+
+ protected void addRecord(SAMRecord thisRead, int offset, String fileName, boolean useRNASeqStrand)
+ {
+ int coverage[][] = plots.get(fileName);
+ if(coverage == null)
+ {
+ coverage = new int[nBins][2];
+ for(int k=0; k<nBins; k++)
+ for(int l=0; l<2; l++)
+ coverage[k][l] = 0;
+ plots.put(fileName, coverage);
+ }
+
+ final int col;
+ if(plotByStrand && !isPlotHeatMap() && isNegativeStrand(thisRead, useRNASeqStrand))
+ col = 1;
+ else
+ col = 0;
+ final List<AlignmentBlock> blocks = thisRead.getAlignmentBlocks();
+ for(int j=0; j<blocks.size(); j++)
+ {
+ AlignmentBlock block = blocks.get(j);
+ int refStart = block.getReferenceStart();
+ for(int k=0; k<block.getLength(); k++)
+ {
+ int pos = refStart + k + offset;
+ int bin = pos/windowSize;
+ if(bin < 0 || bin > nBins-1)
+ continue;
+
+ coverage[bin][col]+=1;
+ if(coverage[bin][col] > max)
+ max = coverage[bin][col];
+
+ if(includeCombined)
+ {
+ combinedCoverage[bin][col]+=1;
+ if(combinedCoverage[bin][col] > max)
+ max = combinedCoverage[bin][col];
+ }
+ }
+ }
+ }
+
+ protected void draw(Graphics2D g2, int wid, int hgt, List<Short> hideBamList)
+ {
+ int size = bamView.bamList.size();
+ if(includeCombined)
+ {
+ lines = getLineAttributes(size+1);
+ lines[size].setLineColour(Color.black);
+ }
+ else
+ lines = getLineAttributes(size);
+
+ if(plotHeatMap)
+ heatPlots = new Vector<HeatMapLn>();
+ Enumeration<String> plotEum = plots.keys();
+ while(plotEum.hasMoreElements())
+ {
+ String fName = plotEum.nextElement();
+ int[][] thisPlot = plots.get(fName);
+
+ int idx;
+ if(fName.equals("-1"))
+ idx = lines.length-1;
+ else
+ idx = bamView.bamList.indexOf(fName);
+
+ final LineAttributes line = lines[idx];
+ if(plotHeatMap)
+ {
+ if(hideBamList != null)
+ idx = adjustIdx(idx, hideBamList);
+ drawHeatMap(g2, hgt, line, idx, thisPlot, fName,
+ (idx == plots.size()-1));
+ }
+ else
+ drawLinePlot(g2, wid, hgt, line, thisPlot);
+ }
+ }
+
+ /**
+ * Adjust for BAM's that have been hidden
+ * @param index
+ * @param hideBamList
+ * @return
+ */
+ private int adjustIdx(int index, List<Short> hideBamList)
+ {
+ int shiftIdx = index;
+ for(short i=0; i<index; i++)
+ if(hideBamList.contains(i))
+ shiftIdx--;
+ return shiftIdx;
+ }
+
+ private void drawLinePlot(final Graphics2D g2, int wid, int hgt, LineAttributes line, int[][] thisPlot)
+ {
+ g2.setColor(line.getLineColour());
+ int hgt2 = hgt/2;
+ float maxVal = getValue(max, logScale);
+
+ if(line.getPlotType() == LineAttributes.PLOT_TYPES[0])
+ {
+ g2.setStroke(line.getStroke());
+ for(int i=1; i<thisPlot.length; i++)
+ {
+ int x0 = (int) ((((i-1)*(windowSize)) + windowSize/2.f)*pixPerBase);
+ int x1 = (int) (((i*(windowSize)) + windowSize/2.f)*pixPerBase);
+ int y0, y1;
+ if(plotByStrand)
+ {
+ for(int col=0; col<2; col++)
+ {
+ final int factor;
+ if(col == 0)
+ factor = 1; // fwd strand
+ else
+ factor = -1; // reverse strand
+
+ y0 = (int) (hgt2 - (factor)*((getValue(thisPlot[i-1][col], logScale)/maxVal)*hgt2));
+ y1 = (int) (hgt2 - (factor)*((getValue(thisPlot[i][col], logScale)/maxVal)*hgt2));
+
+ g2.drawLine(x0, y0, x1, y1);
+ }
+ }
+ else
+ {
+ y0 = (int) (hgt - ((getValue(thisPlot[i-1][0], logScale)/maxVal)*hgt));
+ y1 = (int) (hgt - ((getValue(thisPlot[i][0], logScale)/maxVal)*hgt));
+ g2.drawLine(x0, y0, x1, y1);
+ }
+ }
+ }
+ else // filled plots
+ {
+ g2.setComposite(makeComposite(0.75f));
+
+ if(plotByStrand)
+ {
+ final GeneralPath shapeFwd = new GeneralPath();
+ shapeFwd.moveTo(0,hgt2);
+ final GeneralPath shapeBwd = new GeneralPath();
+ shapeBwd.moveTo(0,hgt2);
+
+ for(int i=0; i<thisPlot.length; i++)
+ {
+ float xpos = i*windowSize*pixPerBase;
+ for(int col=0; col<2; col++)
+ {
+ if(col == 0)
+ shapeFwd.lineTo(xpos,
+ hgt2 - ((getValue(thisPlot[i][col], logScale)/maxVal)*hgt2));
+ else
+ shapeBwd.lineTo(xpos,
+ hgt2 + ((getValue(thisPlot[i][col], logScale)/maxVal)*hgt2));
+ }
+ }
+
+ shapeBwd.lineTo(wid,hgt2);
+ shapeFwd.lineTo(wid,hgt2);
+ g2.fill(shapeBwd);
+ g2.fill(shapeFwd);
+ }
+ else
+ {
+ final GeneralPath shape = new GeneralPath();
+ shape.moveTo(0,hgt);
+ for(int i=0; i<thisPlot.length; i++)
+ {
+ float xpos = i*windowSize*pixPerBase;
+ shape.lineTo(xpos,
+ hgt - ((getValue(thisPlot[i][0], logScale)/maxVal)*hgt));
+ }
+ shape.lineTo(wid,hgt);
+ g2.fill(shape);
+ }
+ }
+
+ if(plotByStrand)
+ {
+ g2.setColor(Color.GRAY);
+ g2.drawLine(0, hgt2, wid+1, hgt2);
+ }
+ }
+
+ /**
+ * Draw as heat map
+ * @param g2
+ * @param hgt
+ * @param line
+ * @param idx
+ * @param thisPlot
+ */
+ private void drawHeatMap(final Graphics2D g2, int hgt, LineAttributes line, int idx, int[][] thisPlot, String fName, boolean lastPlot)
+ { // heat map
+ final int NSHADES = 240;
+ final int plotHgt = hgt/plots.size();
+ final int plotPos = (hgt*idx)/plots.size();
+ final Color definedColours[] = Plot.makeColours(line.getLineColour(), NSHADES);
+
+ heatPlots.add(new HeatMapLn(plotPos, plotPos+plotHgt, fName));
+
+ float maxVal = getValue(max, logScale);
+ for(int i=0; i<thisPlot.length; i++)
+ {
+ int xpos = (int) (i*windowSize*pixPerBase);
+ // this is a number between 0.0 and 1.0
+ final float scaledValue = getValue(thisPlot[i][0], logScale) / maxVal;
+ // set color based on value
+ int colourIdx =
+ (int)(definedColours.length * 0.999 * scaledValue);
+
+ if(colourIdx > definedColours.length - 1)
+ colourIdx = definedColours.length - 1;
+ else if (colourIdx <= 0)
+ continue;
+
+ g2.setColor(definedColours[ colourIdx ]);
+ g2.fillRect(xpos, plotPos, (int) (windowSize*2*pixPerBase), plotHgt);
+ }
+
+ if(showGrid && !lastPlot)
+ {
+ g2.setColor(Color.darkGray);
+ g2.drawLine(0, plotPos+plotHgt-1, bamView.getWidth(), plotPos+plotHgt-1);
+ }
+
+ if(selected.contains(fName))
+ {
+ g2.setColor(Color.darkGray);
+ Stroke stroke = g2.getStroke();
+ g2.setStroke(new BasicStroke(2.f));
+ g2.drawLine(0, plotPos+1, bamView.getWidth(), plotPos+1);
+ g2.drawLine(0, plotPos+plotHgt-1, bamView.getWidth(), plotPos+plotHgt-1);
+ g2.setStroke(stroke);
+ }
+ }
+
+ private AlphaComposite makeComposite(float alpha)
+ {
+ int type = AlphaComposite.SRC_OVER;
+ return(AlphaComposite.getInstance(type, alpha));
+ }
+
+ protected static LineAttributes[] getLineAttributes(int nsize)
+ {
+ if(lines == null)
+ lines = LineAttributes.init(nsize);
+ else if(lines.length < nsize)
+ {
+ LineAttributes tmpLines[] = LineAttributes.init(nsize);
+ for(int i=0;i<lines.length;i++)
+ tmpLines[i] = lines[i];
+ lines = tmpLines;
+ }
+ return lines;
+ }
+
+ /**
+ * @return the redraw
+ */
+ protected static boolean isRedraw()
+ {
+ if(redraw)
+ {
+ redraw = false;
+ return true;
+ }
+ return redraw;
+ }
+
+ /**
+ * @param plotByStrand the plotByStrand to set
+ */
+ protected void setPlotByStrand(boolean plotByStrand)
+ {
+ redraw = true;
+ this.plotByStrand = plotByStrand;
+ }
+
+ /**
+ * @param plotHeatMap the plotHeatMap to set
+ */
+ protected void setPlotHeatMap(boolean plotHeatMap)
+ {
+ this.plotHeatMap = plotHeatMap;
+ }
+
+ /**
+ * @return the plotHeatMap
+ */
+ protected boolean isPlotHeatMap()
+ {
+ return plotHeatMap;
+ }
+
+ /**
+ * Return tooltip text for a given position
+ * @param e
+ * @return
+ */
+ public String getToolTipText(int ypos)
+ {
+ if(heatPlots == null)
+ return null;
+ for(HeatMapLn h: heatPlots)
+ {
+ if(ypos > h.yTop && ypos < h.yBtm)
+ return h.toString();
+ }
+
+ return null;
+ }
+
+ protected void showLabels(boolean showLabel)
+ {
+ //
+ this.showGrid = showLabel;
+ bamView.repaint();
+ }
+
+ private void defineOpts()
+ {
+ final JPanel opts = new JPanel(new GridBagLayout());
+ final GridBagConstraints c = new GridBagConstraints();
+
+ final JTextField newBaseMax = new JTextField(Integer.toString(bamView.getMaxBases()), 10);
+ c.gridy = 0;
+ if(setMaxBases)
+ {
+ final JLabel labMax1 = new JLabel("Zoom level before switching");
+ final JLabel labMax2 = new JLabel("to coverage view (in bases):");
+ c.anchor = GridBagConstraints.WEST;
+ opts.add(labMax1, c);
+ c.gridy = c.gridy+1;
+ opts.add(labMax2, c);
+ opts.add(newBaseMax, c);
+ }
+
+ final JTextField newWinSize = new JTextField(Integer.toString(userWinSize), 10);
+ final JLabel lab = new JLabel("Window size:");
+ lab.setEnabled(!autoWinSize);
+ newWinSize.setEnabled(!autoWinSize);
+
+ c.gridy = c.gridy+1;
+ c.anchor = GridBagConstraints.EAST;
+ opts.add(lab, c);
+ opts.add(newWinSize, c);
+
+ final JCheckBox autoSet = new JCheckBox("Automatically set window size", autoWinSize);
+ autoSet.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ lab.setEnabled(!autoSet.isSelected());
+ newWinSize.setEnabled(!autoSet.isSelected());
+ }
+ });
+ c.anchor = GridBagConstraints.WEST;
+ c.gridy = c.gridy+1;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ opts.add(autoSet, c);
+
+ final JCheckBox showCombined = new JCheckBox("Show combined plot", includeCombined);
+ if(bamView.bamList.size() == 1)
+ showCombined.setEnabled(false);
+ c.gridy = c.gridy+1;
+ opts.add(showCombined, c);
+
+ final JCheckBox byStrand = new JCheckBox("Plot by strand", plotByStrand);
+ c.gridy = c.gridy+1;
+ opts.add(byStrand, c);
+
+ final JCheckBox logMenu = new JCheckBox("Log scale", logScale);
+ c.gridy = c.gridy+1;
+ opts.add(logMenu, c);
+
+ String window_options[] = { "OK", "Cancel" };
+ int select = JOptionPane.showOptionDialog(null, opts, "Coverage Options",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+ window_options, window_options[0]);
+
+ if(select == 1)
+ return;
+
+ redraw = true;
+ autoWinSize = autoSet.isSelected();
+ includeCombined = showCombined.isSelected();
+ plotByStrand = byStrand.isSelected();
+ logScale = logMenu.isSelected();
+
+ try
+ {
+ userWinSize = Integer.parseInt(newWinSize.getText().trim());
+ if(setMaxBases)
+ bamView.setMaxBases(Integer.parseInt(newBaseMax.getText().trim()));
+ }
+ catch (NumberFormatException nfe)
+ {
+ return;
+ }
+ }
+
+ /**
+ * Click on heatmap
+ * @param y
+ */
+ protected void singleClick(boolean isShiftDown, int ypos)
+ {
+ if(!isPlotHeatMap())
+ return;
+
+ String sel = null;
+ for(HeatMapLn h: heatPlots)
+ {
+ if(ypos > h.yTop && ypos < h.yBtm)
+ sel = h.fName;
+ }
+
+ if(selected.contains(sel))
+ {
+ if(!isShiftDown)
+ selected.clear();
+ else
+ selected.remove(sel);
+ }
+ else
+ {
+ if(!isShiftDown)
+ selected.clear();
+ selected.add(sel);
+ }
+ }
+
+ protected boolean hasSelectedBams()
+ {
+ return (selected.size() > 0);
+ }
+
+ protected List<String> getSelected()
+ {
+ return selected;
+ }
+
+ class HeatMapLn
+ {
+ private int yTop, yBtm;
+ private String fName;
+ HeatMapLn(int yTop, int yBtm, String fName)
+ {
+ this.yTop = yTop;
+ this.yBtm = yBtm;
+ this.fName = fName;
+ }
+
+ public String toString()
+ {
+ final File f = new File(fName);
+ return f.getName();
+ }
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/FileSelectionDialog.java b/uk/ac/sanger/artemis/components/alignment/FileSelectionDialog.java
new file mode 100644
index 0000000..c5944bf
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/FileSelectionDialog.java
@@ -0,0 +1,358 @@
+package uk.ac.sanger.artemis.components.alignment;
+
+
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.List;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.components.StickyFileChooser;
+import uk.ac.sanger.artemis.components.variant.VCFview;
+
+/**
+ * File selection panel to allow input of DNA sequences
+ */
+public class FileSelectionDialog extends JDialog
+{
+ private static final long serialVersionUID = 1L;
+
+ private JPanel dialog = new JPanel(new GridBagLayout());
+ private GridBagConstraints c = new GridBagConstraints();
+ private int row = 0;
+ private List<JTextField> bamFields = new Vector<JTextField>(30);
+ private JTextField referenceField = new JTextField(30);
+ private boolean statusOK = false;
+
+ public FileSelectionDialog(String fileNames[])
+ {
+ for(int i=0; i<fileNames.length; i++)
+ {
+ JTextField bamField = new JTextField(fileNames[i]);
+ bamFields.add(bamField);
+ }
+ statusOK = true;
+ }
+
+ /**
+ * Constructor to display any given input files and options provided and
+ * @param f
+ * @param showReferenceOption
+ * @param program - program name
+ * @param fileType - type of file (e.g. bam, vcf)
+ */
+ public FileSelectionDialog(Frame f, boolean showReferenceOption,
+ final String program, final String fileType)
+ {
+ super(f, program+" :: Select Files", true);
+
+ addBamField(fileType, null);
+
+ JButton addMoreFiles = new JButton("Add More");
+ addMoreFiles.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ addBamField(fileType, null);
+ }
+ });
+
+ row += 100;
+ c.gridx = 1;
+ c.gridy = row;
+ dialog.add(addMoreFiles, c);
+
+ if(showReferenceOption)
+ {
+ c.gridy = ++row;
+ c.gridx = 0;
+ dialog.add(new JLabel(" Reference sequence file (optional): "), c);
+ c.gridy = ++row;
+ dialog.add(referenceField, c);
+ JButton selectReference = new JButton("Select...");
+ addActionListener(selectReference, referenceField, fileType);
+ c.gridx = 1;
+ dialog.add(selectReference, c);
+ }
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ statusOK = true;
+ FileSelectionDialog.this.dispose();
+ }
+ });
+ c.gridy = ++row;
+ dialog.add(okButton, c);
+ getContentPane ().add (dialog, "South");
+
+ row = 1;
+
+ pack();
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ setLocation (new Point ((screen.width - getSize ().width) / 2,
+ (screen.height - getSize ().height) / 2));
+ setVisible (true);
+ }
+
+ /**
+ * Add a text field to the dialog for adding in a path
+ * to a BAM file.
+ */
+ private void addBamField(String fileType, String fileName)
+ {
+ JTextField bamField = new JTextField(30);
+ bamFields.add(bamField);
+
+ if(fileName != null)
+ bamField.setText(fileName);
+ bamField.setPreferredSize(
+ new Dimension(200,bamField.getPreferredSize().height));
+ c.gridy = row;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.WEST;
+ dialog.add(new JLabel(" "+fileType+" file: "), c);
+ c.gridy = ++row;
+ dialog.add(bamField, c);
+ c.gridx = 1;
+ JButton selectBam = new JButton("Select...");
+ addActionListener(selectBam, bamField, fileType);
+ dialog.add(selectBam, c);
+
+ pack();
+ }
+
+ /**
+ * Add action listener to a button.
+ * @param fileSelectionButton
+ * @param tfield
+ * @param fileType
+ */
+ private void addActionListener(final JButton fileSelectionButton,
+ final JTextField tfield,
+ final String fileType)
+ {
+ final javax.swing.filechooser.FileFilter filter =
+ new javax.swing.filechooser.FileFilter()
+ {
+ public boolean accept(final File file)
+ {
+ if(file.isDirectory() ||
+ file.getName().matches(VCFview.VCFFILE_SUFFIX) ||
+ file.getName().matches(BamView.BAM_SUFFIX))
+ return true;
+ return false;
+ }
+
+ public String getDescription()
+ {
+ return "BAM / VCF files";
+ }
+ };
+
+ fileSelectionButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ StickyFileChooser fileChooser = new StickyFileChooser();
+ fileChooser.setMultiSelectionEnabled(true);
+ fileChooser.setFileFilter(filter);
+
+ int status = fileChooser.showOpenDialog(null);
+ if(status == StickyFileChooser.CANCEL_OPTION)
+ return;
+
+
+ File[] files = fileChooser.getSelectedFiles();
+ tfield.setText(files[0].getAbsolutePath());
+
+ if(files.length > 1)
+ {
+ for(int i=1; i<files.length; i++)
+ addBamField(fileType, files[i].getAbsolutePath());
+ }
+ }
+ });
+ }
+
+ /**
+ * Return true only if this is a list of filenames.
+ * @param filename
+ * @return
+ */
+ public static boolean isListOfFiles(String filename)
+ {
+ if(filename.startsWith("http") || filename.startsWith("ftp"))
+ {
+ try
+ {
+ URL urlBamIndexFile = new URL(filename);
+ InputStreamReader reader = new InputStreamReader(urlBamIndexFile.openStream());
+
+ BufferedReader is = new BufferedReader(reader);
+ String s = is.readLine();
+ is.close();
+ reader.close();
+
+ if(s != null && (s.trim().startsWith("http") || s.trim().startsWith("ftp")))
+ return true;
+ }
+ catch (IOException e){}
+
+ return false;
+ }
+
+ try
+ {
+ File f = new File(filename);
+ if (f.exists())
+ {
+ FileInputStream fstream = new FileInputStream(f);
+ DataInputStream in = new DataInputStream(fstream);
+ BufferedReader br = new BufferedReader(new InputStreamReader(in));
+
+ String line = br.readLine().trim();
+ br.close();
+ fstream.close();
+
+ if(line.startsWith("http") || line.startsWith("ftp"))
+ return true;
+ f = new File(line);
+ if (f.exists())
+ return true;
+ }
+ }
+ catch (IOException e)
+ {
+ return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * Read a list of file names from a file.
+ * @param filename
+ * @return
+ */
+ public static List<String> getListOfFiles(String filename)
+ {
+ List<String> bamFiles = new Vector<String>();
+ try
+ {
+ if(filename.startsWith("http"))
+ {
+ try
+ {
+ URL urlBamIndexFile = new URL(filename);
+ InputStreamReader reader = new InputStreamReader(urlBamIndexFile.openStream());
+ addFilesToListFromReader(reader, bamFiles);
+ reader.close();
+ }
+ catch (IOException e){}
+ }
+ else
+ {
+ File f = new File(filename);
+ if (f.exists())
+ {
+ FileInputStream fstream = new FileInputStream(f);
+ InputStreamReader reader = new InputStreamReader(fstream);
+ addFilesToListFromReader(reader, bamFiles);
+ reader.close();
+ }
+ }
+ }
+ catch (IOException e){}
+ return bamFiles;
+ }
+
+ /**
+ * Read a list of files in and add them to the collection.
+ * @param in
+ * @param bamFiles
+ * @throws IOException
+ */
+ private static void addFilesToListFromReader(Reader in, List<String> bamFiles)
+ throws IOException
+ {
+ BufferedReader br = new BufferedReader(in);
+ String line;
+ while ((line = br.readLine()) != null)
+ {
+ line = line.trim();
+ if(!line.equals(""))
+ bamFiles.add(line);
+ }
+ br.close();
+ }
+
+ /**
+ * Get the BAM or VCF files as a <code>List</code> of <code>String</code>'s.
+ * @return
+ */
+ public List<String> getFiles(String patternStr)
+ {
+ if(!statusOK)
+ return new Vector<String>();
+ Pattern p = Pattern.compile(patternStr);
+
+ List<String> files = new Vector<String>();
+ for(int i=0; i<bamFields.size(); i++)
+ {
+ String file = bamFields.get(i).getText();
+ if(file != null && !file.equals(""))
+ {
+ if(isListOfFiles(file))
+ {
+ List<String> filesInList = getListOfFiles(file);
+ for(int j=0; j<filesInList.size(); j++)
+ {
+ Matcher m = p.matcher(filesInList.get(j));
+ if(m.matches())
+ files.add(filesInList.get(j));
+ }
+ }
+ else
+ {
+ Matcher m = p.matcher(file);
+ if(m.matches())
+ files.add(file);
+ }
+ }
+ }
+ return files;
+ }
+
+ /**
+ * Get the reference
+ * @return
+ */
+ public String getReferenceFile()
+ {
+ return referenceField.getText();
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/GroupBamFrame.java b/uk/ac/sanger/artemis/components/alignment/GroupBamFrame.java
new file mode 100644
index 0000000..ebf7c55
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/GroupBamFrame.java
@@ -0,0 +1,320 @@
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+
+import uk.ac.sanger.artemis.components.Utilities;
+
+class GroupBamFrame extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private String[] groups = new String[]{ "Default" };
+ private JPanel groupPanel = new JPanel(new GridBagLayout());
+ private JPanel bamPanel = new JPanel(new GridBagLayout());
+ private HashMap<JCheckBoxMenuItem, JComboBox> bamGroupMap =
+ new HashMap<JCheckBoxMenuItem, JComboBox> ();
+
+ private HashMap<String, JCheckBox> groupList =
+ new HashMap<String, JCheckBox>();
+ private static final int PAD = 2;
+
+ private BamView bamView;
+ private JMenu bamFilesMenu;
+
+ GroupBamFrame(final BamView bamView, final JMenu bamFilesMenu)
+ {
+ super("Alignment Groups");
+
+ this.bamView = bamView;
+ this.bamFilesMenu = bamFilesMenu;
+
+ final JPanel centerPanel = new JPanel(new GridBagLayout());
+ final JScrollPane jsp = new JScrollPane(centerPanel);
+ getContentPane().add(jsp, BorderLayout.CENTER);
+ centerPanel.setBackground(Color.WHITE);
+
+ ///
+ /// number of groups
+ Box xBox = Box.createHorizontalBox();
+ xBox.add(new JLabel("Add Group:"));
+ final JTextField newGroup = new JTextField(10);
+ xBox.add(newGroup);
+ newGroup.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ addGroup(newGroup.getText().trim());
+ }
+ });
+ newGroup.addFocusListener(new java.awt.event.FocusAdapter() {
+ public void focusGained(java.awt.event.FocusEvent evt) {
+ SwingUtilities.invokeLater( new Runnable() {
+ public void run() {
+ newGroup.selectAll();
+ }
+ });
+ }
+ });
+
+ final JButton addGroupButton = new JButton("ADD");
+ xBox.add(addGroupButton);
+ addGroupButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ if(newGroup.getText() == null ||
+ newGroup.getText().trim().equals(""))
+ {
+ JOptionPane.showMessageDialog(GroupBamFrame.this,
+ "Type in the name for the new group.", "Group name missing",
+ JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ addGroup(newGroup.getText().trim());
+ }
+ });
+ xBox.add(Box.createHorizontalGlue());
+ getContentPane().add(xBox, BorderLayout.NORTH);
+
+ //
+ xBox = Box.createHorizontalBox();
+ final JButton close = new JButton("CLOSE");
+ close.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ setVisible(false);
+ }
+ });
+ xBox.add(close);
+ xBox.add(Box.createHorizontalGlue());
+ getContentPane().add(xBox, BorderLayout.SOUTH);
+
+
+ ///
+ ///
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridy = 0;
+ c.gridx = 0;
+ c.ipadx = PAD;
+ c.ipady = PAD;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridwidth = 2;
+ final JLabel alignLabel = new JLabel("ALIGNMENTS:");
+ alignLabel.setFont(alignLabel.getFont().deriveFont(Font.BOLD));
+ centerPanel.add(alignLabel, c);
+ c.gridwidth = 1;
+ c.gridy += 1;
+ centerPanel.add(Box.createVerticalStrut(5), c);
+ c.gridy += 1;
+
+ bamPanel.setBackground(Color.WHITE);
+ centerPanel.add(bamPanel, c);
+
+ c.gridx = 10;
+ c.gridheight = GridBagConstraints.REMAINDER;
+ centerPanel.add(groupPanel, c);
+ groupPanel.setBackground(Color.WHITE);
+
+ Utilities.rightJustifyFrame(this);
+ }
+
+ protected void updateAndDisplay()
+ {
+ updateBamPanel();
+ updateGroupPanel();
+ pack();
+ setVisible(true);
+ }
+
+ protected void addGroup(final String newGroup)
+ {
+ final String tmpGroups[] = new String[groups.length+1];
+ System.arraycopy(groups, 0, tmpGroups, 0, groups.length);
+ tmpGroups[tmpGroups.length-1] = newGroup;
+ groups = tmpGroups;
+
+ updateGroupPanel();
+ updateBamPanel();
+ validate();
+ }
+
+ private void updateBamPanel()
+ {
+ final Component cs[] = bamFilesMenu.getMenuComponents();
+ bamPanel.removeAll();
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridy = 0;
+ c.gridx = 0;
+ c.ipadx = PAD;
+ c.ipady = PAD;
+ c.anchor = GridBagConstraints.WEST;
+
+ for(Component cp : cs)
+ {
+ if(cp instanceof JCheckBoxMenuItem)
+ {
+ final JCheckBoxMenuItem cbBam = (JCheckBoxMenuItem) cp;
+ final String bam = cbBam.getText();
+ c.gridy += 1;
+ c.gridx = 0;
+ bamPanel.add(new JLabel(bam), c);
+
+ c.gridx = 1;
+ bamPanel.add(new JLabel(cbBam.getIcon()), c);
+
+ c.gridx = 2;
+ JComboBox groupCombo = new JComboBox( groups );
+ bamPanel.add(groupCombo, c);
+
+ if(bamGroupMap.containsKey(cbBam))
+ groupCombo.setSelectedItem(
+ (String) bamGroupMap.get(cbBam).getSelectedItem());
+
+ bamGroupMap.put(cbBam, groupCombo);
+ }
+ }
+ }
+
+ /**
+ * Add a BAM to a specified group
+ * @param bam
+ * @param group
+ */
+ protected void addToGroup(String bam, String group)
+ {
+ final Component cs[] = bamFilesMenu.getMenuComponents();
+ for(Component cp : cs)
+ {
+ if(cp instanceof JCheckBoxMenuItem)
+ {
+ final JCheckBoxMenuItem cbBam = (JCheckBoxMenuItem) cp;
+ final String thisBam = cbBam.getText();
+ if(bam.equals(thisBam))
+ {
+ bamGroupMap.get(cbBam).setSelectedItem(group);
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * For a give BAM file return the group it belongs to.
+ * @param bamName
+ * @return
+ */
+ protected String getGroupName(final String bamName)
+ {
+ Iterator<JCheckBoxMenuItem> it = bamGroupMap.keySet().iterator();
+ while(it.hasNext())
+ {
+ JCheckBoxMenuItem cbs = it.next();
+ if(cbs.getText().equals(bamName))
+ return (String) bamGroupMap.get(cbs).getSelectedItem();
+ }
+ return null;
+ }
+
+ protected int getNumberOfGroups()
+ {
+ return groups.length;
+ }
+
+ /**
+ * Find the maximum number of BAM files found in the groups.
+ * @return
+ */
+ protected int getMaximumBamsInGroup()
+ {
+ int max = 1;
+ int grpMax[] = new int[getNumberOfGroups()];
+ for(int i=0; i<grpMax.length; i++)
+ grpMax[i] = 0;
+ Iterator<JCheckBoxMenuItem> it = bamGroupMap.keySet().iterator();
+ while(it.hasNext())
+ {
+ JCheckBoxMenuItem cbs = it.next();
+ String grp = (String) bamGroupMap.get(cbs).getSelectedItem();
+ for(int i=0; i<groups.length; i++)
+ {
+ if(grp.equals(groups[i]))
+ {
+ grpMax[i]++;
+ break;
+ }
+ }
+ }
+
+ for(int i=0; i<grpMax.length; i++)
+ {
+ if(grpMax[i] > max)
+ max = grpMax[i];
+ }
+ return max;
+ }
+
+ private void updateGroupPanel()
+ {
+ groupPanel.removeAll();
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridy = 0;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.WEST;
+
+ final JLabel groupLabel = new JLabel("GROUPS:");
+ groupLabel.setFont(groupLabel.getFont().deriveFont(Font.BOLD));
+ groupPanel.add(groupLabel, c);
+ c.gridy += 2;
+ for(final String group: groups)
+ {
+ c.gridy += 1;
+
+ final JCheckBox cbox;
+ if(groupList.containsKey(group))
+ cbox = groupList.get(group);
+ else
+ {
+ cbox = new JCheckBox(group, true);
+ groupList.put(group, cbox);
+ }
+ groupPanel.add(cbox, c);
+ cbox.addItemListener(new ItemListener(){
+ public void itemStateChanged(ItemEvent arg0)
+ {
+ Iterator<JCheckBoxMenuItem> it = bamGroupMap.keySet().iterator();
+ while(it.hasNext())
+ {
+ JCheckBoxMenuItem cbs = it.next();
+ String thisGroup = (String) bamGroupMap.get(cbs).getSelectedItem();
+ if(group.equals(thisGroup))
+ cbs.setSelected(cbox.isSelected());
+ }
+ }
+ });
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/LineAttributes.java b/uk/ac/sanger/artemis/components/alignment/LineAttributes.java
new file mode 100644
index 0000000..f469941
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/LineAttributes.java
@@ -0,0 +1,397 @@
+/* LineAttributes.java
+ *
+ * created: 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.List;
+import java.util.Random;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JColorChooser;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.ListCellRenderer;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * Define line attributes for graphs lines
+ */
+public class LineAttributes
+{
+ /** defines the colour */
+ private Color lineColour = Color.black;
+ /** defines the line Stroke - size and style */
+ private BasicStroke stroke;
+
+ private static float dotDash[] = {10.f, 5.f, 3.f, 5.f};
+ private static float dash[] = {10.f};
+
+ private static BasicStroke style1 =
+ new BasicStroke(1.f);
+ private static BasicStroke style2 =
+ new BasicStroke(1.f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER,
+ 1.f, dash, 0.f);
+ private static BasicStroke style3 =
+ new BasicStroke(1.f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER,
+ 1.f, dotDash, 0.f);
+
+ /** available stroke types */
+ private static BasicStroke[] STROKES =
+ new BasicStroke[]{ style1, style2, style3 };
+
+ /** fill in underneath wiggle plots */
+ public static String PLOT_TYPES[] =
+ { "Open", "Filled" };
+ private String plotType = PLOT_TYPES[0];
+
+ /**
+ * Contruct a LineAttributes instance
+ * @param lineColour
+ */
+ public LineAttributes(Color lineColour)
+ {
+ this.lineColour = lineColour;
+ this.stroke = new BasicStroke(1.f);
+ }
+
+ public Color getLineColour()
+ {
+ return lineColour;
+ }
+
+ public void setLineColour(Color lineColour)
+ {
+ this.lineColour = lineColour;
+ }
+
+ public BasicStroke getStroke()
+ {
+ return stroke;
+ }
+
+ public void setStroke(BasicStroke stroke)
+ {
+ this.stroke = stroke;
+ }
+
+ protected static BasicStroke[] getStrokes()
+ {
+ return STROKES;
+ }
+
+ /**
+ * Given a stroke return the index for it based on the style.
+ * @param stroke
+ * @return
+ */
+ private static int getStyleIndex(BasicStroke stroke)
+ {
+ float myDash[] = stroke.getDashArray();
+ if(myDash != null &&
+ myDash.length == dotDash.length)
+ return 2;
+ else if(myDash != null &&
+ myDash.length == dash.length &&
+ myDash[0] == dash[0])
+ return 1;
+ else
+ return 0;
+ }
+
+ public String getPlotType()
+ {
+ return plotType;
+ }
+
+ public static LineAttributes[] init(int numPlots)
+ {
+ final Color frameColour[] = {
+ Color.red,
+ Color.blue,
+ Color.black,
+ new Color(0,200,0),
+ Color.magenta,
+ new Color(100, 100, 0),
+ new Color(0, 100, 100),
+ new Color(100, 0, 100),
+ new Color(255, 100, 0),
+ new Color(0, 255, 255),
+ new Color(100, 0, 255),
+ new Color(150, 0, 75),
+ new Color(255, 200, 0),
+ new Color(255, 0, 150)};
+
+ final LineAttributes lines[] = new LineAttributes[numPlots];
+
+ if(numPlots == 1)
+ {
+ lines[0] = new LineAttributes(Color.black);
+ return lines;
+ }
+
+ final Random rand = new Random();
+ for(int i=0; i<numPlots; i++)
+ {
+ if(i < frameColour.length)
+ lines[i] = new LineAttributes(frameColour[i]);
+ else
+ lines[i] = new LineAttributes(
+ new Color(rand.nextFloat(), rand.nextFloat(), rand.nextFloat()));
+ }
+ return lines;
+ }
+
+
+ /**
+ * Utility used by uk.ac.sanger.artemis.components.Plot
+ * @param numPlots
+ * @param lines
+ * @param plot
+ * @return
+ */
+ public static LineAttributes[] configurePlots(
+ final List<String> bamList,
+ final LineAttributes[] lines,
+ final JPanel covergePanel)
+ {
+ JPanel panel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ final int numPlots = bamList.size();
+ final LineAttributes[] thislines;
+ if(lines.length < numPlots)
+ thislines = init(numPlots);
+ else
+ thislines = lines;
+
+ int gridx = 0;
+ int gridy = 0;
+ c.gridy = gridy++;
+ c.gridx = gridx;
+ // open / filled
+ panel.add(new JLabel("Plot Options:"), c);
+ final JComboBox openPlot = new JComboBox(PLOT_TYPES);
+ openPlot.setSelectedItem(thislines[0].plotType);
+ openPlot.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ for(int i=0; i<numPlots; i++)
+ thislines[i].plotType =
+ (String) openPlot.getSelectedItem();
+ covergePanel.repaint();
+ }
+ });
+ c.gridy = gridy++;
+ panel.add(openPlot, c);
+
+ c.gridy = gridy++;
+ panel.add(new JLabel("Line Options:"), c);
+ c.gridx = gridx++;
+ c.gridy = gridy++;
+ gridx++;
+
+ final JCheckBox applyToAll = new JCheckBox("Apply colour to all");
+
+ panel.add(new JLabel("Colour"), c);
+ c.gridx = gridx++;
+ panel.add(new JLabel("Line style"), c);
+
+ c.gridx = gridx;
+ panel.add(new JLabel("Line size"), c);
+
+ for(int i=0; i<numPlots; i++)
+ {
+ c.gridy = gridy++;
+ final int colourNumber = i;
+ gridx = 0;
+
+ c.gridx = 0;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.anchor = GridBagConstraints.NORTHWEST;
+ final JLabel bamFileName = new JLabel(bamList.get(i));
+ panel.add(bamFileName,c);
+
+ c.gridwidth = 1;
+ c.gridy = gridy++;
+ final JLabel colourLabel = new JLabel(" ");
+ colourLabel.setBackground(thislines[i].getLineColour());
+ colourLabel.setOpaque(true);
+ c.gridx = gridx++;
+ panel.add(colourLabel,c);
+
+ JButton butt = new JButton("Select");
+ butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ Color newColour = JColorChooser.showDialog(null, "Colour Chooser",
+ thislines[colourNumber].getLineColour());
+
+ if(applyToAll.isSelected())
+ {
+ for(int iplot=0; iplot<numPlots; iplot++)
+ thislines[iplot].setLineColour(newColour);
+ }
+ else
+ thislines[colourNumber].setLineColour(newColour);
+ colourLabel.setBackground(thislines[colourNumber].getLineColour());
+ covergePanel.repaint();
+ }
+ });
+ c.gridx = gridx++;
+ panel.add(butt, c);
+
+ // line style
+ final JSlider slider = new JSlider(1, 10,
+ (int)lines[colourNumber].getStroke().getLineWidth());
+ Integer index[] = new Integer[STROKES.length];
+ for(int j=0; j<index.length; j++)
+ index[j] = j;
+ final JComboBox lineStyle = new JComboBox(index);
+ lineStyle.setRenderer(new LineStyleListRenderer());
+ lineStyle.setSelectedIndex(
+ LineAttributes.getStyleIndex(thislines[colourNumber].getStroke()));
+ lineStyle.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ thislines[colourNumber].setStroke(
+ STROKES[lineStyle.getSelectedIndex()]);
+ setLineSize(covergePanel, slider, thislines, colourNumber);
+ }
+ });
+ c.gridx = gridx++;
+ panel.add(lineStyle, c);
+
+
+ // line size
+ slider.addChangeListener(new ChangeListener()
+ {
+ public void stateChanged(ChangeEvent e)
+ {
+ setLineSize(covergePanel, slider, thislines, colourNumber);
+ }
+ });
+ c.gridx = gridx;
+ panel.add(slider, c);
+
+ }
+
+ c.gridx = 0;
+ c.gridy = gridy++;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.anchor = GridBagConstraints.WEST;
+ panel.add(applyToAll, c);
+
+ JScrollPane jsp = new JScrollPane(panel,
+ JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+ JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
+
+ if(panel.getPreferredSize().height > d.height/2)
+ {
+ d.setSize(jsp.getPreferredSize().width, d.height/2);
+ jsp.setPreferredSize(d);
+ }
+
+ String config_options[] = { "OK" };
+ JOptionPane.showOptionDialog(null,
+ jsp, "Configure Lines",
+ JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null, config_options, config_options[0]);
+ return thislines;
+ }
+
+
+ private static void setLineSize(JPanel covergePanel, JSlider slider,
+ LineAttributes[] thislines, int number)
+ {
+ BasicStroke oldStroke = thislines[number].getStroke();
+ BasicStroke newStroke = new BasicStroke(slider.getValue(),
+ BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER,
+ 1.f, oldStroke.getDashArray(), 0.f);
+ thislines[number].setStroke(newStroke);
+ covergePanel.repaint();
+ }
+}
+
+
+/**
+ * Renderer for the JComboBox to define different line styles.
+ */
+class LineStyleListRenderer extends JComponent implements ListCellRenderer
+{
+ private static final long serialVersionUID = 1L;
+ private int selectedIndex = 0;
+ public LineStyleListRenderer()
+ {
+ super();
+ setMinimumSize(new Dimension(100, 20));
+ setPreferredSize(new Dimension(100, 20));
+ }
+
+ /**
+ * This method finds the selected index.
+ */
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus)
+ {
+ selectedIndex = ((Integer) value).intValue();
+ return this;
+ }
+
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+ g2.setStroke(LineAttributes.getStrokes()[selectedIndex]);
+ g2.drawLine(10, 10, 90, 10);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/LookSeqPanel.java b/uk/ac/sanger/artemis/components/alignment/LookSeqPanel.java
new file mode 100644
index 0000000..75f345f
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/LookSeqPanel.java
@@ -0,0 +1,628 @@
+/* LookSeqFrame.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.color.ColorSpace;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorConvertOp;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.imageio.ImageIO;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JCheckBox;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.circular.TextFieldInt;
+import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
+import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
+import uk.ac.sanger.artemis.components.FeatureDisplay;
+import uk.ac.sanger.artemis.components.SwingWorker;
+
+public class LookSeqPanel extends JPanel
+ implements DisplayAdjustmentListener
+{
+ private static final long serialVersionUID = 1L;
+ /** image icon */
+ private BufferedImage ii;
+ /** URL lookseq service */
+ private String urlStr;
+ /** the query options (appended to lookseq service url) */
+ private String queryStr;
+
+ private FeatureDisplay feature_display;
+ private int lastStart = -1;
+ private int lastEnd = -1;
+ private int drawCrossHairAt = -1;
+ final JPopupMenu popup = new JPopupMenu("Plot Options");
+ private boolean isGreyedOut = false;
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(LookSeqPanel.class);
+
+ public LookSeqPanel()
+ {
+ super();
+ setUpPopupMenu();
+ }
+
+ public LookSeqPanel(URL url)
+ {
+ super();
+ try
+ {
+ ii = ImageIO.read(url);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ setUpPopupMenu();
+ }
+
+ public LookSeqPanel(final String urlStr, final String queryStr)
+ {
+ super();
+ setImage(urlStr, queryStr);
+ setUpPopupMenu();
+ }
+
+ /**
+ * Build the popup menu.
+ */
+ private void setUpPopupMenu()
+ {
+ JCheckBoxMenuItem optionCoverage = new JCheckBoxMenuItem("Coverage", false);
+ optionCoverage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ queryStr = queryStr.replaceFirst("view=\\w+", "view=coverage");
+ setImage(urlStr, queryStr);
+ revalidate();
+ }
+ });
+ popup.add(optionCoverage);
+
+ JCheckBoxMenuItem optionIndel = new JCheckBoxMenuItem("Paired reads", true);
+ optionIndel.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ queryStr = queryStr.replaceFirst("view=\\w+", "view=indel");
+ setImage(urlStr, queryStr);
+ revalidate();
+ }
+ });
+ popup.add(optionIndel);
+
+ JCheckBoxMenuItem optionPileup = new JCheckBoxMenuItem("Pileup", false);
+ optionPileup.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ queryStr = queryStr.replaceFirst("view=\\w+", "view=pileup");
+ setImage(urlStr, queryStr);
+ revalidate();
+ }
+ });
+ popup.add(optionPileup);
+
+ ButtonGroup group = new ButtonGroup();
+ group.add(optionCoverage);
+ group.add(optionIndel);
+ group.add(optionPileup);
+
+ popup.add(new JSeparator());
+ final JCheckBoxMenuItem perfectIndel =
+ new JCheckBoxMenuItem("Show perfect paired matches", true);
+ perfectIndel.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setDisplayOption(perfectIndel.isSelected(), "perfect");
+ }
+ });
+ popup.add(perfectIndel);
+
+ final JCheckBoxMenuItem snps =
+ new JCheckBoxMenuItem("Show paired reads with SNPs", true);
+ snps.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setDisplayOption(snps.isSelected(), "snps");
+ }
+ });
+ popup.add(snps);
+
+ final JCheckBoxMenuItem inversions =
+ new JCheckBoxMenuItem("Show inversions", true);
+ inversions.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setDisplayOption(inversions.isSelected(), "inversions");
+ }
+ });
+ popup.add(inversions);
+
+ final JCheckBoxMenuItem single =
+ new JCheckBoxMenuItem("Show single reads", false);
+ single.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setDisplayOption(single.isSelected(), "single");
+ }
+ });
+ popup.add(single);
+
+ final JCheckBoxMenuItem inversions_ext =
+ new JCheckBoxMenuItem("Show inversions_ext", false);
+ inversions_ext.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setDisplayOption(inversions_ext.isSelected(), "inversions_ext");
+ }
+ });
+ popup.add(inversions_ext);
+
+ final JCheckBoxMenuItem pairlinks =
+ new JCheckBoxMenuItem("Show link pairs", true);
+ pairlinks.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setDisplayOption(pairlinks.isSelected(), "pairlinks");
+ }
+ });
+ popup.add(pairlinks);
+
+ final JCheckBoxMenuItem potsnps =
+ new JCheckBoxMenuItem("Show known SNPs", true);
+ potsnps.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setDisplayOption(potsnps.isSelected(), "potsnps");
+ }
+ });
+ popup.add(potsnps);
+
+ final JCheckBoxMenuItem uniqueness =
+ new JCheckBoxMenuItem("Show uniqueness", true);
+ uniqueness.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setDisplayOption(inversions_ext.isSelected(), "uniqueness");
+ }
+ });
+ popup.add(uniqueness);
+ popup.add(new JSeparator());
+
+ final JMenuItem setIndelMaxSize = new JMenuItem("Maximum InDel Size...");
+ setIndelMaxSize.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setMaxIndelSize();
+ }
+ });
+ popup.add(setIndelMaxSize);
+ addMouseListener(mouseListener);
+ }
+
+ public void setUrl(final String urlStr, final String queryStr)
+ {
+ this.urlStr = urlStr;
+ this.queryStr = queryStr;
+ }
+
+ public void setImage(final String urlStr, final String queryStr)
+ {
+ this.urlStr = urlStr;
+ this.queryStr = queryStr;
+
+ try
+ {
+ ii = ImageIO.read(new URL(urlStr+queryStr));
+
+ setPreferredSize(new Dimension(ii.getWidth(), ii.getHeight()));
+ logger4j.debug("LookSeq URL :: "+urlStr+queryStr);
+ logger4j.debug("Proxy Settings :: "+System.getProperty("http.proxyHost")+":"+
+ System.getProperty("http.proxyPort"));
+ }
+ catch (MalformedURLException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Override the paintComponent
+ */
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ if(ii != null)
+ {
+ if(isGreyedOut)
+ {
+ ColorSpace csGray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
+ new ImageIcon(new ColorConvertOp(csGray, null).filter(ii, null)).paintIcon(this,g,0,0);
+ }
+ else
+ new ImageIcon(ii).paintIcon(this,g,0,0);
+ }
+
+ if(drawCrossHairAt > -1)
+ drawCrossHair(g);
+ }
+
+ private void drawCrossHair(Graphics g)
+ {
+ int xpos = (int) ((((float)drawCrossHairAt/(lastEnd-lastStart)))*getWidth());
+ g.drawLine(xpos, 0, xpos, getHeight());
+ g.drawString(Integer.toString(drawCrossHairAt+lastStart), xpos+1, 10);
+ }
+
+ /**
+ * Set the maximum size.
+ */
+ private void setMaxIndelSize()
+ {
+ JPanel panel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ String maxdist = getText(queryStr,"maxdist=");
+ c.gridx = 0;
+ c.gridy = 0;
+ panel.add(new JLabel("Set maximum indel size"), c);
+
+ TextFieldInt maxIndel = new TextFieldInt();
+ maxIndel.setColumns(10);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ panel.add(maxIndel, c);
+
+ JCheckBox defaultValue = new JCheckBox("use default");
+ c.gridy = 1;
+ panel.add(defaultValue, c);
+
+ if(maxdist == null)
+ defaultValue.setSelected(true);
+ else
+ {
+ defaultValue.setSelected(false);
+ maxIndel.setValue(Integer.parseInt(maxdist));
+ }
+
+ String window_options[] = { "Set", "Cancel" };
+ int select = JOptionPane.showOptionDialog(null,
+ panel, "Lookseq Options", JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE, null, window_options,
+ window_options[0]);
+
+ if(select == 1)
+ return;
+ if(defaultValue.isSelected())
+ queryStr = queryStr.replaceFirst(
+ "&maxdist=\\w+", "");
+ else
+ {
+ if(queryStr.indexOf("maxdist=") > -1)
+ queryStr = queryStr.replaceFirst(
+ "&maxdist=\\w+", "&maxdist="+maxIndel.getText().trim());
+ else
+ queryStr = queryStr.concat("&maxdist="+maxIndel.getText().trim());
+ }
+
+ setImage(urlStr, queryStr);
+ repaint();
+ revalidate();
+ }
+
+ /**
+ * Called when the display is changed.
+ */
+ public void displayAdjustmentValueChanged(final DisplayAdjustmentEvent event)
+ {
+ SwingWorker worker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ if(lastStart == event.getStart() && lastEnd == event.getEnd())
+ return null;
+ try
+ {
+ Thread.currentThread().sleep(500);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+
+ if(event.getStart() != ((FeatureDisplay)event.getSource()).getForwardBaseAtLeftEdge())
+ {
+ if(!isGreyedOut)
+ {
+ isGreyedOut = true;
+ repaint();
+ }
+ return null;
+ }
+
+ isGreyedOut = false;
+ lastStart = event.getStart();
+ lastEnd = event.getEnd();
+ setDisplay(lastStart, lastEnd, event);
+ return null;
+ }
+ };
+ worker.start();
+ }
+
+ /**
+ * Set the start and end base positions to display.
+ * @param start
+ * @param end
+ * @param event
+ */
+ public void setDisplay(int start,
+ int end,
+ DisplayAdjustmentEvent event)
+ {
+ queryStr = queryStr.replaceFirst("from=\\d+", "from="+Integer.toString(start));
+ queryStr = queryStr.replaceFirst("to=\\d+", "to="+Integer.toString(end));
+
+ if(feature_display != null)
+ {
+ int width = feature_display.getSize().width;
+
+ int possible_last_base =
+ (int)(feature_display.getForwardBaseAtLeftEdge() +
+ feature_display.getMaxVisibleBases());
+
+ // scale the image when scrolled passed the sequence length
+ if(possible_last_base > end)
+ width = (int)(width * (float)((float)(end-start)/
+ (float)(possible_last_base-start)));
+
+ queryStr = queryStr.replaceFirst("width=\\d+", "width="+Integer.toString(width));
+ }
+
+ setImage(urlStr, queryStr);
+ drawCrossHairAt = -1;
+ repaint();
+ if(event == null ||
+ event.getType() == DisplayAdjustmentEvent.SCALE_ADJUST_EVENT)
+ revalidate();
+ }
+
+ /**
+ * Utility for changing display options.
+ * @param selected
+ * @param option
+ */
+ private void setDisplayOption(boolean selected, String option)
+ {
+ if(selected)
+ queryStr = queryStr.replaceFirst("display=", "display=\\|"+option);
+ else
+ queryStr = queryStr.replaceAll("\\|"+option, "");
+
+ setImage(urlStr, queryStr);
+ repaint();
+ revalidate();
+ }
+
+ /**
+ * Get the base position from the pixel position on the panel
+ * @param position
+ * @return
+ */
+ private int getBasePositionAt(int position)
+ {
+ return (int) ((((float)position)/getWidth())*(lastEnd-lastStart));
+ }
+
+ final MouseListener mouseListener = new MouseAdapter()
+ {
+ /**
+ * Listen for mouse press events.
+ **/
+ public void mousePressed(MouseEvent event)
+ {
+ if(event.isPopupTrigger())
+ popup.show(LookSeqPanel.this, event.getX(), event.getY());
+ else if(event.getClickCount() == 1 &&
+ event.getButton() == MouseEvent.BUTTON1)
+ {
+ drawCrossHairAt = getBasePositionAt(event.getX());
+ repaint();
+ }
+ else if(event.getClickCount() == 2)
+ {
+ drawCrossHairAt = -1;
+ repaint();
+ }
+ }
+ };
+
+ public void setFeatureDisplay(FeatureDisplay feature_display)
+ {
+ this.feature_display = feature_display;
+ }
+
+ /**
+ * Show Lookseq options
+ */
+ public void showOptions()
+ {
+ final JPanel optionsPanel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ final JTextField urlStrField = new JTextField(urlStr);
+ c.gridy = 0;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ optionsPanel.add(new JLabel("Server:"), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ optionsPanel.add(urlStrField, c);
+
+ String sampleStr = getText(queryStr,"chr=");
+ final JTextField sampleField = new JTextField(sampleStr,40);
+ c.gridy = 1;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ optionsPanel.add(new JLabel("Sample:"), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ optionsPanel.add(sampleField, c);
+
+ String laneStr = getText(queryStr,"lane=");
+ final JTextField laneField = new JTextField(laneStr,40);
+ c.gridy = 2;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ optionsPanel.add(new JLabel("Lane:"), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ optionsPanel.add(laneField, c);
+
+
+
+ String proxyHost = "";
+ if(System.getProperty("http.proxyHost") != null)
+ proxyHost = System.getProperty("http.proxyHost");
+
+ final JTextField proxyHostField = new JTextField(proxyHost,40);
+ c.gridy = 3;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ optionsPanel.add(new JLabel("Proxy Host:"), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ optionsPanel.add(proxyHostField, c);
+
+
+ String proxyPort = "";
+ if(System.getProperty("http.proxyPort") != null)
+ proxyPort = System.getProperty("http.proxyPort");
+
+ final JTextField proxyPortField = new JTextField(proxyPort,40);
+ c.gridy = 4;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ optionsPanel.add(new JLabel("Proxy Port:"), c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ optionsPanel.add(proxyPortField, c);
+
+
+ String window_options[] = { "Display" };
+ JOptionPane.showOptionDialog(null,
+ optionsPanel,
+ "Lookseq Options", JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE, null, window_options,
+ window_options[0]);
+
+ if(!proxyHostField.getText().trim().equals(""))
+ {
+ System.getProperties().put("http.proxyHost", proxyHostField.getText().trim());
+ System.getProperties().put("http.proxyPort", proxyPortField.getText().trim());
+ System.getProperties().put("proxySet","true");
+ }
+
+ urlStr = urlStrField.getText().trim();
+ queryStr = queryStr.replaceFirst(
+ "chr=[^&]+", "chr="+sampleField.getText().trim());
+
+ queryStr = queryStr.replaceFirst(
+ "lane=[^&]+", "lane="+laneField.getText().trim());
+ }
+
+ /**
+ * Return the value of a sub-string in a string
+ * @param str
+ * @param subStr
+ * @return
+ */
+ private String getText(String str, String subStr)
+ {
+ int beginIndex = str.indexOf(subStr);
+ if(beginIndex == -1)
+ return null;
+
+ int endIndex = str.indexOf("&", beginIndex);
+ if(endIndex == -1)
+ endIndex = str.length();
+ return str.substring(beginIndex+subStr.length(), endIndex);
+ }
+
+ public String getQueryStr()
+ {
+ return queryStr;
+ }
+
+ public static void main(String args[])
+ {
+ String urlStr = "http://www.sanger.ac.uk/cgi-bin/teams/team112/lookseq/get_data.pl?";
+ String queryStr = "from=157682&to=479328&chr=MAL1&output=image&width=1024&lane=sample_2a&view=indel&display=|perfect|snps|inversions|pairlinks|potsnps|uniqueness|&debug=0";
+ LookSeqPanel lookseq = new LookSeqPanel(urlStr, queryStr);
+
+ JFrame f = new JFrame();
+ f.getContentPane().add(lookseq);
+ f.pack();
+ f.setVisible(true);
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/components/alignment/MappedReads.java b/uk/ac/sanger/artemis/components/alignment/MappedReads.java
new file mode 100644
index 0000000..7b13366
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/MappedReads.java
@@ -0,0 +1,916 @@
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.text.DecimalFormat;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.components.FeatureDisplay;
+import uk.ac.sanger.artemis.components.FileViewer;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+import net.sf.samtools.SAMFileReader;
+
+public class MappedReads
+{
+ private JProgressBar progressBar;
+ private JLabel progressTxt = new JLabel();
+
+ private FeatureVector features;
+ private String refName;
+ private Hashtable<String, SAMFileReader> samFileReaderHash;
+ private List<String> bamList;
+ private List<Short> hideBamList;
+ private Vector<String> seqNames;
+ private HashMap<String, Integer> offsetLengths;
+ private boolean concatSequences;
+ private HashMap<String, Integer> seqLengths;
+ private int sequenceLength;
+ private SAMRecordPredicate samRecordFlagPredicate;
+ private SAMRecordMapQPredicate samRecordMapQPredicate;
+ private boolean contained;
+ private boolean useIntrons;
+ private boolean useStrandTag = false;
+ private JDialog dialog = new JDialog((JFrame)null, "Calculating", true);;
+
+ private int mappedReads[];
+
+ /**
+ * Calculate the total number of mapped reads.
+ * @param refName
+ * @param samFileReaderHash
+ * @param bamList
+ * @param seqNames
+ * @param offsetLengths
+ * @param concatSequences
+ * @param seqLengths
+ * @param sequenceLength
+ */
+ public MappedReads(
+ final FeatureVector features,
+ final String refName,
+ final Hashtable<String, SAMFileReader> samFileReaderHash,
+ final List<String> bamList,
+ final Vector<String> seqNames,
+ final HashMap<String, Integer> offsetLengths,
+ final boolean concatSequences,
+ final HashMap<String, Integer> seqLengths,
+ final int sequenceLength,
+ final SAMRecordPredicate samRecordFlagPredicate,
+ SAMRecordMapQPredicate samRecordMapQPredicate,
+ final boolean contained,
+ final boolean useIntrons,
+ final boolean allRefSeqs,
+ final boolean useStrandTag)
+ {
+ this.features = features;
+ this.refName = refName;
+ this.samFileReaderHash = samFileReaderHash;
+ this.bamList = bamList;
+ this.seqNames = seqNames;
+ this.offsetLengths = offsetLengths;
+ this.concatSequences = concatSequences;
+ this.seqLengths = seqLengths;
+ this.sequenceLength = sequenceLength;
+ this.samRecordFlagPredicate = samRecordFlagPredicate;
+ this.samRecordMapQPredicate = samRecordMapQPredicate;
+ this.contained = contained;
+ this.useIntrons = useIntrons;
+ this.useStrandTag = useStrandTag;
+
+ progressBar = new JProgressBar(0, sequenceLength);
+ progressBar.setValue(0);
+ progressBar.setStringPainted(true);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ progressTxt.setText("Total number of mapped reads");
+ panel.add(progressTxt, BorderLayout.NORTH);
+ panel.add(progressBar, BorderLayout.CENTER);
+
+ dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ panel.setOpaque(true);
+ dialog.setContentPane(panel);
+ dialog.pack();
+ centerDialog();
+
+ CalculateTotalMappedReads cmr = new CalculateTotalMappedReads(allRefSeqs);
+ cmr.start();
+ dialog.setVisible(true);
+ }
+
+ /**
+ * Read count for selected features.
+ * @param features
+ * @param refName
+ * @param samFileReaderHash
+ * @param bamList
+ * @param seqNames
+ * @param offsetLengths
+ * @param concatSequences
+ * @param seqLengths
+ * @param samRecordFlagPredicate
+ * @param samRecordMapQPredicate
+ * @param contained
+ * @param useIntrons
+ */
+ public MappedReads(
+ final FeatureVector features,
+ final String refName,
+ final Hashtable<String, SAMFileReader> samFileReaderHash,
+ final List<String> bamList,
+ final Vector<String> seqNames,
+ final HashMap<String, Integer> offsetLengths,
+ final boolean concatSequences,
+ final HashMap<String, Integer> seqLengths,
+ final SAMRecordPredicate samRecordFlagPredicate,
+ final SAMRecordMapQPredicate samRecordMapQPredicate,
+ final boolean contained,
+ final boolean useIntrons,
+ final boolean useStrandTag)
+ {
+ this.features = features;
+ this.refName = refName;
+ this.samFileReaderHash = samFileReaderHash;
+ this.bamList = bamList;
+ this.seqNames = seqNames;
+ this.offsetLengths = offsetLengths;
+ this.concatSequences = concatSequences;
+ this.seqLengths = seqLengths;
+ this.samRecordFlagPredicate = samRecordFlagPredicate;
+ this.samRecordMapQPredicate = samRecordMapQPredicate;
+ this.contained = contained;
+ this.useIntrons = useIntrons;
+ this.useStrandTag = useStrandTag;
+
+ progressBar = new JProgressBar(0, features.size());
+ progressBar.setValue(0);
+ progressBar.setStringPainted(true);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ progressTxt.setText("Number of mapped reads for "+features.size()+" features");
+ panel.add(progressTxt, BorderLayout.NORTH);
+ panel.add(progressBar, BorderLayout.CENTER);
+
+ panel.setOpaque(true);
+ dialog.setContentPane(panel);
+ dialog.pack();
+ centerDialog();
+
+ CalculateMappedReads cmr = new CalculateMappedReads();
+ cmr.start();
+ dialog.setVisible(true);
+ }
+
+
+
+ /**
+ * Search for new features based on a threshold of read counts in the intergenic
+ * and anti-sense regions of existing annotations. This should exclude rRNA and
+ * tRNA regions. If two or more BAM files are loaded it should create features
+ * based on the combined read peak span.
+ * @param refName
+ * @param bamView
+ * @param samFileReaderHash
+ * @param bamList
+ * @param seqNames
+ * @param offsetLengths
+ * @param concatSequences
+ * @param seqLengths
+ * @param groupsFrame
+ * @param threshold
+ * @param minSize
+ * @param minBams
+ * @param readsOnOppositeStrand assume reads are on the opposite strand if true.
+ * @param contained
+ */
+ public MappedReads(
+ final String refName,
+ final BamView bamView,
+ final Hashtable<String, SAMFileReader> samFileReaderHash,
+ final Vector<String> seqNames,
+ final HashMap<String, Integer> offsetLengths,
+ final boolean concatSequences,
+ final HashMap<String, Integer> seqLengths,
+ final GroupBamFrame groupsFrame,
+ final int threshold,
+ final int minSize,
+ final int minBams,
+ final boolean readsOnOppositeStrand,
+ final boolean contained)
+ {
+ this.refName = refName;
+ this.samFileReaderHash = samFileReaderHash;
+ this.bamList = bamView.bamList;
+ this.hideBamList = bamView.hideBamList;
+ this.seqNames = seqNames;
+ this.offsetLengths = offsetLengths;
+ this.concatSequences = concatSequences;
+ this.seqLengths = seqLengths;
+ this.samRecordFlagPredicate = bamView.getSamRecordFlagPredicate();
+ this.samRecordMapQPredicate = bamView.getSamRecordMapQPredicate();
+
+ this.contained = contained;
+
+ final FeatureDisplay feature_display = bamView.getFeatureDisplay();
+ progressBar = new JProgressBar(0, feature_display.getSequenceLength());
+ progressBar.setValue(0);
+ progressBar.setStringPainted(true);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ progressTxt.setText("");
+ panel.add(progressTxt, BorderLayout.NORTH);
+ panel.add(progressBar, BorderLayout.CENTER);
+
+ panel.setOpaque(true);
+ dialog.setTitle("Search");
+ dialog.setContentPane(panel);
+ dialog.pack();
+ centerDialog();
+
+ final CalculateNewFeatures cmr = new CalculateNewFeatures(
+ feature_display, refName, groupsFrame, threshold, minSize, minBams, readsOnOppositeStrand);
+ cmr.start();
+ dialog.setVisible(true);
+ }
+
+ private void centerDialog()
+ {
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ final int x_position =(screen.width - dialog.getSize().width) / 2;
+ int y_position =(screen.height - dialog.getSize().height) / 2;
+
+ if(y_position < 10)
+ y_position = 10;
+ dialog.setLocation(new Point(x_position, y_position));
+ }
+
+ class CalculateMappedReads extends SwingWorker
+ {
+ Hashtable<String, List<ReadCount>> featureReadCount;
+ public Object construct()
+ {
+ featureReadCount = new Hashtable<String, List<ReadCount>>();
+ for (int i = 0; i < features.size(); i++)
+ {
+ Feature f = features.elementAt(i);
+ progressBar.setValue(i);
+
+ int start = f.getRawFirstBase();
+ int end = f.getRawLastBase();
+ float fLen = BamUtils.getFeatureLength(f);
+ List<ReadCount> sampleCounts = new Vector<ReadCount>();
+
+ for (int j = 0; j < bamList.size(); j++)
+ {
+ String bam = bamList.get(j);
+ float cnt[] = new float[2];
+
+ if (!useIntrons && f.getSegments().size() > 1)
+ {
+ for (int k = 0; k < f.getSegments().size(); k++)
+ {
+ start = f.getSegments().elementAt(k).getRawRange().getStart();
+ end = f.getSegments().elementAt(k).getRawRange().getEnd();
+ float tmpcnt[] = new float[2];
+ tmpcnt = BamUtils.getCount(start, end, bam, refName, samFileReaderHash,
+ seqNames, offsetLengths, concatSequences, seqLengths,
+ samRecordFlagPredicate, samRecordMapQPredicate, contained, useStrandTag);
+ cnt[0] += tmpcnt[0];
+ cnt[1] += tmpcnt[1];
+ }
+ }
+ else
+ cnt = BamUtils.getCount(start, end, bam, refName, samFileReaderHash, seqNames,
+ offsetLengths, concatSequences, seqLengths,
+ samRecordFlagPredicate, samRecordMapQPredicate, contained, useStrandTag);
+
+ if (mappedReads != null)
+ {
+ cnt[0] = (cnt[0] / (((float) mappedReads[j] / 1000000.f) * (fLen / 1000.f)));
+ cnt[1] = (cnt[1] / (((float) mappedReads[j] / 1000000.f) * (fLen / 1000.f)));
+ }
+
+ sampleCounts.add( new ReadCount(cnt, f.isForwardFeature()) );
+ }
+
+ featureReadCount.put(ReadCountDialog.getFeatureName(f), sampleCounts);
+ }
+ return null;
+ }
+
+ public void finished()
+ {
+ final DecimalFormat df;
+ if(mappedReads != null)
+ df = new DecimalFormat("0.000");
+ else
+ df = new DecimalFormat("0");
+
+ // use two possible titles used for the truncated names and
+ // the full names of BAM's when saved to file
+ final StringBuilder hdr = new StringBuilder();
+ final StringBuilder title = new StringBuilder();
+ final StringBuilder titleToSave = new StringBuilder();
+ final StringBuilder body = new StringBuilder();
+
+ for (int j = 0; j < bamList.size(); j++)
+ {
+ String bam = bamList.get(j);
+ hdr.append("#BAM: " + bam);
+ if (mappedReads != null)
+ {
+ final DecimalFormat df2 = new DecimalFormat("0.000000");
+ hdr.append(" Mapped Reads/million: "
+ + df2.format(((float) mappedReads[j]) / 1000000.f));
+ }
+ hdr.append("\n");
+ }
+ hdr.append("\n");
+
+ final Enumeration<String> enumFeat = featureReadCount.keys();
+ int n = 0;
+ while (enumFeat.hasMoreElements())
+ {
+ final String fId = enumFeat.nextElement();
+ if(n == 0)
+ {
+ for (int i = 0; i < fId.length(); i++)
+ {
+ titleToSave.append(" ");
+ title.append(" ");
+ }
+ titleToSave.append("\t");
+ title.append("\t");
+ for (String bam: bamList)
+ {
+ String name = new File(bam).getName();
+ titleToSave.append(name+"\t");
+ if(mappedReads == null)
+ {
+ if(name.length() > 21)
+ name = " "+name.substring(0, 19) + "...";
+ else
+ {
+ int len = 21-name.length();
+ name = " "+name;
+ for (int i = 0; i < len; i++)
+ name = name+" ";
+ }
+ }
+ else if(mappedReads != null)
+ {
+ if(name.length() > 28)
+ name = " "+name.substring(0, 26) + "...";
+ else
+ {
+ int len = 28-name.length();
+ name = " "+name;
+ for (int i = 0; i < len; i++)
+ name = name+" ";
+ }
+ }
+ title.append(name+"\t");
+ }
+
+ title.append("\n");
+ titleToSave.append("\n");
+ for (int i = 0; i < fId.length(); i++)
+ {
+ title.append(" ");
+ titleToSave.append(" ");
+ }
+ title.append("\t");
+ titleToSave.append("\t");
+ for (int j = 0; j < bamList.size(); j++)
+ {
+ if(mappedReads != null)
+ {
+ title.append(" Sense Antisense Total\t");
+ titleToSave.append(" Sense Antisense Total\t");
+ }
+ else
+ {
+ title.append(" Sense Antisense Total\t");
+ titleToSave.append(" Sense Antisense Total\t");
+ }
+ }
+ title.append("\n");
+ titleToSave.append("\n");
+ }
+
+ body.append(fId + "\t");
+ final List<ReadCount> cnts = featureReadCount.get(fId);
+
+ for (ReadCount c: cnts)
+ {
+ pad(body, c.senseCnt, df);
+ body.append(" ");
+ pad(body, c.antiCnt, df);
+ body.append(" ");
+ pad(body, c.senseCnt+c.antiCnt, df);
+ body.append("\t");
+ }
+ body.append("\n");
+ n++;
+ }
+
+ final FileViewer viewer;
+ final ActionListener saveListener = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ FileViewer.writeToFile(hdr.toString()+
+ titleToSave.toString()+body.toString());
+ }
+ };
+ if (mappedReads != null)
+ viewer = new FileViewer("RPKM", true, false, true, saveListener);
+ else
+ viewer = new FileViewer("Read Count", true, false, true, saveListener);
+ viewer.getTextPane().setText(hdr.toString()+title.toString()+body.toString());
+
+ dialog.dispose();
+ }
+ }
+
+ private static void pad(StringBuilder buff, float f, final DecimalFormat df)
+ {
+ if(f < 10)
+ buff.append(" ");
+ else if(f < 100)
+ buff.append(" ");
+ else if(f < 1000)
+ buff.append(" ");
+ else if(f < 10000)
+ buff.append(" ");
+ else if(f < 100000)
+ buff.append(" ");
+ else if(f < 1000000)
+ buff.append(" ");
+ buff.append(df.format(f));
+ }
+
+ class CalculateTotalMappedReads extends SwingWorker
+ {
+ private boolean useAllRefSeqs;
+ CalculateTotalMappedReads(boolean useAllRefSeqs)
+ {
+ this.useAllRefSeqs = useAllRefSeqs;
+ }
+
+ public Object construct()
+ {
+ mappedReads = new int[bamList.size()];
+ if(concatSequences || !useAllRefSeqs)
+ calc(refName, sequenceLength);
+ else
+ {
+ for (String name : seqNames)
+ {
+ progressTxt.setText(name);
+ int thisLength = seqLengths.get(name);
+ progressBar.setValue(0);
+ progressBar.setMaximum(thisLength);
+ calc(name, thisLength);
+ }
+ }
+
+ progressBar.setValue(0);
+ progressBar.setMaximum(features.size());
+ progressTxt.setText("RPKM values for "+features.size()+" features");
+ CalculateMappedReads cmr = new CalculateMappedReads();
+ cmr.start();
+ return null;
+ }
+
+ private void calc(final String refName, final int sequenceLength)
+ {
+ int MAX_BASE_CHUNK = 2000 * 60;
+ boolean contained = false;
+ for (int i = 0; i < sequenceLength; i += MAX_BASE_CHUNK)
+ {
+ progressBar.setValue(i);
+ int sbegc = i;
+ int sendc = i + MAX_BASE_CHUNK - 1;
+
+ for (int j = 0; j < bamList.size(); j++)
+ {
+ String bam = bamList.get(j);
+ if (concatSequences)
+ {
+ int len = 0;
+ int lastLen = 1;
+ for (String name : seqNames)
+ {
+ int thisLength = seqLengths.get(name);
+ len += thisLength;
+
+ if ((lastLen >= sbegc && lastLen < sendc)
+ || (len >= sbegc && len < sendc)
+ || (sbegc >= lastLen && sbegc < len)
+ || (sendc >= lastLen && sendc < len))
+ {
+ int offset = offsetLengths.get(name);
+ int thisStart = sbegc - offset;
+ if (thisStart < 1)
+ thisStart = 1;
+ int thisEnd = sendc - offset;
+ if (thisEnd > thisLength)
+ thisEnd = thisLength;
+
+ mappedReads[j] += BamUtils.count(bam, samFileReaderHash, name,
+ thisStart, thisEnd, samRecordFlagPredicate,
+ samRecordMapQPredicate, contained, false, useStrandTag)[0];
+
+ }
+ lastLen = len;
+ }
+ }
+ else
+ {
+ mappedReads[j] += BamUtils.count(bam, samFileReaderHash, refName, sbegc,
+ sendc, samRecordFlagPredicate, samRecordMapQPredicate,
+ contained, false, useStrandTag)[0];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Find new features from read count peaks.
+ */
+ class CalculateNewFeatures extends SwingWorker
+ {
+ private EntryGroup entryGroup;
+ private Bases bases;
+ private String refSeq;
+ private int threshold;
+ private int minSize;
+ private int minBams;
+ private boolean readsOnOppositeStrand;
+ private GroupBamFrame groupsFrame;
+
+ CalculateNewFeatures(final FeatureDisplay feature_display,
+ final String refSeq,
+ final GroupBamFrame groupsFrame,
+ final int threshold,
+ final int minSize,
+ final int minBams,
+ final boolean readsOnOppositeStrand)
+ {
+ entryGroup = feature_display.getEntryGroup();
+ bases = feature_display.getBases();
+ this.refSeq = refSeq;
+ this.groupsFrame = groupsFrame;
+ this.threshold = threshold;
+ this.minSize = minSize;
+ this.minBams = minBams;
+ this.readsOnOppositeStrand = readsOnOppositeStrand;
+ }
+
+ class MarkerObj
+ {
+ MarkerRange r;
+ int bamIdx;
+ MarkerObj(MarkerRange r, int bamIdx)
+ {
+ this.r = r;
+ this.bamIdx = bamIdx;
+ }
+ }
+
+ public Object construct()
+ {
+ final int MAX_BASE_CHUNK = 2000 * 60;
+ Key excluseKeys[] = { new Key("rRNA"), new Key("tRNA") };
+
+ final int beg = 1;
+ final int end = bases.getLength();
+
+ int fwdStart = -1;
+ int revStart = -1;
+ final List<MarkerObj> fwdMarkers = new Vector<MarkerObj>();
+ final List<MarkerObj> revMarkers = new Vector<MarkerObj>();
+ for (short i = 0; i < bamList.size(); i++)
+ {
+ if(hideBamList.contains(i))
+ continue;
+
+ for(int j=beg; j<end; j+=MAX_BASE_CHUNK)
+ {
+ progressBar.setValue((j + (i*end)) / bamList.size());
+ if(j > end)
+ continue;
+ int start = j;
+ int stop = j+MAX_BASE_CHUNK;
+
+ try
+ {
+ int cnt[][] = new int[stop-start+1][2];
+ for (int row = 0; row < cnt.length; row++)
+ for (int col = 0; col < 2; col++)
+ cnt[row][col] = 0;
+
+ if (concatSequences)
+ {
+ for (String name : seqNames)
+ {
+ int len = seqLengths.get(name);
+ int offset = offsetLengths.get(name);
+
+ if( (start >= offset && start <= offset+len) ||
+ (stop >= offset && start <= offset+len) )
+ {
+ int thisStart = start-offset;
+ if(thisStart < 1)
+ thisStart = 1;
+ int thisEnd = stop-offset;
+ if(thisEnd > len)
+ thisEnd = len;
+
+ int concatShift = 0;
+ if(offset > start)
+ concatShift = offset-start;
+
+ cnt =
+ BamUtils.countOverRange(bamList.get(i), samFileReaderHash,
+ name, thisStart, thisEnd, concatShift, cnt,
+ samRecordFlagPredicate, samRecordMapQPredicate);
+ }
+ }
+ }
+ else
+ cnt =
+ BamUtils.countOverRange(bamList.get(i), samFileReaderHash,
+ refSeq, start, stop, 0, cnt,
+ samRecordFlagPredicate, samRecordMapQPredicate);
+
+ for(int k=0; k<cnt.length; k++)
+ {
+ final Range r = new Range(start+k, start+k+1);
+ // find forward strand potential features
+ fwdStart = findFeatures(
+ (!readsOnOppositeStrand ? cnt[k][0] : cnt[k][1]), true,
+ fwdStart, j+k, r, excluseKeys, fwdMarkers, entryGroup, i);
+
+ // find reverse strand potential features
+ revStart = findFeatures(
+ (!readsOnOppositeStrand ? cnt[k][1] : cnt[k][0]), false,
+ revStart, j+k, r, excluseKeys, revMarkers, entryGroup, i);
+ }
+ }
+ catch (OutOfRangeException e1)
+ {
+ e1.printStackTrace();
+ }
+ }
+ }
+
+ final Entry newEntry = entryGroup.createEntry (
+ "align_"+threshold+"_"+minBams+"_"+minSize+(!readsOnOppositeStrand ? "":"_opp"));
+ createFeatures(fwdMarkers, true, newEntry);
+ createFeatures(revMarkers, false, newEntry);
+ return null;
+ }
+
+
+ private void createFeatures(final List<MarkerObj> markers, final boolean isFwd, final Entry newEntry)
+ {
+ final Key key = Key.CDS;
+
+ // merge overlapping markers
+ final Set<Integer> ignoreMarkers = new HashSet<Integer>();
+ for(int i=0; i<markers.size(); i++)
+ {
+ if(ignoreMarkers.contains(i))
+ continue;
+
+ Set<Integer> bamIdxList = new HashSet<Integer>();
+ MarkerObj mi = markers.get(i);
+ MarkerRange mri = mi.r;
+ bamIdxList.add(mi.bamIdx);
+ for(int j=i+1; j<markers.size(); j++)
+ {
+ if(ignoreMarkers.contains(j))
+ continue;
+ MarkerObj mj = markers.get(j);
+ MarkerRange mrj = mj.r;
+
+ if(mri.overlaps(mrj))
+ {
+ bamIdxList.add(mj.bamIdx);
+ mri = mri.combineRanges(mrj, false);
+ ignoreMarkers.add(j);
+ }
+ }
+
+ if(bamIdxList.size() >= minBams)
+ {
+ if(groupsFrame != null && minBams > 1)
+ {
+ boolean foundMinBams = false;
+ Hashtable<String, Integer> groupCluster = new Hashtable<String, Integer>();
+ for(int j=0; j<bamIdxList.size(); j++)
+ {
+ File f = new File(bamList.get(j));
+ String grp = groupsFrame.getGroupName( f.getName() );
+ if(groupCluster.containsKey(grp))
+ {
+ int val = groupCluster.get(grp)+1;
+ if(val >= minBams)
+ {
+ foundMinBams = true;
+ break;
+ }
+ groupCluster.put(grp, val);
+ }
+ else
+ groupCluster.put(grp, 1);
+ }
+ if(!foundMinBams)
+ continue;
+ }
+
+ // create a new feature
+ try
+ {
+ newEntry.createFeature(key,
+ ( isFwd ? mri.createLocation() : mri.createLocation().getComplement()), null);
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * Search for new features based on read counts.
+ * @param cnt read count
+ * @param isFwd strand to find features on is the forward strand if true
+ * @param fStart start of a new feature or -1 if not identified yet
+ * @param pos current base position
+ * @param range current base range
+ * @param excluseKeys feature keys of regions to avoid looking in
+ * @param markers list of new features
+ * @param entryGroup entry group
+ * @return
+ */
+ private int findFeatures(final int cnt,
+ final boolean isFwd,
+ int fStart,
+ final int pos,
+ final Range range,
+ final Key excluseKeys[],
+ final List<MarkerObj> markers,
+ final EntryGroup entryGroup,
+ final int bamIdx)
+ {
+ if(cnt >= threshold && fStart == -1) // START FEATURE
+ {
+ boolean exclude = false;
+ try
+ {
+ final FeatureVector features =
+ entryGroup.getFeaturesInRange(range);
+ for(int k=0; k<features.size(); k++)
+ {
+ Feature f = features.elementAt(k);
+ if( f.isProteinFeature() && ! (isFwd ^ f.isForwardFeature()) ) // on same strand
+ return fStart;
+
+ for(int l=0; l<excluseKeys.length; l++)
+ if(f.getKey().equals(excluseKeys[l]))
+ exclude = true;
+ }
+ }
+ catch (OutOfRangeException e1){ }
+
+ if(!exclude)
+ fStart = range.getStart();
+ }
+ else if(cnt < threshold && fStart != -1)
+ {
+ try
+ {
+ boolean exclude = false;
+ final FeatureVector features = entryGroup.getFeaturesInRange(range);
+ for(int k=0; k<features.size(); k++)
+ {
+ Feature f = features.elementAt(k);
+ for(int l=0; l<excluseKeys.length; l++)
+ if(f.getKey().equals(excluseKeys[l]))
+ exclude = true;
+ }
+
+ if(!exclude &&
+ (range.getStart()-fStart) >= minSize)
+ {
+ final MarkerRange marker = new MarkerRange(
+ bases.getForwardStrand(), fStart, range.getStart());
+ markers.add( new MarkerObj(marker, bamIdx) );
+ }
+ }
+ catch (OutOfRangeException e1){}
+
+ return -1;
+ }
+ else if (fStart != -1)
+ {
+ try
+ {
+ FeatureVector features = entryGroup.getFeaturesInRange(range);
+ for(int k=0; k<features.size(); k++)
+ {
+ boolean exclude = false;
+ Feature f = features.elementAt(k);
+ for(int l=0; l<excluseKeys.length; l++)
+ if(f.getKey().equals(excluseKeys[l]))
+ exclude = true;
+
+ if( exclude ||
+ (f.isProteinFeature() && ! (isFwd ^ f.isForwardFeature())) ) // on same strand
+ {
+ if((f.getRawFirstBase()-fStart) >= minSize)
+ {
+ final MarkerRange marker = new MarkerRange(
+ bases.getForwardStrand(), fStart, f.getRawFirstBase() );
+ markers.add( new MarkerObj(marker, bamIdx) );
+ }
+ return -1;
+ }
+ }
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+
+ }
+ return fStart;
+ }
+
+
+ public void finished()
+ {
+ dialog.dispose();
+ }
+ }
+
+ class ReadCount
+ {
+ private float senseCnt = 0;
+ private float antiCnt = 0;
+ ReadCount(float[] cnt, boolean isFwd)
+ {
+ if(isFwd)
+ {
+ senseCnt = cnt[0];
+ antiCnt = cnt[1];
+ }
+ else
+ {
+ senseCnt = cnt[1];
+ antiCnt = cnt[0];
+ }
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/alignment/PairedReadComparator.java b/uk/ac/sanger/artemis/components/alignment/PairedReadComparator.java
new file mode 100644
index 0000000..4a64e23
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/PairedReadComparator.java
@@ -0,0 +1,51 @@
+/* SAMRecordComparator
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.util.Comparator;
+
+import net.sf.samtools.SAMRecord;
+
+import uk.ac.sanger.artemis.components.alignment.BamView.PairedRead;
+
+
+ class PairedReadComparator implements Comparator<Object>
+ {
+ public int compare(Object o1, Object o2)
+ {
+ SAMRecord sam1 = ((PairedRead) o1).sam1.sam;
+ SAMRecord sam2 = ((PairedRead) o2).sam1.sam;
+
+ int start1 = sam1.getAlignmentStart();
+ if(sam1.getAlignmentEnd() < start1)
+ start1 = sam1.getAlignmentEnd();
+
+ int start2 = sam2.getAlignmentStart();
+ if(sam2.getAlignmentEnd() < start2)
+ start2 = sam1.getAlignmentEnd();
+
+ return start1-start2;
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/PopupMessageFrame.java b/uk/ac/sanger/artemis/components/alignment/PopupMessageFrame.java
new file mode 100644
index 0000000..1cca618
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/PopupMessageFrame.java
@@ -0,0 +1,177 @@
+/* PopupMessageFrame
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.Dimension;
+import java.awt.Point;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.SwingWorker;
+
+/**
+ * Create undecorated popup frames.
+ */
+class PopupMessageFrame extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private JTextArea textArea = new JTextArea();
+ private double thisHeight;
+
+ PopupMessageFrame()
+ {
+ super();
+ getRootPane().putClientProperty("Window.alpha", new Float(0.8f));
+ textArea.setWrapStyleWord(true);
+ textArea.setEditable(false);
+ setUndecorated(true);
+ getContentPane().add(textArea);
+ }
+
+ PopupMessageFrame(String msg)
+ {
+ this();
+ textArea.setFont(textArea.getFont().deriveFont(14.f));
+ textArea.setText(msg);
+ setTitle(msg);
+ pack();
+ thisHeight = getSize().getHeight();
+ }
+
+ protected void showWaiting(final String msg, final JPanel mainPanel)
+ {
+ PopupThread hide = new PopupThread(true, msg, mainPanel);
+ hide.execute();
+ }
+
+ protected void hideFrame()
+ {
+ PopupThread hide = new PopupThread(false, null, null);
+ hide.execute();
+ }
+
+ protected void show(final String msg, final JPanel mainPanel,
+ final long aliveTime)
+ {
+ PopupThread p = new PopupThread(msg, mainPanel, aliveTime) ;
+ p.execute();
+ }
+
+ class PopupThread extends SwingWorker<String, Object>
+ {
+ private boolean show;
+ private String msg;
+ private JPanel mainPanel;
+ private long aliveTime = -1;
+
+ PopupThread(boolean show, String msg, JPanel mainPanel)
+ {
+ this.show = show;
+ this.msg = msg;
+ this.mainPanel = mainPanel;
+ }
+
+ PopupThread(String msg, JPanel mainPanel, long aliveTime)
+ {
+ this(true, msg, mainPanel);
+ this.aliveTime = aliveTime;
+ }
+
+ @Override
+ protected String doInBackground() throws Exception
+ {
+ if(aliveTime > 0)
+ showFrameForGivenTime();
+ else if(show)
+ showFrame();
+ else
+ hideFrame();
+ return null;
+ }
+
+ private void hideFrame()
+ {
+ if(getSize().getHeight() < thisHeight)
+ return;
+
+ Dimension d = getSize();
+ double hgt = thisHeight/100.d;
+ try
+ {
+ for(int i=0; i<100; i++)
+ {
+ Thread.sleep(1);
+ d.setSize(d.getWidth(), thisHeight-(hgt*i));
+ setPreferredSize(d);
+ pack();
+ }
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ setVisible(false);
+
+ d.setSize(d.getWidth(), thisHeight);
+ setPreferredSize(d);
+ pack();
+ }
+
+ private void showFrame()
+ {
+ textArea.setText(msg);
+ pack();
+ Point p = mainPanel.getLocationOnScreen();
+ p.x += (mainPanel.getWidth() - PopupMessageFrame.this.getWidth()) / 2;
+ p.y += mainPanel.getHeight() / 2;
+ setLocation(p);
+ setVisible(true);
+ }
+
+ private void showFrameForGivenTime()
+ {
+ textArea.setText(msg);
+ pack();
+ Point p = mainPanel.getLocationOnScreen();
+ p.x += (mainPanel.getWidth() - PopupMessageFrame.this.getWidth()) / 2;
+ p.y += mainPanel.getHeight() / 2;
+ setLocation(p);
+
+ setVisible(true);
+ requestFocus();
+
+ try
+ {
+ Thread.sleep(aliveTime);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ setVisible(false);
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/alignment/PrintBamView.java b/uk/ac/sanger/artemis/components/alignment/PrintBamView.java
new file mode 100644
index 0000000..7e745fc
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/PrintBamView.java
@@ -0,0 +1,195 @@
+/* PrintBamView
+ * created: 2012
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2012 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+import javax.swing.Box;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import org.apache.batik.dom.GenericDOMImplementation;
+import org.apache.batik.svggen.SVGGeneratorContext;
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.batik.svggen.SVGGraphics2DIOException;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.PrintArtemis;
+import uk.ac.sanger.artemis.components.StickyFileChooser;
+
+public class PrintBamView
+{
+ /**
+ * Print to a jpeg or png file
+ */
+ public static void print(JPanel containerPanel)
+ {
+ // file chooser
+ final StickyFileChooser fc = new StickyFileChooser();
+ File fselect = new File(fc.getCurrentDirectory()+
+ System.getProperty("file.separator")+
+ "bamview.png");
+ fc.setSelectedFile(fselect);
+
+ // file name prefix
+ Box YBox = Box.createVerticalBox();
+ JLabel labFormat = new JLabel("Select Format:");
+ Font font = labFormat.getFont();
+ labFormat.setFont(font.deriveFont(Font.BOLD));
+ YBox.add(labFormat);
+
+ Box bacross = Box.createHorizontalBox();
+ final JComboBox formatSelect = new JComboBox(PrintArtemis.getImageFormats());
+ formatSelect.setSelectedItem("png");
+ formatSelect.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ String selected;
+ if(fc.getSelectedFile() != null)
+ {
+ selected = fc.getSelectedFile().getAbsolutePath();
+ String fmts[] = PrintArtemis.getImageFormats();
+ for(int i=0; i<fmts.length; i++)
+ selected = selected.replaceAll("."+fmts[i]+"$", "");
+ }
+ else
+ selected = "bamview";
+
+ fc.setSelectedFile(new File(selected+"."+
+ formatSelect.getSelectedItem()));
+ }
+ });
+
+ Dimension d = formatSelect.getPreferredSize();
+ formatSelect.setMaximumSize(d);
+ bacross.add(Box.createHorizontalGlue());
+ bacross.add(formatSelect);
+ YBox.add(bacross);
+
+ // file prefix & format options
+ fc.setAccessory(formatSelect);
+ int n = fc.showSaveDialog(null);
+ if(n == JFileChooser.CANCEL_OPTION)
+ return;
+
+ if(((String)formatSelect.getSelectedItem()).equals("svg"))
+ {
+ createSVG(fc.getSelectedFile(), containerPanel);
+ return;
+ }
+
+ try
+ {
+ javax.imageio.ImageIO.write(
+ createImage(containerPanel),
+ (String)formatSelect.getSelectedItem(),
+ new File(fc.getSelectedFile().getAbsolutePath()));
+ }
+ catch(NoClassDefFoundError ex)
+ {
+ JOptionPane.showMessageDialog(null,
+ "This option requires Java 1.4 or higher.");
+ }
+ catch ( IOException e )
+ {
+ System.out.println("Java 1.4+ is required");
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Scalable Vector Graphics (SVG)
+ * @param fout
+ * @param containerPanel
+ */
+ private static void createSVG(final File fout, final JPanel containerPanel)
+ {
+ final DOMImplementation domImpl =
+ GenericDOMImplementation.getDOMImplementation();
+ final Document doc = domImpl.createDocument(
+ "http://www.w3.org/2000/svg", "svg", null);
+
+ SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(doc);
+ ctx.setComment("Generated by BamView with Batik SVG Generator");
+ final SVGGraphics2D svgG = new SVGGraphics2D(ctx, true);
+ svgG.setFont(Options.getOptions().getFont());
+ svgG.setSVGCanvasSize( containerPanel.getSize() );
+ containerPanel.paintAll(svgG);
+
+ try
+ {
+ final Writer out = new OutputStreamWriter(
+ new FileOutputStream(fout), "UTF-8");
+ svgG.stream(out, true);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ e.printStackTrace();
+ }
+ catch (SVGGraphics2DIOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+
+ return;
+ }
+
+ /**
+ * Returns a generated image
+ * @param pageIndex page number
+ * @return image
+ */
+ private static RenderedImage createImage(final JPanel containerPanel)
+ {
+ // Create a buffered image in which to draw
+ BufferedImage bufferedImage = new BufferedImage(
+ containerPanel.getWidth(),containerPanel.getHeight(),
+ BufferedImage.TYPE_INT_RGB);
+ // Create a graphics contents on the buffered image
+ Graphics2D g2d = bufferedImage.createGraphics();
+ containerPanel.paintAll(g2d);
+ return bufferedImage;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/ReadCountDialog.java b/uk/ac/sanger/artemis/components/alignment/ReadCountDialog.java
new file mode 100644
index 0000000..17861ff
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/ReadCountDialog.java
@@ -0,0 +1,188 @@
+/* ReadCountDialog.java
+ *
+ * created: 2012
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2012 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.FeatureDisplay;
+import uk.ac.sanger.artemis.components.ListSelectionPanel;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+ class ReadCountDialog extends JDialog
+ {
+ private static final long serialVersionUID = 1L;
+ private static ListSelectionPanel systematicListSelectionPanel;
+ private static StringVector qNames;
+ private int status = 0;
+
+ ReadCountDialog(final JFrame frame, final String title,
+ final FeatureDisplay feature_display, final Box yBox)
+ {
+ super(frame, title, true);
+
+ qNames = null;
+ final Object systematic_names[] =
+ Options.getOptions().getSystematicQualifierNames().toArray();
+ final String[] description =
+ { "Qualifiers to search when getting the feature(s) name." } ;
+
+ if(systematicListSelectionPanel == null)
+ systematicListSelectionPanel =
+ new ListSelectionPanel(feature_display.getEntryGroup(), systematic_names,
+ description, false);
+ systematicListSelectionPanel.setVisible(false);
+ final JButton advanced = new JButton("Name Options >>");
+ advanced.setToolTipText("Define the qualifier list to search for the feature name");
+ advanced.addActionListener(new ActionListener(){
+
+ public void actionPerformed(ActionEvent arg0)
+ {
+ systematicListSelectionPanel.setVisible(
+ !systematicListSelectionPanel.isVisible());
+ if(systematicListSelectionPanel.isVisible())
+ advanced.setText("Name Options <<");
+ else
+ advanced.setText("Name Options >>");
+ pack();
+ }
+ });
+
+ final JPanel pane = new JPanel(new GridBagLayout());
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = 0;
+ c.ipady = 10;
+ c.anchor = GridBagConstraints.NORTHWEST;
+ pane.add(yBox, c);
+
+ c.gridy = 1;
+ pane.add(advanced, c);
+
+ c.gridy = 2;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ pane.add(systematicListSelectionPanel, c);
+
+ Box xBox = Box.createHorizontalBox();
+ final JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ setVisible(false);
+ dispose();
+ status = 0;
+
+ Options.getOptions().setSystematicQualifierNames(
+ systematicListSelectionPanel.getResultString());
+ }
+ });
+ xBox.add(okButton);
+
+ final JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ setVisible(false);
+ dispose();
+ status = -1;
+ }
+ });
+ xBox.add(cancelButton);
+ c.fill = GridBagConstraints.NONE;
+ c.gridy = 3;
+ pane.add(xBox, c);
+
+ getContentPane().add(pane);
+ pack();
+ setLocationRelativeTo(frame);
+ setVisible(true);
+ }
+
+ protected static String getFeatureName(Feature f)
+ {
+ if(qNames == null)
+ {
+ qNames = new StringVector();
+ Object objs[] = systematicListSelectionPanel.getResultArray();
+ for(Object o: objs)
+ qNames.add((String)o);
+ }
+
+ return pickName(f, qNames);
+ }
+
+ protected int getStatus()
+ {
+ return status;
+ }
+
+ /**
+ * Look at the qualifier_names one-by-one and return the first value of the
+ * first qualifier found.
+ **/
+ private static String pickName(final Feature f, final StringVector qualifier_names)
+ {
+ int qn_size = qualifier_names.size();
+ for(int i = 0; i < qn_size; ++i)
+ {
+ try
+ {
+ final Qualifier qualifier =
+ f.getQualifierByName((String)qualifier_names.elementAt(i));
+
+ if(qualifier != null)
+ {
+ final StringVector values = qualifier.getValues();
+
+ if(values != null && values.size() > 0)
+ {
+ for(int j=0; j<values.size(); j++)
+ {
+ final String value = (String)values.elementAt(j);
+ if(value != null && !value.endsWith("current=false") && !value.equals(""))
+ return value;
+ }
+ }
+ }
+ }
+ catch(InvalidRelationException e){}
+ }
+
+ return f.getIDString();
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/ReadGroupsFrame.java b/uk/ac/sanger/artemis/components/alignment/ReadGroupsFrame.java
new file mode 100644
index 0000000..ce60152
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/ReadGroupsFrame.java
@@ -0,0 +1,178 @@
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JColorChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+import net.sf.samtools.SAMReadGroupRecord;
+
+class ReadGroupsFrame extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private LineAttributes[] colours;
+ private List<SAMReadGroupRecord> hideReadGroups = new Vector<SAMReadGroupRecord>();
+
+ ReadGroupsFrame(List<SAMReadGroupRecord> readGroups, BamView bamView)
+ {
+ super("Read Groups");
+
+ colours = LineAttributes.init(readGroups.size());
+ update(readGroups, bamView);
+
+ JFrame f = (JFrame) SwingUtilities.getAncestorOfClass(JFrame.class, bamView);
+
+ int hgt = 250;
+ if(hgt < f.getHeight()/2)
+ hgt = f.getHeight()/2;
+ setPreferredSize(new Dimension(300, hgt));
+
+ pack();
+
+ final Point p = f.getLocationOnScreen();
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ int y = (screen.height-getSize().height)/2;
+ if(y < 10)
+ y = 10;
+ int x = p.x-getSize().width;
+ if(x < 10)
+ x = 10;
+ setLocation(new Point(x, y));
+
+ setDefaultCloseOperation(HIDE_ON_CLOSE);
+ }
+
+ private void update(final List<SAMReadGroupRecord> readGroups,
+ final BamView bamView)
+ {
+ final JPanel mainPanel = new JPanel(new BorderLayout());
+ final JPanel panel = new JPanel(new GridBagLayout());
+ final JScrollPane jsp = new JScrollPane(panel);
+ mainPanel.add(jsp, BorderLayout.CENTER);
+ getContentPane().add(mainPanel);
+
+ final JButton close = new JButton("CLOSE");
+ close.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ setVisible(false);
+ }
+ });
+ mainPanel.add(close, BorderLayout.SOUTH);
+
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.WEST;
+
+ if(readGroups.size() == 0)
+ {
+ panel.add(new JLabel("No read groups"), c);
+ return;
+ }
+
+ c.gridx = 0;
+ JLabel rgLab = new JLabel("Read Groups");
+ rgLab.setFont(rgLab.getFont().deriveFont(Font.BOLD));
+ panel.add(rgLab, c);
+
+ c.gridx = 1;
+ JLabel colLab = new JLabel("Colour");
+ colLab.setFont(rgLab.getFont().deriveFont(Font.BOLD));
+ panel.add(colLab, c);
+
+ final JButton toggle = new JButton("Toggle");
+ final List<JCheckBox> checkBoxes = new Vector<JCheckBox>();
+ c.gridx = 2;
+ panel.add(toggle, c);
+ toggle.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent arg0)
+ {
+ for(JCheckBox cb: checkBoxes)
+ cb.setSelected(!cb.isSelected());
+ }
+ });
+
+ c.gridy = 1;
+ for(int i=0; i<readGroups.size(); i++)
+ {
+ final SAMReadGroupRecord grp = readGroups.get(i);
+
+ c.gridx = 0;
+ final JLabel lab = new JLabel(grp.getId());
+ lab.setToolTipText(grp.getDescription());
+ panel.add(lab, c);
+
+ c.gridx = 1;
+ final LineAttributes lnAttr = colours[i];
+ final JButton butt = new JButton("Select", bamView.getImageIcon(lnAttr.getLineColour()));
+ butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ Color newColour = JColorChooser.showDialog(null, "Colour Chooser",
+ lnAttr.getLineColour());
+
+ lnAttr.setLineColour(newColour);
+ butt.setIcon(bamView.getImageIcon(lnAttr.getLineColour()));
+ bamView.repaint();
+ }
+ });
+ panel.add(butt, c);
+
+ c.gridx = 2;
+ final JCheckBox show = new JCheckBox("Show", true);
+ checkBoxes.add(show);
+ show.addItemListener(new ItemListener(){
+ public void itemStateChanged(ItemEvent arg0)
+ {
+ if(show.isSelected())
+ hideReadGroups.remove(grp);
+ else
+ hideReadGroups.add(grp);
+ bamView.repaintBamView();
+ }
+ });
+ panel.add(show, c);
+
+ c.gridy+=1;
+ }
+ }
+
+ protected Color getReadGroupColour(final List<SAMReadGroupRecord> readGroups, final SAMReadGroupRecord rg)
+ {
+ if(rg == null)
+ return Color.BLACK;
+ final String rgId = rg.getId();
+ for(int i=0; i<readGroups.size(); i++)
+ {
+ final SAMReadGroupRecord grp = readGroups.get(i);
+ if(grp.getId().equals(rgId))
+ return colours[i].getLineColour();
+ }
+ return Color.BLACK;
+ }
+
+ protected boolean isReadGroupVisible(final SAMReadGroupRecord rg)
+ {
+ return !hideReadGroups.contains(rg);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/RunSamTools.java b/uk/ac/sanger/artemis/components/alignment/RunSamTools.java
new file mode 100644
index 0000000..d7da5e7
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/RunSamTools.java
@@ -0,0 +1,336 @@
+/* RunSamTools
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.io.*;
+import java.util.List;
+
+import net.sf.samtools.SAMRecord;
+
+/**
+* Used to run an samtools process this reads stdout and
+* stderr in separate threads.
+*/
+public class RunSamTools
+{
+
+ /** running process */
+ private Process p;
+ /** standard out */
+ private List<SAMRecord> readsInView;
+ /** standard error */
+ private StringBuffer stderr = new StringBuffer();
+ private StringBuffer stdout = new StringBuffer();
+ private String initialIOError = null;
+
+ /** running directory */
+ //private File project;
+ /** process status */
+ private String status;
+ private StdoutHandler stdouth;
+ private StderrHandler stderrh;
+
+ /**
+ * @param cmd command to run
+ * @param env environment
+ * @param project running directory
+ */
+ public RunSamTools(String cmd[],
+ String[] envp,
+ File project,
+ List<SAMRecord> readsInView)
+ {
+ //this.project = project;
+ this.readsInView = readsInView;
+ status = "0";
+
+ Runtime run = Runtime.getRuntime();
+ try
+ {
+ p = run.exec(cmd,envp,project);
+
+ // 2 threads to read in stdout & stderr buffers
+ // to prevent blocking
+ stdouth = new StdoutHandler(this);
+ stderrh = new StderrHandler(this);
+ stdouth.start();
+ stderrh.start();
+ }
+ catch(IOException ioe)
+ {
+ System.err.println("Error executing: "+
+ cmd[0]);
+ initialIOError = ioe.getMessage();
+ status = "1";
+ }
+ }
+
+ /**
+ * Read in the process stderr.
+ */
+ private void readProcessStderr()
+ {
+ BufferedInputStream stderrStream = null;
+ BufferedReader stderrRead = null;
+ try
+ {
+ //String line;
+ stderrStream =
+ new BufferedInputStream(p.getErrorStream());
+ stderrRead =
+ new BufferedReader(new InputStreamReader(stderrStream));
+ char c[] = new char[100];
+ int nc = 0;
+
+ while((nc = stderrRead.read(c,0,100)) != -1)
+ stderr = stderr.append(new String(c,0,nc));
+ }
+ catch (IOException io)
+ {
+ System.err.println("RunEmbossApplication2: Error in "+
+ "collecting standard out");
+ }
+ finally
+ {
+ try
+ {
+ if(stderrStream!=null)
+ stderrStream.close();
+ }
+ catch(IOException ioe)
+ {
+ System.err.println("RunEmbossApplication2: Error closing stream");
+ }
+ try
+ {
+ if(stderrRead!=null)
+ stderrRead.close();
+ }
+ catch(IOException ioe)
+ {
+ System.err.println("RunEmbossApplication2: Error closing reader");
+ }
+ }
+
+ return;
+ }
+
+ /**
+ * Read in the process stdout.
+ */
+ private void readProcessStdout()
+ {
+ InputStreamReader stdoutStream = null;
+ BufferedReader stdoutRead = null;
+ try
+ {
+ //String line;
+ stdoutStream =
+ new InputStreamReader(p.getInputStream());
+ stdoutRead =
+ new BufferedReader(stdoutStream);
+
+ String line;
+ while((line = stdoutRead.readLine()) != null)
+ {
+ if(readsInView != null)
+ {
+ String fields[] = line.split("\t");
+
+ SAMRecord pread = new SAMRecord(null);
+ pread.setReadName(fields[0]);
+ pread.setFlags(Integer.parseInt(fields[1]));
+ pread.setReferenceName(fields[2]);
+ pread.setAlignmentStart(Integer.parseInt(fields[3]));
+ pread.setMappingQuality(Integer.parseInt(fields[4]));
+ pread.setCigarString(fields[5]);
+ pread.setMateReferenceName(fields[6]);
+ pread.setMateAlignmentStart( Integer.parseInt(fields[7]));
+ pread.setInferredInsertSize(Integer.parseInt(fields[8]));
+ pread.setReadString(fields[9]);
+
+ readsInView.add(pread);
+ }
+ else
+ stdout.append(line+"\n");
+ }
+ }
+ catch (IOException io)
+ {
+ System.err.println("RunEmbossApplication2: Error in "+
+ "collecting standard out");
+ }
+ finally
+ {
+ try
+ {
+ if(stdoutStream!=null)
+ stdoutStream.close();
+ }
+ catch(IOException ioe)
+ {
+ System.err.println("RunEmbossApplication2: Error closing stream");
+ }
+ try
+ {
+ if(stdoutRead!=null)
+ stdoutRead.close();
+ }
+ catch(IOException ioe)
+ {
+ System.err.println("RunEmbossApplication2: Error closing reader");
+ }
+ }
+
+ return;
+ }
+
+ /**
+ * @return standard out
+ */
+ public void waitForStdout()
+ {
+ try
+ {
+ // make sure we hang around for stdout
+ while(stdouth.isAlive())
+ Thread.sleep(10);
+ }
+ catch(InterruptedException ie)
+ {
+ ie.printStackTrace();
+ }
+
+ return;
+ }
+
+ /**
+ * @return standard out
+ */
+ public String getProcessStdout()
+ {
+ try
+ {
+ // make sure we hang around for stdout
+ while(stdouth.isAlive())
+ Thread.sleep(10);
+ }
+ catch(InterruptedException ie)
+ {
+ ie.printStackTrace();
+ }
+
+ return stdout.toString();
+ }
+
+ /**
+ * @return stderr
+ */
+ public String getProcessStderr()
+ {
+ try
+ {
+ // make sure we hang around for stdout
+ while(stderrh.isAlive())
+ Thread.sleep(10);
+ }
+ catch(InterruptedException ie)
+ {
+ ie.printStackTrace();
+ }
+
+ return new String(stderr.toString().trim());
+ }
+
+ /**
+ * Wait for the process to end
+ */
+ public void waitFor()
+ {
+ try
+ {
+ int exitVal = p.waitFor();
+
+ if(exitVal != 0)
+ System.out.println("Exit value:: "+exitVal);
+ }
+ catch(InterruptedException ie)
+ {
+ ie.printStackTrace();
+ }
+ }
+
+ /**
+ * @return process
+ */
+ public Process getProcess()
+ {
+ return p;
+ }
+
+ /**
+ * @return status
+ */
+ public String getStatus()
+ {
+ return status;
+ }
+
+ class StdoutHandler extends Thread
+ {
+ RunSamTools rea;
+
+ protected StdoutHandler(RunSamTools rea)
+ {
+ this.rea = rea;
+ }
+
+ public void run()
+ {
+ rea.readProcessStdout();
+ }
+ }
+
+ class StderrHandler extends Thread
+ {
+ RunSamTools rea;
+
+ protected StderrHandler(RunSamTools rea)
+ {
+ this.rea = rea;
+ }
+
+ public void run()
+ {
+ rea.readProcessStderr();
+ }
+ }
+
+ public String getInitialIOError()
+ {
+ return initialIOError;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/alignment/SAMRecordComparator.java b/uk/ac/sanger/artemis/components/alignment/SAMRecordComparator.java
new file mode 100644
index 0000000..0799938
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SAMRecordComparator.java
@@ -0,0 +1,58 @@
+/* SAMRecordComparator
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.util.Comparator;
+
+import uk.ac.sanger.artemis.components.alignment.BamViewRecord;
+
+ class SAMRecordComparator implements Comparator<Object>
+ {
+ public int compare(Object o1, Object o2)
+ {
+ BamViewRecord pr1 = (BamViewRecord) o1;
+ BamViewRecord pr2 = (BamViewRecord) o2;
+
+ int cmp = pr1.sam.getReadName().compareTo(pr2.sam.getReadName());
+ if(cmp == 0)
+ {
+ short fl1 = pr1.bamIndex; // bam file index
+ short fl2 = pr2.bamIndex;
+ if(fl1 != -1 && fl2 != -1)
+ {
+ if(fl1 < fl2)
+ return -1;
+ else if(fl1 > fl2)
+ return 1;
+ }
+
+ if(pr1.sam.getAlignmentStart() < pr2.sam.getAlignmentStart())
+ return -1;
+ else
+ return 1;
+ }
+ return cmp;
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/SAMRecordFilter.java b/uk/ac/sanger/artemis/components/alignment/SAMRecordFilter.java
new file mode 100644
index 0000000..f769a5b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SAMRecordFilter.java
@@ -0,0 +1,208 @@
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.Vector;
+
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.JTextField;
+
+/**
+ * Filter reads bases on their mapping quality (mapq) or the
+ * flags that are set in the BAM file.
+ */
+class SAMRecordFilter extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+
+ public SAMRecordFilter(final BamView bamView)
+ {
+ super("Filter Reads");
+
+ JPanel pane = (JPanel) getContentPane();
+ pane.setLayout(new GridBagLayout());
+
+ int nflags = SAMRecordFlagPredicate.FLAGS.length;
+ GridBagConstraints c = new GridBagConstraints();
+
+ int nrows = 0;
+ c.ipadx = 5;
+ c.ipady = 2;
+ c.gridx = 0;
+ c.gridwidth = 2;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridy = nrows++;
+
+ // MAPQ
+ pane.add(new JLabel(" By Mappying Quality (mapq) cut-off:"),c);
+
+ c.gridy = nrows++;
+ c.gridwidth = 1;
+ final JTextField cutOff = new JTextField(12);
+ if(bamView.getSamRecordMapQPredicate() != null)
+ cutOff.setText(Integer.toString(bamView.getSamRecordMapQPredicate().cutOff));
+
+ cutOff.addKeyListener(new KeyAdapter()
+ {
+ public void keyPressed(KeyEvent e)
+ {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER)
+ setQualityCutOff(cutOff, bamView);
+ }
+ });
+ pane.add(cutOff,c);
+
+ c.gridx = 1;
+ final JButton setCutOff = new JButton("SET");
+ setCutOff.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setQualityCutOff(cutOff, bamView);
+ }
+ });
+ pane.add(setCutOff,c);
+
+ // FLAGS
+ c.gridwidth = 2;
+ c.gridx = 0;
+ c.gridy = nrows++;
+ pane.add(new JSeparator(),c);
+
+ c.gridy = nrows++;
+ pane.add(new JLabel(" By SAM FLAG column:"),c);
+ c.gridy = nrows++;
+ pane.add(new JLabel(" Select below to show or hide only the reads with "),c);
+ c.gridy = nrows++;
+ pane.add(new JLabel(" the flag set."),c);
+
+ final JComboBox flagCombo[] = new JComboBox[nflags];
+
+ final String[] items = {"", "SHOW", "HIDE"};
+ c.gridwidth = 1;
+ for(int j=0; j<nflags; j++)
+ {
+ flagCombo[j] = new JComboBox(items);
+ if(SAMRecordFlagPredicate.FLAGS_DESCRIPTION[j].equalsIgnoreCase("Read Unmapped"))
+ flagCombo[j].setSelectedItem("HIDE");
+
+ flagCombo[j].addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ filterChange(bamView, flagCombo);
+ }
+ });
+
+ c.gridy = nrows++;
+ c.gridx = 0;
+ pane.add(flagCombo[j], c);
+ c.gridx = 1;
+ pane.add(new JLabel(SAMRecordFlagPredicate.FLAGS_DESCRIPTION[j]), c);
+ }
+
+ JButton closeFrame = new JButton("Close");
+ closeFrame.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setVisible(false);
+ }
+ });
+ c.gridx = 1;
+ c.gridy = nrows++;
+ c.fill = GridBagConstraints.NONE;
+ pane.add(closeFrame, c);
+
+ pack();
+
+ rightJustifyFrame(this);
+ setVisible(true);
+ }
+
+ private void rightJustifyFrame(JFrame frame)
+ {
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ int yPos = (screen.height - frame.getSize().height) / 2;
+ int xPos = screen.width - frame.getSize().width;
+ if(yPos < 10)
+ yPos = 10;
+
+ frame.setLocation(new Point(xPos, yPos));
+ }
+
+ private void setQualityCutOff(final JTextField cutOff,
+ final BamView bamView)
+ {
+ String cutOffStr = cutOff.getText().trim();
+ if(cutOffStr.equals(""))
+ bamView.setSamRecordMapQPredicate(null);
+ else
+ {
+ try
+ {
+ SAMRecordMapQPredicate predicate =
+ new SAMRecordMapQPredicate(Integer.parseInt(cutOffStr));
+ bamView.setSamRecordMapQPredicate(predicate);
+ }
+ catch(NumberFormatException nfe)
+ {
+ bamView.setSamRecordMapQPredicate(null);
+ }
+ }
+ bamView.repaint();
+ }
+
+ private void filterChange(final BamView bamView, final JComboBox flagCheck[])
+ {
+ int nflags = SAMRecordFlagPredicate.FLAGS.length;
+ int flagCombined = 0;
+ Vector <SAMRecordPredicate> predicates = new Vector<SAMRecordPredicate>();
+
+ for (int j = 0; j < nflags; j++)
+ {
+ String opt = (String) flagCheck[j].getSelectedItem();
+ if (opt.equals("HIDE"))
+ flagCombined = flagCombined | SAMRecordFlagPredicate.FLAGS[j];
+ else if(opt.equals("SHOW"))
+ predicates.add(new SAMRecordFlagPredicate( SAMRecordFlagPredicate.FLAGS[j], false ));
+ }
+
+ if (flagCombined == 0 && predicates.size() == 0)
+ bamView.setSamRecordFlagPredicate(null);
+ else
+ {
+ final SAMRecordPredicate predicate;
+ if(predicates.size() == 0)
+ predicate = new SAMRecordFlagPredicate(flagCombined);
+ else if(flagCombined == 0)
+ {
+ predicate =
+ new SAMRecordFlagConjunctionPredicate(predicates, SAMRecordFlagConjunctionPredicate.OR);
+ }
+ else
+ {
+ SAMRecordFlagPredicate p1 = new SAMRecordFlagPredicate(flagCombined);
+ SAMRecordFlagConjunctionPredicate p2 =
+ new SAMRecordFlagConjunctionPredicate(predicates, SAMRecordFlagConjunctionPredicate.OR);
+
+ predicate = new SAMRecordFlagConjunctionPredicate(p1, p2,
+ SAMRecordFlagConjunctionPredicate.OR);
+ }
+ bamView.setSamRecordFlagPredicate(predicate);
+ }
+ bamView.repaint();
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/SAMRecordFlagConjunctionPredicate.java b/uk/ac/sanger/artemis/components/alignment/SAMRecordFlagConjunctionPredicate.java
new file mode 100644
index 0000000..57d8294
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SAMRecordFlagConjunctionPredicate.java
@@ -0,0 +1,73 @@
+/* SAMRecordFlagConjunctionPredicate
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ **/
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.util.Vector;
+
+import net.sf.samtools.SAMRecord;
+
+/**
+ * Test the SAMRecord flag.
+ **/
+public class SAMRecordFlagConjunctionPredicate implements SAMRecordPredicate
+{
+ private Vector<SAMRecordPredicate> predicates;
+ private int type; // AND or OR
+ protected static int AND = 0;
+ protected static int OR = 1;
+
+ public SAMRecordFlagConjunctionPredicate(SAMRecordPredicate p1, SAMRecordPredicate p2, int type)
+ {
+ predicates = new Vector<SAMRecordPredicate>();
+ predicates.add(p1);
+ predicates.add(p2);
+ this.type = type;
+ }
+
+ public SAMRecordFlagConjunctionPredicate(Vector<SAMRecordPredicate> predicates, int type)
+ {
+ this.predicates = predicates;
+ this.type = type;
+ }
+
+ public boolean testPredicate(SAMRecord samRecord)
+ {
+ for(SAMRecordPredicate predicate: predicates)
+ {
+ if(predicate.testPredicate(samRecord))
+ {
+ if(type == OR)
+ return true;
+ }
+ else
+ {
+ if(type == AND)
+ return false;
+ }
+ }
+
+ if(type == AND)
+ return true;
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/SAMRecordFlagPredicate.java b/uk/ac/sanger/artemis/components/alignment/SAMRecordFlagPredicate.java
new file mode 100644
index 0000000..d3b8ea9
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SAMRecordFlagPredicate.java
@@ -0,0 +1,115 @@
+/* SAMRecordPredicate
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ **/
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import net.sf.samtools.SAMRecord;
+
+/**
+ * Test the SAMRecord flag.
+ **/
+public class SAMRecordFlagPredicate implements SAMRecordPredicate
+{
+ private int flag;
+ private boolean isSet;
+
+ protected static final int READ_PAIRED_FLAG = 0x1;
+ protected static final int PROPER_PAIR_FLAG = 0x2;
+ protected static final int READ_UNMAPPED_FLAG = 0x4;
+ protected static final int MATE_UNMAPPED_FLAG = 0x8;
+ protected static final int READ_STRAND_FLAG = 0x10;
+ protected static final int MATE_STRAND_FLAG = 0x20;
+ protected static final int FIRST_OF_PAIR_FLAG = 0x40;
+ protected static final int SECOND_OF_PAIR_FLAG = 0x80;
+ protected static final int NOT_PRIMARY_ALIGNMENT_FLAG = 0x100;
+ protected static final int READ_FAILS_VENDOR_QUALITY_CHECK_FLAG = 0x200;
+ protected static final int DUPLICATE_READ_FLAG = 0x400;
+
+ protected static final String[] FLAGS_DESCRIPTION =
+ {
+ "Read Paired",
+ "Proper Pair",
+ "Read Unmapped",
+ "Mate Unmapped",
+ "Read on Negative Strand",
+ "Mate on Negative Strand",
+ "First of Pair",
+ "Second of Pair",
+ "Not Primary Alignment",
+ "Read Fails Vendor Quality Check",
+ "Duplicate Read"
+ };
+
+ protected static int[] FLAGS =
+ {
+ READ_PAIRED_FLAG,
+ PROPER_PAIR_FLAG,
+ READ_UNMAPPED_FLAG,
+ MATE_UNMAPPED_FLAG,
+ READ_STRAND_FLAG,
+ MATE_STRAND_FLAG,
+ FIRST_OF_PAIR_FLAG,
+ SECOND_OF_PAIR_FLAG,
+ NOT_PRIMARY_ALIGNMENT_FLAG,
+ READ_FAILS_VENDOR_QUALITY_CHECK_FLAG,
+ DUPLICATE_READ_FLAG
+ };
+
+
+ public SAMRecordFlagPredicate(int flag)
+ {
+ this(flag, true);
+ }
+
+ public SAMRecordFlagPredicate(int flag, boolean isSet)
+ {
+ this.flag = flag;
+ this.isSet = isSet;
+ }
+
+ /**
+ * Test the given SAMRecord against this predicate.
+ * @param feature The SAMRecord to test the predicate against.
+ * @return Return true if and only if this predicate is true for the given
+ * SAMRecord.
+ **/
+ public boolean testPredicate (final SAMRecord samRecord)
+ {
+ if(!isSet)
+ return !isFlagSet(samRecord.getFlags());
+ return isFlagSet(samRecord.getFlags());
+ }
+
+ private boolean isFlagSet(int thisFlag)
+ {
+ for(int i=0; i<FLAGS.length; i++)
+ {
+ if((flag & FLAGS[i]) == FLAGS[i])
+ {
+ if((thisFlag & FLAGS[i]) == FLAGS[i])
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/alignment/SAMRecordList.java b/uk/ac/sanger/artemis/components/alignment/SAMRecordList.java
new file mode 100644
index 0000000..4323e25
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SAMRecordList.java
@@ -0,0 +1,272 @@
+/* SAMRecordList
+ *
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollBar;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
+import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.components.alignment.BamViewRecord;
+
+
+import net.sf.samtools.SAMRecord;
+
+public class SAMRecordList extends JPanel
+ implements DisplayAdjustmentListener
+{
+ private static final long serialVersionUID = 1L;
+ private BamView bamView;
+ private JScrollBar verticalScroll = new JScrollBar(JScrollBar.VERTICAL);
+ private int font_size =12;
+ private int highlight = -1;
+
+ public SAMRecordList(final BamView bamView)
+ {
+ this.bamView = bamView;
+ setBackground(Color.white);
+
+ font_size = Options.getOptions().getFont().getSize();
+
+ final JFrame f = new JFrame("Reads");
+ JPanel mainPanel = (JPanel) f.getContentPane();
+ JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING));
+ header.add(new JLabel("NAME :: COORDINATES :: LENGTH :: QUALITY :: DIRECTION"));
+ JButton close = new JButton("CLOSE");
+
+ mainPanel.add(verticalScroll, BorderLayout.EAST);
+ mainPanel.add(this, BorderLayout.CENTER);
+ mainPanel.add(header, BorderLayout.NORTH);
+ mainPanel.add(close, BorderLayout.SOUTH);
+
+ setPreferredSize(new Dimension(450,500));
+ f.pack();
+ f.setVisible(true);
+ f.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ f.addWindowListener(new WindowAdapter()
+ {
+ public void windowClosed(WindowEvent e)
+ {
+ windowClose(f);
+ }
+ });
+
+ verticalScroll.addAdjustmentListener(new AdjustmentListener(){
+ public void adjustmentValueChanged(AdjustmentEvent arg0)
+ {
+ repaint();
+ }
+ });
+
+ addMouseListener(new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ highlight = e.getY()/font_size + verticalScroll.getValue();
+ if(highlight >= bamView.getReadsInView().size())
+ return;
+
+ setHighlight();
+ if(e.getClickCount() > 1)
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ BamView.openFileViewer(bamView.getHighlightSAMRecord().sam,
+ bamView.getMate(bamView.getHighlightSAMRecord()),
+ bamView.bamList);
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+
+ setFocusable(true);
+ addKeyListener(new KeyAdapter()
+ {
+ public void keyPressed(KeyEvent e)
+ {
+ int keyCode = e.getKeyCode();
+ switch( keyCode ) {
+ case KeyEvent.VK_UP:
+ if(highlight == 0)
+ break;
+ highlight--;
+ setHighlight();
+
+ if(highlight < verticalScroll.getValue())
+ verticalScroll.setValue(highlight);
+ break;
+ case KeyEvent.VK_DOWN:
+ if(highlight >= bamView.getReadsInView().size()-1)
+ break;
+ highlight++;
+ setHighlight();
+
+ if(highlight > verticalScroll.getValue()+verticalScroll.getVisibleAmount())
+ verticalScroll.setValue(highlight);
+ break;
+ }
+
+ }
+ });
+
+
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ windowClose(f);
+ }
+ });
+
+ if(bamView.getFeatureDisplay() != null)
+ bamView.getFeatureDisplay().addDisplayAdjustmentListener(this);
+ }
+
+
+ private void setHighlight()
+ {
+ bamView.setHighlightSAMRecord( bamView.getReadsInView().get(highlight) );
+ bamView.repaint();
+ repaint();
+ }
+
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+
+ int nrecordsShown = getHeight()/font_size;
+ List<BamViewRecord> readsInView = bamView.getReadsInView();
+ verticalScroll.setMaximum(readsInView.size());
+ verticalScroll.setVisibleAmount(nrecordsShown);
+
+ int fst = verticalScroll.getValue();
+ int lst = nrecordsShown+fst+1;
+
+ if(lst > readsInView.size())
+ lst = readsInView.size();
+
+ String highlightedSAMRecord = (bamView.getHighlightSAMRecord() == null ?
+ null : bamView.getHighlightSAMRecord().sam.getReadName());
+
+ String fmt = getFormatString(fst, lst, readsInView);
+ for(int i=fst; i<lst; i++)
+ {
+ SAMRecord thisRead = readsInView.get(i).sam;
+
+ if(highlightedSAMRecord != null && highlightedSAMRecord.equals(thisRead.getReadName()))
+ {
+ g.setColor(Color.pink);
+ g.fillRect(0, ((i-fst)*font_size)+1, getWidth(), font_size);
+ g.setColor(Color.black);
+ }
+
+ String readStr = String.format(fmt,
+ thisRead.getReadName(),
+ thisRead.getAlignmentStart()+".."+thisRead.getAlignmentEnd(),
+ thisRead.getReadLength(),
+ thisRead.getMappingQuality(),
+ (thisRead.getReadNegativeStrandFlag() ? "-" : "+"));
+ g.drawString(readStr, 5, (((i-fst)*font_size)+font_size));
+ }
+ }
+
+ private String getFormatString(int fst, int lst, List<BamViewRecord> readsInView)
+ {
+ int nameWidth = 10;
+ int coordWidth = 10;
+ int lenWidth = 4;
+ int qualWidth = 4;
+
+ for(int i=fst; i<lst; i++)
+ {
+ SAMRecord thisRead = readsInView.get(i).sam;
+ int thisWidth = thisRead.getReadName().length();
+ if(thisWidth > nameWidth)
+ nameWidth = thisWidth;
+
+ thisWidth = (thisRead.getAlignmentStart()+".."+thisRead.getAlignmentEnd()).length();
+ if(thisWidth > coordWidth)
+ coordWidth = thisWidth;
+
+ thisWidth = (String.valueOf(thisRead.getReadLength()) ).length();
+ if(thisWidth > lenWidth)
+ lenWidth = thisWidth;
+
+ thisWidth = (String.valueOf(thisRead.getMappingQuality())).length();
+ if(thisWidth > qualWidth)
+ qualWidth = thisWidth;
+ }
+
+ nameWidth++;
+ coordWidth++;
+ return "%1$-"+nameWidth+"s| %2$-"+coordWidth+"s| %3$-"+lenWidth+"s| %4$-"+qualWidth+"s| %5$-2s";
+ }
+
+ public void displayAdjustmentValueChanged(DisplayAdjustmentEvent event)
+ {
+ SwingWorker worker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ try
+ {
+ Thread.sleep(1500);
+ }
+ catch (InterruptedException e){}
+
+ repaint();
+ return null;
+ }
+ };
+ worker.start();
+ }
+
+ private void windowClose(JFrame f)
+ {
+ f.dispose();
+ if(bamView.getFeatureDisplay() != null)
+ bamView.getFeatureDisplay().removeDisplayAdjustmentListener(SAMRecordList.this);
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/alignment/SAMRecordMapQPredicate.java b/uk/ac/sanger/artemis/components/alignment/SAMRecordMapQPredicate.java
new file mode 100644
index 0000000..60136ae
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SAMRecordMapQPredicate.java
@@ -0,0 +1,52 @@
+/* SAMRecordMapQPredicate
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ **/
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import net.sf.samtools.SAMRecord;
+
+/**
+ * Test the SAMRecord mapping quality - mapq flag.
+ **/
+public class SAMRecordMapQPredicate implements SAMRecordPredicate
+{
+ protected int cutOff;
+
+ public SAMRecordMapQPredicate(int cutOff)
+ {
+ this.cutOff = cutOff;
+ }
+
+ /**
+ * Test the given SAMRecord against this predicate.
+ * @param feature The SAMRecord to test the predicate against.
+ * @return Return true if and only if this predicate is true for the given
+ * SAMRecord.
+ **/
+ public boolean testPredicate (final SAMRecord samRecord)
+ {
+ if(samRecord.getMappingQuality() > cutOff)
+ return true;
+ return false;
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/alignment/SAMRecordPositionComparator.java b/uk/ac/sanger/artemis/components/alignment/SAMRecordPositionComparator.java
new file mode 100644
index 0000000..0f44699
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SAMRecordPositionComparator.java
@@ -0,0 +1,54 @@
+/* SAMRecordPositionComparator
+ *
+ * created: 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.util.Comparator;
+
+import net.sf.samtools.SAMRecord;
+import uk.ac.sanger.artemis.components.alignment.BamViewRecord;
+
+ class SAMRecordPositionComparator implements Comparator<Object>
+ {
+ public BamView bamView;
+ public SAMRecordPositionComparator(BamView bamView)
+ {
+ this.bamView = bamView;
+ }
+
+ public int compare(Object o1, Object o2)
+ {
+ SAMRecord pr1 = ((BamViewRecord) o1).sam;
+ SAMRecord pr2 = ((BamViewRecord) o2).sam;
+
+ int offset1 = bamView.getSequenceOffset(pr1.getReferenceName());
+ int offset2 = bamView.getSequenceOffset(pr2.getReferenceName());
+
+ if(pr1.getAlignmentStart()+offset1 < pr2.getAlignmentStart()+offset2)
+ return -1;
+ else if(pr1.getAlignmentStart()+offset1 > pr2.getAlignmentStart()+offset2)
+ return 1;
+ return 0;
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/alignment/SAMRecordPredicate.java b/uk/ac/sanger/artemis/components/alignment/SAMRecordPredicate.java
new file mode 100644
index 0000000..4b9fe5e
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SAMRecordPredicate.java
@@ -0,0 +1,41 @@
+/* SAMRecordPredicate
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ **/
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import net.sf.samtools.SAMRecord;
+
+/**
+ * Each object that implements this interface represents a predicate that can
+ * be tested with a SAMRecord reference.
+ **/
+
+public interface SAMRecordPredicate {
+ /**
+ * Test the given SAMRecord against this predicate.
+ * @param feature The SAMRecord to test the predicate against.
+ * @return Return true if and only if this predicate is true for the given
+ * SAMRecord.
+ **/
+ boolean testPredicate (final SAMRecord samRecord);
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/alignment/SnpPanel.java b/uk/ac/sanger/artemis/components/alignment/SnpPanel.java
new file mode 100644
index 0000000..40eb553
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/alignment/SnpPanel.java
@@ -0,0 +1,224 @@
+/* SnpPanel
+ *
+ * created: 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.alignment;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.GeneralPath;
+import java.util.List;
+
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+import net.sf.samtools.AlignmentBlock;
+import net.sf.samtools.SAMRecord;
+
+ public class SnpPanel extends AbstractGraphPanel
+ {
+ private static final long serialVersionUID = 1L;
+ private Bases bases;
+ private float minBaseQualityFilter = 0;
+ private int snpCount[];
+
+ public SnpPanel(final BamView bamView, Bases bases)
+ {
+ super();
+ this.bamView = bamView;
+ this.bases = bases;
+ initPopupMenu(popup);
+
+ JMenuItem configure = new JMenuItem("Filter by Base Quality...");
+ configure.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ // filter by base quality
+ JTextField filterField = new JTextField(Float.toString(minBaseQualityFilter));
+
+ int status = JOptionPane.showConfirmDialog(SnpPanel.this,
+ filterField, "Base Quality Filter",
+ JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return;
+ try
+ {
+ minBaseQualityFilter = Float.parseFloat(filterField.getText());
+ }
+ catch(NumberFormatException nfe)
+ {
+ JOptionPane.showMessageDialog(SnpPanel.this, nfe.getMessage(),
+ "Number Format", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ });
+ popup.add(configure);
+ }
+
+ /**
+ * Override
+ */
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+
+ if(bases == null || nBins == 0 || snpCount == null)
+ return;
+
+ drawSelectionRange(g2, pixPerBase, start, end, getHeight(), Color.PINK);
+ draw(g2);
+ drawMax(g2);
+ }
+
+ protected void init(BamView bamView, float pixPerBase, int start, int end)
+ {
+ this.bamView = bamView;
+ setPixPerBase(pixPerBase);
+ setStartAndEnd(start, end);
+ if(autoWinSize)
+ {
+ windowSize = (bamView.getBasesInView()/300);
+ userWinSize = windowSize;
+ }
+ else
+ windowSize = userWinSize;
+
+ if(windowSize < 1)
+ windowSize = 1;
+ nBins = Math.round((end-start+1.f)/windowSize);
+ max = 0;
+ snpCount = null;
+ }
+
+ protected void draw(Graphics2D g2)
+ {
+ g2.setColor(Color.red);
+ g2.setStroke(new BasicStroke(1.f));
+
+ if(windowSize == 1)
+ {
+ GeneralPath shape = new GeneralPath();
+ shape.moveTo(0,getHeight());
+ for(int i=0; i<snpCount.length; i++)
+ {
+ float xpos1 = ((i*windowSize) )*pixPerBase;
+ float xpos2 = ((i*windowSize) + windowSize)*pixPerBase;
+
+ shape.lineTo(xpos1,getHeight());
+ shape.lineTo(xpos1,
+ getHeight() - (((float)snpCount[i]/(float)max)*getHeight()));
+ shape.lineTo(xpos2,
+ getHeight() - (((float)snpCount[i]/(float)max)*getHeight()));
+
+ shape.lineTo(xpos2,getHeight());
+ }
+
+ shape.lineTo(getWidth(),getHeight());
+ g2.fill(shape);
+ }
+ else
+ {
+ for(int i=1; i<snpCount.length; i++)
+ {
+ int x0 = (int) (((i*windowSize) - windowSize/2.f)*pixPerBase);
+ int y0 = (int) (getHeight() - (((float)snpCount[i-1]/(float)max)*getHeight()));
+ int x1 = (int) ((((i+1)*windowSize) - windowSize/2.f)*pixPerBase);
+ int y1 = (int) (getHeight() - (((float)snpCount[i]/(float)max)*getHeight()));
+
+ g2.drawLine(x0, y0, x1, y1);
+ }
+ }
+ }
+
+ /**
+ * Display the SNPs for the given read.
+ * @param g2
+ * @param thisRead
+ * @param pixPerBase
+ * @param ypos
+ */
+ protected void addRecord(final SAMRecord thisRead, int offset)
+ {
+ if(snpCount == null)
+ {
+ snpCount = new int[nBins];
+ for(int i=0; i<snpCount.length; i++)
+ snpCount[i] = 0;
+ }
+
+ final int thisStart = thisRead.getAlignmentStart();
+ final int thisEnd = thisRead.getAlignmentEnd();
+
+ // use alignment blocks of the contiguous alignment of
+ // subsets of read bases to a reference sequence
+ final List<AlignmentBlock> blocks = thisRead.getAlignmentBlocks();
+ final byte[] phredQuality = thisRead.getBaseQualities();
+ try
+ {
+ char[] refSeq = bases.getSubSequenceC(
+ new Range(thisStart+offset, thisEnd+offset), Bases.FORWARD);
+ byte[] readSeq = thisRead.getReadBases();
+
+ offset = offset - bamView.getBaseAtStartOfView();
+ for(int i=0; i<blocks.size(); i++)
+ {
+ AlignmentBlock block = blocks.get(i);
+ for(int j=0; j<block.getLength(); j++)
+ {
+ int readPos = block.getReadStart()-1+j;
+ int refPos = block.getReferenceStart()+j;
+
+ if (Character.toUpperCase(refSeq[refPos-thisStart]) != Character.toUpperCase( (char)readSeq[readPos] ))
+ {
+ if(phredQuality[readPos] < minBaseQualityFilter)
+ continue;
+ int bin = (int)((refPos+offset) / windowSize);
+
+ if(bin < 0 || bin > nBins-1)
+ continue;
+
+ snpCount[bin]+=1;
+ if(snpCount[bin] > max)
+ max = snpCount[bin];
+ }
+ }
+ }
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/database/DatabaseEntrySource.java b/uk/ac/sanger/artemis/components/database/DatabaseEntrySource.java
new file mode 100644
index 0000000..f8bbf93
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/database/DatabaseEntrySource.java
@@ -0,0 +1,661 @@
+/* DatabaseEntrySource.java
+ *
+ * created: Mar 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.database;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.IOException;
+import java.io.Serializable;
+import javax.swing.JCheckBox;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntrySource;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.DatabaseLocationParser;
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.components.filetree.LocalAndRemoteFileManager;
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+import uk.ac.sanger.artemis.io.UI;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.InvalidKeyException;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Range;
+
+/**
+ *
+ * This is an EntrySource that reads Entry objects from a relational database.
+ *
+ */
+public class DatabaseEntrySource implements EntrySource, Serializable
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ private String location;
+
+ private JPasswordField pfield;
+
+ private boolean splitGFFEntry;
+
+ private boolean readOnly = false;
+
+ /**
+ * Create a new DatabaseEntrySource.
+ */
+ public DatabaseEntrySource()
+ {
+ }
+
+ /**
+ * @param bases
+ * The Bases object to pass to the Entry constructor.
+ * @param show_progress
+ * If true a InputStreamProgressDialog will be shown while loading.
+ * (Not implemented)
+ * @exception OutOfRangeException
+ * Thrown if one of the features in the Entry is out of range of
+ * the Bases object.
+ * @return null if and only if the read is cancelled by the user or if the
+ * read fails.
+ */
+ public Entry getEntry(final Bases bases, final boolean show_progress)
+ throws OutOfRangeException, IOException
+ {
+ return null;
+ }
+
+ public Entry getEntry(final boolean show_progress)
+ throws OutOfRangeException, IOException
+ {
+ return null;
+ }
+
+ /**
+ * Get an Entry object from this source.
+ *
+ * @param id
+ * Feature ID to read in.
+ * @exception OutOfRangeException
+ * Thrown if one of the features in the Entry is out of range of
+ * the Bases object.
+ * @exception NoSequenceException
+ * Thrown if the entry that we read has no sequence.
+ * @return null if and only if the user cancels the read or if the read fails.
+ */
+ public Entry getEntry(String id, String schema,
+ InputStreamProgressListener progress_listener)
+ throws OutOfRangeException, NoSequenceException, IOException
+ {
+ return makeFromID(null, id, schema, progress_listener, null);
+ }
+
+ public Entry getEntry(String id, String schema,
+ InputStreamProgressListener progress_listener, Range range)
+ throws OutOfRangeException, NoSequenceException, IOException
+ {
+ return makeFromID(null, id, schema, progress_listener, range);
+ }
+
+ /**
+ * Returns true if and only if this EntrySource always returns "full" entries.
+ * ie. entries that contain features and sequence.
+ */
+ public boolean isFullEntrySource()
+ {
+ return true;
+ }
+
+ /**
+ * Return the name of this source (for display to the user in menus).
+ */
+ public String getSourceName()
+ {
+ return "Database";
+ }
+
+ /**
+ *
+ * Set the database location as:
+ * jdbc:postgresql://host:port/database?user=username
+ *
+ */
+ public boolean setLocation(final boolean promptUser)
+ {
+ ILoginPrompt promptPanel; // = new DatabaseLoginPrompt();
+
+ if (UI.mode == UI.UIMode.SWING)
+ {
+ promptPanel = new DatabaseLoginPrompt();
+ }
+ else
+ {
+ promptPanel = new DatabaseLoginPromptConsole();
+ }
+
+
+ if(promptUser)
+ {
+
+ boolean userEntered = promptPanel.prompt();
+ if (userEntered == false)
+ {
+ return false;
+ }
+
+//
+// int n = JOptionPane.showConfirmDialog(null, promptPanel,
+// "Enter Database Address",
+// JOptionPane.OK_CANCEL_OPTION,
+// JOptionPane.QUESTION_MESSAGE);
+// if(n == JOptionPane.CANCEL_OPTION)
+// return false;
+ }
+
+ pfield = promptPanel.getPfield();
+ DatabaseLocationParser dlp = new DatabaseLocationParser();
+ dlp.setHost(promptPanel.getServer());
+ dlp.setPort(promptPanel.getPort());
+ dlp.setDatabase(promptPanel.getDb());
+ dlp.setUsername(promptPanel.getUser());
+ dlp.setSSL(promptPanel.getSSL());
+
+ location = dlp.getCompleteURL();
+ return true;
+ }
+
+
+
+ public DatabaseDocument getDatabaseDocument()
+ {
+// ArtemisConsole.info(location, "location");
+// ArtemisConsole.info(pfield.getText(), "password");
+ return new DatabaseDocument(location, pfield);
+ }
+
+
+ /**
+ * Set whether to split into separate entries.
+ * @param splitGFFEntry
+ */
+ protected void setSplitGFF(final boolean splitGFFEntry)
+ {
+ this.splitGFFEntry = splitGFFEntry;
+ }
+
+ /**
+ * Given an database feature identifier, this makes an <code>Entry</code>.
+ *
+ * @param bases
+ * If this is null a new Bases object will be created for the Entry
+ * once it has been read from the server. If not null then it will be
+ * passed to the Entry constructor.
+ * @param id
+ * The id of the entry in the database
+ * @param schema
+ * The schema of the entry in the database
+ * @exception OutOfRangeException
+ * Thrown if one of the features in the Entry is out of range of
+ * the Bases object.
+ */
+ private Entry makeFromID(final Bases bases, final String id,
+ final String schema,
+ InputStreamProgressListener progress_listener,
+ final Range range)
+ throws OutOfRangeException, IOException
+ {
+ try
+ {
+ DatabaseDocumentEntry db_entry = null;
+
+ DatabaseDocument doc = new DatabaseDocument(location, pfield, id,
+ schema, splitGFFEntry,
+ progress_listener);
+ doc.setLazyFeatureLoad(LocalAndRemoteFileManager.lazyLoad.isSelected());
+ if(range != null)
+ doc.setRange(range);
+ db_entry = new DatabaseDocumentEntry(doc, null);
+
+ db_entry.setReadOnly(isReadOnly());
+
+ final Bases real_bases;
+
+ if(bases == null)
+ {
+ if (db_entry.getSequence() == null)
+ {
+ UI.error("The selected entry contains no sequence: " + id, "No Sequence");
+ return null;
+ }
+
+ real_bases = new Bases(db_entry.getSequence());
+ }
+ else
+ real_bases = bases;
+
+ return new Entry(real_bases, db_entry);
+ }
+ catch(InvalidKeyException e)
+ {
+ UI.error("Unexpected error while accessing " + id + ": " + e, "Invalid Key");
+ }
+ catch(EntryInformationException e)
+ {
+ UI.error("Failed to get entry: " + e,"Entry Information Exception");
+ }
+
+ return null;
+ }
+
+ protected DatabaseDocumentEntry[] makeFromGff(final DatabaseDocument doc,
+ String id, String schema)
+ throws OutOfRangeException, IOException
+ {
+ DatabaseDocumentEntry[] db_entry = null;
+
+ try
+ {
+ DatabaseDocument[] new_docs = doc.getGffDocuments(location, id, schema);
+ db_entry = new DatabaseDocumentEntry[new_docs.length];
+
+ for(int i = 0; i < new_docs.length; i++)
+ {
+ new_docs[i].setLazyFeatureLoad(doc.isLazyFeatureLoad());
+ db_entry[i] = new DatabaseDocumentEntry(new_docs[i], null);
+ }
+ }
+ catch (EntryInformationException e)
+ {
+// JOptionPane.showMessageDialog(null,
+// "Failed to get entry: " + e,
+// "Entry Information Exception",
+// JOptionPane.ERROR_MESSAGE);
+ UI.error("Failed to get entry: " + e,"Entry Information Exception");
+ }
+
+ return db_entry;
+ }
+
+ public String getLocation()
+ {
+ return location;
+ }
+
+ public JPasswordField getPfield()
+ {
+ return pfield;
+ }
+
+ public boolean isReadOnly()
+ {
+ return readOnly;
+ }
+
+ public void setReadOnly(boolean readOnly)
+ {
+ this.readOnly = readOnly;
+ }
+
+}
+
+/*
+ * A common interface for login prompting, irrespective of the implementation.
+ */
+interface ILoginPrompt
+{
+ boolean prompt();
+ JPasswordField getPfield();
+ String getServer();
+ String getPort();
+ String getDb();
+ String getUser();
+ boolean getSSL();
+}
+
+/**
+ * Database login panel
+ */
+class DatabaseLoginPrompt extends JPanel implements ILoginPrompt
+{
+ private static final long serialVersionUID = 1L;
+ private JPasswordField pfield;
+ private JTextField server;
+ private JTextField port;
+ private JTextField db;
+ private JTextField user;
+ private JCheckBox ssl;
+ public DatabaseLoginPrompt()
+ {
+ super(new GridBagLayout());
+
+ final GridBagConstraints c = new GridBagConstraints();
+
+ // column 1
+ int nrow = 1;
+ c.anchor = GridBagConstraints.EAST;
+ c.gridx = 0;
+ c.gridy = nrow;
+ add(new JLabel("Server : "), c);
+
+ c.gridy = ++nrow;
+ add(new JLabel("Port : "), c);
+
+ c.gridy = ++nrow;
+ add(new JLabel("Database : "), c);
+
+ c.gridy = ++nrow;
+ add(new JLabel("User : "), c);
+
+ c.gridy = ++nrow;
+ add(new JLabel("Password : "), c);
+
+ c.gridy = ++nrow;
+ add(new JLabel("SSL : "), c);
+
+ // column 2
+ nrow = 1;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.gridx = 1;
+ c.gridy = nrow;
+ server = new JTextField("localhost");
+ add(server, c);
+
+ c.gridy = ++nrow;
+ port = new JTextField("5432");
+ add(port, c);
+
+ c.gridy = ++nrow;
+ db = new JTextField("chado");
+ add(db, c);
+
+ c.gridy = ++nrow;
+ user = new JTextField("");
+ add(user, c);
+
+ c.gridy = ++nrow;
+ pfield = new JPasswordField(16);
+ add(pfield, c);
+
+ c.gridy = ++nrow;
+ ssl = new JCheckBox();
+ add(ssl, c);
+
+ // given -Dchado=localhost:port/dbname?username
+ if(System.getProperty("chado") != null)
+ setFromURL(System.getProperty("chado").trim());
+
+ if (System.getProperty("chadoPassword") != null)
+ {
+ //System.out.println("detected chadoPassword: " + System.getProperty("chadoPassword"));
+ pfield.setText(System.getProperty("chadoPassword"));
+ }
+
+ c.gridx = 1;
+ c.gridy = ++nrow;
+ add(getServerComboBox(), c);
+
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ add(new JLabel("Available databases : "), c);
+ }
+
+ public boolean prompt()
+ {
+ int n = JOptionPane.showConfirmDialog(null, this,
+ "Enter Database Address",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(n == JOptionPane.CANCEL_OPTION)
+ return false;
+ return true;
+ }
+
+ private void setFromURL(String db_url)
+ {
+ DatabaseLocationParser dlp = new DatabaseLocationParser(db_url);
+ server.setText(dlp.getHost());
+ port.setText("" + dlp.getPort());
+ db.setText(dlp.getDatabase());
+ user.setText(dlp.getUsername());
+ ssl.setSelected(dlp.isSSLEnabled());
+ }
+
+ /**
+ * Get a combo box of the available database servers.
+ * @return
+ */
+ private JExtendedComboBox getServerComboBox()
+ {
+ // known servers
+ StringVector serversVector = Options.getOptions().getOptionValues("chado_servers");
+ if(serversVector == null)
+ {
+ serversVector = new StringVector();
+ serversVector.add("GeneDB (read-only)");
+ serversVector.add("db.genedb.org:5432/snapshot?genedb_ro");
+ }
+
+
+ // set the default to the chado property value
+ if(System.getProperty("chado") != null &&
+ !serversVector.contains(System.getProperty("chado").trim()))
+ {
+ serversVector.add(0, "Default");
+ serversVector.add(1, System.getProperty("chado").trim());
+ }
+
+ final String servers[][] = new String[2][serversVector.size()/2];
+
+ int nserver = 0;
+ for(int i=0; i<serversVector.size(); i+=2)
+ {
+ servers[0][nserver] = (String) serversVector.get(i);
+ servers[1][nserver] = (String) serversVector.get(i+1);
+ nserver++;
+ }
+
+ final JExtendedComboBox serverSelection = new JExtendedComboBox(
+ servers[0], false);
+
+ serverSelection.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent arg0)
+ {
+ String selectedServerUrl = null;
+ for(int i=0; i<servers[0].length; i++)
+ {
+ if(serverSelection.getSelectedItem().equals(servers[0][i]))
+ selectedServerUrl = servers[1][i];
+ }
+ if(selectedServerUrl != null)
+ setFromURL(selectedServerUrl);
+ }
+ });
+ return serverSelection;
+ }
+
+ public JPasswordField getPfield()
+ {
+ return pfield;
+ }
+
+ public String getDb()
+ {
+ return db.getText().trim();
+ }
+
+ public String getPort()
+ {
+ return port.getText().trim();
+ }
+
+ public String getServer()
+ {
+ return server.getText().trim();
+ }
+
+ public String getUser()
+ {
+ return user.getText().trim();
+ }
+
+ public boolean getSSL()
+ {
+ return ssl.isSelected();
+ }
+}
+
+class DatabaseLoginPromptConsole implements ILoginPrompt
+{
+ private String password;
+ private String server;
+ private String port;
+ private String db;
+ private String user;
+ private String ssl;
+
+ public DatabaseLoginPromptConsole()
+ {
+
+ if(System.getProperty("chado") != null)
+ {
+ //System.out.println("detected chado : " + System.getProperty("chado"));
+ setFromURL(System.getProperty("chado").trim());
+ }
+
+
+ if (System.getProperty("chadoPassword") != null)
+ {
+ //System.out.println("detected chadoPassword: " + System.getProperty("chadoPassword"));
+ password = System.getProperty("chadoPassword");
+ }
+ }
+
+ private void setFromURL(String db_url)
+ {
+ DatabaseLocationParser dlp = new DatabaseLocationParser(db_url);
+ server = dlp.getHost();
+ port = "" + dlp.getPort();
+ db = dlp.getDatabase();
+ user = dlp.getUsername();
+ ssl = dlp.isSSLEnabled() ? "y" : "";
+ }
+
+ public JPasswordField getPfield()
+ {
+ JPasswordField pfield = new JPasswordField(16);
+ pfield.setText(password);
+ return pfield;
+ }
+
+ public String getDb()
+ {
+ return db;
+ }
+
+ public String getPort()
+ {
+ return port;
+ }
+
+ public String getServer()
+ {
+ return server;
+ }
+
+ public String getUser()
+ {
+ return user;
+ }
+
+ public boolean getSSL()
+ {
+ return ssl.toLowerCase().equals("y") || ssl.toLowerCase().equals("yes");
+ }
+
+ public boolean prompt()
+ {
+ boolean userEntered = false;
+ if (UI.mode == UI.UIMode.SCRIPT)
+ {
+ return false;
+ }
+
+ if (db == null)
+ {
+ db = UI.userInput("Enter Database", false);
+ userEntered = true;
+ }
+ if (port == null)
+ {
+ port = UI.userInput("Enter Port", false);
+ userEntered = true;
+ }
+ if (server == null)
+ {
+ server = UI.userInput("Enter Server",false);
+ userEntered = true;
+ }
+ if (user == null)
+ {
+ user = UI.userInput("Enter User", false);
+ userEntered = true;
+ }
+ if (password == null)
+ {
+ password = UI.userInput("Enter Password", true);
+ userEntered = true;
+ }
+ if (ssl == null)
+ {
+ ssl = UI.userInput("Use SSL [y/n]", false);
+ userEntered = true;
+ }
+
+ /**
+ GSV found that that this should always return true
+ or else the connection doesn't use some of the
+ parameters passed in on the command line and fails
+ @TODO must investigate why...
+ */
+ return true;
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/database/DatabaseJPanel.java b/uk/ac/sanger/artemis/components/database/DatabaseJPanel.java
new file mode 100644
index 0000000..166ec24
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/database/DatabaseJPanel.java
@@ -0,0 +1,790 @@
+/* DatabaseJPanel.java
+ *
+ * created: June 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2005,2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/database/DatabaseJPanel.java,v 1.25 2009-06-03 10:24:17 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components.database;
+
+import uk.ac.sanger.artemis.components.*;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.util.InputStreamProgressEvent;
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.ValidateFeature;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JTextField;
+import javax.swing.JTree;
+import javax.swing.JScrollPane;
+import javax.swing.JPanel;
+import javax.swing.JLabel;
+import javax.swing.BorderFactory;
+import javax.swing.ListSelectionModel;
+import javax.swing.border.Border;
+import javax.swing.tree.TreePath;
+
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureLoc;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.Cursor;
+import java.awt.FontMetrics;
+import java.io.*;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+public class DatabaseJPanel extends JPanel
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ private static JLabel status_line = new JLabel("");
+ private static boolean splitGFFEntry = false;
+ private JTree tree;
+ private DatabaseDocument doc;
+ private static Vector<String> opening = new Vector<String>();
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(DatabaseJPanel.class);
+
+ public DatabaseJPanel(final DatabaseEntrySource entry_source,
+ final Splash splash_main)
+ {
+ setLayout(new BorderLayout());
+ tree = getDatabaseTree(entry_source);
+
+ // Listen for when the selection changes.
+ MouseListener mouseListener = new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ if(e.getClickCount() == 2 && !e.isPopupTrigger())
+ showSelected(entry_source, splash_main);
+ }
+ };
+ tree.addMouseListener(mouseListener);
+
+ JScrollPane scroll = new JScrollPane(tree);
+
+ Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ Dimension dim_frame = new Dimension(screen.width * 2 / 10,
+ screen.height * 6 / 10);
+ scroll.setPreferredSize(dim_frame);
+
+ add(scroll, BorderLayout.CENTER);
+
+ final FontMetrics fm = this.getFontMetrics(status_line.getFont());
+
+ final int font_height = fm.getHeight() + 10;
+ status_line.setMinimumSize(new Dimension(100, font_height));
+ status_line.setPreferredSize(new Dimension(100, font_height));
+
+ Border loweredbevel = BorderFactory.createLoweredBevelBorder();
+ Border raisedbevel = BorderFactory.createRaisedBevelBorder();
+ Border compound = BorderFactory.createCompoundBorder(raisedbevel,
+ loweredbevel);
+ status_line.setBorder(compound);
+ add(status_line, BorderLayout.SOUTH);
+
+ Box xBox = Box.createHorizontalBox();
+ JLabel title_line = new JLabel("DATABASE :: "+
+ entry_source.getDatabaseDocument().getName());
+ title_line.setMinimumSize(new Dimension(100, font_height));
+ title_line.setPreferredSize(new Dimension(250, font_height));
+ title_line.setBorder(compound);
+ xBox.add(title_line);
+
+ final JTextField openGeneText = new JTextField(6);
+ openGeneText.addKeyListener(new KeyAdapter()
+ {
+ public void keyPressed(KeyEvent e)
+ {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER)
+ getEntryEditFromDatabase(entry_source, splash_main, DatabaseJPanel.this,
+ openGeneText.getText().trim());
+ }
+ });
+ JButton openBtn = new JButton("Open:");
+ openBtn.setToolTipText("Open Artemis for a chromosome or at a given gene");
+ openBtn.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ getEntryEditFromDatabase(entry_source, splash_main, DatabaseJPanel.this,
+ openGeneText.getText().trim());
+ }
+ });
+ xBox.add(Box.createHorizontalGlue());
+ xBox.add(openBtn);
+ xBox.add(openGeneText);
+
+ add(xBox, BorderLayout.NORTH);
+ }
+
+ /**
+ * Show the selected sequence in the tree
+ * @param entry_source
+ * @param tree
+ * @param splash_main
+ */
+ public void showSelected(final DatabaseEntrySource entry_source,
+ final Splash splash_main)
+ {
+ try
+ {
+ TreePath path = tree.getLeadSelectionPath();
+ if(path == null)
+ return;
+ DatabaseTreeNode seq_node =
+ (DatabaseTreeNode)path.getLastPathComponent();
+ String node_name = (String)seq_node.getUserObject();
+ String userName = doc.getUserName();
+
+ final String id = seq_node.getFeatureId();
+ if(id != null)
+ {
+ boolean isMitochondrial = false;
+ if(seq_node.getFeatureType() != null &&
+ seq_node.getFeatureType().startsWith("mitochondrial_"))
+ isMitochondrial = true;
+ boolean readOnly = DatabaseTreeNode.setOrganismProps(
+ seq_node.getOrganism().getOrganismProps(), isMitochondrial);
+ getEntryEditFromDatabase(id, entry_source, tree,
+ status_line, stream_progress_listener,
+ splitGFFEntry, splash_main,
+ node_name, userName, readOnly);
+ }
+ }
+ catch(NullPointerException npe)
+ {
+ npe.printStackTrace();
+ }
+ }
+
+
+ /**
+ * Validate the selected chromosome/genome
+ * @param entrySrc
+ */
+ public void validate(final DatabaseEntrySource entrySrc)
+ {
+ try
+ {
+ final TreePath path = tree.getLeadSelectionPath();
+ if(path == null)
+ return;
+ DatabaseTreeNode node =
+ (DatabaseTreeNode)path.getLastPathComponent();
+ final String userName = doc.getUserName();
+ String id = node.getFeatureId();
+ final FileViewer fv = new FileViewer(
+ (String) node.getUserObject(), false, false, true);
+ int nfail = 0;
+
+ if(id != null)
+ nfail = openValidatePanel(fv, entrySrc, id, userName, node, nfail);
+ else
+ {
+ if(!node.isExplored())
+ node.explore();
+ for(int i=0; i<node.getChildCount(); i++)
+ {
+ DatabaseTreeNode child = (DatabaseTreeNode)node.getChildAt(i);
+ id = child.getFeatureId();
+
+ if(id == null)
+ {
+ for(int j=0; j<child.getChildCount(); j++)
+ {
+ DatabaseTreeNode child2 =
+ (DatabaseTreeNode)child.getChildAt(j);
+ id = child2.getFeatureId();
+ nfail = openValidatePanel(fv, entrySrc, id, userName, child2, nfail);
+ }
+ }
+ else
+ nfail = openValidatePanel(fv, entrySrc, id, userName, child, nfail);
+ }
+ }
+
+ fv.setTitle(fv.getTitle()+" ::: Failed: "+nfail);
+ fv.setVisible(true);
+ }
+ catch(NullPointerException npe)
+ {
+ npe.printStackTrace();
+ }
+ }
+
+ /**
+ * Create entry and validate
+ * @param fv
+ * @param entrySrc
+ * @param srcFeatureId
+ * @param userName
+ * @param node
+ * @param nfail
+ * @return
+ */
+ private int openValidatePanel(final FileViewer fv,
+ final DatabaseEntrySource entrySrc,
+ final String srcFeatureId,
+ final String userName,
+ final DatabaseTreeNode node,
+ int nfail)
+ {
+ try
+ {
+ stream_progress_listener.progressMade("Validating... "+(String)node.getUserObject());
+ boolean isMitochondrial = false;
+ if(((String)node.getPreviousNode().getUserObject()).startsWith("mitochondrial_"))
+ isMitochondrial = true;
+ DatabaseTreeNode.setOrganismProps(
+ node.getOrganism().getOrganismProps(), isMitochondrial);
+
+ final Entry entry = entrySrc.getEntry(srcFeatureId, userName,
+ stream_progress_listener, null);
+ ((DatabaseDocumentEntry)entry.getEMBLEntry()).setReadOnly(true);
+ final EntryGroup entryGrp = new SimpleEntryGroup(entry.getBases());
+ entryGrp.add(entry);
+
+ final ValidateFeature validate = new ValidateFeature(entryGrp);
+ uk.ac.sanger.artemis.io.FeatureVector features = entry.getEMBLEntry().getAllFeatures();
+
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ if(!validate.featureValidate(f, fv, true))
+ nfail++;
+
+ entry.dispose();
+ stream_progress_listener.progressMade("Validated: "+(String)node.getUserObject());
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ catch (NoSequenceException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ return nfail;
+ }
+
+ /**
+ * Open an Artemis EntryEdit window
+ * @param entry_source
+ * @param splash_main
+ * @param stream_progress_listener
+ * @param entryName e.g. Pfalciparum:Pf3D7_09 or
+ * Pfalciparum:Pf3D7_09:20050..40000
+ * @return
+ * @throws Exception
+ */
+ public static EntryEdit show(final DatabaseEntrySource entry_source,
+ final Splash splash_main,
+ final InputStreamProgressListener stream_progress_listener,
+ final String entryName) throws Exception
+ {
+ return show(entry_source,
+ (splash_main == null ? null : splash_main.getCanvas()),
+ (splash_main == null ? null : splash_main.getStatusLabel()),
+ splash_main, stream_progress_listener,
+ entryName, false, false);
+ }
+
+
+ /**
+ * Open an Artemis EntryEdit window
+ * @param entry_source
+ * @param srcComponent
+ * @param status_line
+ * @param splash_main
+ * @param stream_progress_listener
+ * @param entryName e.g. Pfalciparum:Pf3D7_09 or
+ * Pfalciparum:Pf3D7_09:20050..40000
+ * @param isNotSrcFeature true is the entry name may not be a source feature
+ * @return
+ * @throws Exception
+ */
+ private static EntryEdit show(final DatabaseEntrySource entry_source,
+ final Component srcComponent,
+ final JLabel status_line,
+ final Splash splash_main,
+ final InputStreamProgressListener stream_progress_listener,
+ final String entryName,
+ final boolean isNotSrcFeature,
+ final boolean splitGFFEntry) throws Exception
+ {
+ final String entry[] = entryName.split(":");
+ String url = (String)entry_source.getLocation();
+ int index = url.indexOf("?");
+
+ String userName = url.substring(index+1).trim();
+ if(userName.startsWith("user="))
+ userName = userName.substring(5);
+
+ DatabaseDocument doc = entry_source.getDatabaseDocument();
+
+ Range range = null;
+ if(entry.length>2)
+ {
+ if(entry[2].indexOf("..") > -1)
+ {
+ String ranges[] = entry[2].split("\\.\\.");
+ if(ranges.length == 2)
+ {
+ try
+ {
+ range = new Range(Integer.parseInt(ranges[0]),
+ Integer.parseInt(ranges[1]));
+ }
+ catch(Exception e){ e.printStackTrace(); }
+ }
+ }
+ }
+
+ Feature f = doc.getFeatureByUniquename(entry[1]);
+
+ if(isNotSrcFeature && f.getFeatureLocsForFeatureId() != null
+ && f.getFeatureLocsForFeatureId().size() > 0)
+ {
+ final Collection<FeatureLoc> flocs = f.getFeatureLocsForFeatureId();
+ if(flocs.size() > 1)
+ {
+ final Object flocsArr[] = flocs.toArray();
+ final String uniqueNames[] = new String[flocsArr.length];
+ for(int i=0; i<flocsArr.length; i++)
+ {
+ Feature thisF = ((FeatureLoc) flocsArr[i]).getFeatureBySrcFeatureId();
+ uniqueNames[i] = thisF.getUniqueName() + " (" + thisF.getCvTerm().getName() + ")";
+ }
+ final JList list = new JList(uniqueNames);
+ list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
+ list.setSelectedIndex(0);
+ int status = JOptionPane.showConfirmDialog(null,
+ new JScrollPane(list), "Choose", JOptionPane.OK_CANCEL_OPTION);
+ if(status == JOptionPane.CANCEL_OPTION)
+ return null;
+ f = ((FeatureLoc)flocsArr[list.getSelectedIndex()]).getFeatureBySrcFeatureId();
+ logger4j.debug("Opening... "+f.getUniqueName());
+ }
+ else
+ {
+ final Iterator<FeatureLoc> it = flocs.iterator();
+ f = it.next().getFeatureBySrcFeatureId();
+ }
+ }
+
+ if(f == null)
+ throw new Exception(entryName+" not found!");
+
+ boolean isMitochondrial = false;
+ if(f.getCvTerm().getName().startsWith("mitochondrial_"))
+ isMitochondrial = true;
+ boolean readOnly = DatabaseTreeNode.setOrganismProps(
+ f.getOrganism().getOrganismProps(), isMitochondrial);
+ // warn when opening duplicate entries at the same time
+ if(opening.contains(f.getUniqueName()))
+ {
+ int status = JOptionPane.showOptionDialog(null,
+ f.getUniqueName()+" already opening. Continue?",
+ "Open", JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE, null,
+ new String[] {"Yes", "No"}, "No");
+
+ if(status != JOptionPane.YES_OPTION)
+ return null;
+ }
+
+ opening.add(f.getUniqueName());
+ EntryEdit ee = openEntry(Integer.toString(f.getFeatureId()), entry_source,
+ srcComponent, status_line,
+ stream_progress_listener,
+ splitGFFEntry, splash_main, f.getUniqueName(), userName, range, readOnly);
+ opening.remove(f.getUniqueName());
+
+ return ee;
+ }
+
+ /**
+ * Retrieve a database entry.
+ * @param srcfeatureId
+ * @param entry_source
+ * @param srcComponent
+ * @param status_line
+ * @param stream_progress_listener
+ * @param splitGFFEntry
+ * @param splash_main
+ * @param dbDocumentName
+ * @param userName
+ */
+ private static void getEntryEditFromDatabase(
+ final String srcfeatureId,
+ final DatabaseEntrySource entry_source,
+ final JComponent srcComponent,
+ final JLabel status_line,
+ final InputStreamProgressListener stream_progress_listener,
+ final boolean splitGFFEntry,
+ final Splash splash_main,
+ final String dbDocumentName,
+ final String userName,
+ final boolean readOnly)
+ {
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+
+ status_line.setText("Retrieving sequence....");
+ srcComponent.setCursor(cbusy);
+ try
+ {
+ while(DatabaseDocument.isCvThreadAlive())
+ Thread.sleep(5);
+ openEntry(srcfeatureId, entry_source, srcComponent, status_line, stream_progress_listener,
+ splitGFFEntry, splash_main, dbDocumentName, userName, null, readOnly);
+ }
+ catch(RuntimeException re)
+ {
+ logger4j.warn(re.getMessage());
+
+ re.printStackTrace();
+ final DatabaseEntrySource entry_source = new DatabaseEntrySource();
+ entry_source.setLocation(true);
+
+ String url = entry_source.getLocation();
+ int index = url.indexOf("?");
+
+ String userName = url.substring(index+1).trim();
+ if(userName.startsWith("user="))
+ userName = userName.substring(5);
+
+ openEntry(srcfeatureId, entry_source, srcComponent, status_line, stream_progress_listener,
+ splitGFFEntry, splash_main, dbDocumentName, userName, null, readOnly);
+ }
+ catch(InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ srcComponent.setCursor(cdone);
+ return null;
+ }
+
+ };
+ entryWorker.start();
+
+ }
+
+ /**
+ * Open an Artemis entry given a feature name. If the name is
+ * a feature on the sequence (i.e. not the source feature) the
+ * display will open at that region.
+ * @param entry_source
+ * @param splash_main
+ * @param featureName
+ */
+ public static void getEntryEditFromDatabase(final DatabaseEntrySource entry_source,
+ final Splash splash_main,
+ final Component comp,
+ final String featureName)
+ {
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+ try
+ {
+ comp.setCursor(cbusy);
+
+ EntryEdit ee = show(entry_source,
+ comp, status_line, splash_main,
+ stream_progress_listener, ":" + featureName,
+ true, splitGFFEntry);
+ goTo(ee, featureName);
+ }
+ catch (Exception npe)
+ {
+ //npe.printStackTrace();
+ JOptionPane.showMessageDialog(comp,
+ featureName + " not opened/found!",
+ "Failed to Open", JOptionPane.WARNING_MESSAGE);
+ logger4j.debug(featureName + " not found!");
+ }
+ finally
+ {
+ comp.setCursor(cdone);
+ }
+ return null;
+ }
+ };
+ entryWorker.start();
+ }
+
+ /**
+ * Open an Artemis entry
+ * @param srcfeatureId
+ * @param entry_source
+ * @param srcComponent
+ * @param status_line
+ * @param stream_progress_listener
+ * @param splitGFFEntry
+ * @param splash_main
+ * @param dbDocumentName
+ * @param userName
+ * @param range range for to retrieve features in
+ * @return
+ */
+ private static EntryEdit openEntry(
+ final String srcfeatureId,
+ final DatabaseEntrySource entry_source,
+ final Component srcComponent,
+ final JLabel status_line,
+ final InputStreamProgressListener stream_progress_listener,
+ final boolean splitGFFEntry,
+ final Splash splash_main,
+ final String dbDocumentName,
+ final String userName,
+ final Range range,
+ final boolean readOnly)
+ {
+ Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+ try
+ {
+ if(range != null)
+ logger4j.info("LOAD FEATURES IN THE RANGE : "+range.toString());
+ entry_source.setSplitGFF(splitGFFEntry);
+
+ final Entry entry = entry_source.getEntry(srcfeatureId, userName,
+ stream_progress_listener, range);
+
+ DatabaseDocumentEntry db_entry = (DatabaseDocumentEntry) entry
+ .getEMBLEntry();
+ db_entry.setReadOnly(readOnly);
+ DatabaseDocument doc = (DatabaseDocument) db_entry.getDocument();
+ doc.setName(dbDocumentName);
+
+ if(entry == null)
+ {
+ srcComponent.setCursor(cdone);
+ status_line.setText("No entry.");
+ return null;
+ }
+
+ final EntryEdit new_entry_edit = ArtemisMain.makeEntryEdit(entry);
+
+ // add gff entries
+ if(splitGFFEntry)
+ {
+ final DatabaseDocumentEntry[] entries = entry_source.makeFromGff(
+ (DatabaseDocument) db_entry.getDocument(), srcfeatureId, userName);
+
+ for(int i = 0; i < entries.length; i++)
+ {
+ if(entries[i] == null)
+ continue;
+
+ final Entry new_entry = new Entry(new_entry_edit.getEntryGroup()
+ .getBases(), entries[i]);
+ new_entry_edit.getEntryGroup().add(new_entry);
+ }
+ }
+
+ new_entry_edit.setVisible(true);
+ status_line.setText("Sequence loaded.");
+ return new_entry_edit;
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(splash_main, "read failed: one of the features in "
+ + " the entry has an out of range " + "location: "
+ + e.getMessage());
+ }
+ catch(NoSequenceException e)
+ {
+ new MessageDialog(splash_main, "read failed: entry contains no sequence");
+ }
+ catch(IOException e)
+ {
+ new MessageDialog(splash_main, "read failed due to IO error: " + e);
+ }
+ return null;
+ }
+
+ /**
+ * Create database organism JTree.
+ */
+ private JTree getDatabaseTree(final DatabaseEntrySource entry_source)
+ {
+ HashMap entries = null;
+
+ while(entries == null)
+ {
+ try
+ {
+ doc = entry_source.getDatabaseDocument();
+ final DatabaseTreeNode top = new DatabaseTreeNode("");
+
+ File cacheFile = new File(Options.CACHE_PATH+
+ ((String)doc.getLocation()).replaceAll("[/:=\\?]", "_"));
+
+ if(System.getProperty("database_manager_cache_off") == null &&
+ cacheFile.exists())
+ {
+ doc.ping();
+ doc.loadCvTerms();
+ DatabaseTreeNode node = null;
+ try
+ {
+ FileInputStream fis = new FileInputStream(cacheFile);
+ logger4j.debug("USING CACHE :: "+cacheFile.getAbsolutePath());
+ ObjectInputStream in = new ObjectInputStream(fis);
+ node = (DatabaseTreeNode) in.readObject();
+ node.setDbDoc(doc);
+ in.close();
+
+ return new DatabaseJTree((DatabaseTreeNode) node.getParent());
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ }
+ catch (ClassNotFoundException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ else if(System.getProperty("database_manager_cache_off") != null)
+ logger4j.debug("Database manager cache off");
+
+ final List<Organism> organisms = doc.getOrganismsContainingSrcFeatures();
+ for(int i=0; i<organisms.size(); i++)
+ {
+ Organism org = organisms.get(i);
+
+ String name = org.getCommonName();
+ if(name == null || name.equals(""))
+ name = org.getGenus() + "." + org.getSpecies();
+
+ DatabaseTreeNode orgNode =
+ new DatabaseTreeNode(name, false, org, doc.getUserName(), doc);
+ top.add(orgNode);
+ }
+ return new DatabaseJTree(top);
+ }
+ catch(Exception e)
+ {
+ doc.reset();
+ if(!entry_source.setLocation(true))
+ return null;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * An InputStreamProgressListener used to update the error label with the
+ * current number of chars read.
+ */
+ private static final InputStreamProgressListener stream_progress_listener =
+ new InputStreamProgressListener()
+ {
+ public void progressMade(final InputStreamProgressEvent event)
+ {
+ final int char_count = event.getCharCount();
+ if(char_count == -1)
+ status_line.setText("");
+ else
+ status_line.setText("chars read so far: " + char_count);
+ }
+
+ public void progressMade(String progress)
+ {
+ status_line.setText(progress);
+ }
+ };
+
+ public void setSplitGFFEntry(boolean splitGFFEntry)
+ {
+ this.splitGFFEntry = splitGFFEntry;
+ }
+
+ /**
+ * Go to a named feature
+ * @param ee
+ * @param geneName
+ */
+ private static void goTo(final EntryEdit ee, final String geneName)
+ {
+ Selection selection = ee.getSelection();
+ selection.clear();
+
+ final StringVector qualifiers_to_search = new StringVector();
+ qualifiers_to_search.add(Options.getOptions().getAllGeneNames());
+
+ FeatureVector features = ee.getEntryGroup().getAllFeatures();
+ for (int i = 0; i < features.size(); i++)
+ {
+ uk.ac.sanger.artemis.Feature thisFeature = features.elementAt(i);
+ if (thisFeature.containsText(geneName, true, false, qualifiers_to_search))
+ {
+ selection.set(thisFeature);
+ break;
+ }
+ }
+ ee.getGotoEventSource().gotoBase(selection.getLowestBaseOfSelection());
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/database/DatabaseJTree.java b/uk/ac/sanger/artemis/components/database/DatabaseJTree.java
new file mode 100644
index 0000000..0592f1b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/database/DatabaseJTree.java
@@ -0,0 +1,172 @@
+/* DatabaseJTree.java
+ *
+ * created: October 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+*/
+
+package uk.ac.sanger.artemis.components.database;
+
+
+import java.awt.Cursor;
+import java.awt.Point;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.DragSourceDragEvent;
+import java.awt.dnd.DragSourceDropEvent;
+import java.awt.dnd.DragSourceEvent;
+import java.awt.dnd.DragSourceListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JTree;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeExpansionListener;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+
+/**
+*
+* File node for local file tree manager
+*
+*/
+public class DatabaseJTree extends JTree
+ implements DragGestureListener,
+ DragSourceListener
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * @param file file node file
+ *
+ */
+ public DatabaseJTree(DatabaseTreeNode rootNode)
+ {
+ super(rootNode);
+ getSelectionModel().setSelectionMode(
+ TreeSelectionModel.SINGLE_TREE_SELECTION);
+
+
+ DragSource dragSource = DragSource.getDefaultDragSource();
+
+ dragSource.createDefaultDragGestureRecognizer(
+ this, // component where drag originates
+ DnDConstants.ACTION_COPY_OR_MOVE, // actions
+ this); // drag gesture recognizer
+
+ addTreeExpansionListener(new TreeExpansionListener()
+ {
+ public void treeExpanded(TreeExpansionEvent e)
+ {
+ TreePath path = e.getPath();
+ if(path != null)
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ DatabaseTreeNode node = (DatabaseTreeNode)path.getLastPathComponent();
+
+ if(!node.isExplored())
+ {
+ node.explore();
+ DefaultTreeModel model = (DefaultTreeModel)getModel();
+ model.nodeStructureChanged(node);
+ }
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ public void treeCollapsed(TreeExpansionEvent e){}
+ });
+
+
+ }
+
+ private boolean isLeafSelection()
+ {
+ TreePath path = getLeadSelectionPath();
+ if(path == null)
+ return false;
+
+ DatabaseTreeNode node = (DatabaseTreeNode)path.getLastPathComponent();
+ return node.isLeaf();
+ }
+
+ private DatabaseTreeNode getSelectedNode()
+ {
+ TreePath path = getLeadSelectionPath();
+ if(path == null)
+ return null;
+
+ return (DatabaseTreeNode)path.getLastPathComponent();
+ }
+
+ public void dragGestureRecognized(DragGestureEvent dge)
+ {
+ // ignore if mouse popup trigger
+ InputEvent ie = dge.getTriggerEvent();
+ if(ie instanceof MouseEvent)
+ if(((MouseEvent)ie).isPopupTrigger())
+ return;
+
+ // drag only files
+ if(isLeafSelection())
+ {
+ final int nlist = getSelectionCount();
+ if(nlist > 1)
+ {
+
+ }
+ else
+ {
+ dge.startDrag(DragSource.DefaultCopyDrop, // cursor
+ null,
+ new Point(0,0),
+ (Transferable)getSelectedNode(), // transferable data
+ this); // drag source listener
+ }
+ }
+
+ }
+
+ public void dragDropEnd(DragSourceDropEvent dsde)
+ {
+ }
+
+ public void dragEnter(DragSourceDragEvent dsde)
+ {
+ }
+
+ public void dragExit(DragSourceEvent dse)
+ {
+ }
+
+ public void dragOver(DragSourceDragEvent dsde)
+ {
+ }
+
+ public void dropActionChanged(DragSourceDragEvent dsde)
+ {
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/database/DatabaseTreeNode.java b/uk/ac/sanger/artemis/components/database/DatabaseTreeNode.java
new file mode 100644
index 0000000..5a576b5
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/database/DatabaseTreeNode.java
@@ -0,0 +1,327 @@
+/* DatabaseTreeNode.java
+ *
+ * created: October 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+*/
+
+package uk.ac.sanger.artemis.components.database;
+
+import java.awt.Frame;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.organism.OrganismProp;
+import org.gmod.schema.sequence.Feature;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.Splash;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+* File node for local file tree manager
+*/
+public class DatabaseTreeNode extends DefaultMutableTreeNode
+ implements Transferable, Serializable
+{
+ private static final long serialVersionUID = 1L;
+ public static final DataFlavor DATABASETREENODE =
+ new DataFlavor(DatabaseTreeNode.class, "Work Package");
+ public static final DataFlavor STRING_DATA_FLAVOUR =
+ new DataFlavor(String.class, "text/plain");
+ private static final DataFlavor flavors[] =
+ { DATABASETREENODE, DataFlavor.stringFlavor };
+
+ private String featureId;
+ private String featureType; // e.g. chromosome, mitochondrial_chromosome
+ private transient Organism organism;
+ private String organismCommonName;
+ private boolean isLeaf = false;
+ private String userName;
+ private static transient DatabaseDocument dbDoc;
+ private boolean explored = false;
+
+ protected DatabaseTreeNode(final String name)
+ {
+ super(name);
+ }
+
+ private DatabaseTreeNode(final String name,
+ final boolean isLeaf)
+ {
+ super(name);
+ this.isLeaf = isLeaf;
+ }
+
+ /**
+ * Top node constructor
+ * @param name
+ * @param isLeaf
+ * @param organism
+ * @param userName
+ * @param dbDoc
+ */
+ protected DatabaseTreeNode(final String name,
+ final boolean isLeaf,
+ final Organism organism,
+ final String userName,
+ final DatabaseDocument dbDoc)
+ {
+ super(name);
+ this.isLeaf = isLeaf;
+ this.organism = organism;
+ this.userName = userName;
+ this.dbDoc = dbDoc;
+ setOrganismCommonName();
+ }
+
+ /**
+ * Leaf constructor
+ * @param name
+ * @param organism
+ * @param featureId
+ * @param userName
+ */
+ private DatabaseTreeNode(final String name,
+ final Organism organism,
+ final String featureId,
+ final String featureType,
+ final String userName)
+ {
+ super(name);
+ this.organism = organism;
+ this.featureId = featureId;
+ this.featureType = featureType;
+ this.userName = userName;
+ if(getOrganism() != null)
+ setOrganismCommonName();
+ }
+
+ public String getOrganismCommonName()
+ {
+ return organismCommonName;
+ }
+
+ private void setOrganismCommonName()
+ {
+ this.organismCommonName = getOrganism().getCommonName();
+ if(organismCommonName == null || organismCommonName.equals(""))
+ organismCommonName = getOrganism().getGenus() + "." + getOrganism().getSpecies();
+ }
+
+ /**
+ * Use the OrganismProps to set the translation table and
+ * determine in this is a read only entry.
+ * @param op
+ * @return
+ */
+ public static boolean setOrganismProps(Set<OrganismProp> op, final boolean isMitochondrial)
+ {
+ Splash splash = getSplash();
+ boolean readOnly = false;
+ final Iterator<OrganismProp> it = op.iterator();
+ while (it.hasNext())
+ {
+ OrganismProp organismProp = it.next();
+ if(splash != null)
+ {
+ if( (isMitochondrial &&
+ organismProp.getCvTerm().getName().equals("mitochondrialTranslationTable")) ||
+ (!isMitochondrial &&
+ organismProp.getCvTerm().getName().equals("translationTable")))
+ splash.setTranslationTable(organismProp.getValue());
+ }
+
+ if(organismProp.getCvTerm().getName().equals("frozen") &&
+ organismProp.getValue().equals("yes"))
+ readOnly = true;
+ }
+ return readOnly;
+ }
+
+ private static Splash getSplash()
+ {
+ Frame[] frames = JFrame.getFrames();
+ for(int i=0;i<frames.length;i++)
+ {
+ if(frames[i] instanceof Splash)
+ return (Splash)frames[i];
+ }
+ return null;
+ }
+
+ /** @return true if node is a directory */
+ public boolean getAllowsChildren() { return !isLeaf; }
+ /** @return true if node is a file */
+ public boolean isLeaf() { return isLeaf; }
+ /** @return true if node is a directory */
+ public boolean isDirectory() { return !isLeaf; }
+ /** @return true if explored */
+ public boolean isExplored() { return explored; }
+
+ /**
+ * Explore the tree node if this is a node with child nodes.
+ */
+ public void explore()
+ {
+ if(isLeaf)
+ return;
+
+ List<Feature> sequenceList = dbDoc.getResidueFeatures(new Integer(getOrganism().getOrganismId()));
+ Hashtable<String, DatabaseTreeNode> sequenceNode = new Hashtable<String, DatabaseTreeNode>();
+
+ for(int i=0;i<sequenceList.size(); i++)
+ {
+ Feature f = sequenceList.get(i);
+ DatabaseTreeNode typeNode;
+ if(!sequenceNode.containsKey(f.getCvTerm().getName()))
+ {
+ typeNode = new DatabaseTreeNode(f.getCvTerm().getName(), false);
+ add(typeNode);
+ sequenceNode.put(f.getCvTerm().getName(), typeNode);
+ }
+ else
+ typeNode = (DatabaseTreeNode) sequenceNode.get(f.getCvTerm().getName());
+
+ DatabaseTreeNode seqNode = new DatabaseTreeNode(
+ f.getUniqueName(), getOrganism(),
+ Integer.toString(f.getFeatureId()),
+ f.getCvTerm().getName(), getUserName());
+
+ seqNode.isLeaf = true;
+ typeNode.add(seqNode);
+ typeNode.explored = true;
+ }
+ explored = true;
+
+ if(System.getProperty("database_manager_cache_off") == null)
+ writeCache();
+ }
+
+ /**
+ * Write out this node to the cache directory
+ */
+ private void writeCache()
+ {
+ try
+ {
+ File dir = new File(Options.CACHE_PATH);
+ if(!dir.exists())
+ dir.mkdirs();
+ FileOutputStream fos = new FileOutputStream(Options.CACHE_PATH +
+ ((String)dbDoc.getLocation()).replaceAll("[/:=\\?]", "_"));
+
+ ObjectOutputStream out = new ObjectOutputStream(fos);
+ out.writeObject(this);
+ out.close();
+ }
+ catch(Exception ex)
+ {
+ JOptionPane.showMessageDialog(null, ex.getMessage());
+ }
+ }
+
+
+// Transferable
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return flavors;
+ }
+
+ public boolean isDataFlavorSupported(DataFlavor f)
+ {
+ if(f.equals(DATABASETREENODE) || f.equals(DataFlavor.stringFlavor))
+ return true;
+ return false;
+ }
+
+ public Object getTransferData(DataFlavor d)
+ throws UnsupportedFlavorException, IOException
+ {
+ if(d.equals(DATABASETREENODE))
+ return new DatabaseTreeNode((String)getUserObject(),
+ organism,
+ getFeatureId(), getFeatureType(), getUserName());
+ else if(d.equals(DataFlavor.stringFlavor))
+ {
+ String name = getOrganism().getCommonName();
+ if(name == null || name.equals(""))
+ name = getOrganism().getGenus() + "." + getOrganism().getSpecies();
+ return name+":featureId="+getFeatureId();
+ }
+ else throw new UnsupportedFlavorException(d);
+ }
+
+ public String getFeatureId()
+ {
+ return featureId;
+ }
+
+ public String getFeatureType()
+ {
+ return featureType;
+ }
+
+ protected void setDbDoc(DatabaseDocument dbDoc)
+ {
+ DatabaseTreeNode.dbDoc = dbDoc;
+ }
+
+ public Organism getOrganism()
+ {
+ if(organism == null && organismCommonName != null)
+ organism = dbDoc.getOrganismByCommonName(organismCommonName);
+
+ return organism;
+ }
+
+ public String getUserName()
+ {
+ return userName;
+ }
+
+//Serializable
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException
+ {
+ out.defaultWriteObject();
+ }
+
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/filetree/AbstractCellEditor.java b/uk/ac/sanger/artemis/components/filetree/AbstractCellEditor.java
new file mode 100644
index 0000000..015e885
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/AbstractCellEditor.java
@@ -0,0 +1,100 @@
+/*
+ *
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import java.awt.Component;
+import java.awt.event.*;
+import java.awt.AWTEvent;
+import javax.swing.*;
+import javax.swing.event.*;
+import java.util.EventObject;
+import java.io.Serializable;
+
+/**
+ *
+ * A base class for CellEditors, providing default implementations for all
+ * methods in the CellEditor interface and support for managing a series
+ * of listeners.
+ *
+ */
+public class AbstractCellEditor implements CellEditor
+{
+
+ protected EventListenerList listenerList = new EventListenerList();
+
+ public Object getCellEditorValue() { return null; }
+ public boolean isCellEditable(EventObject e) { return true; }
+ public boolean shouldSelectCell(EventObject anEvent) { return false; }
+ public boolean stopCellEditing() { return true; }
+ public void cancelCellEditing() {}
+
+ public void addCellEditorListener(CellEditorListener l)
+ {
+ listenerList.add(CellEditorListener.class, l);
+ }
+
+ public void removeCellEditorListener(CellEditorListener l)
+ {
+ listenerList.remove(CellEditorListener.class, l);
+ }
+
+ /**
+ * Notify all listeners that have registered interest for
+ * notification on this event type.
+ * @see EventListenerList
+ */
+ protected void fireEditingStopped()
+ {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for(int i = listeners.length-2; i>=0; i-=2)
+ {
+ if(listeners[i]==CellEditorListener.class) {
+ ((CellEditorListener)listeners[i+1]).editingStopped(new ChangeEvent(this));
+ }
+ }
+ }
+
+ /**
+ * Notify all listeners that have registered interest for
+ * notification on this event type.
+ * @see EventListenerList
+ */
+ protected void fireEditingCanceled()
+ {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length-2; i>=0; i-=2)
+ {
+ if(listeners[i]==CellEditorListener.class) {
+ ((CellEditorListener)listeners[i+1]).editingCanceled(new ChangeEvent(this));
+ }
+ }
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/components/filetree/DatabaseEntryFilterPanel.java b/uk/ac/sanger/artemis/components/filetree/DatabaseEntryFilterPanel.java
new file mode 100644
index 0000000..a0ea0b2
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/DatabaseEntryFilterPanel.java
@@ -0,0 +1,183 @@
+/********************************************************************
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Library General Public
+* License as published by the Free Software Foundation; either
+* version 2 of the License, or (at your option) any later version.
+*
+* This library 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
+* Library General Public License for more details.
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this library; if not, write to the
+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*
+* Copyright (C) Genome Research Limited
+*
+********************************************************************/
+package uk.ac.sanger.artemis.components.filetree;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.components.KeyChoice;
+import uk.ac.sanger.artemis.io.GFFEntryInformation;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+
+/**
+ * This displays the feature key information used to split the data read
+ * from the database into separate Artemis entries. The name of each entry
+ * is displayed along with an editable list of feature keys that belong
+ * to that entry.
+ */
+public class DatabaseEntryFilterPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ private JTextField[] nameField;
+ private JComboBox[] keyList;
+
+ public DatabaseEntryFilterPanel()
+ {
+ super(new GridBagLayout());
+
+ final GridBagConstraints c = new GridBagConstraints();
+
+ // current feature types in each entry
+ String types[][][] = DatabaseDocument.getTYPES();
+ nameField = new JTextField[types.length];
+ keyList = new JComboBox[types.length];
+
+ c.gridx = 1;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.WEST;
+ JLabel nameLabel = new JLabel("Entry Name");
+ Font fontBold = nameLabel.getFont().deriveFont(Font.BOLD);
+ nameLabel.setFont(fontBold);
+ add(nameLabel, c);
+ c.gridx = 2;
+ JLabel featureKeyLabel = new JLabel("Feature Key List");
+ featureKeyLabel.setFont(fontBold);
+ add(featureKeyLabel, c);
+ c.gridx = 3;
+ c.gridwidth = 3;
+ JLabel featureKeyEditLabel = new JLabel("Edit Feature Key List");
+ featureKeyEditLabel.setFont(fontBold);
+ add(featureKeyEditLabel, c);
+ c.gridwidth = 1;
+
+ c.gridy = 1;
+ c.gridx = 0;
+ c.fill = GridBagConstraints.BOTH;
+ c.gridwidth = 6;
+ add(new JSeparator(), c);
+ c.gridwidth = 1;
+ c.gridy = 2;
+ add(Box.createVerticalStrut(10), c);
+ c.fill = GridBagConstraints.NONE;
+
+ c.gridx = 0;
+ c.gridy = 3;
+ add(new JLabel("1 "), c);
+ c.gridx = 1;
+ add(new JLabel("Default"), c);
+
+ for(int i = 0; i < types.length; i++)
+ {
+ final KeyChoice keyChoice = new KeyChoice(new GFFEntryInformation());
+ int gridx = 0;
+
+ c.gridx = gridx;
+ c.gridy = i + 4;
+
+ add(new JLabel(Integer.toString(i+2)), c);
+
+ c.gridx = ++gridx;
+ nameField[i] = new JTextField(types[i][0][0], 20);
+ add(nameField[i], c);
+
+ keyList[i] = new JComboBox(types[i][1]);
+ keyList[i].setPreferredSize(new Dimension(
+ keyChoice.getPreferredSize().width, keyList[i].getPreferredSize().height));
+ final int MAX_VISIBLE_ROWS = 30;
+ keyList[i].setMaximumRowCount (MAX_VISIBLE_ROWS);
+ keyList[i].setEditable(false);
+ final JComboBox thiskeyList = keyList[i];
+
+ c.gridx = ++gridx;
+ add(keyList[i], c);
+ c.gridx = ++gridx;
+ JButton deleteKey = new JButton("Delete");
+ add(deleteKey, c);
+ deleteKey.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ thiskeyList.removeItem(thiskeyList.getSelectedItem());
+ thiskeyList.revalidate();
+ }
+ });
+
+ c.gridx = ++gridx;
+ JButton addKey = new JButton("Add :");
+ add(addKey, c);
+ c.gridx = ++gridx;
+
+ add(keyChoice, c);
+
+
+ addKey.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ for(int i=0; i<thiskeyList.getItemCount(); i++)
+ if(thiskeyList.getItemAt(i).equals(keyChoice.getSelectedItem()))
+ return;
+ thiskeyList.addItem(keyChoice.getSelectedItem().getKeyString());
+ thiskeyList.setSelectedItem(keyChoice.getSelectedItem().getKeyString());
+ }
+ });
+ }
+ }
+
+ /**
+ * Method to set the Entry names (from the nameField text fields) and
+ * set the feature types in each entry (based on keyList values).
+ */
+ protected void setTypesForEntries()
+ {
+ String newTypes[][][] = new String[nameField.length][2][];
+ for(int i=0; i<nameField.length; i++)
+ {
+ newTypes[i][0] = new String[1];
+ newTypes[i][0][0] = nameField[i].getText().trim() ;
+
+ String[] keys = new String[keyList[i].getItemCount()];
+ //System.out.print(newTypes[i][0][0]);
+
+ for(int j=0; j<keyList[i].getItemCount(); j++)
+ {
+ keys[j] = (String) keyList[i].getItemAt(j);
+ //System.out.print(" "+keys[j]);
+ }
+ //System.out.println();
+ newTypes[i][1] = keys;
+ }
+ DatabaseDocument.setTYPES(newTypes);
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/filetree/FileList.java b/uk/ac/sanger/artemis/components/filetree/FileList.java
new file mode 100644
index 0000000..818aec7
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/FileList.java
@@ -0,0 +1,218 @@
+/********************************************************************
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Library General Public
+* License as published by the Free Software Foundation; either
+* version 2 of the License, or (at your option) any later version.
+*
+* This library 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
+* Library General Public License for more details.
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this library; if not, write to the
+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*
+* @author: Copyright (C) Tim Carver
+*
+********************************************************************/
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import uk.ac.sanger.artemis.j2ssh.FTProgress;
+import uk.ac.sanger.artemis.j2ssh.SshFileManager;
+import uk.ac.sanger.artemis.j2ssh.SshPSUClient;
+
+import java.util.Hashtable;
+import java.util.zip.GZIPOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.File;
+
+import com.sshtools.j2ssh.sftp.FileAttributes;
+
+public class FileList
+{
+ /** vector containing directories */
+ private Hashtable vdir;
+ protected static SshFileManager ssh_client;
+
+ public FileList()
+ {
+ if(ssh_client == null)
+ ssh_client = new SshFileManager();
+ }
+
+ public FileList(SshFileManager ssh_client)
+ {
+ FileList.ssh_client = ssh_client;
+ }
+
+ /**
+ *
+ * Routine to obtain a directory listing distinguishing between
+ * files and directories.
+ * @param dir remote directory to list
+ *
+ */
+ protected Hashtable getDirList(String dir)
+ {
+ try
+ {
+ ssh_client.remoteList(dir);
+ }
+ catch(IOException ioe)
+ {
+ return null;
+ }
+ vdir = ssh_client.getDirList();
+ return ssh_client.getFileList();
+// Collections.sort(vfile);
+ }
+
+ /**
+ *
+ * Delete the given file / directory
+ *
+ */
+ protected boolean delete(String file)
+ {
+ return ssh_client.delete(file);
+ }
+
+ /**
+ *
+ * Make a directory
+ *
+ */
+ protected boolean mkdir(String dir)
+ {
+ return ssh_client.mkdir(dir);
+ }
+
+
+ /**
+ *
+ * Print working directory
+ *
+ */
+ protected String pwd()
+ {
+ return ssh_client.pwd();
+ }
+
+ /**
+ *
+ * Rename a file
+ *
+ */
+ protected boolean rename(String old_file, String new_file)
+ {
+ return ssh_client.rename(old_file, new_file);
+ }
+
+
+ /**
+ *
+ * Put a file
+ *
+ */
+ protected boolean put(String dir, File local_file,
+ FTProgress monitor, boolean force)
+ {
+ return ssh_client.put(dir, local_file, monitor, force);
+ }
+
+ /**
+ *
+ * @param name of file to get status for
+ *
+ */
+ public FileAttributes stat(String filename)
+ {
+ return ssh_client.stat(filename);
+ }
+
+
+ /**
+ *
+ * Get the file contents
+ *
+ */
+ public byte[] getFileContents(String file, FTProgress monitor)
+ {
+ return ssh_client.getFileContents(file, monitor);
+ }
+
+ /**
+ * Transfer an entry from a zip file on the remote file system and
+ * create a copy on the local file system.
+ * @param zipFile
+ * @param entry
+ * @param dir_name
+ * @return
+ */
+ public File getZipEntryContents(String zipFile, String entry, File dir_name)
+ {
+ String stdOut = getZipEntry(zipFile, entry, "unzip -p "+zipFile+" "+entry+".gz | gunzip");
+ if(stdOut == null || stdOut.equals(""))
+ stdOut = getZipEntry(zipFile, entry, "unzip -p "+zipFile+" "+entry);
+
+ if(stdOut == null || stdOut.equals(""))
+ return null;
+ // write to the local file system
+ try
+ {
+ File localfn = new File(dir_name.getAbsoluteFile(), entry+".gz");
+ localfn.getParentFile().mkdirs();
+ if(localfn.createNewFile())
+ {
+ GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(localfn));
+ out.write(stdOut.getBytes());
+ out.close();
+ return localfn;
+ }
+ }
+ catch (IOException e)
+ {
+ System.err.println(e.getMessage());
+ }
+ return null;
+ }
+
+ private String getZipEntry(String zipFile, String entry, String cmd)
+ {
+ SshPSUClient sshClient = new SshPSUClient(cmd);
+ sshClient.start();
+ while(sshClient.isAlive() || sshClient.isAlive())
+ try
+ {
+ Thread.sleep(2);
+ }
+ catch (InterruptedException e1){}
+
+ //String stdErr = sshClient.getStdErr();
+ return sshClient.getStdOut();
+ }
+
+
+ /**
+ *
+ * Gets whether this name is a directory
+ * @return true if it is a directory
+ *
+ */
+ public boolean isDirectory(String d)
+ {
+ return vdir.containsKey(d);
+ }
+
+ public static boolean isConnected()
+ {
+ if(ssh_client == null)
+ return false;
+ return ssh_client.isConnected();
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/filetree/FileManager.java b/uk/ac/sanger/artemis/components/filetree/FileManager.java
new file mode 100644
index 0000000..e2809db
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/FileManager.java
@@ -0,0 +1,306 @@
+/********************************************************************
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Library General Public
+* License as published by the Free Software Foundation; either
+* version 2 of the License, or (at your option) any later version.
+*
+* This library 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
+* Library General Public License for more details.
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this library; if not, write to the
+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*
+* Copyright (C) Genome Research Limited
+*
+********************************************************************/
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.Options;
+
+import javax.swing.table.TableColumn;
+import javax.swing.*;
+import java.io.File;
+import java.io.FileFilter;
+import java.awt.event.*;
+import java.awt.geom.*;
+import java.awt.*;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.Enumeration;
+
+public class FileManager extends JFrame
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public FileManager(JFrame frame)
+ {
+ this(frame,getArtemisFilter());
+ }
+
+ /**
+ *
+ * File Manager Frame
+ * @param frame parent frame
+ *
+ */
+ public FileManager(JFrame frame, FileFilter filter)
+ {
+ super("File Manager");
+
+ FileSystemModel model = new FileSystemModel(getLocalDirectories(), filter, this);
+ JTreeTable ftree = new JTreeTable(model);
+ JScrollPane jsp = new JScrollPane(ftree);
+ jsp.getViewport().setBackground(Color.white);
+
+ JPanel pane = (JPanel)getContentPane();
+ pane.setLayout(new BorderLayout());
+ pane.add(jsp, BorderLayout.CENTER);
+ setJMenuBar(makeMenuBar(pane,ftree));
+ pane.add(getFileFileterComboBox(model, ftree), BorderLayout.SOUTH);
+
+ Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ Dimension panelSize = new Dimension((int)(screen.getWidth()/3),
+ (int)(screen.getHeight()/2));
+ jsp.setPreferredSize(panelSize);
+
+ int width = panelSize.width;
+ TableColumn col0 = ftree.getColumnModel().getColumn(0);
+ col0.setPreferredWidth( (int)(width*0.60) );
+
+ TableColumn col1 = ftree.getColumnModel().getColumn(1);
+ col1.setPreferredWidth( (int)(width*0.12) );
+
+ TableColumn col2 = ftree.getColumnModel().getColumn(2);
+ col2.setPreferredWidth( (int)(width*0.28) );
+
+ pack();
+
+ int yloc = (int)((screen.getHeight()-getHeight())/2);
+ setLocation(0,yloc);
+ setVisible(true);
+ }
+
+ /**
+ *
+ * Look in j2ssh.properties for local directories.
+ *
+ */
+ private File[] getLocalDirectories()
+ {
+ final Properties settings = new Properties();
+ ClassLoader cl = getClass().getClassLoader();
+ // try out of the classpath
+ try
+ {
+ settings.load(cl.getResourceAsStream("j2ssh.properties"));
+ }
+ catch (Exception e)
+ {
+ }
+
+ Enumeration enum_prop = settings.propertyNames();
+ Vector dirs = new Vector();
+
+ dirs.add(new File(System.getProperty("user.home")));
+ dirs.add(new File(System.getProperty("user.dir")));
+
+ while(enum_prop.hasMoreElements())
+ {
+ final String property = (String)enum_prop.nextElement();
+ File f = new File(settings.getProperty(property));
+ if(property.startsWith("localdir") && f.exists())
+ dirs.add(f);
+ }
+
+ File fdirs[] = new File[dirs.size()];
+ for(int i=0; i<dirs.size(); i++)
+ fdirs[i] = (File)dirs.get(i);
+
+ return fdirs;
+ }
+
+ protected JComboBox getFileFileterComboBox(final FileSystemModel model,
+ final JTreeTable ftree)
+ {
+ String[] filters = { "Artemis Files", "Sequence Files",
+ "Feature Files", "All Files" };
+ final JComboBox comboFilter = new JComboBox(filters);
+ comboFilter.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ String select = (String)comboFilter.getSelectedItem();
+ if(select.equals("Artemis Files"))
+ model.setFilter(getArtemisFilter());
+ else if(select.equals("Sequence Files"))
+ model.setFilter(getSequenceFilter());
+ else if(select.equals("Feature Files"))
+ model.setFilter(getFeatureFilter());
+ else if(select.equals("All Files"))
+ {
+ model.setFilter(new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if(pathname.getName().startsWith("."))
+ return false;
+ return true;
+ }
+ });
+ }
+
+ ftree.refreshAll();
+ ftree.revalidate();
+ }
+ });
+ return comboFilter;
+ }
+
+ /**
+ *
+ * Get a file filter for sequence and feature suffixes.
+ * @return file filter
+ */
+ protected static FileFilter getArtemisFilter()
+ {
+ final StringVector sequence_suffixes =
+ Options.getOptions().getOptionValues("sequence_file_suffixes");
+
+ final StringVector feature_suffixes =
+ Options.getOptions().getOptionValues("feature_file_suffixes");
+
+ final FileFilter artemis_filter = new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if(pathname.isDirectory() &&
+ !pathname.getName().startsWith("."))
+ return true;
+
+ for(int i = 0; i<sequence_suffixes.size(); ++i)
+ {
+ final String suffix = (String)sequence_suffixes.elementAt(i);
+
+ if(pathname.getName().endsWith("." + suffix) ||
+ pathname.getName().endsWith("." + suffix + ".gz"))
+ return true;
+ }
+
+ for(int i = 0; i<feature_suffixes.size(); ++i)
+ {
+ final String suffix = (String)feature_suffixes.elementAt(i);
+
+ if(pathname.getName().endsWith("." + suffix) ||
+ pathname.getName().endsWith("." + suffix + ".gz"))
+ return true;
+ }
+ return false;
+ }
+ };
+ return artemis_filter;
+ }
+
+
+ /**
+ *
+ * Get a file filter for feature suffixes.
+ * @return file filter
+ */
+ protected static FileFilter getFeatureFilter()
+ {
+ final StringVector feature_suffixes =
+ Options.getOptions().getOptionValues("feature_file_suffixes");
+
+ final FileFilter feature_filter = new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if(pathname.isDirectory() &&
+ !pathname.getName().startsWith("."))
+ return true;
+
+ for(int i = 0; i<feature_suffixes.size(); ++i)
+ {
+ final String suffix = (String)feature_suffixes.elementAt(i);
+
+ if(pathname.getName().endsWith("." + suffix) ||
+ pathname.getName().endsWith("." + suffix + ".gz"))
+ return true;
+ }
+ return false;
+ }
+ };
+ return feature_filter;
+ }
+
+ /**
+ *
+ * Get a file filter for sequence suffixes.
+ * @return file filter
+ */
+ protected static FileFilter getSequenceFilter()
+ {
+ final StringVector sequence_suffixes =
+ Options.getOptions().getOptionValues("sequence_file_suffixes");
+
+ final FileFilter seq_filter = new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if(pathname.isDirectory() &&
+ !pathname.getName().startsWith("."))
+ return true;
+
+ for(int i = 0; i<sequence_suffixes.size(); ++i)
+ {
+ final String suffix = (String)sequence_suffixes.elementAt(i);
+
+ if(pathname.getName().endsWith("." + suffix) ||
+ pathname.getName().endsWith("." + suffix + ".gz"))
+ return true;
+ }
+
+ return false;
+ }
+ };
+ return seq_filter;
+ }
+
+ /**
+ *
+ * Set up a menu and tool bar
+ * @param pane panel to add toolbar to
+ * @param ftree file tree display
+ *
+ */
+ private JMenuBar makeMenuBar(JPanel pane, final JTreeTable ftree)
+ {
+ JMenuBar mBar = new JMenuBar();
+ JMenu fileMenu = new JMenu("File");
+ mBar.add(fileMenu);
+
+ JMenuItem fileMenuClose = new JMenuItem("Close");
+ fileMenuClose.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setVisible(false);
+ }
+ });
+ fileMenu.add(fileMenuClose);
+
+ return mBar;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/filetree/FileNode.java b/uk/ac/sanger/artemis/components/filetree/FileNode.java
new file mode 100644
index 0000000..f28be55
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/FileNode.java
@@ -0,0 +1,160 @@
+/********************************************************************
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Library General Public
+* License as published by the Free Software Foundation; either
+* version 2 of the License, or (at your option) any later version.
+*
+* This library 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
+* Library General Public License for more details.
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this library; if not, write to the
+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*
+* Copyright (C) Genome Research Limited
+*
+********************************************************************/
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import java.awt.datatransfer.*;
+import javax.swing.tree.*;
+import java.io.*;
+import java.util.*;
+
+/**
+*
+* File node for local file tree manager
+*
+*/
+public class FileNode extends DefaultMutableTreeNode
+ implements Transferable, Serializable
+{
+ private boolean isDir;
+ /** data flavour of a file node */
+ public static DataFlavor FILENODE =
+ new DataFlavor(FileNode.class, "Local file");
+ /** flavours file node and string */
+ static DataFlavor flavors[] = { FILENODE, DataFlavor.stringFlavor };
+
+ /**
+ *
+ * @param file file node file
+ *
+ */
+ public FileNode(File file)
+ {
+ setUserObject(file);
+ this.isDir = file.isDirectory();
+ }
+
+ /** Determine if this is a directory */
+ public boolean getAllowsChildren() { return isDirectory(); }
+ /** Determine if this is a file */
+ public boolean isLeaf() { return !isDirectory(); }
+ /** Get the File this node represents */
+ public File getFile() { return (File)getUserObject(); }
+ /** Determine if this is a directory */
+ public boolean isDirectory()
+ {
+ return isDir;
+ }
+
+ public void setDirectory(boolean isDir)
+ {
+ this.isDir = isDir;
+ }
+
+ /**
+ *
+ * Returns the name of the file
+ *
+ */
+ public String toString()
+ {
+ File file = (File)getUserObject();
+ String filename = file.toString();
+ int index = filename.lastIndexOf(File.separator);
+
+ return (index != -1 && index != filename.length()-1) ?
+ filename.substring(index+1) :
+ filename;
+ }
+
+ private Object child_cache[];
+
+ public Object[] getChildren(FileFilter filter)
+ {
+ if(!isDirectory())
+ return null;
+
+ if(child_cache != null)
+ return child_cache;
+
+ File file = getFile();
+ File[] children = file.listFiles(filter);
+
+ if(children == null)
+ return null;
+
+// if(child_cache != null &&
+// child_cache.length == children.length)
+// return child_cache;
+
+// sort into alphabetic order
+ java.util.Arrays.sort(children);
+ child_cache = new Object[children.length];
+ for(int i=0; i < children.length; ++i)
+ {
+ child_cache[i] = new FileNode(children[i]);
+ ((FileNode)child_cache[i]).setParent(this);
+ }
+ return child_cache;
+ }
+
+ public void reset()
+ {
+ child_cache = null;
+ }
+
+
+// Transferable
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return flavors;
+ }
+
+ public boolean isDataFlavorSupported(DataFlavor f)
+ {
+ if(f.equals(FILENODE) || f.equals(DataFlavor.stringFlavor))
+ return true;
+ return false;
+ }
+
+ public Object getTransferData(DataFlavor d)
+ throws UnsupportedFlavorException, IOException
+ {
+ if(d.equals(FILENODE))
+ return this;
+ else if(d.equals(DataFlavor.stringFlavor))
+ return getFile().getAbsolutePath();
+ else throw new UnsupportedFlavorException(d);
+ }
+
+//Serializable
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException
+ {
+ out.defaultWriteObject();
+ }
+
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/filetree/FileSystemModel.java b/uk/ac/sanger/artemis/components/filetree/FileSystemModel.java
new file mode 100644
index 0000000..f92a449
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/FileSystemModel.java
@@ -0,0 +1,251 @@
+/*
+ *
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+import java.awt.Cursor;
+
+import javax.swing.JFrame;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import uk.ac.sanger.artemis.components.filetree.FileNode;
+import uk.ac.sanger.artemis.components.filetree.RemoteFileNode;
+
+/**
+ *
+ * FileSystemModel is a TreeTableModel representing a hierarchical file
+ * system. Nodes in the FileSystemModel are FileNodes which, when they
+ * are directory nodes, cache their children to avoid repeatedly querying
+ * the real file system.
+ *
+ */
+public class FileSystemModel extends DefaultTreeModel
+{
+
+ // Names of the columns.
+ static protected String[] cNames = {"Name", "Size", "Modified"};
+
+ // Types of the columns.
+ static protected Class[] cTypes = {TreeTableModel.class, Integer.class, String.class};
+ /** busy cursor */
+ private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ /** done cursor */
+ private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+
+ private FileFilter filter;
+ private JFrame frame;
+ SimpleDateFormat formatter = new SimpleDateFormat("MMM dd HH:mm yyyy");
+
+ public FileSystemModel(final JFrame frame)
+ {
+ super(new FileNode(new File("")));
+ this.frame = frame;
+
+ filter = new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if(pathname.isDirectory() ||
+ !pathname.getName().startsWith("."))
+ return true;
+ return false;
+ }
+ };
+
+ FileNode rootNode = (FileNode)getRoot();
+ rootNode.setDirectory(true);
+
+ rootNode.add( new FileNode(new File( System.getProperty("user.dir") )) );
+ rootNode.add( new FileNode(new File( System.getProperty("user.home") )) );
+ }
+
+ public FileSystemModel(File rt[], FileFilter filter, JFrame frame)
+ {
+ super(new FileNode(new File("")));
+
+ this.frame = frame;
+ this.filter = filter;
+ FileNode rootNode = (FileNode)getRoot();
+ rootNode.setDirectory(true);
+
+ File homeDir = new File(System.getProperty("user.home"));
+ rootNode.add(new FileNode(homeDir));
+
+ for(int i=0; i<rt.length; i++)
+ {
+ if(rt[i].compareTo(homeDir) != 0)
+ rootNode.add( new FileNode(rt[i]) );
+ }
+ }
+
+ public FileSystemModel(String froots[], final JFrame frame)
+ {
+ super(new RemoteFileNode(true));
+ this.frame = frame;
+
+ RemoteFileNode rootNode = (RemoteFileNode)getRoot();
+ for(int i=0; i<froots.length; i++)
+ {
+ File f = new File(froots[i]);
+ RemoteFileNode node = new RemoteFileNode(froots[i], f.getName(),
+ null, null, true);
+ rootNode.add(node);
+ }
+ }
+
+ public FileFilter getFilter()
+ {
+ return filter;
+ }
+
+
+ public void setFilter(FileFilter filter)
+ {
+ this.filter = filter;
+ }
+
+ //
+ // Some convenience methods.
+ //
+
+ protected Object[] getChildren(Object node)
+ {
+ if(node instanceof FileNode)
+ {
+ FileNode fileNode = ((FileNode)node);
+
+ if(fileNode.getFile().getName().equals(""))
+ {
+ FileNode[] children = new FileNode[fileNode.getChildCount()];
+ for(int i=0; i<children.length; i++)
+ children[i] = (FileNode)fileNode.getChildAt(i);
+ return children;
+ }
+
+ return fileNode.getChildren(filter);
+ }
+ else
+ {
+ RemoteFileNode fileNode = ((RemoteFileNode)node);
+ return fileNode.getChildren();
+ }
+ }
+
+ //
+ // The TreeModel interface
+ //
+ public int getChildCount(Object node)
+ {
+ if(node instanceof RemoteFileNode)
+ frame.setCursor(cbusy);
+ Object[] children = getChildren(node);
+
+ if(node instanceof RemoteFileNode)
+ frame.setCursor(cdone);
+
+ return (children == null) ? 0 : children.length;
+ }
+
+ public Object getChild(Object node, int i)
+ {
+ return getChildren(node)[i];
+ }
+
+ // The superclass's implementation would work, but this is more efficient.
+ public boolean isLeaf(Object node)
+ {
+ return ((TreeNode)node).isLeaf();
+ }
+
+ //
+ // The TreeTableNode interface.
+ //
+
+ public int getColumnCount()
+ {
+ return cNames.length;
+ }
+
+ public String getColumnName(int column)
+ {
+ return cNames[column];
+ }
+
+ public Class getColumnClass(int column)
+ {
+ return cTypes[column];
+ }
+
+ public Object getValueAt(Object node, int column)
+ {
+ if(node instanceof FileNode)
+ {
+ File file = ((FileNode)node).getFile();
+ try
+ {
+ switch(column)
+ {
+ case 0:
+ return file.getName();
+ case 1:
+ return file.isFile() ? new Integer((int)file.length()) : null;
+// case 2:
+//return file.isFile() ? "File" : "Directory";
+ case 2:
+ return file.exists() ?
+ formatter.format(new Date(file.lastModified())) : null;
+// DateFormat.getDateTimeInstance().format(new Date(file.lastModified())) : null;
+ }
+ }
+ catch(SecurityException se) { }
+ }
+ else
+ {
+ try
+ {
+ switch(column)
+ {
+ case 0:
+ return ((RemoteFileNode)node).getFile();
+ case 1:
+ return !((RemoteFileNode)node).isDirectory() ?
+ ((RemoteFileNode)node).length() : null;
+// case 2:
+//return file.isFile() ? "File" : "Directory";
+ case 2:
+ return ((RemoteFileNode)node).getModifiedTime() != null ?
+ formatter.format(((RemoteFileNode)node).getModifiedTime()) : null;
+// DateFormat.getDateTimeInstance().format(((RemoteFileNode)node).getModifiedTime()) : null;
+ }
+ }
+ catch(SecurityException se) { }
+ }
+
+ return null;
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/components/filetree/JTreeTable.java b/uk/ac/sanger/artemis/components/filetree/JTreeTable.java
new file mode 100644
index 0000000..8fbb2a9
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/JTreeTable.java
@@ -0,0 +1,1027 @@
+/*
+ *
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import javax.swing.*;
+import javax.swing.tree.*;
+import javax.swing.table.*;
+import javax.swing.filechooser.FileSystemView;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.*;
+import java.awt.image.BufferedImage;
+import java.util.Vector;
+
+import uk.ac.sanger.artemis.j2ssh.FileTransferProgressMonitor;
+import uk.ac.sanger.artemis.j2ssh.FTProgress;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.SimpleEntryInformation;
+import uk.ac.sanger.artemis.components.EntryEdit;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+
+/**
+*
+* This example shows how to create a simple JTreeTable component,
+* by using a JTree as a renderer (and editor) for the cells in a
+* particular column in the JTable.
+*
+* modified from the example at:
+* http://java.sun.com/products/jfc/tsc/articles/treetable1/
+*
+*/
+public class JTreeTable extends JTable
+ implements DragGestureListener,
+ DragSourceListener, DropTargetListener, ActionListener,
+ Autoscroll
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ /** popup menu */
+ private JPopupMenu popup;
+ /** busy cursor */
+ private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ /** done cursor */
+ private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+ /** file separator */
+ private String fs = new String(System.getProperty("file.separator"));
+ /** AutoScroll margin */
+ private static final int AUTOSCROLL_MARGIN = 45;
+ /** used by AutoScroll method */
+ private Insets autoscrollInsets = new Insets( 0, 0, 0, 0 );
+
+ protected TreeTableCellRenderer tree;
+
+ public JTreeTable(TreeModel treeTableModel)
+ {
+ super();
+
+ DragSource dragSource = DragSource.getDefaultDragSource();
+
+ dragSource.createDefaultDragGestureRecognizer(
+ this, // component where drag originates
+ DnDConstants.ACTION_COPY_OR_MOVE, // actions
+ this); // drag gesture recognizer
+
+ setDropTarget(new DropTarget(this,this));
+
+ // Create the tree. It will be used as a renderer and editor.
+ tree = new TreeTableCellRenderer(treeTableModel);
+
+ // Install a tableModel representing the visible rows in the tree.
+ super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
+
+ // Force the JTable and JTree to share their row selection models.
+ tree.setSelectionModel(new DefaultTreeSelectionModel()
+ {
+ /***/
+ private static final long serialVersionUID = 1L;
+
+ // Extend the implementation of the constructor, as if:
+ /* public this() */
+ {
+ setSelectionModel(listSelectionModel);
+ }
+ });
+ // Make the tree and table row heights the same.
+ tree.setRowHeight(getRowHeight());
+
+ // Install the tree editor renderer and editor.
+ setDefaultRenderer(TreeTableModel.class, tree);
+ setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
+
+ setShowGrid(false);
+ setIntercellSpacing(new Dimension(3, 0));
+
+ //Listen for when a file is selected
+ MouseListener mouseListener = new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent me)
+ {
+ if(me.getClickCount() == 2 && isFileSelection() &&
+ !me.isPopupTrigger())
+ {
+ setCursor(cbusy);
+ FileNode node = getSelectedNode();
+ String selected = node.getFile().getAbsolutePath();
+ showFilePane(selected);
+ setCursor(cdone);
+ }
+ }
+ };
+ this.addMouseListener(mouseListener);
+
+ // Popup menu
+ addMouseListener(new PopupListener());
+ popup = new JPopupMenu();
+
+ JMenuItem menuItem = new JMenuItem("Refresh");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+
+ JMenuItem gotoItem = new JMenuItem("GoTo Directory...");
+ gotoItem.addActionListener(this);
+ popup.add(gotoItem);
+ popup.add(new JSeparator());
+ //open menu
+ JMenu openMenu = new JMenu("Open With");
+ popup.add(openMenu);
+ menuItem = new JMenuItem("Jemboss Alignment Editor");
+ menuItem.addActionListener(this);
+ openMenu.add(menuItem);
+ menuItem = new JMenuItem("Artemis");
+ menuItem.addActionListener(this);
+ openMenu.add(menuItem);
+
+ menuItem = new JMenuItem("Rename...");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+ menuItem = new JMenuItem("New Folder...");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+ menuItem = new JMenuItem("Delete...");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+ popup.add(new JSeparator());
+ menuItem = new JMenuItem("De-select All");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+
+// Set the first visible column to 10 pixels wide
+ TableColumn col = getColumnModel().getColumn(1);
+ col.setPreferredWidth(10);
+ }
+
+ public TreeTableCellRenderer getTree()
+ {
+ return tree;
+ }
+
+ /**
+ *
+ * Get FileNode of selected node
+ * @return node that is currently selected
+ *
+ */
+ public FileNode getSelectedNode()
+ {
+ TreePath path = tree.getLeadSelectionPath();
+ if(path == null)
+ return null;
+ FileNode node = (FileNode)path.getLastPathComponent();
+ return node;
+ }
+
+ /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
+ * paint the editor. The UI currently uses different techniques to
+ * paint the renderers and editors and overriding setBounds() below
+ * is not the right thing to do for an editor. Returning -1 for the
+ * editing row in this case, ensures the editor is never painted.
+ */
+ public int getEditingRow()
+ {
+ return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : editingRow;
+ }
+
+
+ private void refresh(FileNode node)
+ {
+ node.removeAllChildren();
+ node.reset();
+
+ node.getChildren( ((FileSystemModel)tree.getModel()).getFilter() );
+ ((DefaultTreeModel)tree.getModel()).nodeStructureChanged(node);
+ tree.revalidate();
+ tree.repaint();
+ revalidate();
+ }
+
+ protected void refreshAll()
+ {
+ FileSystemModel model = (FileSystemModel)tree.getModel();
+ Object root = model.getRoot();
+
+ Vector vnodes = new Vector();
+ addChildren(vnodes, (FileNode)root, model);
+
+ for(int i = 0; i<vnodes.size(); i++)
+ refresh((FileNode)vnodes.get(i));
+
+ tree.revalidate();
+ repaint();
+ revalidate();
+ }
+
+ private void addChildren(Vector v, FileNode node, FileSystemModel model)
+ {
+ int nchild = model.getChildCount(node);
+
+ for(int i = 0; i<nchild; i++)
+ {
+ FileNode fn = (FileNode)model.getChild(node, i);
+
+ if(fn.isDirectory())
+ v.add(fn);
+ }
+ }
+
+ /**
+ *
+ * Get FileNodes of selected nodes
+ * @return node that is currently selected
+ *
+ */
+ private FileNode[] getSelectedNodes()
+ {
+ TreePath path[] = tree.getSelectionPaths();
+ if(path == null)
+ return null;
+
+ int numberSelected = path.length;
+ FileNode nodes[] = new FileNode[numberSelected];
+ for(int i=0;i<numberSelected;i++)
+ nodes[i] = (FileNode)path[i].getLastPathComponent();
+
+ return nodes;
+ }
+
+
+ /**
+ *
+ * Get selected files
+ * @return node that is currently selected
+ *
+ */
+ private File[] getSelectedFiles()
+ {
+ FileNode[] fn = getSelectedNodes();
+ int numberSelected = fn.length;
+ File files[] = new File[numberSelected];
+ for(int i=0;i<numberSelected;i++)
+ files[i] = fn[i].getFile();
+
+ return files;
+ }
+
+ /**
+ *
+ * Popup menu actions
+ * @param e action event
+ *
+ */
+ public void actionPerformed(ActionEvent e)
+ {
+ JMenuItem source = (JMenuItem)(e.getSource());
+ FileNode node = getSelectedNode();
+
+ if(source.getText().equals("Refresh"))
+ {
+ if(node == null)
+ return;
+ else if(node.isLeaf())
+ node = (FileNode)node.getParent();
+
+ node.removeAllChildren();
+ node.reset();
+ node.getChildren( ((FileSystemModel)tree.getModel()).getFilter() );
+ ((DefaultTreeModel)tree.getModel()).nodeStructureChanged(node);
+ tree.repaint();
+ revalidate();
+ repaint();
+ return;
+ }
+
+ if(source.getText().startsWith("GoTo Directory"))
+ {
+ String dir = JOptionPane.showInputDialog(this, "GoTo Directory",
+ System.getProperty("home.dir"));
+
+ File fileDir = new File(dir);
+ if(!fileDir.exists())
+ {
+ JOptionPane.showMessageDialog(this, dir + " not found.");
+ return;
+ }
+ else if(fileDir.isFile())
+ {
+ JOptionPane.showMessageDialog(this, dir + " is a file.");
+ return;
+ }
+ FileNode rootNode = (FileNode) ((DefaultTreeModel)tree.getModel()).getRoot();
+ FileNode newNode = new FileNode(fileDir);
+ rootNode.add(newNode);
+
+ ((DefaultTreeModel)tree.getModel()).nodeStructureChanged(rootNode);
+ revalidate();
+ return;
+ }
+
+ if(node == null)
+ {
+ JOptionPane.showMessageDialog(null,"No file selected.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ final File f = node.getFile();
+
+ if(source.getText().equals("Jemboss Alignment Editor"))
+ {
+ org.emboss.jemboss.editor.AlignJFrame ajFrame =
+ new org.emboss.jemboss.editor.AlignJFrame(f);
+ ajFrame.setVisible(true);
+ }
+ else if(source.getText().equals("Artemis"))
+ {
+ setCursor(cbusy);
+ String selected = node.getFile().getAbsolutePath();
+ showFilePane(selected);
+ setCursor(cdone);
+ }
+ else if(source.getText().equals("New Folder..."))
+ {
+ if(node.isLeaf())
+ node = (FileNode)node.getParent();
+
+ String path = node.getFile().getAbsolutePath();
+
+ String inputValue = JOptionPane.showInputDialog(null,
+ "Folder Name","Create New Folder in "+path,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(inputValue != null && !inputValue.equals("") )
+ {
+ String fullname = path+fs+inputValue;
+ File dir = new File(fullname);
+
+ if(dir.exists())
+ JOptionPane.showMessageDialog(null, fullname+" alread exists!",
+ "Error", JOptionPane.ERROR_MESSAGE);
+ else
+ {
+ if(dir.mkdir())
+ refresh(node);
+ else
+ JOptionPane.showMessageDialog(null,
+ "Cannot make the folder\n"+fullname,
+ "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+ else if(source.getText().equals("Delete..."))
+ {
+ File fn[] = getSelectedFiles();
+ String[] names = new String[fn.length];
+ for(int i=0; i<fn.length;i++)
+ names[i] = fn[i].getAbsolutePath();
+
+ JList list = new JList(names);
+ JScrollPane jsp = new JScrollPane(list);
+ int n = JOptionPane.showConfirmDialog(null,
+ jsp,
+ "Delete "+fn.length+" Files",
+ JOptionPane.YES_NO_OPTION);
+
+ FileNode nodes[] = getSelectedNodes();
+ if(n == JOptionPane.YES_OPTION)
+ for(int i=0; i<nodes.length;i++)
+ deleteFile(nodes[i]);
+ }
+ else if(source.getText().equals("Rename..."))
+ {
+ String inputValue = (String)JOptionPane.showInputDialog(null,
+ "New File Name","Rename "+f.getName(),
+ JOptionPane.QUESTION_MESSAGE,null,null,f.getName());
+
+ if(inputValue != null && !inputValue.equals("") )
+ {
+ String path = f.getParent();
+ String fullname = path+fs+inputValue;
+ File newFile = new File(fullname);
+
+ try
+ {
+ renameFile(f,node,newFile.getCanonicalPath());
+ }
+ catch(IOException ioe){}
+ }
+ }
+ else if(source.getText().equals("De-select All"))
+ clearSelection();
+ }
+
+ /**
+ *
+ * Method to rename a file and update the filenode's.
+ * @param oldFile file to rename
+ * @param oldNode filenode to be removed
+ * @param newFullName name of the new file
+ *
+ */
+ private void renameFile(final File oldFile, final FileNode oldNode,
+ String newFullName)
+ {
+ final File fnew = new File(newFullName);
+ if(fnew.exists())
+ JOptionPane.showMessageDialog(null, newFullName+" alread exists!",
+ "Warning", JOptionPane.ERROR_MESSAGE);
+ else
+ {
+ if(oldFile.renameTo(fnew))
+ {
+ Runnable renameFileInTree = new Runnable()
+ {
+ public void run ()
+ {
+ refresh((FileNode)oldNode.getParent());
+ };
+ };
+ SwingUtilities.invokeLater(renameFileInTree);
+ }
+ else
+ JOptionPane.showMessageDialog(null,
+ "Cannot rename \n"+oldFile.getAbsolutePath()+
+ "\nto\n"+fnew.getAbsolutePath(), "Rename Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ return;
+ }
+
+
+ /**
+ *
+ * Delete a file from the tree
+ * @param node node to delete
+ *
+ */
+ public void deleteFile(final FileNode node)
+ {
+ File f = node.getFile();
+ if(f.delete())
+ {
+ Runnable deleteFileFromTree = new Runnable()
+ {
+ public void run () { refresh((FileNode)node.getParent()); };
+ };
+ SwingUtilities.invokeLater(deleteFileFromTree);
+ }
+ else
+ JOptionPane.showMessageDialog(null,"Cannot delete\n"+
+ f.getAbsolutePath(),"Warning",
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ /**
+ *
+ * Opens a JFrame with the file contents displayed.
+ * @param filename file name to display
+ *
+ */
+ public void showFilePane(final String filename)
+ {
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ EntryEdit entry_edit;
+ public Object construct()
+ {
+ try
+ {
+ EntryInformation new_entry_information =
+ new SimpleEntryInformation(Options.getArtemisEntryInformation());
+
+ final Entry entry = new Entry(EntryFileDialog.getEntryFromFile(
+ null, new FileDocument(new File(filename)),
+ new_entry_information, true));
+ if(entry == null)
+ return null;
+
+ final EntryGroup entry_group =
+ new SimpleEntryGroup(entry.getBases());
+
+ entry_group.add(entry);
+ entry_edit = new EntryEdit(entry_group);
+ return null;
+ }
+ catch(NoSequenceException e)
+ {
+ new MessageDialog(null, "read failed: entry contains no sequence");
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(null, "read failed: one of the features in " +
+ " the entry has an out of range " +
+ "location: " + e.getMessage());
+
+ }
+ catch(NullPointerException npe)
+ {
+ npe.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public void finished()
+ {
+ if(entry_edit != null)
+ entry_edit.setVisible(true);
+ }
+ };
+ entryWorker.start();
+
+ }
+
+ /**
+ *
+ * Return true if selected node is a file
+ * @return true is a file is selected, false if
+ * a directory is selected
+ *
+ */
+ public boolean isFileSelection()
+ {
+ TreePath path = tree.getLeadSelectionPath();
+ if(path == null)
+ return false;
+
+ FileNode node = (FileNode)path.getLastPathComponent();
+ return node.isLeaf();
+ }
+
+
+ //
+ // The renderer used to display the tree nodes, a JTree.
+ //
+ public class TreeTableCellRenderer extends JTree implements TableCellRenderer
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+ protected int visibleRow;
+
+ public TreeTableCellRenderer(TreeModel model)
+ {
+ super(model);
+ }
+
+ public void setBounds(int x, int y, int w, int h)
+ {
+ super.setBounds(x, 0, w, JTreeTable.this.getHeight());
+ }
+
+ public void paint(Graphics g)
+ {
+ g.translate(0, -visibleRow * getRowHeight());
+ super.paint(g);
+ }
+
+ public Component getTableCellRendererComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ boolean hasFocus,
+ int row, int column)
+ {
+ if(isSelected)
+ setBackground(table.getSelectionBackground());
+ else
+ setBackground(table.getBackground());
+
+ visibleRow = row;
+ return this;
+ }
+ }
+
+ //
+ // The editor used to interact with tree nodes, a JTree.
+ //
+
+ public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor
+ {
+ public Component getTableCellEditorComponent(JTable table, Object value,
+ boolean isSelected, int r, int c)
+ {
+ return tree;
+ }
+ }
+
+ /**
+ *
+ * Popup menu listener
+ *
+ */
+ class PopupListener extends MouseAdapter
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ }
+
+ private boolean writeByteFile(byte[] contents, File fn)
+ {
+ if(fn.exists())
+ {
+ int n = JOptionPane.showConfirmDialog(null,
+ "Overwrite \n"+fn.getName()+"?",
+ "Overwrite File",
+ JOptionPane.YES_NO_OPTION);
+ if(n == JOptionPane.NO_OPTION)
+ return false;
+ }
+ else if(!fn.getParentFile().canWrite())
+ JOptionPane.showMessageDialog(null,"Cannot write to "+fn.getName(),
+ "Write Permission Denied",
+ JOptionPane.WARNING_MESSAGE);
+
+ try
+ {
+ FileOutputStream out = new FileOutputStream(fn);
+ out.write(contents);
+ out.close();
+ }
+ catch(FileNotFoundException fnfe) {return false;}
+ catch(IOException ioe) {return false;}
+
+ return true;
+ }
+
+
+ private void localDrop(DropTargetDropEvent e, Vector vnode, FileNode dropNode)
+ {
+ try
+ {
+ for(int i=0; i<vnode.size(); i++)
+ {
+ FileNode fn = (FileNode)vnode.get(i);
+// fn = getNode(fn.getFile().getAbsolutePath());
+
+ if (dropNode.isLeaf())
+ {
+ e.rejectDrop();
+ return;
+ }
+
+ String dropDir = dropNode.getFile().getAbsolutePath();
+ String newFullName = dropDir+fs+fn.toString();
+ renameFile(fn.getFile(),fn,newFullName);
+ }
+ }
+ catch(Exception ufe){}
+ }
+
+ private void remoteDrop(final DropTargetDropEvent e,
+ final Vector vnode, final FileNode dropNode)
+ {
+ SwingWorker getFileWorker = new SwingWorker()
+ {
+ FileTransferProgressMonitor monitor;
+
+ public Object construct()
+ {
+ try
+ {
+ monitor = new FileTransferProgressMonitor(JTreeTable.this);
+ for(int i=0; i<vnode.size(); i++)
+ {
+ final RemoteFileNode fn = (RemoteFileNode)vnode.get(i);
+ final File dropDest;
+ String dropDir = null;
+ if (dropNode.isLeaf())
+ {
+ FileNode pn = (FileNode)dropNode.getParent();
+ dropDir = pn.getFile().getAbsolutePath();
+ dropDest = new File(dropDir,fn.getFile());
+ }
+ else
+ {
+ dropDir = dropNode.getFile().getAbsolutePath();
+ dropDest = new File(dropDir,fn.getFile());
+ }
+
+ try
+ {
+ FTProgress progress = monitor.add(fn.getFile());
+
+ final byte[] contents = fn.getFileContents(progress);
+ //final String ndropDir = dropDir;
+
+ Runnable updateTheTree = new Runnable()
+ {
+ public void run ()
+ {
+ if(writeByteFile(contents, dropDest))
+ {
+ if(dropNode.isLeaf())
+ refresh((FileNode)dropNode.getParent());
+ else
+ refresh(dropNode);
+ }
+ };
+ };
+ SwingUtilities.invokeLater(updateTheTree);
+ }
+ catch (Exception exp)
+ {
+ System.out.println("FileTree: caught exception");
+ exp.printStackTrace();
+ }
+ }
+ e.getDropTargetContext().dropComplete(true);
+ }
+ catch (Exception exp)
+ {
+ e.rejectDrop();
+ }
+ return null;
+ }
+
+ public void finished()
+ {
+ if(monitor != null)
+ monitor.close();
+ }
+ };
+ getFileWorker.start();
+ }
+
+
+////////////////////
+// DRAG AND DROP
+////////////////////
+// drag source
+ public void dragGestureRecognized(DragGestureEvent e)
+ {
+ // ignore if mouse popup trigger
+ InputEvent ie = e.getTriggerEvent();
+ if(ie instanceof MouseEvent)
+ if(((MouseEvent)ie).isPopupTrigger())
+ return;
+
+ // drag only files
+ if(isFileSelection())
+ {
+ final int nlist = tree.getSelectionCount();
+ if(nlist > 1)
+ {
+ TransferableFileNodeList list = new TransferableFileNodeList(nlist);
+ FileNode nodes[] = getSelectedNodes();
+ for(int i=0; i<nodes.length; i++)
+ list.add(nodes[i]);
+
+ BufferedImage buff = getImage(nodes[0].getFile());
+ e.startDrag(DragSource.DefaultCopyDrop, // cursor
+ buff,
+ new Point(0,0),
+ (Transferable)list, // transferable data
+ this); // drag source listener
+ }
+ else
+ {
+ BufferedImage buff = getImage(getSelectedNode().getFile());
+ e.startDrag(DragSource.DefaultCopyDrop, // cursor
+ buff,
+ new Point(0,0),
+ (Transferable)getSelectedNode(), // transferable data
+ this); // drag source listener
+ }
+ }
+ }
+
+ /**
+ *
+ * FileSystemView provides a platform-independent way to get the
+ * appropriate icon
+ *
+ */
+ private BufferedImage getImage(File temp)
+ {
+ // get the right icon
+ FileSystemView fsv = FileSystemView.getFileSystemView( );
+ Icon icn = fsv.getSystemIcon(temp);
+
+ Toolkit tk = Toolkit.getDefaultToolkit( );
+ Dimension dim = tk.getBestCursorSize(
+ icn.getIconWidth( ),icn.getIconHeight( ));
+ BufferedImage buff = new BufferedImage(dim.width,dim.height,
+ BufferedImage.TYPE_INT_ARGB);
+ icn.paintIcon(this,buff.getGraphics( ),0,0);
+ return buff;
+ }
+
+ public void dragDropEnd(DragSourceDropEvent e) {}
+ public void dragEnter(DragSourceDragEvent e) {}
+ public void dragExit(DragSourceEvent e) {}
+ public void dragOver(DragSourceDragEvent e) {}
+ public void dropActionChanged(DragSourceDragEvent e) {}
+
+// drop sink
+ public void dragEnter(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(FileNode.FILENODE) ||
+ e.isDataFlavorSupported(RemoteFileNode.REMOTEFILENODE) ||
+ e.isDataFlavorSupported(TransferableFileNodeList.TRANSFERABLEFILENODELIST))
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+
+ public void drop(final DropTargetDropEvent e)
+ {
+ final Transferable t = e.getTransferable();
+ final FileNode dropNode = getSelectedNode();
+ if(dropNode == null)
+ {
+ e.rejectDrop();
+ return;
+ }
+
+ if(t.isDataFlavorSupported(TransferableFileNodeList.TRANSFERABLEFILENODELIST))
+ {
+ try
+ {
+ TransferableFileNodeList filelist = (TransferableFileNodeList)
+ t.getTransferData(TransferableFileNodeList.TRANSFERABLEFILENODELIST);
+
+ if(filelist.get(0) instanceof RemoteFileNode)
+ remoteDrop(e, filelist, dropNode);
+ else
+ localDrop(e, filelist, dropNode);
+ }
+ catch(UnsupportedFlavorException exp){}
+ catch(IOException ioe){}
+ }
+ else if(t.isDataFlavorSupported(FileNode.FILENODE))
+ {
+ try
+ {
+ Vector v = new Vector();
+ FileNode fn = (FileNode)t.getTransferData(FileNode.FILENODE);
+ v.add(fn);
+ localDrop(e, v, dropNode);
+ }
+ catch(Exception ufe){}
+ }
+ else if(t.isDataFlavorSupported(RemoteFileNode.REMOTEFILENODE))
+ {
+ try
+ {
+ Vector v = new Vector();
+ final RemoteFileNode fn =
+ (RemoteFileNode)t.getTransferData(RemoteFileNode.REMOTEFILENODE);
+ v.add(fn);
+ remoteDrop(e, v, dropNode);
+ }
+ catch(Exception ufe){}
+ }
+ else
+ {
+ e.rejectDrop();
+ return;
+ }
+ }
+
+ /**
+ *
+ * When a suitable DataFlavor is offered over a remote file
+ * node the node is highlighted/selected and the drag
+ * accepted. Otherwise the drag is rejected.
+ *
+ */
+ public void dragOver(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(FileNode.FILENODE))
+ {
+ Point ploc = e.getLocation();
+ TreePath ePath = tree.getPathForLocation(ploc.x,ploc.y);
+ if (ePath == null)
+ {
+ e.rejectDrag();
+ return;
+ }
+ FileNode node = (FileNode)ePath.getLastPathComponent();
+ if(!node.isDirectory())
+ e.rejectDrag();
+ else
+ {
+ tree.setSelectionPath(ePath);
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+ }
+ else if(e.isDataFlavorSupported(RemoteFileNode.REMOTEFILENODE) ||
+ e.isDataFlavorSupported(TransferableFileNodeList.TRANSFERABLEFILENODELIST))
+ {
+ Point ploc = e.getLocation();
+ TreePath ePath = tree.getPathForLocation(ploc.x,ploc.y);
+ if (ePath == null)
+ e.rejectDrag();
+ else
+ {
+ tree.setSelectionPath(ePath);
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+ }
+ else
+ e.rejectDrag();
+
+ return;
+ }
+
+ public void dropActionChanged(DropTargetDragEvent e) {}
+ public void dragExit(DropTargetEvent e){}
+
+////////////////////
+// AUTO SCROLLING //
+////////////////////
+ /**
+ *
+ * Handles the auto scrolling of the JTree.
+ * @param location The location of the mouse.
+ *
+ */
+ public void autoscroll( Point location )
+ {
+ int top = 0, left = 0, bottom = 0, right = 0;
+ Dimension size = getSize();
+ Rectangle rect = getVisibleRect();
+ int bottomEdge = rect.y + rect.height;
+ int rightEdge = rect.x + rect.width;
+ if( location.y - rect.y < AUTOSCROLL_MARGIN && rect.y > 0 )
+ top = AUTOSCROLL_MARGIN;
+ if( location.x - rect.x < AUTOSCROLL_MARGIN && rect.x > 0 )
+ left = AUTOSCROLL_MARGIN;
+ if( bottomEdge - location.y < AUTOSCROLL_MARGIN && bottomEdge < size.height )
+ bottom = AUTOSCROLL_MARGIN;
+ if( rightEdge - location.x < AUTOSCROLL_MARGIN && rightEdge < size.width )
+ right = AUTOSCROLL_MARGIN;
+ rect.x += right - left;
+ rect.y += bottom - top;
+ scrollRectToVisible( rect );
+ }
+
+
+ /**
+ *
+ * Gets the insets used for the autoscroll.
+ * @return The insets.
+ *
+ */
+ public Insets getAutoscrollInsets()
+ {
+ Dimension size = getSize();
+ Rectangle rect = getVisibleRect();
+ autoscrollInsets.top = rect.y + AUTOSCROLL_MARGIN;
+ autoscrollInsets.left = rect.x + AUTOSCROLL_MARGIN;
+ autoscrollInsets.bottom = size.height - (rect.y+rect.height) + AUTOSCROLL_MARGIN;
+ autoscrollInsets.right = size.width - (rect.x+rect.width) + AUTOSCROLL_MARGIN;
+ return autoscrollInsets;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/filetree/LocalAndRemoteFileManager.java b/uk/ac/sanger/artemis/components/filetree/LocalAndRemoteFileManager.java
new file mode 100644
index 0000000..695448a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/LocalAndRemoteFileManager.java
@@ -0,0 +1,766 @@
+/********************************************************************
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Library General Public
+* License as published by the Free Software Foundation; either
+* version 2 of the License, or (at your option) any later version.
+*
+* This library 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
+* Library General Public License for more details.
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this library; if not, write to the
+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*
+* Copyright (C) Genome Research Limited
+*
+********************************************************************/
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.components.database.DatabaseEntrySource;
+import uk.ac.sanger.artemis.components.database.DatabaseJPanel;
+import uk.ac.sanger.artemis.j2ssh.SshLogin;
+import uk.ac.sanger.artemis.j2ssh.SshFileManager;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.Options;
+
+import javax.swing.table.TableColumn;
+import javax.swing.*;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.awt.event.*;
+import java.awt.*;
+import java.util.Properties;
+import java.util.Enumeration;
+import java.util.Vector;
+import javax.swing.border.Border;
+
+public class LocalAndRemoteFileManager extends JFrame
+{
+
+ /***/
+ private static final long serialVersionUID = 1L;
+ private JScrollPane remoteTree;
+ private SshJTreeTable sshtree;
+ private JSplitPane treePane = null;
+ private DatabaseEntrySource entry_source;
+ public static JCheckBoxMenuItem lazyLoad =
+ new JCheckBoxMenuItem("Lazy load feature data", false);
+ private static JCheckBoxMenuItem automaticHistory =
+ new JCheckBoxMenuItem("Automatic History Annotation", false);
+
+ public static JCheckBoxMenuItem domainLoad =
+ new JCheckBoxMenuItem("Display protein domains", false);
+
+ public LocalAndRemoteFileManager(JFrame frame)
+ {
+ this(frame,getArtemisFilter());
+ }
+
+ /**
+ *
+ * File Manager Frame
+ * @param frame parent frame
+ * @param filter file name filter
+ *
+ */
+ public LocalAndRemoteFileManager(JFrame frame, FileFilter filter)
+ {
+ super();
+
+ final JPanel localPanel = new JPanel(new BorderLayout());
+
+ final SshLogin ssh_login = new SshLogin();
+ JTreeTable ftree = new JTreeTable(new FileSystemModel(getLocalDirectories(),
+ filter, this));
+ JScrollPane localTree = new JScrollPane(ftree);
+ localTree.getViewport().setBackground(Color.white);
+ localPanel.add(localTree,BorderLayout.CENTER);
+
+ final JLabel local_status_line = getStatusLabel("LOCAL");
+ localPanel.add(local_status_line,BorderLayout.NORTH);
+
+ final JPanel remotePanel = new JPanel(new BorderLayout());
+
+ //
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ final Dimension panelSize = new Dimension((int)(screen.getWidth()/3),
+ (int)(screen.getHeight()/4));
+ String remote_name = "";
+ final JLabel remote_status_line = getStatusLabel("");
+
+ if(FileList.ssh_client == null) // if no connection etablished yet
+ {
+ final Box bdown = Box.createVerticalBox();
+ JButton connect = new JButton("Connect");
+
+ connect.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ login(remotePanel, bdown, ssh_login, panelSize,
+ local_status_line, remote_status_line);
+ }
+ });
+
+ bdown.add(ssh_login.getLogin());
+ // listen to passwd field for return press
+ JPasswordField pwf = ssh_login.getJPasswordField();
+ pwf.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ login(remotePanel, bdown, ssh_login, panelSize,
+ local_status_line, remote_status_line);
+ }
+ });
+
+ bdown.add(connect);
+ /*int ypos = panelSize.height-connect.getPreferredSize().height;
+ if(ypos>0)
+ bdown.add(Box.createVerticalStrut(ypos/2));*/
+ bdown.add(Box.createVerticalGlue());
+
+ remotePanel.add(bdown, BorderLayout.SOUTH);
+ remotePanel.setPreferredSize(panelSize);
+ }
+ else
+ {
+ FileList flist = new FileList();
+ setRemoteTree(flist, sshtree, remoteTree, remotePanel,
+ panelSize, remote_status_line);
+ }
+
+ remote_status_line.setText("REMOTE "+remote_name);
+ remotePanel.add(remote_status_line,BorderLayout.NORTH);
+
+ treePane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
+ localPanel,remotePanel);
+ treePane.setOneTouchExpandable(true);
+
+ JPanel pane = (JPanel)getContentPane();
+ pane.setLayout(new BorderLayout());
+
+ DbConnectionThread dbthread = null;
+ if(System.getProperty("chado") != null)
+ {
+ setTitle("Database and File Manager");
+ entry_source = new DatabaseEntrySource();
+
+ boolean promptUser = true;
+ if(System.getProperty("read_only") != null)
+ {
+ promptUser = false;
+ entry_source.setReadOnly(true);
+ }
+
+ if(!entry_source.setLocation(promptUser))
+ return;
+
+ JLabel label = new JLabel(" Database Loading...");
+ JPanel dbPane = new JPanel();
+ dbPane.add(label);
+ dbPane.setBackground(Color.white);
+ dbPane.setPreferredSize(panelSize);
+
+ JSplitPane mainSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
+ dbPane, treePane);
+
+ dbthread = new DbConnectionThread(mainSplit, panelSize,
+ entry_source, dbPane);
+ dbthread.start();
+
+ treePane.setDividerLocation((int)(screen.getHeight()/4));
+ mainSplit.setOneTouchExpandable(true);
+ mainSplit.setDividerLocation((int)(screen.getHeight()/4));
+ pane.add(mainSplit, BorderLayout.CENTER);
+ }
+ else
+ {
+ setTitle("File Manager");
+ pane.add(treePane, BorderLayout.CENTER);
+ treePane.setDividerLocation((int)(screen.getHeight()/4));
+ }
+ setJMenuBar(makeMenuBar(pane,ftree,sshtree,localPanel,
+ remotePanel,treePane,panelSize,dbthread));
+ localPanel.add(getFileFileterComboBox(ftree), BorderLayout.SOUTH);
+
+ localTree.setPreferredSize(panelSize);
+
+ // Set the column width
+ int width = panelSize.width;
+ setColumnWidth(ftree, width);
+
+ pack();
+
+ int yloc = (int)((screen.getHeight()-getHeight())/2);
+ setLocation(0,yloc);
+ setVisible(true);
+ }
+
+ private void login(JPanel remotePanel, Box bdown, SshLogin ssh_login,
+ Dimension panelSize, JLabel local_status_line,
+ JLabel remote_status_line)
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+
+ final SshFileManager ssh_fm;
+ try
+ {
+ ssh_fm = new SshFileManager(ssh_login);
+ }
+ catch(NullPointerException npe)
+ {
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ JOptionPane.showMessageDialog(LocalAndRemoteFileManager.this,
+ "Check login details and try again.",
+ "Failed Login", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ FileList flist = new FileList(ssh_fm);
+ remotePanel.remove(bdown);
+ int divider_loc = treePane.getDividerLocation();
+ setRemoteTree(flist, sshtree, remoteTree, remotePanel,
+ panelSize, remote_status_line);
+
+ if(treePane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT)
+ treePane.setBottomComponent(remotePanel);
+ else
+ treePane.setRightComponent(remotePanel);
+
+ treePane.setDividerLocation(divider_loc);
+
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+
+ private void setRemoteTree(final FileList flist, SshJTreeTable sshtree,
+ JScrollPane remoteTree, JPanel remotePanel,
+ final Dimension panelSize, final JLabel remote_status_line)
+ {
+ sshtree = new SshJTreeTable(new FileSystemModel(
+ getRemoteDirectories(flist.pwd()), LocalAndRemoteFileManager.this),
+ LocalAndRemoteFileManager.this);
+ remoteTree = new JScrollPane(sshtree);
+ remoteTree.setPreferredSize(panelSize);
+ remoteTree.getViewport().setBackground(Color.white);
+ remotePanel.add(remoteTree,BorderLayout.CENTER);
+
+ String remote_name = SshLogin.getHostname();
+ if(!SshLogin.getPort().equals(""))
+ remote_name = remote_name + ":" + SshLogin.getPort();
+ remote_status_line.setText("REMOTE "+remote_name);
+ setColumnWidth(sshtree, panelSize.width);
+ }
+
+
+ private void setColumnWidth(JTable table, int width)
+ {
+ TableColumn col0 = table.getColumnModel().getColumn(0);
+ col0.setPreferredWidth( (int)(width*0.60) );
+
+ TableColumn col1 = table.getColumnModel().getColumn(1);
+ col1.setPreferredWidth( (int)(width*0.12) );
+
+ TableColumn col2 = table.getColumnModel().getColumn(2);
+ col2.setPreferredWidth( (int)(width*0.28) );
+ }
+
+ /**
+ *
+ * Create a status JLabel with bevelled border
+ *
+ */
+ private JLabel getStatusLabel(String status)
+ {
+ final JLabel status_line = new JLabel(status);
+ Border loweredbevel = BorderFactory.createLoweredBevelBorder();
+ Border raisedbevel = BorderFactory.createRaisedBevelBorder();
+ Border compound = BorderFactory.createCompoundBorder(raisedbevel,loweredbevel);
+ status_line.setBorder(compound);
+
+ final FontMetrics fm =
+ this.getFontMetrics(status_line.getFont());
+ final int font_height = fm.getHeight()+10;
+
+ status_line.setMinimumSize(new Dimension(100, font_height));
+ status_line.setPreferredSize(new Dimension(100, font_height));
+ return status_line;
+ }
+
+ /**
+ *
+ * Look in j2ssh.properties for local directories.
+ *
+ */
+ private File[] getLocalDirectories()
+ {
+ final Properties settings = SshLogin.getProperties();
+ Enumeration enum_prop = settings.propertyNames();
+ Vector dirs = new Vector();
+
+ dirs.add(new File(System.getProperty("user.home")));
+ dirs.add(new File(System.getProperty("user.dir")));
+
+ while(enum_prop.hasMoreElements())
+ {
+ final String property = (String)enum_prop.nextElement();
+ File f = new File(settings.getProperty(property));
+ if(property.startsWith("localdir") && f.exists())
+ dirs.add(f);
+ }
+
+ File fdirs[] = new File[dirs.size()];
+ for(int i=0; i<dirs.size(); i++)
+ fdirs[i] = (File)dirs.get(i);
+
+ return fdirs;
+ }
+
+ /**
+ *
+ * Look in j2ssh.properties for remote directories.
+ *
+ */
+ private String[] getRemoteDirectories(String pwd)
+ {
+ final Properties settings = SshLogin.getProperties();
+ Enumeration enum_prop = settings.propertyNames();
+ Vector dirs = new Vector();
+ dirs.add(pwd);
+ while(enum_prop.hasMoreElements())
+ {
+ final String property = (String)enum_prop.nextElement();
+ if(property.startsWith("remotedir"))
+ dirs.add(settings.getProperty(property));
+ }
+
+ String sdirs[] = new String[dirs.size()];
+ for(int i=0; i<dirs.size(); i++)
+ sdirs[i] = (String)dirs.get(i);
+
+ return sdirs;
+ }
+
+ protected JComboBox getFileFileterComboBox(final JTreeTable ftree)
+ {
+ String[] filters = { "Artemis Files", "Sequence Files",
+ "Feature Files", "All Files" };
+ final JComboBox comboFilter = new JComboBox(filters);
+ comboFilter.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ FileSystemModel model = (FileSystemModel)(ftree.getTree().getModel());
+ String select = (String)comboFilter.getSelectedItem();
+ if(select.equals("Artemis Files"))
+ model.setFilter(getArtemisFilter());
+ else if(select.equals("Sequence Files"))
+ model.setFilter(getSequenceFilter());
+ else if(select.equals("Feature Files"))
+ model.setFilter(getFeatureFilter());
+ else if(select.equals("All Files"))
+ {
+ model.setFilter(new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if(pathname.getName().startsWith("."))
+ return false;
+ return true;
+ }
+ });
+ }
+ ftree.refreshAll();
+ ftree.revalidate();
+ }
+ });
+ return comboFilter;
+ }
+
+ /**
+ *
+ * Get a file filter for sequence and feature suffixes.
+ * @return file filter
+ */
+ protected static FileFilter getArtemisFilter()
+ {
+ final StringVector sequence_suffixes =
+ Options.getOptions().getOptionValues("sequence_file_suffixes");
+
+ final StringVector feature_suffixes =
+ Options.getOptions().getOptionValues("feature_file_suffixes");
+
+ final FileFilter artemis_filter = new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if(pathname.isDirectory() &&
+ !pathname.getName().startsWith("."))
+ return true;
+
+ for(int i = 0; i<sequence_suffixes.size(); ++i)
+ {
+ final String suffix = (String)sequence_suffixes.elementAt(i);
+
+ if(pathname.getName().endsWith("." + suffix) ||
+ pathname.getName().endsWith("." + suffix + ".gz"))
+ return true;
+ }
+
+ for(int i = 0; i<feature_suffixes.size(); ++i)
+ {
+ final String suffix = (String)feature_suffixes.elementAt(i);
+
+ if(pathname.getName().endsWith("." + suffix) ||
+ pathname.getName().endsWith("." + suffix + ".gz"))
+ return true;
+ }
+ return false;
+ }
+ };
+ return artemis_filter;
+ }
+
+
+ /**
+ *
+ * Get a file filter for feature suffixes.
+ * @return file filter
+ */
+ protected static FileFilter getFeatureFilter()
+ {
+ final StringVector feature_suffixes =
+ Options.getOptions().getOptionValues("feature_file_suffixes");
+
+ final FileFilter feature_filter = new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if(pathname.isDirectory() &&
+ !pathname.getName().startsWith("."))
+ return true;
+
+ for(int i = 0; i<feature_suffixes.size(); ++i)
+ {
+ final String suffix = (String)feature_suffixes.elementAt(i);
+
+ if(pathname.getName().endsWith("." + suffix) ||
+ pathname.getName().endsWith("." + suffix + ".gz"))
+ return true;
+ }
+ return false;
+ }
+ };
+ return feature_filter;
+ }
+
+ /**
+ *
+ * Get a file filter for sequence suffixes.
+ * @return file filter
+ */
+ protected static FileFilter getSequenceFilter()
+ {
+ final StringVector sequence_suffixes =
+ Options.getOptions().getOptionValues("sequence_file_suffixes");
+
+ final FileFilter seq_filter = new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if(pathname.isDirectory() &&
+ !pathname.getName().startsWith("."))
+ return true;
+
+ for(int i = 0; i<sequence_suffixes.size(); ++i)
+ {
+ final String suffix = (String)sequence_suffixes.elementAt(i);
+
+ if(pathname.getName().endsWith("." + suffix) ||
+ pathname.getName().endsWith("." + suffix + ".gz"))
+ return true;
+ }
+
+ return false;
+ }
+ };
+ return seq_filter;
+ }
+
+ /**
+ * @return the automatic history
+ */
+ public static boolean isAutomaticHistory()
+ {
+ return automaticHistory.isSelected();
+ }
+
+ public static void setAutomaticHistory(boolean flag)
+ {
+ automaticHistory.setSelected(flag);
+ }
+
+ /**
+ *
+ * Set up a menu and tool bar
+ * @param pane panel to add toolbar to
+ * @param ftree file tree display
+ *
+ */
+ private JMenuBar makeMenuBar(JPanel pane,
+ final JTreeTable ftree, final SshJTreeTable sshtree,
+ final JPanel localPanel, final JPanel remotePanel,
+ final JSplitPane treePane, final Dimension panelSize,
+ final DbConnectionThread dbthread)
+ {
+ JMenuBar mBar = new JMenuBar();
+ JMenu fileMenu = new JMenu("File");
+ mBar.add(fileMenu);
+
+ JRadioButtonMenuItem prefV = new JRadioButtonMenuItem("Vertical Split");
+ fileMenu.add(prefV);
+ prefV.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ treePane.remove(remotePanel);
+ treePane.remove(localPanel);
+ treePane.setOrientation(JSplitPane.VERTICAL_SPLIT);
+ treePane.setTopComponent(localPanel);
+ treePane.setBottomComponent(remotePanel);
+ remotePanel.setPreferredSize(panelSize);
+ localPanel.setPreferredSize(panelSize);
+
+ pack();
+ treePane.setDividerLocation(0.5);
+ }
+ });
+ prefV.setSelected(true);
+ ButtonGroup group = new ButtonGroup();
+ group.add(prefV);
+
+ JRadioButtonMenuItem prefH = new JRadioButtonMenuItem("Horizontal Split");
+ fileMenu.add(prefH);
+ prefH.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ treePane.remove(remotePanel);
+ treePane.remove(localPanel);
+ treePane.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
+ treePane.setLeftComponent(localPanel);
+ treePane.setRightComponent(remotePanel);
+
+ remotePanel.setPreferredSize(panelSize);
+ localPanel.setPreferredSize(panelSize);
+
+ pack();
+ treePane.setDividerLocation(0.5);
+ }
+ });
+ group.add(prefH);
+// prefH.setSelected(true);
+
+ if(System.getProperty("chado") != null)
+ {
+ fileMenu.add(new JSeparator());
+ final JMenuItem fileShow = new JMenuItem("Open Selected Database Sequence ...");
+ fileShow.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(dbthread.getDatabaseJPanel() != null)
+ dbthread.getDatabaseJPanel().showSelected(entry_source, null);
+ }
+ });
+ fileMenu.add(fileShow);
+
+ final JCheckBoxMenuItem splitGFF = new JCheckBoxMenuItem(
+ "Split into entries ...", false);
+ splitGFF.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(splitGFF.isSelected())
+ {
+ DatabaseEntryFilterPanel messagePanel = new DatabaseEntryFilterPanel();
+ int val = JOptionPane.showConfirmDialog(
+ LocalAndRemoteFileManager.this, messagePanel, "Define Entry",
+ JOptionPane.OK_CANCEL_OPTION);
+
+ if(val == JOptionPane.OK_OPTION)
+ messagePanel.setTypesForEntries();
+ else
+ splitGFF.setSelected(false);
+ }
+
+ dbthread.setSplitGFFEntry(splitGFF.isSelected());
+ }
+ });
+ fileMenu.add(splitGFF);
+
+ if(Options.getOptions().getPropertyTruthValue("show_polypeptide_domains"))
+ domainLoad.setSelected(true);
+ fileMenu.add(domainLoad);
+ fileMenu.add(lazyLoad);
+
+ if(Options.getOptions().getPropertyTruthValue("automatic_history_annotation"))
+ automaticHistory.setSelected(true);
+ fileMenu.add(automaticHistory);
+
+ JMenuItem clearCache = new JMenuItem("Clear database manager cache");
+ clearCache.addActionListener(new ActionListener()
+ {
+
+ public void actionPerformed(ActionEvent e)
+ {
+ File cacheDir = new File(Options.CACHE_PATH);
+
+ if(cacheDir.exists())
+ {
+ File cacheFiles[] = cacheDir.listFiles();
+ if(cacheFiles != null)
+ {
+ for(int i=0; i<cacheFiles.length; i++)
+ cacheFiles[i].delete();
+ }
+ }
+ }
+ });
+ fileMenu.add(clearCache);
+ }
+
+
+ final JMenuItem validate = new JMenuItem("Validate Selected Sequence / Organism ...");
+ validate.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ if(dbthread.getDatabaseJPanel() != null)
+ {
+ dbthread.getDatabaseJPanel().setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ try
+ {
+ dbthread.getDatabaseJPanel().validate(entry_source);
+ }
+ finally
+ {
+ dbthread.getDatabaseJPanel().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ return null;
+ }
+ };
+ entryWorker.start();
+ }
+ });
+ fileMenu.add(new JSeparator());
+ fileMenu.add(validate);
+
+ JMenuItem fileMenuClose = new JMenuItem("Close");
+ fileMenuClose.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setVisible(false);
+ }
+ });
+ fileMenu.add(new JSeparator());
+ fileMenu.add(fileMenuClose);
+
+ // remote tool bar set up
+// JToolBar remoteToolBar = new JToolBar();
+// remotePanel.add(remoteToolBar, BorderLayout.NORTH);
+
+ // local tool bar set up
+// JToolBar toolBar = new JToolBar();
+// localPanel.add(toolBar, BorderLayout.NORTH);
+
+ return mBar;
+ }
+
+
+ private class DbConnectionThread extends Thread
+ {
+ private JSplitPane dbSplitPane;
+ private Dimension panelSize;
+ private DatabaseEntrySource entry_source;
+ private JPanel topPanel;
+ private DatabaseJPanel dbPane;
+ private boolean splitGFFEntry = false;
+
+ public DbConnectionThread(final JSplitPane dbSplitPane,
+ final Dimension panelSize,
+ final DatabaseEntrySource entry_source,
+ final JPanel topPanel)
+ {
+ this.dbSplitPane = dbSplitPane;
+ this.panelSize = panelSize;
+ this.entry_source = entry_source;
+ this.topPanel = topPanel;
+ }
+
+ public void run()
+ {
+ topPanel.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ dbPane = new DatabaseJPanel(entry_source, null);
+ dbPane.setPreferredSize(panelSize);
+ dbPane.setSplitGFFEntry(splitGFFEntry);
+ dbSplitPane.setTopComponent(dbPane);
+ topPanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+
+ protected void setSplitGFFEntry(final boolean splitGFFEntry)
+ {
+ if(dbPane != null)
+ dbPane.setSplitGFFEntry(splitGFFEntry);
+ else
+ this.splitGFFEntry = splitGFFEntry;
+ }
+
+ protected DatabaseJPanel getDatabaseJPanel()
+ {
+ return dbPane;
+ }
+ }
+
+
+ public static void main(String args[])
+ {
+ //final javax.swing.LookAndFeel look_and_feel =
+ // javax.swing.UIManager.getLookAndFeel();
+
+ final javax.swing.plaf.FontUIResource font_ui_resource =
+ Options.getOptions().getFontUIResource();
+
+ java.util.Enumeration keys = UIManager.getDefaults().keys();
+ while(keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ Object value = UIManager.get(key);
+ if(value instanceof javax.swing.plaf.FontUIResource)
+ UIManager.put(key, font_ui_resource);
+ }
+
+ JFrame frame = new LocalAndRemoteFileManager(null);
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ public DatabaseEntrySource getDatabaseEntrySource()
+ {
+ return entry_source;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/filetree/RemoteFileNode.java b/uk/ac/sanger/artemis/components/filetree/RemoteFileNode.java
new file mode 100644
index 0000000..679cefb
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/RemoteFileNode.java
@@ -0,0 +1,372 @@
+/********************************************************************
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Library General Public
+* License as published by the Free Software Foundation; either
+* version 2 of the License, or (at your option) any later version.
+*
+* This library 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
+* Library General Public License for more details.
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this library; if not, write to the
+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*
+* @author: Copyright (C) Tim Carver
+*
+********************************************************************/
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import com.sshtools.j2ssh.sftp.FileAttributes;
+import uk.ac.sanger.artemis.j2ssh.FTProgress;
+import java.awt.datatransfer.*;
+import javax.swing.tree.*;
+import java.io.*;
+import java.util.*;
+
+/**
+*
+* File node for remote file tree manager
+*
+*/
+public class RemoteFileNode extends DefaultMutableTreeNode
+ implements Transferable, Serializable
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ /** true if node is explored */
+ private boolean explored = false;
+ /** true if node is a directory */
+ private boolean isDir = false;
+ /** full name of node */
+ private String fullname;
+ /** path to the file on the server */
+ private String serverPathToFile;
+ /** root directory */
+ private String rootdir;
+
+ /** remote server file roots */
+ private transient String froots;
+ /** file separator for server files */
+ private String fs = "/";
+ /** last modified time */
+ private Date modifiedTime = null;
+ /** file length */
+ private Integer file_length;
+
+ /** parent node */
+ private RemoteFileNode parentNode = null;
+
+
+ final public static DataFlavor REMOTEFILENODE =
+ new DataFlavor(RemoteFileNode.class, "Remote file");
+ static DataFlavor remoteFlavors[] = { REMOTEFILENODE,
+ DataFlavor.stringFlavor };
+
+ public RemoteFileNode(boolean isDir)
+ {
+ this.isDir = isDir;
+ }
+
+ /**
+ *
+ * @param froots remote server file roots
+ * @param file file for this node
+ * @param parentList parent directory listing
+ * @param parent parent to this node
+ *
+ */
+ public RemoteFileNode(String froots, String file,
+ FileList parentList, String parent,
+ FileAttributes fat)
+ {
+ this(froots, file, parentList, parent, false);
+
+ long modTime = fat.getModifiedTime().longValue()*1000;
+ this.modifiedTime = new Date(modTime);
+ this.file_length = new Integer(fat.getSize().intValue());
+ }
+
+ /**
+ *
+ * @param froots remote server file roots
+ * @param file file for this node
+ * @param parentList parent directory listing
+ * @param parent parent to this node
+ * @param ldir true if the node is a directory
+ *
+ */
+ public RemoteFileNode(String froots, String file,
+ FileList parentList, String parent,
+ boolean ldir)
+ {
+ this.froots = froots;
+
+ isDir = ldir;
+ rootdir = froots;
+ serverPathToFile = froots;
+
+ if(parent != null)
+ {
+ if(parent.endsWith("/."))
+ parent = parent.substring(0,parent.length()-1);
+ else if(parent.endsWith(fs))
+ parent = parent.substring(0,parent.length());
+
+ if(parent.equals("."))
+ fullname = file;
+ else
+ {
+ fullname = parent + fs + file;
+ if(serverPathToFile.endsWith(fs))
+ serverPathToFile = serverPathToFile.concat(parent);
+ else
+ serverPathToFile = serverPathToFile.concat(fs+parent);
+ }
+ }
+
+ if(parentList != null)
+ {
+ if(parentList.isDirectory(file))
+ isDir = true;
+ }
+ else if(parent == null)
+ fullname = ".";
+
+ setUserObject(file);
+ }
+
+ /** @return true if node is a directory */
+ public boolean getAllowsChildren() { return isDir; }
+ /** @return true if node is a file */
+ public boolean isLeaf() { return !isDir; }
+ /** @return true if node is a directory */
+ public boolean isDirectory() { return isDir; }
+ /** @return the node name */
+ public String getFile() { return (String)getUserObject(); }
+ /** @return root directory */
+ public String getRootDir() { return rootdir; }
+ /** @return full name of the node */
+ public String getFullName() { return fullname; }
+ /** @return path on server */
+ public String getPathName() { return serverPathToFile; }
+ /** @return true if explored */
+ public boolean isExplored() { return explored; }
+
+ /**
+ *
+ * Get the server name
+ * @return server name
+ *
+ */
+ public String getServerName()
+ {
+ String prefix = serverPathToFile;
+ if(!prefix.endsWith(fs))
+ prefix = prefix.concat(fs);
+
+ if(fullname.equals("."))
+ return prefix;
+
+ return prefix + (String)getUserObject();
+ }
+
+
+ private Object child_cache[];
+
+ public Object[] getChildren()
+ {
+ if(!isDir)
+ return null;
+
+ if(getRootDir() == null)
+ {
+ Enumeration child_enum = children();
+ child_cache = new Object[getChildCount()];
+ int i = 0;
+ while(child_enum.hasMoreElements())
+ child_cache[i++] = child_enum.nextElement();
+ return child_cache;
+ }
+
+ if(child_cache != null)
+ return child_cache;
+
+ FileList flist = new FileList();
+
+ String dir;
+ if(getRootDir().equals(""))
+ dir = new String("~/"+getFullName());
+ else
+ dir = new String(getRootDir()+"/"+getFullName());
+ dir = dir.trim();
+
+ Hashtable children = flist.getDirList(dir);
+
+ if(children == null)
+ return null;
+
+ Object files[] = children.keySet().toArray();
+ Arrays.sort(files);
+
+ Vector vchildren = new Vector();
+ for(int i=0;i<files.length;i++)
+ {
+ String fn = (String)files[i];
+ if(!fn.startsWith("."))
+ {
+ FileAttributes fat = (FileAttributes)children.get(fn);
+
+// Date modifiedTime = (Date)children.get(fn);
+ RemoteFileNode node = new RemoteFileNode(froots,fn,
+ flist,fullname,fat);
+ node.setParentNode(this);
+ vchildren.add(node);
+ add(node);
+ }
+ }
+
+ child_cache = vchildren.toArray();
+ return child_cache;
+ }
+
+ protected void setParentNode(RemoteFileNode parentNode)
+ {
+ this.parentNode = parentNode;
+ }
+
+ public RemoteFileNode getParentNode()
+ {
+ return parentNode;
+ }
+
+
+ public void reset()
+ {
+ child_cache = null;
+ }
+
+ public Date getModifiedTime()
+ {
+ return modifiedTime;
+ }
+
+ protected Integer length()
+ {
+ return file_length;
+ }
+
+ public boolean delete()
+ {
+ FileList flist = new FileList();
+ return flist.delete(getRootDir()+"/"+getFullName());
+ }
+
+ public boolean mkdir(String dir)
+ {
+ FileList flist = new FileList();
+ return flist.mkdir(dir);
+ }
+
+ public boolean rename(String new_file)
+ {
+ FileList flist = new FileList();
+ return flist.rename(getRootDir()+"/"+getFullName(), new_file);
+ }
+
+ public boolean put(File local_file, FTProgress monitor)
+ {
+ String dir;
+ if(!isDirectory())
+ {
+ dir = getRootDir()+"/"+getFullName();
+ int index = dir.lastIndexOf("/");
+ dir = dir.substring(0,index);
+ }
+ else
+ dir = getRootDir()+"/"+getFullName();
+
+ return put(dir, local_file, monitor, false);
+ }
+
+ public boolean put(String dir, File local_file,
+ FTProgress monitor, boolean force)
+ {
+ FileList flist = new FileList();
+ boolean lput = flist.put(dir, local_file, monitor, force);
+ if(!lput)
+ return false;
+
+ if(!isDirectory())
+ {
+ FileAttributes fat = flist.stat(dir);
+ long modTime = fat.getModifiedTime().longValue()*1000;
+ modifiedTime = new Date(modTime);
+ file_length = new Integer(fat.getSize().intValue());
+ }
+
+ return true;
+ }
+
+ public void stat()
+ {
+ FileList flist = new FileList();
+ FileAttributes fat = flist.stat(getServerName());
+ long modTime = fat.getModifiedTime().longValue()*1000;
+ modifiedTime = new Date(modTime);
+ file_length = new Integer(fat.getSize().intValue());
+ }
+
+ public byte[] getFileContents(FTProgress monitor)
+ {
+ FileList flist = new FileList();
+ return flist.getFileContents(getRootDir()+"/"+getFullName(), monitor);
+ }
+
+ public byte[] getFileContents(FTProgress monitor, String filename)
+ {
+ FileList flist = new FileList();
+ return flist.getFileContents(filename, monitor);
+ }
+
+// Transferable
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return remoteFlavors;
+ }
+
+ public boolean isDataFlavorSupported(DataFlavor f)
+ {
+ if(f.equals(REMOTEFILENODE) || f.equals(DataFlavor.stringFlavor))
+ return true;
+ return false;
+ }
+
+ public Object getTransferData(DataFlavor d)
+ throws UnsupportedFlavorException, IOException
+ {
+ if(d.equals(REMOTEFILENODE))
+ return this;
+ else if(d.equals(DataFlavor.stringFlavor))
+ return getServerName();
+ else throw new UnsupportedFlavorException(d);
+ }
+
+// Serializable
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException
+ {
+ out.defaultWriteObject();
+ }
+
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/filetree/SshJTreeTable.java b/uk/ac/sanger/artemis/components/filetree/SshJTreeTable.java
new file mode 100644
index 0000000..7c17558
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/SshJTreeTable.java
@@ -0,0 +1,1009 @@
+/*
+ *
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import javax.swing.*;
+import javax.swing.tree.*;
+import javax.swing.table.*;
+import javax.swing.filechooser.FileSystemView;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.*;
+import java.awt.image.BufferedImage;
+import java.util.Vector;
+import java.util.Enumeration;
+
+import uk.ac.sanger.artemis.j2ssh.FileTransferProgressMonitor;
+import uk.ac.sanger.artemis.j2ssh.FTProgress;
+
+import uk.ac.sanger.artemis.util.RemoteFileDocument;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.SimpleEntryInformation;
+import uk.ac.sanger.artemis.components.EntryEdit;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+
+/**
+*
+* This example shows how to create a simple SshJTreeTable component,
+* by using a JTree as a renderer (and editor) for the cells in a
+* particular column in the JTable.
+*
+* modified from the example at:
+* http://java.sun.com/products/jfc/tsc/articles/treetable1/
+*
+*/
+public class SshJTreeTable extends JTable
+ implements DragGestureListener,
+ DragSourceListener, DropTargetListener, ActionListener,
+ Autoscroll
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ /** popup menu */
+ private JPopupMenu popup;
+ /** busy cursor */
+ private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ /** done cursor */
+ private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+ /** line separator */
+ private String ls = new String(System.getProperty("line.separator"));
+ /** AutoScroll margin */
+ private static final int AUTOSCROLL_MARGIN = 45;
+ /** used by AutoScroll method */
+ private Insets autoscrollInsets = new Insets( 0, 0, 0, 0 );
+ protected TreeTableCellRenderer tree;
+ private JFrame frame;
+
+ public SshJTreeTable(TreeModel treeTableModel, final JFrame frame)
+ {
+ super();
+
+ this.frame = frame;
+ DragSource dragSource = DragSource.getDefaultDragSource();
+
+ dragSource.createDefaultDragGestureRecognizer(
+ this, // component where drag originates
+ DnDConstants.ACTION_COPY_OR_MOVE, // actions
+ this); // drag gesture recognizer
+
+ setDropTarget(new DropTarget(this,this));
+
+ // Create the tree. It will be used as a renderer and editor.
+ tree = new TreeTableCellRenderer(treeTableModel);
+
+ // Install a tableModel representing the visible rows in the tree.
+ super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
+
+ // Force the JTable and JTree to share their row selection models.
+ tree.setSelectionModel(new DefaultTreeSelectionModel()
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ // Extend the implementation of the constructor, as if:
+ /* public this() */
+ {
+ setSelectionModel(listSelectionModel);
+ }
+ });
+ // Make the tree and table row heights the same.
+ tree.setRowHeight(getRowHeight());
+
+ // Install the tree editor renderer and editor.
+ setDefaultRenderer(TreeTableModel.class, tree);
+ setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
+
+ setShowGrid(false);
+ setIntercellSpacing(new Dimension(3, 0));
+
+// popup menu
+ addMouseListener(new PopupListener());
+ popup = new JPopupMenu();
+ JMenuItem menuItem = new JMenuItem("Refresh");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+ popup.add(new JSeparator());
+//open menu
+ JMenu openMenu = new JMenu("Open With");
+ popup.add(openMenu);
+ menuItem = new JMenuItem("Jemboss Aligmnment Editor");
+ menuItem.addActionListener(this);
+ openMenu.add(menuItem);
+ menuItem = new JMenuItem("Artemis");
+ menuItem.addActionListener(this);
+ openMenu.add(menuItem);
+
+ menuItem = new JMenuItem("Rename...");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+ menuItem = new JMenuItem("New Folder...");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+ menuItem = new JMenuItem("Delete...");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+ popup.add(new JSeparator());
+ menuItem = new JMenuItem("De-select All");
+ menuItem.addActionListener(this);
+ popup.add(menuItem);
+
+
+ //Listen for when a file is selected
+ addMouseListener(new MouseListener()
+ {
+ public void mouseClicked(MouseEvent me)
+ {
+ if(me.getClickCount() == 2 && isFileSelection() &&
+ !me.isPopupTrigger())
+ {
+ RemoteFileNode node = (RemoteFileNode)tree.getLastSelectedPathComponent();
+ if(node==null)
+ return;
+ frame.setCursor(cbusy);
+ if(node.isLeaf())
+ showFilePane(node);
+ frame.setCursor(cdone);
+ }
+ }
+ public void mousePressed(MouseEvent me){}
+ public void mouseEntered(MouseEvent me){}
+ public void mouseExited(MouseEvent me){}
+ public void mouseReleased(MouseEvent me){}
+ });
+
+ }
+
+ /**
+ *
+ * Determine if selected node is a file
+ * @return true if the selected node is a file
+ *
+ */
+ public boolean isFileSelection()
+ {
+ TreePath path = tree.getLeadSelectionPath();
+ if(path == null)
+ return false;
+ RemoteFileNode node = (RemoteFileNode)path.getLastPathComponent();
+ return !node.isDirectory();
+ }
+
+ /**
+ *
+ * Get FileNode of selected node
+ * @return node that is currently selected
+ *
+ */
+ public RemoteFileNode getSelectedNode()
+ {
+ TreePath path = tree.getLeadSelectionPath();
+ if(path == null)
+ return null;
+ RemoteFileNode node = (RemoteFileNode)path.getLastPathComponent();
+ return node;
+ }
+
+ /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
+ * paint the editor. The UI currently uses different techniques to
+ * paint the renderers and editors and overriding setBounds() below
+ * is not the right thing to do for an editor. Returning -1 for the
+ * editing row in this case, ensures the editor is never painted.
+ */
+ public int getEditingRow()
+ {
+ return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : editingRow;
+ }
+
+
+ protected void refresh(RemoteFileNode node)
+ {
+ node.reset();
+ node.removeAllChildren();
+ node.getChildren();
+ ((DefaultTreeModel)tree.getModel()).nodeStructureChanged(node);
+ }
+
+ /**
+ *
+ * Get FileNodes of selected nodes
+ * @return node that is currently selected
+ *
+ */
+ private RemoteFileNode[] getSelectedNodes()
+ {
+ TreePath path[] = tree.getSelectionPaths();
+ if(path == null)
+ return null;
+
+ int numberSelected = path.length;
+ RemoteFileNode nodes[] = new RemoteFileNode[numberSelected];
+ for(int i=0;i<numberSelected;i++)
+ nodes[i] = (RemoteFileNode)path[i].getLastPathComponent();
+
+ return nodes;
+ }
+
+
+ /**
+ *
+ * Popup menu actions
+ * @param e action event
+ *
+ */
+ public void actionPerformed(ActionEvent e)
+ {
+ JMenuItem source = (JMenuItem)(e.getSource());
+ final RemoteFileNode node = getSelectedNode();
+ if(node == null)
+ {
+ JOptionPane.showMessageDialog(null,"No file selected.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ final String fn = node.getFullName();
+ final String parent = node.getPathName();
+ String rootPath = node.getRootDir();
+ RemoteFileNode pn = node;
+
+ if(source.getText().equals("Refresh"))
+ {
+ if(node.isLeaf() && node.getParentNode() != null)
+ refresh(node.getParentNode());
+ else
+ refresh(node);
+ }
+ else if(source.getText().equals("Jemboss Aligmnment Editor"))
+ {
+ FileTransferProgressMonitor monitor =
+ new FileTransferProgressMonitor(SshJTreeTable.this);
+ FTProgress progress = monitor.add(node.getFile());
+
+ final byte[] contents = node.getFileContents(progress);
+ monitor.close();
+
+ org.emboss.jemboss.editor.AlignJFrame ajFrame =
+ new org.emboss.jemboss.editor.AlignJFrame(new String(contents), fn);
+ ajFrame.setVisible(true);
+ }
+ else if(source.getText().equals("Artemis"))
+ showFilePane(node);
+ else if(source.getText().equals("New Folder..."))
+ {
+ final String inputValue = JOptionPane.showInputDialog(null,
+ "Folder Name","Create New Folder in",
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(node.isLeaf())
+ pn = (RemoteFileNode)node.getParent();
+
+
+ String newNode = pn.getServerName();
+ if(!newNode.endsWith("/"))
+ newNode = newNode.concat("/");
+ newNode = newNode.concat(inputValue);
+
+ if(nodeExists(pn,newNode))
+ return;
+
+ if(inputValue != null && !inputValue.equals("") )
+ {
+ node.mkdir(newNode);
+
+ Runnable addDirToTree = new Runnable()
+ {
+ public void run () { refresh(node); };
+ };
+ SwingUtilities.invokeLater(addDirToTree);
+ }
+ }
+ else if(source.getText().equals("Delete..."))
+ {
+ RemoteFileNode nodes[] = getSelectedNodes();
+ String sname[] = new String[nodes.length];
+ for(int i=0;i<nodes.length;i++)
+ sname[i] = nodes[i].getServerName();
+
+ JList list = new JList(sname);
+ JScrollPane jsp = new JScrollPane(list);
+ int n = JOptionPane.showConfirmDialog(null,
+ jsp, "Delete "+nodes.length+" File(s)",
+ JOptionPane.YES_NO_OPTION);
+
+ if(n == JOptionPane.YES_OPTION)
+ {
+ frame.setCursor(cbusy);
+ for(int i=0;i<nodes.length;i++)
+ deleteNode(nodes[i]);
+ frame.setCursor(cdone);
+ }
+ }
+ else if(source.getText().equals("De-select All"))
+ clearSelection();
+ else if(source.getText().equals("Rename..."))
+ {
+ if(node.isLeaf())
+ {
+ String inputValue = (String)JOptionPane.showInputDialog(null,
+ "New File Name","Rename "+fn,
+ JOptionPane.QUESTION_MESSAGE,null,null,fn);
+
+ pn = (RemoteFileNode)node.getParent();
+
+ if(inputValue != null && !inputValue.equals("") )
+ {
+ String newfile = null;
+ if(parent.endsWith("/"))
+ newfile = parent+inputValue;
+ else
+ newfile = parent+"/"+inputValue;
+
+ if(!nodeExists(pn,newfile))
+ {
+ rename(node,rootPath+"/"+inputValue);
+
+ Runnable addDirToTree = new Runnable()
+ {
+ public void run ()
+ {
+ refresh(node.getParentNode());
+ };
+ };
+ SwingUtilities.invokeLater(addDirToTree);
+ }
+ }
+ }
+ }
+
+ }
+
+
+ /**
+ *
+ * Rename a node from the tree
+ * @param node node to rename
+ * @param newfile new file name
+ *
+ */
+ private void rename(final RemoteFileNode node,
+ final String newfile)
+ {
+ frame.setCursor(cbusy);
+
+ boolean lrename = node.rename(newfile);
+ frame.setCursor(cdone);
+
+ if(!lrename)
+ return;
+ }
+
+
+ /**
+ *
+ * Delete a node (file or directory) from the tree
+ * and from the server
+ * @param node node to remove
+ *
+ */
+ private void deleteNode(final RemoteFileNode node)
+ {
+ boolean deleted = false;
+ deleted = node.delete();
+
+ if(!deleted && !node.isLeaf())
+ JOptionPane.showMessageDialog(null,"Cannot delete"+ls+
+ node.getServerName()+
+ ls+"this directory is not empty","Warning",
+ JOptionPane.ERROR_MESSAGE);
+ else if(deleted)
+ {
+ Runnable deleteFileFromTree = new Runnable()
+ {
+ public void run ()
+ {
+ if(node.isLeaf() && node.getParentNode() != null)
+ refresh(node.getParentNode());
+ else
+ refresh(node);
+ };
+ };
+ SwingUtilities.invokeLater(deleteFileFromTree);
+ }
+
+ }
+
+
+ /**
+ *
+ * Test if a child node exists
+ * @param parentNode parent node
+ * @param child child to test for
+ *
+ */
+ public boolean nodeExists(RemoteFileNode parentNode, final String child)
+ {
+ if(!parentNode.isDirectory())
+ parentNode = (RemoteFileNode)parentNode.getParent();
+
+ RemoteFileNode childNode = getChildNode(parentNode,child);
+
+ if(childNode != null)
+ {
+ Runnable warnMsg = new Runnable()
+ {
+ public void run ()
+ {
+ String ls = System.getProperty("line.separator");
+ JOptionPane.showMessageDialog(null, child+ls+" already exists!",
+ "File Exists",
+ JOptionPane.ERROR_MESSAGE);
+ };
+ };
+ SwingUtilities.invokeLater(warnMsg);
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ *
+ * Gets the child node of a parent node
+ * @param parent parent node
+ * @param childName name of child
+ * @return the child node
+ *
+ */
+ private RemoteFileNode getChildNode(RemoteFileNode parent, String childName)
+ {
+ for(Enumeration children = parent.children();
+ children.hasMoreElements() ;)
+ {
+ RemoteFileNode childNode = (RemoteFileNode)children.nextElement();
+ String nodeName = childNode.getServerName();
+// System.out.println(nodeName+" childName= "+childName);
+ if(childName.equals(nodeName))
+ return childNode;
+ }
+
+ return null;
+ }
+
+
+ /**
+ *
+ * Opens a JFrame with the file contents displayed.
+ * @param filename file name
+ *
+ */
+ public static void showFilePane(final RemoteFileNode node)
+ {
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ EntryEdit entry_edit;
+ public Object construct()
+ {
+ try
+ {
+ EntryInformation new_entry_information =
+ new SimpleEntryInformation(Options.getArtemisEntryInformation());
+
+ final Entry entry = new Entry(EntryFileDialog.getEntryFromFile(
+ null, new RemoteFileDocument(node),
+ new_entry_information, true));
+ if(entry == null)
+ return null;
+
+ final EntryGroup entry_group =
+ new SimpleEntryGroup(entry.getBases());
+
+ entry_group.add(entry);
+ entry_edit = new EntryEdit(entry_group);
+ return null;
+ }
+ catch(NoSequenceException e)
+ {
+ new MessageDialog(null, "read failed: entry contains no sequence");
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(null, "read failed: one of the features in " +
+ " the entry has an out of range " +
+ "location: " + e.getMessage());
+
+ }
+ catch(NullPointerException npe){}
+
+ return null;
+ }
+
+ public void finished()
+ {
+ if(entry_edit != null)
+ entry_edit.setVisible(true);
+ }
+ };
+ entryWorker.start();
+ }
+
+ //
+ // The renderer used to display the tree nodes, a JTree.
+ //
+ public class TreeTableCellRenderer extends JTree implements TableCellRenderer
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+ protected int visibleRow;
+
+ public TreeTableCellRenderer(TreeModel model)
+ {
+ super(model);
+ }
+
+ public void setBounds(int x, int y, int w, int h)
+ {
+ super.setBounds(x, 0, w, SshJTreeTable.this.getHeight());
+ }
+
+ public void paint(Graphics g)
+ {
+ g.translate(0, -visibleRow * getRowHeight());
+ super.paint(g);
+ }
+
+ public Component getTableCellRendererComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ boolean hasFocus,
+ int row, int column)
+ {
+ if(isSelected)
+ setBackground(table.getSelectionBackground());
+ else
+ setBackground(table.getBackground());
+
+ visibleRow = row;
+ return this;
+ }
+ }
+
+ //
+ // The editor used to interact with tree nodes, a JTree.
+ //
+
+ public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor
+ {
+ public Component getTableCellEditorComponent(JTable table, Object value,
+ boolean isSelected, int r, int c)
+ {
+ return tree;
+ }
+ }
+
+ /**
+ *
+ * Popup menu listener
+ *
+ */
+ class PopupListener extends MouseAdapter
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ }
+
+ private void remoteDrop(DropTargetDropEvent e, Vector vnode)
+ {
+ try
+ {
+ Point ploc = e.getLocation();
+ TreePath dropPath = tree.getPathForLocation(ploc.x,ploc.y);
+ if(dropPath != null)
+ {
+ RemoteFileNode drop = (RemoteFileNode)dropPath.getLastPathComponent();
+ final RemoteFileNode fdropPath;
+ if(!drop.isDirectory())
+ fdropPath = (RemoteFileNode)drop.getParent();
+ else
+ fdropPath = drop;
+
+ for(int i=0; i<vnode.size();i++)
+ {
+ final RemoteFileNode fn = (RemoteFileNode)vnode.get(i);
+
+ String serverName = fdropPath.getServerName()+"/"+fn.getFile();
+ if(!nodeExists(fdropPath,serverName))
+ {
+ rename(fn, serverName);
+
+ Runnable addDirToTree = new Runnable()
+ {
+ public void run ()
+ {
+ refresh(fdropPath);
+ repaint();
+ };
+ };
+ SwingUtilities.invokeLater(addDirToTree);
+ }
+ }
+ }
+ }
+ catch(Exception ex){ ex.printStackTrace(); }
+ }
+
+ private void localDrop(final DropTargetDropEvent e, final Vector fn)
+ {
+ //put this in separate thread for progress bar
+ SwingWorker putWorker = new SwingWorker()
+ {
+ FileTransferProgressMonitor monitor;
+ public Object construct()
+ {
+ try
+ {
+ Point ploc = e.getLocation();
+ TreePath dropPath = tree.getPathForLocation(ploc.x,ploc.y);
+
+ if(dropPath != null)
+ {
+ monitor = new FileTransferProgressMonitor(SshJTreeTable.this);
+ for(int i=0; i<fn.size(); i++)
+ {
+ final File lfn = ((FileNode)fn.get(i)).getFile();
+
+ RemoteFileNode pn = (RemoteFileNode)dropPath.getLastPathComponent();
+ if(!pn.isDirectory())
+ pn = (RemoteFileNode)pn.getParent();
+
+ String serverName;
+ if(pn.getServerName().endsWith("/"))
+ serverName = pn.getServerName()+lfn.getName();
+ else
+ serverName = pn.getServerName()+"/"+lfn.getName();
+
+ if(!nodeExists(pn,serverName))
+ {
+ FTProgress progress = monitor.add(lfn.getName());
+ pn.put(lfn, progress);
+
+ try
+ {
+ //add file to remote file tree
+ if(pn.isLeaf())
+ pn = (RemoteFileNode)pn.getParent();
+
+ refresh(pn);
+ Object[] children = pn.getChildren();
+ for(int j = 0; j<children.length; j++)
+ {
+ String childNode = ((RemoteFileNode)children[j]).getFile();
+ if(childNode.equals(lfn.getName()))
+ tree.scrollPathToVisible(new TreePath( ((RemoteFileNode)children[j]).getPath()));
+ }
+ }
+ catch (Exception exp) {}
+ }
+ else
+ e.rejectDrop();
+ }
+ }
+ }
+ catch (Exception ex) {}
+
+ return null;
+ }
+
+ public void finished()
+ {
+ if(monitor != null)
+ monitor.close();
+ }
+ };
+ putWorker.start();
+ }
+
+////////////////////
+// DRAG AND DROP
+////////////////////
+// drag source
+ public void dragGestureRecognized(DragGestureEvent e)
+ {
+ // ignore if mouse popup trigger
+ InputEvent ie = e.getTriggerEvent();
+ if(ie instanceof MouseEvent)
+ if(((MouseEvent)ie).isPopupTrigger())
+ return;
+
+ // drag only files
+ if(isFileSelection())
+ {
+ final int nlist = tree.getSelectionCount();
+ if(nlist > 1)
+ {
+ TransferableFileNodeList list = new TransferableFileNodeList(nlist);
+ RemoteFileNode nodes[] = getSelectedNodes();
+ for(int i=0; i<nodes.length; i++)
+ list.add(nodes[i]);
+
+ BufferedImage buff = getImage();
+ e.startDrag(DragSource.DefaultCopyDrop, // cursor
+ buff,
+ new Point(0,0),
+ (Transferable)list, // transferable data
+ this); // drag source listener
+ }
+ else
+ {
+ BufferedImage buff = getImage();
+ e.startDrag(DragSource.DefaultCopyDrop, // cursor
+ buff,
+ new Point(0,0),
+ (Transferable)getSelectedNode(), // transferable data
+ this); // drag source listener
+ }
+ }
+ }
+
+ /**
+ *
+ * FileSystemView provides a platform-independent way to get the
+ * appropriate icon
+ *
+ */
+ private BufferedImage getImage()
+ {
+ File temp = new File(System.getProperty("user.dir"));
+ File[] files = temp.listFiles();
+ for(int i=0; i<files.length; i++)
+ {
+ if(files[i].isFile())
+ {
+ temp = files[i];
+ break;
+ }
+ }
+
+ // get the right icon
+ FileSystemView fsv = FileSystemView.getFileSystemView( );
+ Icon icn = fsv.getSystemIcon(temp);
+
+ Toolkit tk = Toolkit.getDefaultToolkit( );
+ Dimension dim = tk.getBestCursorSize(
+ icn.getIconWidth( ),icn.getIconHeight( ));
+ BufferedImage buff = new BufferedImage(dim.width,dim.height,
+ BufferedImage.TYPE_INT_ARGB);
+ icn.paintIcon(this,buff.getGraphics( ),0,0);
+ return buff;
+ }
+
+ /**
+ *
+ * FileSystemView provides a platform-independent way to get the
+ * appropriate icon
+ *
+ */
+ /*private BufferedImage getImage(File temp)
+ {
+ // get the right icon
+ FileSystemView fsv = FileSystemView.getFileSystemView( );
+ Icon icn = fsv.getSystemIcon(temp);
+
+ Toolkit tk = Toolkit.getDefaultToolkit( );
+ Dimension dim = tk.getBestCursorSize(
+ icn.getIconWidth( ),icn.getIconHeight( ));
+ BufferedImage buff = new BufferedImage(dim.width,dim.height,
+ BufferedImage.TYPE_INT_ARGB);
+ icn.paintIcon(this,buff.getGraphics( ),0,0);
+ return buff;
+ }*/
+
+ public void dragDropEnd(DragSourceDropEvent e) {}
+ public void dragEnter(DragSourceDragEvent e) {}
+ public void dragExit(DragSourceEvent e) {}
+ public void dragOver(DragSourceDragEvent e) {}
+ public void dropActionChanged(DragSourceDragEvent e) {}
+
+// drop sink
+ public void dragEnter(DropTargetDragEvent e)
+ {
+ if(e.isDataFlavorSupported(FileNode.FILENODE) ||
+ e.isDataFlavorSupported(RemoteFileNode.REMOTEFILENODE) ||
+ e.isDataFlavorSupported(TransferableFileNodeList.TRANSFERABLEFILENODELIST))
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+
+ public void drop(final DropTargetDropEvent e)
+ {
+ final Transferable t = e.getTransferable();
+
+ if(t.isDataFlavorSupported(TransferableFileNodeList.TRANSFERABLEFILENODELIST))
+ {
+ try
+ {
+ TransferableFileNodeList filelist = (TransferableFileNodeList)
+ t.getTransferData(TransferableFileNodeList.TRANSFERABLEFILENODELIST);
+
+ if(filelist.get(0) instanceof RemoteFileNode)
+ remoteDrop(e, filelist);
+ else
+ localDrop(e, filelist);
+ }
+ catch(UnsupportedFlavorException exp){}
+ catch(IOException ioe){}
+ }
+ else if(t.isDataFlavorSupported(RemoteFileNode.REMOTEFILENODE))
+ {
+ try
+ {
+ Vector v = new Vector();
+ RemoteFileNode node =
+ (RemoteFileNode)t.getTransferData(RemoteFileNode.REMOTEFILENODE);
+ v.add(node);
+ remoteDrop(e, v);
+ }
+ catch(UnsupportedFlavorException exp){}
+ catch(IOException ioe){}
+ }
+ else if(t.isDataFlavorSupported(FileNode.FILENODE))
+ {
+ try
+ {
+ Vector v = new Vector();
+ FileNode fn = (FileNode)t.getTransferData(FileNode.FILENODE);
+ v.add(fn);
+ localDrop(e, v);
+ }
+ catch(UnsupportedFlavorException exp){}
+ catch(IOException ioe){}
+ }
+ else
+ e.rejectDrop();
+ }
+
+ /**
+ *
+ * When a suitable DataFlavor is offered over a remote file
+ * node the node is highlighted/selected and the drag
+ * accepted. Otherwise the drag is rejected.
+ *
+ */
+ public void dragOver(DropTargetDragEvent e)
+ {
+ if (e.isDataFlavorSupported(FileNode.FILENODE))
+ {
+ Point ploc = e.getLocation();
+ TreePath ePath = tree.getPathForLocation(ploc.x,ploc.y);
+ if (ePath == null)
+ e.rejectDrag();
+ else
+ {
+ tree.setSelectionPath(ePath);
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+ }
+ else if(e.isDataFlavorSupported(RemoteFileNode.REMOTEFILENODE) ||
+ e.isDataFlavorSupported(TransferableFileNodeList.TRANSFERABLEFILENODELIST))
+ {
+ Point ploc = e.getLocation();
+ TreePath ePath = tree.getPathForLocation(ploc.x,ploc.y);
+ if (ePath == null)
+ {
+ e.rejectDrag();
+ return;
+ }
+
+ RemoteFileNode node = (RemoteFileNode)ePath.getLastPathComponent();
+ if(!node.isDirectory())
+ {
+ e.rejectDrag();
+ return;
+ }
+ tree.setSelectionPath(ePath);
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+ else
+ e.rejectDrag();
+ }
+
+ public void dropActionChanged(DropTargetDragEvent e) {}
+ public void dragExit(DropTargetEvent e){}
+
+////////////////////
+// AUTO SCROLLING //
+////////////////////
+ /**
+ *
+ * Handles the auto scrolling of the JTree.
+ * @param location The location of the mouse.
+ *
+ */
+ public void autoscroll( Point location )
+ {
+ int top = 0, left = 0, bottom = 0, right = 0;
+ Dimension size = getSize();
+ Rectangle rect = getVisibleRect();
+ int bottomEdge = rect.y + rect.height;
+ int rightEdge = rect.x + rect.width;
+ if( location.y - rect.y < AUTOSCROLL_MARGIN && rect.y > 0 )
+ top = AUTOSCROLL_MARGIN;
+ if( location.x - rect.x < AUTOSCROLL_MARGIN && rect.x > 0 )
+ left = AUTOSCROLL_MARGIN;
+ if( bottomEdge - location.y < AUTOSCROLL_MARGIN && bottomEdge < size.height )
+ bottom = AUTOSCROLL_MARGIN;
+ if( rightEdge - location.x < AUTOSCROLL_MARGIN && rightEdge < size.width )
+ right = AUTOSCROLL_MARGIN;
+ rect.x += right - left;
+ rect.y += bottom - top;
+ scrollRectToVisible( rect );
+ }
+
+
+ /**
+ *
+ * Gets the insets used for the autoscroll.
+ * @return The insets.
+ *
+ */
+ public Insets getAutoscrollInsets()
+ {
+ Dimension size = getSize();
+ Rectangle rect = getVisibleRect();
+ autoscrollInsets.top = rect.y + AUTOSCROLL_MARGIN;
+ autoscrollInsets.left = rect.x + AUTOSCROLL_MARGIN;
+ autoscrollInsets.bottom = size.height - (rect.y+rect.height) + AUTOSCROLL_MARGIN;
+ autoscrollInsets.right = size.width - (rect.x+rect.width) + AUTOSCROLL_MARGIN;
+ return autoscrollInsets;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/filetree/TransferableFileNodeList.java b/uk/ac/sanger/artemis/components/filetree/TransferableFileNodeList.java
new file mode 100644
index 0000000..0ad68d0
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/TransferableFileNodeList.java
@@ -0,0 +1,77 @@
+/********************************************************************
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Library General Public
+* License as published by the Free Software Foundation; either
+* version 2 of the License, or (at your option) any later version.
+*
+* This library 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
+* Library General Public License for more details.
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this library; if not, write to the
+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+* Boston, MA 02111-1307, USA.
+*
+* @author: Copyright (C) Tim Carver
+*
+********************************************************************/
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import java.awt.datatransfer.*;
+import java.io.*;
+import java.util.*;
+
+/**
+*
+* File node for remote file tree manager
+*
+*/
+public class TransferableFileNodeList extends Vector
+ implements Transferable, Serializable
+{
+ final public static DataFlavor TRANSFERABLEFILENODELIST =
+ new DataFlavor(TransferableFileNodeList.class, "file list");
+ static DataFlavor remoteFlavors[] = { TRANSFERABLEFILENODELIST };
+
+ public TransferableFileNodeList(int n)
+ {
+ super(n);
+ }
+
+// Transferable
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return remoteFlavors;
+ }
+
+ public boolean isDataFlavorSupported(DataFlavor f)
+ {
+ return f.equals(TRANSFERABLEFILENODELIST);
+ }
+
+ public Object getTransferData(DataFlavor d)
+ throws UnsupportedFlavorException, IOException
+ {
+ if(d.equals(TRANSFERABLEFILENODELIST))
+ return this;
+ else throw new UnsupportedFlavorException(d);
+ }
+
+// Serializable
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException
+ {
+ out.defaultWriteObject();
+ }
+
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/components/filetree/TreeTableModel.java b/uk/ac/sanger/artemis/components/filetree/TreeTableModel.java
new file mode 100644
index 0000000..70dc518
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/TreeTableModel.java
@@ -0,0 +1,76 @@
+/*
+ *
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import javax.swing.tree.TreeModel;
+
+/**
+ * TreeTableModel is the model used by a JTreeTable. It extends TreeModel
+ * to add methods for getting inforamtion about the set of columns each
+ * node in the TreeTableModel may have. Each column, like a column in
+ * a TableModel, has a name and a type associated with it. Each node in
+ * the TreeTableModel can return a value for each of the columns and
+ * set that value if isCellEditable() returns true.
+ *
+ * @version %I% %G%
+ *
+ * @author Philip Milne
+ * @author Scott Violet
+ */
+public interface TreeTableModel extends TreeModel
+{
+ /**
+ * Returns the number ofs availible column.
+ */
+ public int getColumnCount();
+
+ /**
+ * Returns the name for column number <code>column</code>.
+ */
+ public String getColumnName(int column);
+
+ /**
+ * Returns the type for column number <code>column</code>.
+ */
+ public Class getColumnClass(int column);
+
+ /**
+ * Returns the value to be displayed for node <code>node</code>,
+ * at column number <code>column</code>.
+ */
+ public Object getValueAt(Object node, int column);
+
+ /**
+ * Indicates whether the the value for node <code>node</code>,
+ * at column number <code>column</code> is editable.
+ */
+ public boolean isCellEditable(Object node, int column);
+
+ /**
+ * Sets the value for node <code>node</code>,
+ * at column number <code>column</code>.
+ */
+ public void setValueAt(Object aValue, Object node, int column);
+}
+
diff --git a/uk/ac/sanger/artemis/components/filetree/TreeTableModelAdapter.java b/uk/ac/sanger/artemis/components/filetree/TreeTableModelAdapter.java
new file mode 100644
index 0000000..9714344
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/filetree/TreeTableModelAdapter.java
@@ -0,0 +1,103 @@
+/*
+ *
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.filetree;
+
+import javax.swing.table.AbstractTableModel;
+import javax.swing.JTree;
+import javax.swing.tree.*;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeExpansionListener;
+
+/**
+ * This is a wrapper class takes a TreeTableModel and implements
+ * the table model interface. The implementation is trivial, with
+ * all of the event dispatching support provided by the superclass:
+ * the AbstractTableModel.
+ *
+ * @version %I% %G%
+ *
+ * @author Philip Milne
+ * @author Scott Violet
+ */
+
+
+public class TreeTableModelAdapter extends AbstractTableModel
+{
+ JTree tree;
+ TreeModel treeTableModel;
+
+ public TreeTableModelAdapter(TreeModel treeTableModel, JTree tree) {
+ this.tree = tree;
+ this.treeTableModel = treeTableModel;
+
+ tree.addTreeExpansionListener(new TreeExpansionListener() {
+ // Don't use fireTableRowsInserted() here;
+ // the selection model would get updated twice.
+ public void treeExpanded(TreeExpansionEvent event) {
+ fireTableDataChanged();
+ }
+ public void treeCollapsed(TreeExpansionEvent event) {
+ fireTableDataChanged();
+ }
+ });
+ }
+
+ // Wrappers, implementing TableModel interface.
+
+ public int getColumnCount() {
+ return ((FileSystemModel)treeTableModel).getColumnCount();
+ }
+
+ public String getColumnName(int column) {
+ return ((FileSystemModel)treeTableModel).getColumnName(column);
+ }
+
+ public Class getColumnClass(int column) {
+ return ((FileSystemModel)treeTableModel).getColumnClass(column);
+ }
+
+ public int getRowCount() {
+ return tree.getRowCount();
+ }
+
+ protected Object nodeForRow(int row) {
+ TreePath treePath = tree.getPathForRow(row);
+ return treePath.getLastPathComponent();
+ }
+
+ public Object getValueAt(int row, int column) {
+ return ((FileSystemModel)treeTableModel).getValueAt(nodeForRow(row), column);
+ }
+
+ public boolean isCellEditable(int row, int column) {
+ return true;
+// return ((FileSystemModel)treeTableModel).isCellEditable(nodeForRow(row), column);
+ }
+
+ public void setValueAt(Object value, int row, int column) {
+// ((FileSystemModel)treeTableModel).setValueAt(value, nodeForRow(row), column);
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/components/genebuilder/AutoCompleteComboDocument.java b/uk/ac/sanger/artemis/components/genebuilder/AutoCompleteComboDocument.java
new file mode 100644
index 0000000..8bb527b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/AutoCompleteComboDocument.java
@@ -0,0 +1,231 @@
+/* AutoCompleteComboDocument.java
+ * from http://www.orbital-computer.de/JComboBox/
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.JComboBox;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.PlainDocument;
+
+import org.gmod.schema.cv.CvTerm;
+
+ public class AutoCompleteComboDocument extends PlainDocument
+ {
+ private static final long serialVersionUID = 1L;
+ private JComboBox comboBox;
+
+ private JTextComponent editor;
+ // flag to indicate if setSelectedItem has been called
+ // subsequent calls to remove/insertString should be ignored
+ boolean selecting=false;
+ boolean hidePopupOnFocusLoss;
+ boolean hitBackspace=false;
+ boolean hitBackspaceOnSelection;
+
+
+ public AutoCompleteComboDocument(final JComboBox comboBox)
+ {
+ this.comboBox = comboBox;
+
+ editor = (JTextComponent) comboBox.getEditor().getEditorComponent();
+ editor.setDocument(this);
+
+ if(comboBox.getModel().getSize() <= comboBox.getMaximumRowCount())
+ comboBox.setMaximumRowCount(comboBox.getModel().getSize()-1);
+
+ comboBox.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if (!selecting) highlightCompletedText(0);
+ }
+ });
+
+ editor.addKeyListener(new KeyAdapter()
+ {
+ public void keyPressed(KeyEvent e)
+ {
+ if(comboBox.isDisplayable())
+ comboBox.setPopupVisible(true);
+ hitBackspace=false;
+ switch (e.getKeyCode())
+ {
+ // determine if the pressed key is backspace (needed by the remove method)
+ case KeyEvent.VK_BACK_SPACE :
+ hitBackspace=true;
+ hitBackspaceOnSelection=editor.getSelectionStart()!=editor.getSelectionEnd();
+ break;
+ }
+ }
+ });
+
+ // Bug 5100422 on Java 1.5: Editable JComboBox won't hide popup when tabbing out
+ hidePopupOnFocusLoss=System.getProperty("java.version").startsWith("1.5");
+ // Highlight whole text when gaining focus
+ editor.addFocusListener(new FocusAdapter()
+ {
+ public void focusGained(FocusEvent e)
+ {
+ highlightCompletedText(0);
+ }
+ public void focusLost(FocusEvent e)
+ {
+ // Workaround for Bug 5100422 - Hide Popup on focus loss
+ if (hidePopupOnFocusLoss) comboBox.setPopupVisible(false);
+ }
+ });
+ // Handle initially selected object
+ Object selected = comboBox.getSelectedItem();
+ if(selected!=null)
+ setText(getStringValue(selected));
+ highlightCompletedText(0);
+ }
+
+ public void remove(int offs, int len) throws BadLocationException
+ {
+ // return immediately when selecting an item
+ if (selecting)
+ return;
+ if (hitBackspace)
+ {
+ // user hit backspace => move the selection backwards
+ // old item keeps being selected
+ if(offs>0)
+ if(hitBackspaceOnSelection)
+ offs--;
+ else
+ {
+ // User hit backspace with the cursor positioned on the start => beep
+ comboBox.getToolkit().beep(); // when available use: UIManager.getLookAndFeel().provideErrorFeedback(combo
+ }
+ highlightCompletedText(offs);
+ }
+ else
+ super.remove(offs, len);
+ }
+
+ public void insertString(int offs, String str, AttributeSet a)
+ throws BadLocationException
+ {
+ // return immediately when selecting an item
+ if(selecting)
+ return;
+ super.insertString(offs, str, a);
+
+ // lookup and select a matching item
+ Object item = lookupItem(getText(0, getLength()));
+
+ boolean listContainsSelectedItem = true;
+ if(item == null)
+ {
+ item = comboBox.getModel().getSelectedItem();
+ listContainsSelectedItem = false;
+ }
+ setSelectedItem(item);
+ setText(getStringValue(item));
+ // select the completed part
+ if(listContainsSelectedItem)
+ highlightCompletedText(offs + str.length());
+ }
+
+ private void setText(String text)
+ {
+ try
+ {
+ super.remove(0, getLength());
+ super.insertString(0, text, null);
+ }
+ catch(BadLocationException e)
+ {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ private void highlightCompletedText(int start)
+ {
+ editor.setCaretPosition(getLength());
+ editor.moveCaretPosition(start);
+ }
+
+ private void setSelectedItem(Object item)
+ {
+ selecting = true;
+ comboBox.getModel().setSelectedItem(item);
+ selecting = false;
+ }
+
+ private Object lookupItem(String pattern)
+ {
+ Object selectedItem = comboBox.getModel().getSelectedItem();
+ String selectedItemStr = getStringValue(selectedItem);
+
+ // only search if the currently selected does not match
+ if(selectedItemStr != null
+ && startsWithIgnoreCase(selectedItemStr, pattern))
+ return selectedItem;
+ else
+ {
+ // iterate over all items
+ for(int i = 0, n = comboBox.getModel().getSize(); i < n; i++)
+ {
+ Object currentItem = comboBox.getModel().getElementAt(i);
+ String currentItemStr = getStringValue(currentItem);
+
+ // current item starts with the pattern?
+ if(startsWithIgnoreCase(currentItemStr, pattern))
+ return currentItem;
+ }
+ }
+
+ return null;
+ }
+
+ private String getStringValue(Object item)
+ {
+ String itemStr = null;
+ if(item != null)
+ {
+ if(item instanceof String)
+ itemStr = item.toString();
+ else
+ itemStr = ((CvTerm)item).getName();
+ }
+ return itemStr;
+ }
+
+ private boolean startsWithIgnoreCase(String str1, String str2)
+ {
+ return str1.toUpperCase().startsWith(str2.toUpperCase());
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/BasicGeneBuilderFrame.java b/uk/ac/sanger/artemis/components/genebuilder/BasicGeneBuilderFrame.java
new file mode 100644
index 0000000..8949f73
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/BasicGeneBuilderFrame.java
@@ -0,0 +1,1114 @@
+/* BasicGeneBuilderFrame
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.FontMetrics;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryChangeEvent;
+import uk.ac.sanger.artemis.EntryChangeListener;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.KeyChoice;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.components.QualifierTextArea;
+import uk.ac.sanger.artemis.components.Utilities;
+import uk.ac.sanger.artemis.components.genebuilder.cv.CVPanel;
+
+import uk.ac.sanger.artemis.components.genebuilder.gff.BasicPropertiesPanel;
+import uk.ac.sanger.artemis.components.genebuilder.gff.PropertiesPanel;
+import uk.ac.sanger.artemis.components.genebuilder.ortholog.MatchPanel;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.LocationParseException;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierInfo;
+import uk.ac.sanger.artemis.io.QualifierLazyLoading;
+import uk.ac.sanger.artemis.io.QualifierParseException;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.io.StreamQualifier;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+public class BasicGeneBuilderFrame extends JFrame
+ implements EntryChangeListener, FeatureChangeListener
+{
+ private static final long serialVersionUID = 1L;
+ private Feature activeFeature;
+ private ChadoCanonicalGene chadoGene;
+ private ChadoTransactionManager chadoTransactionManager;
+ private EntryGroup entry_group;
+ private Selection selection;
+ private CVPanel cvPanel;
+ private MatchPanel matchForm;
+ private BasicPropertiesPanel propertiesPanel;
+ private ReferencesPanel refPanel;
+ private QualifierTextArea qualifier_text_area;
+ private JTabbedPane tabPane = new JTabbedPane();
+ private JTextField locationText = new JTextField(60);
+ private KeyChoice keyChoice;
+ private int lastTabSelected = 0;
+ private Border empty = new EmptyBorder(0,0,0,0);
+ private JScrollPane jsp = new JScrollPane();
+ private DatabaseDocument dbDoc;
+
+ public BasicGeneBuilderFrame(Feature feature,
+ final EntryGroup entry_group,
+ final Selection selection)
+ {
+ this(feature, entry_group, selection, null);
+ }
+
+ public BasicGeneBuilderFrame(Feature feature,
+ final EntryGroup entry_group,
+ final Selection selection,
+ final ChadoTransactionManager chadoTransactionManager)
+ {
+ super();
+
+ this.activeFeature = feature;
+ this.entry_group = entry_group;
+ this.chadoTransactionManager = chadoTransactionManager;
+ this.selection = selection;
+
+
+ dbDoc =
+ (DatabaseDocument)(
+ (GFFStreamFeature)activeFeature.getEmblFeature()).getDocumentEntry().getDocument();
+
+ chadoGene =
+ ((GFFStreamFeature)feature.getEmblFeature()).getChadoGene();
+
+ // set title
+ final String title = "Artemis Gene Builder: " +
+ ((Feature)chadoGene.getGene().getUserData()).getIDString() +
+ (feature.isReadOnly() ?
+ " - (read only)" : "");
+
+ final List<uk.ac.sanger.artemis.io.Feature> transcripts =
+ chadoGene.getTranscripts();
+
+ JPanel mainPanel = (JPanel) getContentPane();
+ mainPanel.setLayout(new BorderLayout());
+
+ JTabbedPane tabMapViewer = new JTabbedPane();
+ JPanel northPanel = new JPanel(new BorderLayout());
+ JLabel statusLine = createStatusBar();
+
+ final BasicGeneViewerPanel nucViewer = new BasicGeneViewerPanel(
+ this, chadoGene, selection,
+ entry_group, this, statusLine);
+
+ northPanel.add(nucViewer, BorderLayout.CENTER);
+ northPanel.add(statusLine, BorderLayout.NORTH);
+
+ FlowLayout flowLayoutZeroHVgap = new FlowLayout(FlowLayout.LEADING, 0, 0);
+ keyChoice =
+ new KeyChoice(activeFeature.getEntry().getEntryInformation(),
+ activeFeature.getKey());
+ keyChoice.setLayout(flowLayoutZeroHVgap);
+
+ JPanel keyAndLocation = new JPanel(new BorderLayout());
+ keyAndLocation.add(keyChoice, BorderLayout.WEST);
+ keyAndLocation.add(locationText, BorderLayout.CENTER);
+
+ ButtonPanel buttonPanel = new ButtonPanel(this, entry_group);
+ keyAndLocation.add(buttonPanel, BorderLayout.SOUTH);
+
+ northPanel.add(keyAndLocation, BorderLayout.SOUTH);
+ tabMapViewer.addTab("Gene Map", northPanel);
+
+ //
+ // protein map
+ List<uk.ac.sanger.artemis.io.Feature> proteins =
+ ProteinMapPanel.getProteinsWithProteinMapElement(
+ (GFFStreamFeature) getFeature().getEmblFeature());
+ final BasicProteinMapPanel proteinMap;
+ if(proteins != null)
+ {
+ proteinMap =
+ new BasicProteinMapPanel((GFFStreamFeature) proteins.get(0),
+ chadoGene, selection, this);
+
+ final JScrollPane jsp_ppviewer = new JScrollPane(proteinMap);
+ jsp_ppviewer.getViewport().setBackground(Color.white);
+ jsp_ppviewer.setPreferredSize( proteinMap.getPreferredSize() );
+ tabMapViewer.addTab("Protein Map", jsp_ppviewer);
+ }
+ else
+ proteinMap = null;
+
+ mainPanel.add(tabMapViewer, BorderLayout.NORTH);
+ mainPanel.add(tabPane, BorderLayout.CENTER);
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ GFFStreamFeature transcript = (GFFStreamFeature) transcripts.get(i);
+ tabPane.insertTab(GeneUtils.getUniqueName(transcript),
+ null, new JPanel(new BorderLayout()), null, i);
+ }
+
+ tabPane.insertTab("+",
+ null, new JPanel(new BorderLayout()), "Add a transcript", transcripts.size());
+
+ addComponentToTab(tabPane, chadoGene, entry_group, null);
+ updateLocation();
+
+ tabPane.addChangeListener(new ChangeListener()
+ {
+ // This method is called whenever the selected tab changes
+ public void stateChanged(ChangeEvent evt)
+ {
+ int sel = tabPane.getSelectedIndex();
+ if(tabPane.getTitleAt(sel).equals("+"))
+ {
+ uk.ac.sanger.artemis.io.Feature transcript = transcripts.get(lastTabSelected);
+ int status = JOptionPane.showConfirmDialog(BasicGeneBuilderFrame.this,
+ "Make a duplicate transcript feature of "+
+ GeneUtils.getUniqueName(transcript),
+ "Duplicate "+GeneUtils.getUniqueName(transcript),
+ JOptionPane.OK_CANCEL_OPTION);
+ if(status != JOptionPane.OK_OPTION)
+ {
+ tabPane.setSelectedIndex(lastTabSelected);
+ return;
+ }
+
+ String transcriptName = duplicateTranscript(transcript);
+ tabPane.setTitleAt(sel, transcriptName);
+ }
+
+ if(((JComponent)tabPane.getSelectedComponent()).getComponentCount() < 1)
+ {
+ addComponentToTab(tabPane, chadoGene, entry_group, this);
+ }
+
+ setActiveFeature(getSelectedTranscriptFeature());
+ nucViewer.repaint();
+ if(proteinMap != null)
+ proteinMap.repaint();
+ lastTabSelected = sel;
+ }
+ });
+
+ final FlowLayout flow_layout =
+ new FlowLayout(FlowLayout.CENTER, 18, 1);
+ final JPanel ok_cancel_update_panel = new JPanel(flow_layout);
+
+ final JCheckBox oneView = new JCheckBox("Overview", true);
+ oneView.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ try
+ {
+ stopListeningAll();
+ }
+ catch (InvalidRelationException e1){}
+ dispose();
+ new GeneBuilderFrame(getFeature(), entry_group,
+ selection, null);
+ System.setProperty("basic", "false");
+ }
+ });
+ ok_cancel_update_panel.add(oneView);
+
+ if (!getFeature().isReadOnly())
+ {
+ final JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ try
+ {
+ stopListeningAll();
+ }
+ catch (InvalidRelationException e1){}
+
+ Key key = keyChoice.getSelectedItem();
+ final Location location = getLoc();
+ if(location == null)
+ return;
+
+ if(setQualifiers() &&
+ setActiveFeatureKeyAndLocation(key, location))
+ {
+ stopListening();
+ propertiesPanel.updateObsoleteSettings();
+ dispose();
+ }
+ }
+ });
+ ok_cancel_update_panel.add(okButton);
+
+ final JButton applyButton = new JButton("Apply");
+ applyButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ try
+ {
+ stopListeningAll();
+ }
+ catch (InvalidRelationException e1){}
+ Key key = keyChoice.getSelectedItem();
+ final Location location = getLoc();
+
+ setQualifiers();
+ setActiveFeatureKeyAndLocation(key, location);
+ try
+ {
+ addListeners(chadoGene);
+ }
+ catch (InvalidRelationException e1){}
+ }
+ });
+ ok_cancel_update_panel.add(applyButton);
+
+ final JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ stopListening();
+ dispose();
+ }
+ });
+ ok_cancel_update_panel.add(cancelButton);
+ }
+
+ mainPanel.add(ok_cancel_update_panel, BorderLayout.SOUTH);
+
+ tabPane.setBorder(empty);
+ setTitle(title);
+ pack();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ int height = getPreferredSize().height;
+ if(height > (screen.height*0.9))
+ {
+ setSize(getPreferredSize().width+jsp.getVerticalScrollBar().getWidth(),
+ (int)(screen.height*0.9));
+ }
+ Utilities.centreFrame(this);
+ setVisible(true);
+ try
+ {
+ addListeners(chadoGene);
+ }
+ catch (InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+
+ lastTabSelected = tabPane.getSelectedIndex();
+ }
+
+ private Location getLoc()
+ {
+ try
+ {
+ return new Location(locationText.getText());
+ }
+ catch(LocationParseException exception)
+ {
+ final String error_string = exception.getMessage ();
+ System.out.println(error_string);
+ new MessageDialog(null,
+ "Cannot apply changes because of location error: " +
+ error_string);
+ }
+ return null;
+ }
+
+ public void dispose()
+ {
+ dispose(false);
+ }
+
+ /**
+ * Override to ensure the GeneBuilderFrame removes all listeners
+ */
+ protected void dispose(final boolean reopen)
+ {
+ try
+ {
+ stopListeningAll();
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+
+ if(chadoTransactionManager != null &&
+ chadoTransactionManager.hasTransactions())
+ {
+ int select = JOptionPane.showConfirmDialog(this,
+ "Commit changes back to the database?",
+ "Commit", JOptionPane.YES_NO_OPTION);
+
+ if(select == JOptionPane.YES_OPTION)
+ {
+ ChadoTransactionManager.commit(dbDoc, false,chadoTransactionManager);
+ }
+ }
+
+ if(reopen)
+ {
+ new BasicGeneBuilderFrame((Feature) chadoGene.getGene().getUserData(), entry_group,
+ selection, chadoTransactionManager);
+ }
+ else if(GeneUtils.isBoundaryOK(chadoGene) > 0)
+ {
+ int result = JOptionPane.showConfirmDialog(this,
+ "Gene model boundary needs fixing.\nFix this now?",
+ "Gene Boundary", JOptionPane.YES_NO_OPTION);
+ if(result == JOptionPane.YES_OPTION)
+ GeneUtils.checkGeneBoundary(chadoGene);
+ }
+
+ super.dispose();
+ }
+
+ /**
+ * Add feature listeners for each artemis feature.
+ * @throws InvalidRelationException
+ */
+ private void addListeners(final ChadoCanonicalGene chado_gene)
+ throws InvalidRelationException
+ {
+ // add feature listeners
+ uk.ac.sanger.artemis.io.Feature embl_gene =
+ (uk.ac.sanger.artemis.io.Feature)chado_gene.getGene();
+ Feature gene = (Feature)embl_gene.getUserData();
+ gene.addFeatureChangeListener(this);
+
+ if(gene.getEntry() != null)
+ gene.getEntry().addEntryChangeListener(this);
+
+ List<uk.ac.sanger.artemis.io.Feature> transcripts = chado_gene.getTranscripts();
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ uk.ac.sanger.artemis.io.Feature transcript = transcripts.get(i);
+ Feature trans = (Feature)transcript.getUserData();
+
+ if(trans == null)
+ trans = new Feature(transcript);
+
+ trans.addFeatureChangeListener(this);
+
+ if(trans.getEntry() != null)
+ trans.getEntry().addEntryChangeListener(this);
+
+ List<uk.ac.sanger.artemis.io.Feature> exons = chado_gene.getSpliceSitesOfTranscript(
+ (String)trans.getQualifierByName("ID").getValues().get(0),
+ DatabaseDocument.EXONMODEL);
+
+ if(exons == null || exons.size() < 1)
+ continue;
+
+ if(exons.get(0) instanceof org.gmod.schema.sequence.Feature)
+ return;
+
+ for(int j=0; j<exons.size(); j++)
+ {
+ uk.ac.sanger.artemis.io.Feature embl_exon = exons.get(j);
+
+ Feature exon = (Feature)embl_exon.getUserData();
+
+ if(exon == null)
+ exon = new Feature(embl_exon);
+ exon.addFeatureChangeListener(this);
+
+ if(exon.getEntry() != null)
+ exon.getEntry().addEntryChangeListener(this);
+ }
+ }
+ }
+
+ private int getFontHeight()
+ {
+ final FontMetrics fm = getFontMetrics(Options.getOptions().getFont());
+ return fm.getHeight();
+ }
+
+ private JLabel createStatusBar()
+ {
+ JLabel statusLine = new JLabel();
+ statusLine.setMinimumSize(new Dimension(100, getFontHeight()));
+ statusLine.setPreferredSize(new Dimension(100, getFontHeight()));
+
+ Border loweredbevel = BorderFactory.createLoweredBevelBorder();
+ Border raisedbevel = BorderFactory.createRaisedBevelBorder();
+ Border compound = BorderFactory.createCompoundBorder(raisedbevel,loweredbevel);
+ statusLine.setBorder(compound);
+ return statusLine;
+ }
+
+ private void addToPanel(JComponent section, JPanel container,
+ String sectionName, String description, boolean isOpen)
+ {
+ section.setBackground(Color.WHITE);
+ GeneEditorPanel.addDarkSeparator(container);
+ OpenSectionButton butt =
+ GeneEditorPanel.addOpenClosePanel(sectionName, section, container,
+ description);
+ butt.setOpen(isOpen);
+ container.add(section);
+ }
+
+ public Feature getSelectedTranscriptFeature()
+ {
+ int sel = tabPane.getSelectedIndex();
+
+ /*System.out.println("Selected transcript "+
+ GeneUtils.getUniqueName(chadoGene.getTranscripts().get(sel))+" :: "+
+ tabPane.getTitleAt(sel));*/
+ if(sel > chadoGene.getTranscripts().size()-1)
+ return null;
+ return (Feature) chadoGene.getTranscripts().get(sel).getUserData();
+ }
+
+ public List<uk.ac.sanger.artemis.io.Feature> getCDSOfSelectedTranscriptFeature()
+ {
+ Feature transcript = getSelectedTranscriptFeature();
+ String transcriptName = GeneUtils.getUniqueName(transcript.getEmblFeature());
+ return chadoGene.getSpliceSitesOfTranscript(transcriptName, DatabaseDocument.EXONMODEL);
+ }
+
+ private void addComponentToTab(final JTabbedPane tabPane,
+ final ChadoCanonicalGene chadoGene,
+ final EntryGroup entry_group,
+ final ChangeListener changeListener)
+ {
+ Feature transcript = getSelectedTranscriptFeature();
+ String transcriptName = GeneUtils.getUniqueName(transcript.getEmblFeature());
+
+ JPanel panel = new JPanel();
+ jsp.setViewportView(panel);
+ jsp.setBorder(empty);
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBackground(Color.WHITE);
+
+ propertiesPanel = new BasicPropertiesPanel(chadoGene, this);
+ addToPanel(propertiesPanel, panel, "Properties",
+ null,true);
+
+ Feature protein =
+ (Feature) chadoGene.getProteinOfTranscript(transcriptName).getUserData();
+
+ GeneUtils.addLazyQualifiers((GFFStreamFeature)protein.getEmblFeature());
+
+ //
+ // literature
+ refPanel = new ReferencesPanel(protein);
+ addToPanel(refPanel, panel, "Literature/Dbxref", null,
+ !refPanel.isEmpty());
+
+ //
+ // core text
+ qualifier_text_area = new QualifierTextArea();
+ cvPanel = new CVPanel(protein);
+ matchForm = new MatchPanel(protein,
+ (DocumentEntry)getFeature().getEmblFeature().getEntry());
+
+ qualifier_text_area.getDocument().addDocumentListener(
+ new TextAreaDocumentListener(qualifier_text_area));
+ qualifier_text_area.setText(getQualifierString(entry_group, protein));
+
+ addToPanel(qualifier_text_area, panel, "Core", null,
+ !qualifier_text_area.getText().equals(""));
+
+ addToPanel(cvPanel, panel, "Controlled Vocabulary",
+ CVPanel.getDescription(), !cvPanel.isEmpty());
+ matchForm.updateFromFeature(protein);
+ addToPanel(matchForm, panel, "Match",
+ MatchPanel.getDescription(), !matchForm.isEmpty());
+
+ tabPane.removeChangeListener(changeListener);
+ ((JComponent)tabPane.getSelectedComponent()).add(jsp);
+ tabPane.addChangeListener(changeListener);
+ }
+
+ /**
+ * Return a string containing one qualifier per line. These are the
+ * original qualifiers, not the qualifiers from the qualifier_text_area.
+ **/
+ private String getQualifierString(EntryGroup entry_group,
+ Feature feature)
+ {
+ final StringBuffer buffer = new StringBuffer();
+ final QualifierVector qualifiers = feature.getQualifiers();
+
+ for(int qualifier_index = 0; qualifier_index < qualifiers.size();
+ ++qualifier_index)
+ {
+ final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(qualifier_index);
+
+ //
+ // strip out CV / Properties / Match qualifiers
+ if( (CVPanel.isCvTag(this_qualifier)) ||
+ (PropertiesPanel.isPropertiesTag(this_qualifier, feature)) ||
+ (MatchPanel.isMatchTag(this_qualifier)) ||
+ (ReferencesPanel.isReferenceTag(this_qualifier)) ||
+ (ProteinMapPanel.isProteinMapElement(this_qualifier)) )
+ continue;
+
+ if(this_qualifier instanceof QualifierLazyLoading)
+ ((QualifierLazyLoading)this_qualifier).setForceLoad(true);
+
+ final QualifierInfo qualifier_info =
+ getFeature().getEntry().getEntryInformation().getQualifierInfo(this_qualifier.getName());
+
+ final StringVector qualifier_strings =
+ StreamQualifier.toStringVector(qualifier_info, this_qualifier);
+
+ for(int value_index = 0; value_index < qualifier_strings.size();
+ ++value_index)
+ {
+ final String qualifier_string = (String)qualifier_strings.elementAt(value_index);
+ buffer.append(qualifier_string + "\n");
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ public Feature getFeature()
+ {
+ return activeFeature;
+ }
+
+
+ protected void setObsoleteChanged(boolean obs, FeatureVector features)
+ {
+ propertiesPanel.setObsoleteChanged(obs, features);
+ }
+
+ private Entry getEntry()
+ {
+ return getFeature().getEntry();
+ }
+
+ /**
+ * Remove this object as a feature and entry change listener.
+ **/
+ public void stopListening()
+ {
+ getEntry().removeEntryChangeListener(this);
+ getFeature().removeFeatureChangeListener(this);
+ if(cvPanel != null)
+ getFeature().removeFeatureChangeListener(cvPanel);
+ if(matchForm != null)
+ getFeature().removeFeatureChangeListener(matchForm);
+ }
+
+ /**
+ * Remove this object as a feature and entry change listener.
+ **/
+ private void stopListening(final Feature feature)
+ {
+ if(getEntry() != null)
+ getEntry().removeEntryChangeListener(this);
+ feature.removeFeatureChangeListener(this);
+ }
+
+ private void stopListeningAll() throws InvalidRelationException
+ {
+ uk.ac.sanger.artemis.io.Feature embl_gene =
+ (uk.ac.sanger.artemis.io.Feature)chadoGene.getGene();
+ Feature gene = (Feature)embl_gene.getUserData();
+ stopListening(gene);
+
+ List<uk.ac.sanger.artemis.io.Feature> transcripts = chadoGene.getTranscripts();
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ uk.ac.sanger.artemis.io.Feature transcript = transcripts.get(i);
+ Feature trans = (Feature)transcript.getUserData();
+
+ stopListening(trans);
+
+ List<uk.ac.sanger.artemis.io.Feature> exons = chadoGene.getSpliceSitesOfTranscript(
+ (String)trans.getQualifierByName("ID").getValues().get(0),
+ DatabaseDocument.EXONMODEL);
+
+ if(exons == null)
+ continue;
+
+ for(int j=0; j<exons.size(); j++)
+ {
+ uk.ac.sanger.artemis.io.Feature embl_exon = exons.get(j);
+ Feature exon = (Feature)embl_exon.getUserData();
+ stopListening(exon);
+ }
+ }
+ }
+
+ protected void setActiveFeature(final Feature feature)
+ {
+ this.activeFeature = feature;
+ updateLocation();
+ updateKey();
+ }
+
+ /**
+ * Set the key, location and qualifiers of the feature to be the same as
+ * what values currently shown in the components.
+ * @return true if and only if action succeeds. It may fail because of an
+ * illegal location or qualifier, in which case a message will be
+ * displayed before returning.
+ **/
+ private boolean setProteinFeature(uk.ac.sanger.artemis.io.Feature prot)
+ {
+ if(prot == null)
+ return true;
+
+ Feature protein = (Feature) prot.getUserData();
+
+ final Key key;
+ final Location location;
+ if(isActiveFeature(protein))
+ {
+ key = keyChoice.getSelectedItem();
+ location = getLoc();
+ }
+ else
+ {
+ key = protein.getKey();
+ location = protein.getLocation();
+ }
+
+ final QualifierVector qualifiers;
+
+ try
+ {
+ qualifiers =
+ qualifier_text_area.getParsedQualifiers(getEntry().getEntryInformation ());
+
+ QualifierVector oldQualifiers = protein.getQualifiers();
+
+ // preserve protein properties
+ qualifiers.addAll(propertiesPanel.getProteinProperties(protein));
+
+ for(int i=0; i<oldQualifiers.size(); i++)
+ {
+ Qualifier qualifier = (Qualifier) oldQualifiers.get(i);
+ if(BasicProteinMapPanel.isProteinMapElement(qualifier))
+ {
+ qualifiers.addQualifierValues(qualifier);
+ }
+ }
+
+ // if using controlled vocab form
+ if(cvPanel != null)
+ {
+ QualifierVector cvQualifiers = cvPanel.getCvQualifiers();
+ if(cvQualifiers != null && cvQualifiers.size() > 0)
+ qualifiers.addAll(cvQualifiers);
+ }
+
+ if(matchForm != null)
+ {
+ QualifierVector orthologQualifiers = matchForm.getMatchQualifiers();
+ if(orthologQualifiers != null && orthologQualifiers.size() > 0)
+ qualifiers.addAll(orthologQualifiers);
+ }
+
+ if(refPanel != null)
+ {
+ QualifierVector referenceQualifiers = refPanel.getQualifiers();
+ if(referenceQualifiers != null && referenceQualifiers.size() > 0)
+ qualifiers.addAll(referenceQualifiers);
+ }
+ }
+ catch(QualifierParseException exception)
+ {
+ final String error_string = exception.getMessage();
+ System.out.println(error_string);
+ new MessageDialog(this, "Cannot apply changes because of a qualifier " +
+ "error: " + error_string);
+ return false;
+ }
+
+ try
+ {
+ entry_group.getActionController().startAction();
+
+ try
+ {
+ protein.set(key, location, qualifiers);
+ }
+ catch(java.lang.Error err)
+ {
+ err.printStackTrace();
+
+ if(err.getMessage().indexOf("InvalidRelationException")>-1)
+ {
+ JScrollPane jsp = new JScrollPane(new JLabel(err.getMessage()));
+ jsp.setPreferredSize(new Dimension(200,100));
+ JOptionPane.showMessageDialog(null, jsp,
+ "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ return false;
+ }
+ }
+ catch(EntryInformationException e)
+ {
+ final String error_string = e.getMessage();
+ new MessageDialog(this, "Cannot apply changes: " + error_string);
+
+ return false;
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(this, "Cannot apply changes - the location is out of " +
+ "range for this sequence");
+ return false;
+ }
+ catch(ReadOnlyException e)
+ {
+ new MessageDialog(this, "Cannot apply changes - the feature is " +
+ "read only");
+ return false;
+ }
+ finally
+ {
+ entry_group.getActionController ().endAction ();
+ }
+
+ dribble();
+ return true;
+ }
+
+ private boolean setFeaturePropertiesByFeature(Feature f, QualifierVector qualifiers)
+ {
+ final Key key;
+ final Location location;
+ if(isActiveFeature(f))
+ {
+ key = keyChoice.getSelectedItem();
+ location = getLoc();
+ }
+ else
+ {
+ key = f.getKey();
+ location = f.getLocation();
+ }
+
+ QualifierVector oldQualifiers = f.getQualifiers();
+
+ for(int i=0; i<oldQualifiers.size(); i++)
+ {
+ Qualifier qualifier = (Qualifier) oldQualifiers.get(i);
+ if(!PropertiesPanel.isPropertiesTag(qualifier, f))
+ qualifiers.addQualifierValues(qualifier);
+ }
+
+ try
+ {
+ entry_group.getActionController().startAction();
+
+ try
+ {
+ f.set(key, location, qualifiers);
+ }
+ catch(java.lang.Error err)
+ {
+ err.printStackTrace();
+
+ if(err.getMessage().indexOf("InvalidRelationException")>-1)
+ {
+ JScrollPane jsp = new JScrollPane(new JLabel(err.getMessage()));
+ jsp.setPreferredSize(new Dimension(200,100));
+ JOptionPane.showMessageDialog(null, jsp,
+ "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ return false;
+ }
+ }
+ catch(EntryInformationException e)
+ {
+ final String error_string = e.getMessage();
+ new MessageDialog(this, "Cannot apply changes: " + error_string);
+
+ return false;
+ }
+ catch(OutOfRangeException e)
+ {
+ new MessageDialog(this, "Cannot apply changes - the location is out of " +
+ "range for this sequence");
+ return false;
+ }
+ catch(ReadOnlyException e)
+ {
+ new MessageDialog(this, "Cannot apply changes - the feature is " +
+ "read only");
+ return false;
+ }
+ finally
+ {
+ entry_group.getActionController ().endAction ();
+ }
+
+ return true;
+ }
+
+ private boolean isActiveFeature(Feature f)
+ {
+ return activeFeature.getIDString().equals(f.getIDString());
+ }
+
+ /**
+ * Duplicate a given transcript feature and its children.
+ * @param transcripts
+ * @return
+ */
+ private String duplicateTranscript(uk.ac.sanger.artemis.io.Feature lastTranscript)
+ {
+ Feature transcript =
+ GeneViewerPanel.createTranscript(chadoGene, entry_group,
+ lastTranscript.getLocation());
+
+ String transcriptName = GeneUtils.getUniqueName(transcript.getEmblFeature());
+ Iterator<uk.ac.sanger.artemis.io.Feature> it =
+ chadoGene.getChildren(lastTranscript).iterator();
+
+ while(it.hasNext())
+ {
+ uk.ac.sanger.artemis.io.Feature f = it.next();
+ if(f.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL) ||
+ f.getKey().getKeyString().equals("pseudogenic_exon") )
+ {
+ GFFStreamFeature gff_exon = null;
+ RangeVector ranges = f.getLocation().getRanges();
+
+ for(int i=0; i<ranges.size(); i++)
+ gff_exon = GeneViewerPanel.addExonFeature(chadoGene, entry_group, gff_exon,
+ (Range)ranges.get(i), transcriptName, selection, f.getKey(), null);
+ }
+ else if(!f.getKey().equals("polypeptide"))
+ {
+ final String name;
+ if(f.getKey().getKeyString().equals("five_prime_UTR"))
+ name = "5UTR";
+ else if(f.getKey().getKeyString().equals("three_prime_UTR"))
+ name = "3UTR";
+ else
+ name = f.getKey().getKeyString();
+ try
+ {
+ GeneViewerPanel.addFeature(
+ f.getLocation().getTotalRange(), transcriptName, null,
+ transcript.getLocation().isComplement(),
+ true, chadoGene, entry_group, f.getKey(), name);
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ GeneViewerPanel.addProteinFeature(chadoGene, entry_group, transcriptName, transcript);
+ return transcriptName;
+ }
+
+ /**
+ * On Unix machines this method will append the text of the feature to a
+ * file in a current directory called .dribble + <the entry name>
+ **/
+ private void dribble()
+ {
+ if(!Options.isUnixHost())
+ return;
+
+ final String dribble_file_name;
+
+ if(getEntry().getName() != null)
+ dribble_file_name = ".dribble." + getEntry().getName();
+ else
+ dribble_file_name = ".dribble.no_name";
+
+ try
+ {
+ final Writer writer = new FileWriter(dribble_file_name, true);
+ getFeature().writeNative(writer);
+ writer.flush();
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ System.err.println("IO exception while accessing " + dribble_file_name +
+ ": " + e.getMessage());
+ }
+ }
+
+ private void updateKey()
+ {
+ keyChoice.setKey(getFeature().getKey());
+ }
+
+ private void updateLocation()
+ {
+ locationText.setText(getFeature().getLocation().toStringShort());
+ }
+
+ private boolean setActiveFeatureKeyAndLocation(Key key, Location location)
+ {
+ try
+ {
+ activeFeature.set(key, location, activeFeature.getQualifiers());
+ }
+ catch (ReadOnlyException e)
+ {
+ JOptionPane.showMessageDialog(this, "Key",
+ "Read Only", JOptionPane.WARNING_MESSAGE);
+ return false;
+ }
+ catch (EntryInformationException e)
+ {
+ final String error_string = e.getMessage ();
+ JOptionPane.showMessageDialog(this, "Key Error",
+ "Cannot apply changes because of key error: " +
+ error_string, JOptionPane.WARNING_MESSAGE);
+ return false;
+ }
+ catch (OutOfRangeException e)
+ {
+ final String error_string = e.getMessage ();
+ JOptionPane.showMessageDialog(this, "Location Error",
+ "Cannot apply changes because of location error: " +
+ error_string, JOptionPane.WARNING_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ protected boolean setQualifiers()
+ {
+ Feature gene = (Feature) chadoGene.getGene().getUserData();
+ Feature transcript = getSelectedTranscriptFeature();
+
+ QualifierVector geneProperties =
+ propertiesPanel.getGeneProperties(gene);
+
+ if(!setFeaturePropertiesByFeature(gene, geneProperties))
+ return false;
+
+ QualifierVector transcriptProperties =
+ propertiesPanel.getTranscriptProperties(transcript);
+
+ String transcriptName = GeneUtils.getUniqueName(transcript.getEmblFeature());
+ List<uk.ac.sanger.artemis.io.Feature> exons =
+ chadoGene.getSpliceSitesOfTranscript(transcriptName, DatabaseDocument.EXONMODEL);
+
+ if(exons != null)
+ {
+ Feature exon = (Feature) exons.get(0).getUserData();
+ QualifierVector exonProperties =
+ propertiesPanel.getExonProperties(exon);
+ if(!setFeaturePropertiesByFeature(exon, exonProperties))
+ return false;
+ }
+
+ uk.ac.sanger.artemis.io.Feature protein =
+ chadoGene.getProteinOfTranscript(transcriptName);
+
+ return (setFeaturePropertiesByFeature(transcript, transcriptProperties) &&
+ setProteinFeature(protein));
+ }
+
+ protected MatchPanel getMatchForm()
+ {
+ return matchForm;
+ }
+
+ protected JTextField getLocationText()
+ {
+ return locationText;
+ }
+
+ protected Selection getSelection()
+ {
+ return selection;
+ }
+
+ public void entryChanged(EntryChangeEvent event)
+ {
+
+ }
+
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ updateLocation();
+ repaint();
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/BasicGeneViewerPanel.java b/uk/ac/sanger/artemis/components/genebuilder/BasicGeneViewerPanel.java
new file mode 100644
index 0000000..c9bbac2
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/BasicGeneViewerPanel.java
@@ -0,0 +1,823 @@
+/* GeneViewerPanel
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/genebuilder/GeneViewerPanel.java,v 1.85 2009-06-18 14:59:05 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import javax.swing.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+import java.util.Vector;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.FeatureSegment;
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.LastSegmentException;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.FeatureVector;
+
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Feature;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Marker;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+public class BasicGeneViewerPanel extends MapPanel
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /** pop up menu */
+ private JPopupMenu popup;
+
+ private float fraction;
+
+ private int start;
+
+ private MarkerRange click_range = null;
+
+ /**
+ * The last(FeatureSegment) Marker that the user clicked on. This is used
+ * for dragging the ends of segments.
+ **/
+ private Marker click_segment_marker = null;
+
+ /**
+ * This is true if click_segment_marker is the Marker at the start of
+ * segment false otherwise. The value is only useful if
+ * click_segment_marker is set.
+ **/
+ private boolean click_segment_marker_is_start_marker = false;
+
+ /**
+ * When a FeatureSegment Marker drag starts, this is set to the Marker at
+ * the other end of the segment. This is used to check that the drag has
+ * not move the Marker too far(past the end of the segment).
+ **/
+ private Marker other_end_of_segment_marker = null;
+
+ /** This is a record of the feature last clicked */
+ private uk.ac.sanger.artemis.Feature clicked_feature;
+
+ private Point last_cursor_position;
+
+ private BasicGeneBuilderFrame gbFrame;
+
+ /**
+ * The shortcut for Delete Selected Features.
+ **/
+ final static KeyStroke DELETE_FEATURES_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+ final static public int DELETE_FEATURES_KEY_CODE = KeyEvent.VK_DELETE;
+
+ final static KeyStroke CREATE_FEATURES_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_C,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+
+ final static public int CREATE_FEATURES_KEY_CODE = KeyEvent.VK_C;
+
+ final private BasicGeneBuilderFrame gene_builder;
+
+ public BasicGeneViewerPanel(final BasicGeneBuilderFrame gene_builder,
+ final ChadoCanonicalGene chado_gene,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final BasicGeneBuilderFrame gbFrame,
+ final JLabel status_line)
+ {
+ this.chado_gene = chado_gene;
+ this.selection = selection;
+ this.gbFrame = gbFrame;
+ this.gene_builder = gene_builder;
+
+ Dimension dim = new Dimension(400,350);
+ setPreferredSize(dim);
+ setBackground(Color.white);
+
+// Popup menu
+ addMouseListener(new PopupListener());
+ popup = new JPopupMenu();
+ createMenus(popup, entry_group);
+
+ // Listen for mouse motion events so that we can select ranges of bases.
+ addMouseMotionListener(new MouseMotionAdapter()
+ {
+ public void mouseDragged(MouseEvent event)
+ {
+ if(event.isPopupTrigger() || entry_group == null ||
+ event.getButton() == MouseEvent.BUTTON3)
+ return;
+
+ int select_start = (int) ((event.getX() - border) / fraction) + start;
+
+ final Strand strand = ((uk.ac.sanger.artemis.Feature)
+ (chado_gene.getGene().getUserData())).getStrand();
+
+ if(!strand.isForwardStrand())
+ select_start = strand.getBases().getComplementPosition(select_start);
+
+ if(click_segment_marker != null)
+ {
+ final int new_position;
+ MarkerRange drag_range;
+ try
+ {
+ drag_range = new MarkerRange(strand, select_start, select_start + 1);
+ }
+ catch(OutOfRangeException e1)
+ {
+ e1.printStackTrace();
+ return;
+ }
+
+ if(click_segment_marker_is_start_marker)
+ {
+ // the Marker is at the start of the segment
+ new_position = drag_range.getStart().getPosition();
+
+ // don't go past the other end of the segment
+ if(new_position > other_end_of_segment_marker.getPosition())
+ return;
+ }
+ else
+ {
+ new_position = drag_range.getEnd().getPosition();
+
+ // don't go past the other end of the segment
+ if(new_position < other_end_of_segment_marker.getPosition())
+ return;
+ }
+
+ try
+ {
+ click_segment_marker.setPosition(new_position);
+ gene_builder.setActiveFeature(clicked_feature);
+ return;
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected OutOfRangeException");
+ }
+ }
+
+ final MarkerRange selected_range = selection.getMarkerRange();
+ try
+ {
+ MarkerRange drag_range = new MarkerRange(strand, select_start,
+ select_start + 1);
+
+ // final MarkerRange new_marker_range;
+ if(selected_range == null || click_range == null)
+ {
+ click_range = drag_range;
+ status_line.setText("");
+ }
+ else
+ {
+ click_range = selected_range.combineRanges(drag_range, true);
+ status_line.setText(selected_range.getRawRange().getStart() + ".."
+ + selected_range.getRawRange().getEnd());
+ }
+
+ last_cursor_position = event.getPoint();
+ selection.setMarkerRange(click_range);
+ repaint();
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Create menu for gene editor
+ *
+ * @param menu
+ * @param entry_group
+ */
+ protected void createMenus(JComponent menu,
+ final EntryGroup entry_group)
+ {
+ JMenuItem deleteMenu = new JMenuItem("Delete Selected Features");
+ deleteMenu.setAccelerator(DELETE_FEATURES_KEY);
+ deleteMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ FeatureVector features = selection.getAllFeatures();
+ boolean delete = GeneViewerPanel.deleteFeatures(features, chado_gene);
+
+ try
+ {
+ if(delete)
+ {
+ for(int i = 0; i < features.size(); i++)
+ GeneUtils.deleteAllFeature(features.elementAt(i), chado_gene);
+ }
+ else
+ {
+ gbFrame.setObsoleteChanged(true, features);
+ }
+ gbFrame.dispose(true);
+ }
+ catch(NullPointerException npe)
+ {
+ // can't reopen
+ gbFrame.dispose(false);
+ }
+ catch(ReadOnlyException e)
+ {
+ JOptionPane.showMessageDialog(null,
+ e.getMessage(), "Read Only",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ });
+ menu.add(deleteMenu);
+
+
+ final JMenuItem deleteSegmentMenu = new JMenuItem("Delete Selected Exon");
+ deleteSegmentMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ final FeatureSegmentVector features = selection.getAllSegments();
+
+ if(features == null || features.size() < 1)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select an exon and try again.",
+ "Exon to delete not found!",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ else
+ {
+ for(int i=0; i<features.size(); i++)
+ {
+ final uk.ac.sanger.artemis.Feature f = features.elementAt(i).getFeature();
+ if(!f.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL) &&
+ !f.getKey().getKeyString().equals("pseudogenic_exon") )
+ {
+ JOptionPane.showMessageDialog(null,
+ "Other feature types are selected.\nSelect an exon and try again.",
+ "Select exon to delete!",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ }
+ }
+
+ uk.ac.sanger.artemis.FeatureSegment segment = null;
+ int option = JOptionPane.showConfirmDialog(null,
+ "Delete Selected Exon",
+ "Delete Selected Exon",
+ JOptionPane.OK_CANCEL_OPTION);
+
+ if(option == JOptionPane.CANCEL_OPTION)
+ return;
+ try
+ {
+ for(int i = 0; i < features.size(); i++)
+ {
+ segment = features.elementAt(i);
+ segment.removeFromFeature();
+ selection.remove(segment);
+ }
+
+ //selection.add(feature);
+ //gene_builder.setActiveFeature(feature);
+ repaint();
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(LastSegmentException e)
+ {
+ try
+ {
+ GeneUtils.deleteAllFeature(segment.getFeature(), chado_gene);
+ }
+ catch(ReadOnlyException e1)
+ {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }
+ });
+ menu.add(deleteSegmentMenu);
+
+ menu.add(new JSeparator());
+
+ final JMenu createFeatureMenu = new JMenu("Add to transcript in selected range");
+
+ final JMenuItem createExon = new JMenuItem("exon");
+ createExon.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(last_cursor_position == null)
+ return;
+ Feature transcript = gbFrame.getSelectedTranscriptFeature().getEmblFeature();
+
+ if(transcript == null)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript to add an exon to and try again.",
+ "Transcript Not Found",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ String uniquename = GeneUtils.getUniqueName(transcript);
+
+ final List<Feature> exons;
+ final Key exonKey;
+ if(chado_gene.getGene().getKey().getKeyString().equals("pseudogene"))
+ {
+ exons = chado_gene.getSpliceSitesOfTranscript(uniquename, "pseudogenic_exon");
+ exonKey = new Key("pseudogenic_exon");
+ }
+ else
+ {
+ exons = chado_gene.getSpliceSitesOfTranscript(
+ uniquename, DatabaseDocument.EXONMODEL);
+ exonKey = new Key(DatabaseDocument.EXONMODEL);
+ }
+
+ GFFStreamFeature embl_exon = null;
+ if(exons != null && exons.size() > 0)
+ embl_exon = (GFFStreamFeature)exons.get(0);
+
+ Range range_selected = selection.getSelectionRange();
+ GeneViewerPanel.addExonFeature(chado_gene, entry_group, embl_exon,
+ range_selected, uniquename, selection, exonKey, null);
+ repaint();
+ }
+ });
+ createFeatureMenu.add(createExon);
+
+
+
+ JMenuItem createFeature5Utr = new JMenuItem("5'UTR");
+ createFeature5Utr.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ // TODO
+ //addDnaFeature(last_cursor_position, selection,
+ // entry_group, new Key("five_prime_UTR"));
+ }
+ });
+
+
+ JMenuItem createFeature3Utr = new JMenuItem("3'UTR");
+ createFeature3Utr.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ // TODO
+ // addDnaFeature(last_cursor_position, selection,
+ // entry_group, new Key("three_prime_UTR"));
+ }
+ });
+
+
+ JMenuItem createFeatureDna = new JMenuItem("region");
+ createFeatureDna.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ // TODO
+ //addDnaFeature(last_cursor_position, selection,
+ // entry_group, new Key("region"));
+ }
+ });
+
+
+
+ JMenuItem createFeatureProtein = new JMenuItem("polypeptide");
+ createFeatureProtein.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(selection.getAllFeatures().size() < 1)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript and try again.",
+ "Transcript Selection",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ uk.ac.sanger.artemis.Feature transcript = selection.getAllFeatures().elementAt(0);
+ final String transcriptName;
+ try
+ {
+ transcriptName = (String)transcript.getQualifierByName("ID").getValues().get(0);
+ }
+ catch(InvalidRelationException e1)
+ {
+ e1.printStackTrace();
+ return;
+ }
+
+ if(!chado_gene.isTranscript(transcriptName))
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript and try again.",
+ "Transcript Selection",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ // TODO
+ //addProteinFeature(chado_gene, entry_group, transcriptName, transcript);
+ }
+ });
+ createFeatureMenu.add(createFeatureProtein);
+
+ if(!DatabaseDocument.CHADO_INFER_CDS)
+ {
+ createFeatureMenu.add(createFeature5Utr);
+ createFeatureMenu.add(createFeature3Utr);
+ }
+ createFeatureMenu.add(createFeatureDna);
+
+ menu.add(createFeatureMenu);
+
+ menu.add(new JSeparator());
+ JMenuItem adjustCoords = new JMenuItem("Fix selected transcript coordinates");
+ adjustCoords.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ FeatureVector features = selection.getAllFeatures();
+ if(features.size() != 1)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript and try again.",
+ "Transcript Selection",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ final uk.ac.sanger.artemis.Feature transcript = features.elementAt(0);
+ GeneUtils.checkTranscriptBoundary(transcript, chado_gene);
+ // TODO gene_builder.dispose(true);
+ }
+ });
+ menu.add(adjustCoords);
+
+
+ JMenuItem adjustGeneCoords = new JMenuItem("Fix gene model coordinates");
+ adjustGeneCoords.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ GeneUtils.checkGeneBoundary(chado_gene);
+
+ // TODO
+ // gene_builder.dispose(true);
+ }
+ });
+ menu.add(adjustGeneCoords);
+
+ menu.add(new JSeparator());
+
+ final JMenuItem convertPsuedoMenu = new JMenuItem("Convert gene to/from pseudogene");
+ convertPsuedoMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ GeneUtils.convertPseudo(chado_gene);
+ gene_builder.dispose(true);
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ });
+ menu.add(convertPsuedoMenu);
+ }
+
+ /**
+ *
+ */
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2d = (Graphics2D)g;
+ Feature embl_gene = (Feature)chado_gene.getGene();
+ uk.ac.sanger.artemis.Feature gene =
+ (uk.ac.sanger.artemis.Feature)embl_gene.getUserData();
+
+ setFont(uk.ac.sanger.artemis.Options.getOptions().getFont());
+
+ start = embl_gene.getFirstBase();
+ int end = embl_gene.getLastBase();
+ g2d.setColor( gene.getColour() );
+ int ypos = border;
+
+ // draw gene
+ g2d.drawString(gene.getIDString()+
+ ( GeneUtils.isObsolete((GFFStreamFeature)embl_gene) ? " (obsolete)" : "") , border, ypos);
+ GeneViewerPanel.drawFeature(g2d, border,
+ getSize().width - border,
+ ypos, gene.getColour(), 1,
+ selection.contains(gene), 2.f, getFontHeight());
+
+ List<Feature> transcripts = chado_gene.getTranscripts();
+ fraction = (float)(getSize().width - (2*border))/
+ (float)(end-start);
+
+ ypos += border*2;
+
+ try
+ {
+ Feature transcript = gbFrame.getSelectedTranscriptFeature().getEmblFeature();
+ GeneViewerPanel.drawTranscriptOnLine(g2d, transcript,
+ start, end, ypos,
+ fraction, selection, chado_gene,
+ getFontHeight());
+ }
+ catch(NullPointerException npe)
+ {
+ return;
+ }
+
+ ypos += getTranscriptSize();
+
+ setPreferredSize(new Dimension(getSize().width, ypos+border));
+ // draw mouse drag selection
+ if(selection.getMarkerRange() != null &&
+ last_cursor_position != null)
+ {
+ Range range = selection.getSelectionRange();
+
+ int ntranscript = (last_cursor_position.y - (border*3))/getTranscriptSize();
+ if(ntranscript < transcripts.size())
+ {
+ int select_start = border+(int)((range.getStart()-start)*fraction);
+ int select_end = border+(int)((range.getEnd()-start)*fraction);
+ ypos = (border*5)+(getTranscriptSize()*ntranscript);
+ drawFeature(g2d, select_start, select_end,
+ ypos, Color.YELLOW, 1.5f,
+ false, 2.f, getFontHeight());
+ }
+ }
+ }
+
+
+ /**
+ * Given a point on the panel find the feature drawn at that
+ * location.
+ * @param p
+ * @return
+ */
+ private Object getFeatureAt(Point p)
+ {
+ if (p.y <= border + getFontHeight())
+ return chado_gene.getGene();
+
+ Feature transcript = gbFrame.getSelectedTranscriptFeature().getEmblFeature();
+
+ if (p.y >= (border * 3) && p.y <= (border * 3) + getFontHeight())
+ return transcript;
+ else if (p.y >= (border * 3) + getFontHeight()
+ && p.y <= (border * 3) + (getTranscriptSize()))
+ {
+ String transcript_name = GeneUtils.getUniqueName(transcript);
+ List<Feature> splicedFeatures = chado_gene
+ .getSplicedFeaturesOfTranscript(transcript_name);
+
+ if (splicedFeatures != null)
+ {
+ for (int j = 0; j < splicedFeatures.size(); j++)
+ {
+ FeatureSegmentVector segments = ((uk.ac.sanger.artemis.Feature) (splicedFeatures
+ .get(j)).getUserData()).getSegments();
+
+ if (segments.size() == 0)
+ return (Feature) splicedFeatures.get(j);
+
+ for (int k = 0; k < segments.size(); k++)
+ {
+ FeatureSegment segment = segments.elementAt(k);
+ Range segment_range = segment.getRawRange();
+
+ int segment_start = border
+ + (int) ((segment_range.getStart() - start) * fraction);
+ int segment_end = border
+ + (int) ((segment_range.getEnd() - start) * fraction);
+ if (p.x >= segment_start && p.x <= segment_end)
+ return segment;
+ }
+ }
+ }
+
+ final List<Feature> utr_3 = chado_gene
+ .get3UtrOfTranscript(transcript_name);
+ final List<Feature> utr_5 = chado_gene
+ .get5UtrOfTranscript(transcript_name);
+ final List<Feature> utrs = new Vector<Feature>();
+ if (utr_3 != null)
+ utrs.addAll(utr_3);
+ if (utr_5 != null)
+ utrs.addAll(utr_5);
+
+ for (int j = 0; j < utrs.size(); j++)
+ {
+ Feature utr = (Feature) utrs.get(j);
+ Range range = utr.getLocation().getTotalRange();
+ int utr_start = border + (int) ((range.getStart() - start) * fraction);
+ int utr_end = border + (int) ((range.getEnd() - start) * fraction);
+ if (p.x >= utr_start && p.x <= utr_end)
+ return utr;
+ }
+
+ // anything else
+ List<Feature> others = chado_gene
+ .getOtherFeaturesOfTranscript(transcript_name);
+ if (others != null)
+ {
+ for (int j = 0; j < others.size(); j++)
+ {
+ Feature other = others.get(j);
+ Range range = other.getLocation().getTotalRange();
+ int r_start = border + (int) ((range.getStart() - start) * fraction);
+ int r_end = border + (int) ((range.getEnd() - start) * fraction);
+ if (p.x >= r_start && p.x <= r_end)
+ return other;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Popup listener
+ */
+ class PopupListener extends MouseAdapter
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ click_segment_marker = null;
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ maybeShowPopup(e);
+ click_segment_marker = null;
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ {
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ else if(e.getButton() != MouseEvent.BUTTON3 &&
+ e.getClickCount() == 1)
+ {
+ if(selection.getMarkerRange() != null &&
+ e.isShiftDown())
+ return;
+
+ click_range = null;
+
+ if(!e.isShiftDown())
+ selection.clear();
+ Object feature = getFeatureAt(e.getPoint());
+ if(feature == null)
+ return;
+
+ if(e.isShiftDown())
+ {
+ if(feature instanceof Feature)
+ selection.add(
+ (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData());
+ else
+ selection.add((FeatureSegment)feature);
+ }
+ else
+ {
+ if(feature instanceof Feature)
+ selection.set(
+ (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData());
+ else
+ selection.set((FeatureSegment)feature);
+ }
+
+
+ // the following is used by mouseDragged() to edit feature locations
+ if(feature instanceof Feature)
+ clicked_feature = (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData();
+ else
+ clicked_feature = ((FeatureSegment)feature).getFeature();
+ click_segment_marker = null;
+
+ if(Options.getOptions().canDirectEdit() &&
+ !clicked_feature.isReadOnly() &&
+ clicked_feature.getEntry().getBases() != null)
+ {
+ // search the feature to find if the start Marker of any of the
+ // segments matches the start Marker of new_click_range or if an end
+ // Marker matches the end Marker of new_click_range
+
+ final FeatureSegmentVector segments = clicked_feature.getSegments();
+
+ for(int k = 0; k < segments.size(); k++)
+ {
+ FeatureSegment this_segment = segments.elementAt(k);
+ Range segment_range = this_segment.getRawRange();
+
+ int segment_start = border
+ + (int) ((segment_range.getStart() - start) * fraction);
+ int segment_end = border
+ + (int) ((segment_range.getEnd() - start) * fraction);
+
+ // allow very ends of features to be dragable
+ if( (e.getPoint().x >= segment_start && e.getPoint().x < segment_start+3 && e.getPoint().x <= segment_end) ||
+ (e.getPoint().x <= segment_end && e.getPoint().x > segment_end-3 && e.getPoint().x >= segment_start) )
+ {
+ int segment_mid = (segment_start+segment_end)/2;
+
+ if((e.getPoint().x < segment_mid && this_segment.isForwardSegment()) ||
+ (e.getPoint().x > segment_mid && !this_segment.isForwardSegment()))
+ {
+ click_segment_marker = this_segment.getStart();
+ click_segment_marker_is_start_marker = true;
+ other_end_of_segment_marker = this_segment.getEnd();
+ }
+ else
+ {
+ click_segment_marker = this_segment.getEnd();
+ click_segment_marker_is_start_marker = false;
+ other_end_of_segment_marker = this_segment.getStart();
+ }
+ }
+ }
+
+ }
+
+ gene_builder.setActiveFeature(clicked_feature);
+ repaint();
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/BasicProteinMapPanel.java b/uk/ac/sanger/artemis/components/genebuilder/BasicProteinMapPanel.java
new file mode 100644
index 0000000..3442b8f
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/BasicProteinMapPanel.java
@@ -0,0 +1,148 @@
+/* ProteinMapPanel.java
+ *
+ * created: 2008
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.util.List;
+
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.io.Feature;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+
+public class BasicProteinMapPanel extends ProteinMapPanel
+{
+ private static final long serialVersionUID = 1L;
+ private BasicGeneBuilderFrame gbFrame;
+
+ public BasicProteinMapPanel(final Feature feature,
+ final ChadoCanonicalGene chado_gene,
+ final Selection selection,
+ final BasicGeneBuilderFrame gbFrame)
+ {
+ super(feature, chado_gene, selection);
+ this.gbFrame = gbFrame;
+ setFont(uk.ac.sanger.artemis.Options.getOptions().getFont());
+ setPreferredSize(new Dimension(getSize().width, 100));
+ }
+
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2d = (Graphics2D)g;
+ toolTipPositions.clear();
+
+ Feature embl_gene = (Feature)chado_gene.getGene();
+ uk.ac.sanger.artemis.Feature gene =
+ (uk.ac.sanger.artemis.Feature)embl_gene.getUserData();
+
+ int ypos = border;
+ int geneStart = embl_gene.getFirstBase();
+ int geneEnd = embl_gene.getLastBase();
+ float fraction = (float)(getSize().width - (2*border))/
+ (float)(geneEnd-geneStart);
+
+ uk.ac.sanger.artemis.Feature transcript = gbFrame.getSelectedTranscriptFeature();
+ if(transcript == null)
+ return;
+ String transcriptName = GeneUtils.getUniqueName(
+ transcript.getEmblFeature());
+ Feature protein_embl_feature =
+ (Feature)chado_gene.getProteinOfTranscript(transcriptName);
+
+ if(protein_embl_feature == null)
+ return;
+
+ uk.ac.sanger.artemis.Feature protein =
+ (uk.ac.sanger.artemis.Feature)protein_embl_feature.getUserData();
+
+ List<Feature> exons = chado_gene.getSpliceSitesOfTranscript(transcriptName,
+ DatabaseDocument.EXONMODEL);
+ if(exons == null || exons.size() == 0)
+ exons = chado_gene.getSpliceSitesOfTranscript(transcriptName,
+ "pseudogenic_exon");
+
+ if(exons == null || exons.size() == 0)
+ return;
+
+ Feature exon_embl_feature = exons.get(0);
+ uk.ac.sanger.artemis.Feature exon =
+ (uk.ac.sanger.artemis.Feature)exon_embl_feature.getUserData();
+
+ int ppLength = exon.getTranslationBasesLength()/3;
+ int emblStart = protein_embl_feature.getFirstBase();
+ int emblEnd = protein_embl_feature.getLastBase();
+
+ // draw protein
+ g2d.drawString(protein.getIDString()+
+ ( GeneUtils.isObsolete((GFFStreamFeature)embl_gene) ? " (obsolete)" : "") , border, ypos);
+
+ int ppStart = border+(int)((emblStart-geneStart)*fraction);
+ int ppEnd = border+(int)((emblEnd-geneStart)*fraction);
+
+ drawFeature(g2d, ppStart, ppEnd,
+ ypos, protein.getColour(), 1,
+ selection.contains(gene), 2.f, getFontHeight());
+
+ //record position for tooltip
+ Rectangle r = new Rectangle(ppStart, ypos, ppEnd-ppStart, 1*getFontHeight());
+ toolTipPositions.put(r, protein.getIDString()+" length="+ppLength);
+
+ ypos += border*2;
+ ypos = drawDomain(protein_embl_feature,
+ g2d, ypos, ppStart, ppEnd, ppLength);
+
+ QualifierVector qualifiers = protein_embl_feature.getQualifiers();
+ if(qualifiers.getQualifierByName(TMHMM[0]) != null)
+ {
+ g2d.drawString("Transmembrane Domains:", ppStart, ypos);
+ drawPrediction(protein_embl_feature,
+ g2d, ypos, ppStart, ppEnd, ppLength, TMHMM);
+ ypos += border * 2;
+ }
+
+ if(qualifiers.getQualifierByName(SIGNALP[0]) != null)
+ {
+ g2d.drawString("SignalP:", ppStart, ypos);
+ drawPrediction(protein_embl_feature,
+ g2d, ypos, ppStart, ppEnd, ppLength,
+ new String[]{ SIGNALP[0] });
+ ypos += border * 2;
+ }
+
+ Qualifier gpiAnchor;
+ if((gpiAnchor=qualifiers.getQualifierByName(GPI_ANCHORED)) != null)
+ {
+ g2d.drawString("GPI anchor cleavage site:", ppStart, ypos);
+ drawGPIArrow(g2d, gpiAnchor, ppStart, ppEnd, ppLength, ypos);
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/ButtonPanel.java b/uk/ac/sanger/artemis/components/genebuilder/ButtonPanel.java
new file mode 100644
index 0000000..528fc42
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/ButtonPanel.java
@@ -0,0 +1,445 @@
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JToolBar;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.components.TransferAnnotationTool;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.LocationParseException;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+
+public class ButtonPanel extends JToolBar
+{
+ private static final long serialVersionUID = 1L;
+ private BasicGeneBuilderFrame gbFrame;
+
+ public ButtonPanel(final BasicGeneBuilderFrame gbFrame,
+ final EntryGroup entry_group)
+ {
+ super();
+
+ this.gbFrame = gbFrame;
+
+ final JButton complement_button = new JButton("Complement");
+ add(complement_button);
+ complement_button.addActionListener(new ActionListener ()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ complementLocation();
+ }
+ });
+
+ if(GeneUtils.isDatabaseEntry(getFeature().getEmblFeature()))
+ {
+ final JButton refresh_button = new JButton("Refresh");
+ add(refresh_button);
+ refresh_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ refresh();
+ }
+ });
+ }
+
+ final JButton grab_button = new JButton("Grab Range");
+ add(grab_button);
+ grab_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ grabSelectedRange();
+ }
+ });
+
+ final JButton remove_button = new JButton("Remove Range");
+ add(remove_button);
+ remove_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ removeSelectedRange();
+ }
+ });
+
+ final JButton select_button = new JButton("Select Feature");
+ add(select_button);
+ select_button.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ gbFrame.getSelection().set(getFeature());
+ }
+ });
+
+ final JButton transferAnnotationBbutton = new JButton("TAT");
+ add(transferAnnotationBbutton);
+ transferAnnotationBbutton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ new TransferAnnotationTool(getFeature(), entry_group, gbFrame.getMatchForm());
+ }
+ });
+
+ }
+
+ private Feature getFeature()
+ {
+ return gbFrame.getFeature();
+ }
+
+ /**
+ * Complement the current location_text.
+ **/
+ private void complementLocation()
+ {
+ if(GeneUtils.isDatabaseEntry(getFeature().getEmblFeature()))
+ {
+ final ChadoCanonicalGene chadoGene =
+ ((GFFStreamFeature)getFeature().getEmblFeature()).getChadoGene();
+ GeneUtils.complementGeneModel(chadoGene);
+ gbFrame.dispose(true);
+ return;
+ }
+
+ if(rationalizeLocation())
+ {
+ if(gbFrame.getLocationText().getText().startsWith("complement("))
+ {
+ final String new_text = gbFrame.getLocationText().getText().substring(11);
+ if (new_text.endsWith(")"))
+ {
+ final String new_text2 =
+ new_text.substring(0, new_text.length () - 1);
+ gbFrame.getLocationText().setText(new_text2);
+ }
+ else
+ gbFrame.getLocationText().setText(new_text);
+ }
+ else
+ {
+ final String new_text = gbFrame.getLocationText().getText ();
+ gbFrame.getLocationText().setText("complement(" + new_text + ")");
+ }
+ }
+ else
+ new MessageDialog(null, "complement failed - " +
+ "current location cannot be parsed");
+ }
+
+ /**
+ * Attempt to parse the current location_text as a Location. If it can be
+ * parsed it will be canonicalized (ie. the complement, if any, will be
+ * outermost). Returns true if and only if the location_text could be
+ * parsed.
+ **/
+ private boolean rationalizeLocation()
+ {
+ try
+ {
+ final Location location = new Location(gbFrame.getLocationText().getText());
+ gbFrame.getLocationText().setText(location.toStringShort());
+ return true;
+ }
+ catch(LocationParseException e)
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Refresh the annotation for the active feature
+ */
+ private void refresh()
+ {
+ Feature edit_feature = gbFrame.getFeature();
+ // refresh from the database
+ final DatabaseDocument originalDocument =
+ (DatabaseDocument)((DocumentEntry)edit_feature.getEmblFeature().getEntry()).getDocument();
+
+ final Set uniquenames =
+ ((GFFStreamFeature)edit_feature.getEmblFeature()).getSegmentRangeStore().keySet();
+ final Iterator it = uniquenames.iterator();
+ final String uniquename = (String)it.next();
+ final DatabaseDocument newDocument = new DatabaseDocument(originalDocument,
+ uniquename, null, true, null);
+ newDocument.setLazyFeatureLoad(false);
+ newDocument.setReadChildren(false);
+
+ try
+ {
+ DatabaseDocumentEntry dbentry = new DatabaseDocumentEntry(newDocument, null);
+ uk.ac.sanger.artemis.io.Feature databaseFeature = dbentry.getAllFeatures().featureAt(0);
+
+ // compare timelastmodified
+ Qualifier qualifier = edit_feature.getQualifierByName("timelastmodified");
+ String active_timelastmodified = (String)qualifier.getValues().get(0);
+ qualifier = databaseFeature.getQualifierByName("timelastmodified");
+ String database_timelastmodified = (String)qualifier.getValues().get(0);
+
+ if(active_timelastmodified.equals(database_timelastmodified))
+ {
+ JOptionPane.showMessageDialog(this,
+ "No new changes found for the feature\n"+
+ uniquename+"\n"+
+ "in the database since:\n"+database_timelastmodified,
+ "No Updates", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ else
+ {
+ JOptionPane.showMessageDialog(this,
+ "Changes found for the feature\n"+
+ uniquename+"\n"+
+ "in the database at:\n"+database_timelastmodified,
+ "Changes Found", JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ final QualifierVector db_qv = databaseFeature.getQualifiers();
+ final QualifierVector qv = edit_feature.getQualifiers();
+ final QualifierVector new_qv = new QualifierVector();
+
+ for(int i=0; i<qv.size(); i++)
+ {
+ Qualifier q = (Qualifier)qv.get(i);
+ if(q.getName().equals("Parent") ||
+ q.getName().equals("Derives_from") ||
+ q.getName().equals("ID") )
+ new_qv.addQualifierValues(q);
+ }
+
+
+ for(int i=0; i<db_qv.size(); i++)
+ {
+ Qualifier q = (Qualifier)db_qv.get(i);
+ if(q.getName().equals("Parent") ||
+ q.getName().equals("Derives_from") ||
+ q.getName().equals("ID") )
+ continue;
+ new_qv.add(q);
+ }
+
+ edit_feature.getQualifiers().removeAllElements();
+ edit_feature.getQualifiers().addAll(new_qv);
+ gbFrame.setQualifiers();
+ }
+ catch(EntryInformationException e) {}
+ catch(IOException e) {}
+ }
+
+
+ /**
+ * Add the currently selected range to location_text.
+ **/
+ private void grabSelectedRange()
+ {
+ if(!rationalizeLocation ())
+ {
+ new MessageDialog(null,
+ "grab failed - current location cannot be parsed");
+ return;
+ }
+
+ final Range selected_range = gbFrame.getSelection().getSelectionRange();
+
+ if(selected_range == null)
+ {
+ new MessageDialog(null, "grab failed - nothing is selected");
+ return;
+ }
+
+ // save it in case it gets mangled
+ final String old_location_text = gbFrame.getLocationText().getText();
+
+ if (old_location_text.endsWith ("))"))
+ {
+ final String new_text =
+ old_location_text.substring(0, old_location_text.length () - 2);
+
+ gbFrame.getLocationText().setText(new_text + "," + selected_range.getStart () +
+ ".." + selected_range.getEnd () + "))");
+ }
+ else
+ {
+ if(old_location_text.endsWith (")"))
+ {
+ final String new_text =
+ old_location_text.substring(0, old_location_text.length () - 1);
+
+ gbFrame.getLocationText().setText(new_text + "," + selected_range.getStart () +
+ ".." + selected_range.getEnd () + ")");
+ }
+ else
+ gbFrame.getLocationText().setText(old_location_text + "," +
+ selected_range.getStart () +
+ ".." + selected_range.getEnd ());
+ }
+
+ if(!rationalizeLocation())
+ {
+ gbFrame.getLocationText().setText (old_location_text);
+ new MessageDialog(null, "grab failed - location cannot be parsed after " +
+ "grabbing");
+ }
+ }
+
+ /**
+ * Remove the currently selected range of bases from location_text.
+ **/
+ private void removeSelectedRange()
+ {
+ if(!rationalizeLocation())
+ {
+ new MessageDialog(null,
+ "grab failed - current location cannot be parsed");
+ return;
+ }
+
+ final MarkerRange selected_marker_range =
+ gbFrame.getSelection().getMarkerRange();
+
+ if(selected_marker_range == null)
+ {
+ new MessageDialog(null, "remove range failed - no bases are selected");
+ return;
+ }
+
+ final Range selected_range = selected_marker_range.getRawRange();
+
+ if (selected_marker_range.getStrand() != getFeature().getStrand())
+ {
+ new MessageDialog(null, "remove range failed - you need to select " +
+ "some bases on the other strand");
+ return;
+ }
+
+ final Location location;
+
+ try
+ {
+ location = new Location(gbFrame.getLocationText().getText ());
+ }
+ catch (LocationParseException e)
+ {
+ // this shouldn't happen because we called rationalizeLocation ()
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final Range location_total_range = location.getTotalRange();
+
+ if(!selected_range.overlaps(location_total_range))
+ {
+ new MessageDialog(null, "remove range failed - the range you " +
+ "selected does not overlap the feature");
+ return;
+ }
+
+ if(selected_range.contains(location_total_range))
+ {
+ new MessageDialog(null, "remove range failed - the range you " +
+ "selected overlaps the whole feature");
+ return;
+ }
+
+ final RangeVector location_ranges = location.getRanges();
+ final boolean location_is_complemented = location.isComplement();
+
+ final RangeVector new_ranges = new RangeVector();
+
+ // if the selected_range completely covers a range remove the
+ // range. otherwise if the selected_range is completely within one of the
+ // ranges two new ranges are created. if the selected_range is not
+ // completely contained then the appropriate end of the range is truncated
+ for(int i = 0; i < location_ranges.size(); ++i)
+ {
+ final Range this_range = (Range)location_ranges.elementAt(i);
+
+ if(selected_range.overlaps(this_range))
+ {
+ try
+ {
+ if(this_range.contains(selected_range) &&
+ this_range.getStart() != selected_range.getStart() &&
+ this_range.getEnd() != selected_range.getEnd())
+ {
+ // chop a piece out of the middle and make two new ranges
+ final Range new_start_range =
+ this_range.change(selected_range.getEnd() + 1,
+ this_range.getEnd());
+ new_ranges.add(new_start_range);
+ final Range new_end_range =
+ this_range.change(this_range.getStart(),
+ selected_range.getStart() - 1);
+ new_ranges.add(new_end_range);
+ }
+ else
+ {
+ if(selected_range.contains(this_range)) {
+ // delete (ie. don't copy) the range
+ }
+ else
+ {
+ if(this_range.getStart() < selected_range.getStart())
+ {
+ // truncate the end of the range
+ final Range new_start_range =
+ this_range.change(this_range.getStart(),
+ selected_range.getStart() - 1);
+ new_ranges.add(new_start_range);
+ }
+ else
+ {
+ if(this_range.getEnd() > selected_range.getEnd())
+ {
+ // truncate the start of the range
+ final Range new_end_range =
+ this_range.change(selected_range.getEnd() + 1,
+ this_range.getEnd());
+ new_ranges.add(new_end_range);
+ }
+ else
+ throw new Error ("internal error - can't remove range");
+ }
+ }
+ }
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ else
+ new_ranges.add(this_range); // copy it unchanged
+ }
+
+ final Location new_location =
+ new Location(new_ranges, location_is_complemented);
+
+ gbFrame.getLocationText().setText(new_location.toStringShort());
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/GeneBuilderFrame.java b/uk/ac/sanger/artemis/components/genebuilder/GeneBuilderFrame.java
new file mode 100644
index 0000000..648ab84
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/GeneBuilderFrame.java
@@ -0,0 +1,727 @@
+/* GeneBuilderFrame.java
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/genebuilder/GeneBuilderFrame.java,v 1.47 2009-09-24 15:01:27 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.border.Border;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.ItemListener;
+import java.util.List;
+import java.util.Hashtable;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryChangeEvent;
+import uk.ac.sanger.artemis.EntryChangeListener;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.SelectionChangeEvent;
+import uk.ac.sanger.artemis.SelectionChangeListener;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.GotoEventSource;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.FeatureEdit;
+import uk.ac.sanger.artemis.components.Utilities;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.FeatureVector;
+
+public class GeneBuilderFrame extends JFrame
+ implements EntryChangeListener, FeatureChangeListener
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+ private Feature active_feature;
+ private FeatureEdit feature_editor;
+ private GeneViewerPanel viewer;
+ private GeneComponentTree tree;
+ private Box yBox;
+ private Hashtable transcriptBoxes = new Hashtable();
+ private Component glue = Box.createVerticalGlue();
+ private Selection selection;
+ private ChadoCanonicalGene chado_gene;
+ private JLabel status_line = new JLabel("");
+ private GeneBuilderSelectionChangeListener geneBuilderSelectionChangeListener;
+ private JTabbedPane tabpane;
+ private ChadoTransactionManager chadoTransactionManager;
+ private EntryGroup entry_group;
+ private GotoEventSource goto_event_source;
+ private Hashtable geneBuilderHash;
+
+ public GeneBuilderFrame(Feature feature,
+ final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source)
+ {
+ this(feature, entry_group, selection, goto_event_source, null);
+ }
+
+ public GeneBuilderFrame(Feature feature,
+ final EntryGroup entry_group,
+ final Selection selection,
+ final GotoEventSource goto_event_source,
+ final ChadoTransactionManager chadoTransactionManager)
+ {
+ super();
+
+ this.entry_group = entry_group;
+ this.goto_event_source = goto_event_source;
+
+ // set title
+ final String title = "Artemis Gene Builder: " +
+ ((Feature)((GFFStreamFeature)feature.getEmblFeature()).getChadoGene().getGene().getUserData()).getIDString() +
+ (feature.isReadOnly() ?
+ " - (read only)" : "");
+
+ setTitle(title);
+
+ this.selection = selection;
+ if(feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL) ||
+ (DatabaseDocument.CHADO_INFER_CDS && feature.getKey().getKeyString().equals("CDS")))
+ {
+ Feature proteinFeature = getProteinFeature(feature, selection);
+ if(proteinFeature != null)
+ feature = proteinFeature;
+ }
+
+ this.active_feature = feature;
+ this.chadoTransactionManager = chadoTransactionManager;
+
+ if(selection != null)
+ {
+ geneBuilderSelectionChangeListener = new GeneBuilderSelectionChangeListener();
+ selection.addSelectionChangeListener(geneBuilderSelectionChangeListener);
+ }
+
+ final GFFStreamFeature gff_feature = (GFFStreamFeature)feature.getEmblFeature();
+ chado_gene = gff_feature.getChadoGene();
+
+ try
+ {
+ addListeners(chado_gene);
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+
+ tree = new GeneComponentTree(chado_gene, this, selection);
+ final JScrollPane jsp_tree = new JScrollPane(tree);
+ jsp_tree.setPreferredSize( new Dimension(150, jsp_tree.getPreferredSize().height) );
+
+ viewer = new GeneViewerPanel(
+ gff_feature.getChadoGene(), selection,
+ entry_group, this, status_line);
+
+ Box xBox = Box.createHorizontalBox();
+ xBox.add(buildCheckBoxes(viewer, chado_gene));
+ xBox.add(viewer);
+
+ final JTabbedPane tabMapViewer = new JTabbedPane();
+
+ final JScrollPane jsp_viewer = new JScrollPane(xBox);
+ jsp_viewer.getViewport().setBackground(Color.white);
+ jsp_viewer.setPreferredSize( viewer.getPreferredSize() );
+ tabMapViewer.addTab("Gene Map", jsp_viewer);
+
+ //
+ // protein map
+ List proteins = ProteinMapPanel.getProteinsWithProteinMapElement(gff_feature);
+ if(proteins != null)
+ {
+ final ProteinMapPanel proteinMap =
+ new ProteinMapPanel((GFFStreamFeature) proteins.get(0),
+ gff_feature.getChadoGene(), selection);
+
+ final JScrollPane jsp_ppviewer = new JScrollPane(proteinMap);
+ jsp_ppviewer.getViewport().setBackground(Color.white);
+ jsp_ppviewer.setPreferredSize( proteinMap.getPreferredSize() );
+ tabMapViewer.addTab("Protein Map", jsp_ppviewer);
+ }
+
+ ///
+ status_line.setFont(Options.getOptions().getFont());
+ final FontMetrics fm =
+ this.getFontMetrics(status_line.getFont());
+
+ final int font_height = fm.getHeight()+10;
+
+ status_line.setMinimumSize(new Dimension(100, font_height));
+ status_line.setPreferredSize(new Dimension(100, font_height));
+
+ Border loweredbevel = BorderFactory.createLoweredBevelBorder();
+ Border raisedbevel = BorderFactory.createRaisedBevelBorder();
+ Border compound = BorderFactory.createCompoundBorder(raisedbevel,loweredbevel);
+ status_line.setBorder(compound);
+ jsp_viewer.setColumnHeaderView(status_line);
+ ///
+
+ JSplitPane top = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, jsp_tree, tabMapViewer);
+ JSplitPane all = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+ all.setTopComponent(top);
+
+ feature_editor = new FeatureEdit(feature, entry_group,
+ selection, goto_event_source, this);
+
+ feature_editor.getQualifierTextArea().getDocument().addDocumentListener(
+ new TextAreaDocumentListener(feature_editor.getQualifierTextArea()));
+
+ tabpane = new JTabbedPane();
+ tabpane.addTab("Annotation", feature_editor);
+ setTabTitle();
+ all.setBottomComponent(tabpane);
+
+ getContentPane().add(all);
+
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent event)
+ {
+ dispose();
+ }
+ });
+
+ // add menus
+ JMenuBar menuBar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+ menuBar.add(fileMenu);
+ JMenuItem close = new JMenuItem("Close");
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ dispose();
+ }
+ });
+ fileMenu.add(close);
+
+ JMenu editMenu = new JMenu("Edit");
+ menuBar.add(editMenu);
+ viewer.createMenus(editMenu, entry_group);
+ setJMenuBar(menuBar);
+
+ pack();
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+
+ int height = getPreferredSize().height;
+ if(height > (screen.height*0.9))
+ {
+ setSize(getPreferredSize().width, (int)(screen.height*0.9));
+ }
+
+ Utilities.centreFrame(this);
+ setVisible(true);
+ all.setDividerLocation(0.30);
+ }
+
+
+ private Feature getProteinFeature(final Feature feature, final Selection selection)
+ {
+ Feature proteinFeature = null;
+ try
+ {
+ Qualifier idQualifier = feature.getQualifierByName("ID");
+ if(idQualifier != null)
+ {
+ final ChadoCanonicalGene chadoGene = ((GFFStreamFeature)feature.getEmblFeature()).getChadoGene();
+ final String ID = (String)idQualifier.getValues().get(0);
+ final String transcriptName = chadoGene.getTranscriptFromName(ID);
+ proteinFeature = (Feature)chadoGene.getProteinOfTranscript(transcriptName).getUserData();
+ selection.clear();
+ selection.add(proteinFeature);
+ }
+ }
+ catch(Exception e){}
+ return proteinFeature;
+ }
+
+ public void dispose()
+ {
+ dispose(false);
+ }
+
+ /**
+ * Override to ensure the GeneBuilderFrame removes all listeners
+ */
+ public void dispose(final boolean reopen)
+ {
+ try
+ {
+ stopListeningAll();
+ }
+ catch(InvalidRelationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ if(chadoTransactionManager != null &&
+ chadoTransactionManager.hasTransactions())
+ {
+ int select = JOptionPane.showConfirmDialog(this,
+ "Commit changes back to the database?",
+ "Commit", JOptionPane.YES_NO_OPTION);
+
+ if(select == JOptionPane.YES_OPTION)
+ {
+ DatabaseDocument dbDoc =
+ (DatabaseDocument)(
+ (GFFStreamFeature)active_feature.getEmblFeature()).getDocumentEntry().getDocument();
+ ChadoTransactionManager.commit(dbDoc, false,chadoTransactionManager);
+ }
+ }
+
+ if(geneBuilderHash != null)
+ {
+ geneBuilderHash.remove(chado_gene.getGeneUniqueName());
+ }
+
+ if(reopen)
+ {
+ final GeneBuilderFrame gbFrame =
+ new GeneBuilderFrame(active_feature, entry_group,
+ selection, goto_event_source,
+ chadoTransactionManager);
+ if(geneBuilderHash != null)
+ {
+ gbFrame.addGeneBuilderHash(geneBuilderHash);
+ geneBuilderHash.put(chado_gene.getGeneUniqueName(), gbFrame);
+ }
+ }
+ else
+ {
+ if(!chado_gene.getGene().isReadOnly() && GeneUtils.isBoundaryOK(chado_gene) > 0)
+ {
+ int result = JOptionPane.showConfirmDialog(this,
+ "Gene model boundary needs fixing.\nFix this now?",
+ "Gene Boundary", JOptionPane.YES_NO_OPTION);
+ if(result == JOptionPane.YES_OPTION)
+ GeneUtils.checkGeneBoundary(chado_gene);
+ }
+
+ if(!GeneUtils.isStrandOK(chado_gene))
+ {
+ JOptionPane.showMessageDialog(this,
+ "All gene features should be on the same strand.\n"+
+ "Check the strand of each of the features (gene,\n"+
+ "transcript, CDS, polypeptide).",
+ "Check Strand", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+
+ super.dispose();
+ }
+
+ /**
+ * Add the hash from EditMenu. This records what gene builders are
+ * open.
+ * @param geneBuilderFocusHash
+ */
+ public void addGeneBuilderHash(final Hashtable geneBuilderHash)
+ {
+ this.geneBuilderHash = geneBuilderHash;
+ }
+
+ /**
+ * Create a Box component with the checkboxes used to determine if
+ * a transcripts exons are visible in artemis.
+ * @param viewer
+ * @param chado_gene
+ * @return
+ */
+ private Box buildCheckBoxes(final GeneViewerPanel viewer,
+ final ChadoCanonicalGene chado_gene)
+ {
+ yBox = Box.createVerticalBox();
+ yBox.add(Box.createVerticalStrut(viewer.getViewerBorder()*3));
+
+ java.util.List transcripts = chado_gene.getTranscripts();
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ final GFFStreamFeature transcript =
+ (GFFStreamFeature)((uk.ac.sanger.artemis.io.Feature)transcripts.get(i));
+ final String transcript_name =
+ (String)transcript.getQualifierByName("ID").getValues().get(0);
+
+ Box transcriptBox = createTranscriptBox(transcript, transcript_name);
+ yBox.add(transcriptBox);
+ }
+ yBox.add(glue);
+ return yBox;
+ }
+
+ /**
+ * Create a Box containing the checkbox for a transcript
+ * @param transcript
+ * @param transcript_name
+ * @return
+ */
+ private Box createTranscriptBox(final GFFStreamFeature transcript,
+ final String transcript_name)
+ {
+ final JCheckBox cb = new JCheckBox();
+
+ List exons = chado_gene.getSpliceSitesOfTranscript(transcript_name,
+ DatabaseDocument.EXONMODEL);
+ if(exons != null && exons.size() > 0)
+ cb.setSelected(((GFFStreamFeature)exons.get(0)).isVisible());
+ else
+ cb.setSelected(transcript.isVisible());
+
+ cb.setOpaque(false);
+ cb.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ final List exons = chado_gene.getSpliceSitesOfTranscript(transcript_name,
+ DatabaseDocument.EXONMODEL);
+ if(exons == null)
+ return;
+
+ boolean visible = true;
+ if(e.getStateChange() == ItemEvent.DESELECTED)
+ visible = false;
+
+ for(int j=0; j<exons.size(); j++)
+ {
+ GFFStreamFeature embl_exon = (GFFStreamFeature)exons.get(j);
+ embl_exon.setVisible(visible);
+ }
+
+ FeatureVector fv = selection.getAllFeatures();
+ selection.clear();
+ selection.set(fv);
+ }
+ });
+
+ Box transcriptBox = Box.createVerticalBox();
+
+ transcriptBox.add(cb);
+ transcriptBox.add(Box.createVerticalStrut(
+ viewer.getTranscriptSize() - cb.getPreferredSize().height));
+ transcriptBoxes.put(transcript_name, transcriptBox);
+ return transcriptBox;
+ }
+
+ protected void setActiveFeature(final Feature active_feature,
+ final boolean isSet)
+ {
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ this.active_feature = active_feature;
+ feature_editor.stopListening();
+ feature_editor.setActiveFeature(active_feature, isSet);
+ setTabTitle();
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+
+ private void setTabTitle()
+ {
+ try
+ {
+ tabpane.setTitleAt(0, "Annotation :: "+
+ (String)active_feature.getQualifierByName("ID").getValues().get(0));
+ }
+ catch(InvalidRelationException e){}
+ }
+
+ /**
+ * Remove this object as a feature and entry change listener.
+ **/
+ private void stopListening(final Feature feature)
+ {
+ if(getEntry() != null)
+ getEntry().removeEntryChangeListener(this);
+ feature.removeFeatureChangeListener(this);
+
+ if(selection != null && geneBuilderSelectionChangeListener != null)
+ selection.removeSelectionChangeListener(geneBuilderSelectionChangeListener);
+ }
+
+ private void startListening(final Feature feature)
+ {
+ if(getEntry() != null)
+ getEntry().addEntryChangeListener(this);
+ feature.addFeatureChangeListener(this);
+
+ if(selection != null)
+ {
+ geneBuilderSelectionChangeListener = new GeneBuilderSelectionChangeListener();
+ selection.addSelectionChangeListener(geneBuilderSelectionChangeListener);
+ }
+ }
+
+ /**
+ * Implementation of the EntryChangeListener interface. We listen to
+ * EntryChange events so we can notify the user if of this component if the
+ * feature gets deleted.
+ **/
+ public void entryChanged(EntryChangeEvent event)
+ {
+ Feature feature = event.getFeature();
+ stopListening(feature);
+
+ QualifierVector qualifiers = feature.getQualifiers();
+ String name = (String)qualifiers.getQualifierByName("ID").getValues().get(0);
+ switch(event.getType())
+ {
+ case EntryChangeEvent.FEATURE_DELETED:
+ feature.removeFeatureChangeListener(this);
+ tree.deleteNode( name );
+
+ if(chado_gene.isTranscript(name))
+ {
+ Box transBox = (Box)transcriptBoxes.get(name);
+ yBox.remove(transBox);
+ yBox.revalidate();
+ }
+ break;
+ case EntryChangeEvent.FEATURE_ADDED:
+ Qualifier parent_qualifier =
+ qualifiers.getQualifierByName("Parent");
+
+ Qualifier derives_from_qualifier =
+ qualifiers.getQualifierByName("Derives_from");
+
+ if(parent_qualifier == null && derives_from_qualifier == null)
+ return;
+
+ tree.addNode(event.getFeature());
+ feature.addFeatureChangeListener(this);
+
+ // if polypeptide added then we are done
+ if(derives_from_qualifier != null)
+ return;
+
+ final String parent = (String)parent_qualifier.getValues().get(0);
+
+ String gene_name = null;
+ try
+ {
+ gene_name =
+ (String)chado_gene.getGene().getQualifierByName("ID").getValues().get(0);
+ }
+ catch(InvalidRelationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ if(parent.equals(gene_name) &&
+ !transcriptBoxes.containsKey(name))
+ {
+ Box transcriptBox = createTranscriptBox(
+ (GFFStreamFeature)feature.getEmblFeature(), name);
+ yBox.remove(glue);
+ yBox.add(transcriptBox);
+ yBox.add(glue);
+ yBox.revalidate();
+ }
+ default:
+ // do nothing;
+ break;
+ }
+
+ startListening(feature);
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events from the Features in this object so that
+ * we can update the display.
+ * @param event The change event.
+ **/
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ Feature feature = event.getFeature();
+ stopListening(feature);
+ active_feature.resetColour();
+
+ // re-read the information from the feature
+ switch(event.getType())
+ {
+ case FeatureChangeEvent.LOCATION_CHANGED:
+ break;
+ case FeatureChangeEvent.KEY_CHANGED:
+ break;
+ case FeatureChangeEvent.QUALIFIER_CHANGED:
+ break;
+ case FeatureChangeEvent.SEGMENT_CHANGED:
+ QualifierVector old_qualifiers = event.getOldQualifiers();
+ QualifierVector new_qualifiers = feature.getQualifiers();
+ tree.changeNode(
+ (String)old_qualifiers.getQualifierByName("ID").getValues().get(0),
+ (String)new_qualifiers.getQualifierByName("ID").getValues().get(0));
+ default:
+ break;
+ }
+ viewer.repaint();
+ startListening(feature);
+ }
+
+ protected FeatureEdit getFeatureEdit()
+ {
+ return feature_editor;
+ }
+
+ private Entry getEntry()
+ {
+ return active_feature.getEntry();
+ }
+
+ /**
+ * Add feature listeners for each artemis feature.
+ * @throws InvalidRelationException
+ */
+ private void addListeners(final ChadoCanonicalGene chado_gene)
+ throws InvalidRelationException
+ {
+ // add feature listeners
+ uk.ac.sanger.artemis.io.Feature embl_gene =
+ (uk.ac.sanger.artemis.io.Feature)chado_gene.getGene();
+ Feature gene = (Feature)embl_gene.getUserData();
+ gene.addFeatureChangeListener(this);
+
+ if(gene.getEntry() != null)
+ gene.getEntry().addEntryChangeListener(this);
+
+ List transcripts = chado_gene.getTranscripts();
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ uk.ac.sanger.artemis.io.Feature transcript =
+ (uk.ac.sanger.artemis.io.Feature)transcripts.get(i);
+ Feature trans = (Feature)transcript.getUserData();
+
+ if(trans == null)
+ trans = new Feature(transcript);
+
+ trans.addFeatureChangeListener(this);
+
+ if(trans.getEntry() != null)
+ trans.getEntry().addEntryChangeListener(this);
+
+ List exons = chado_gene.getSpliceSitesOfTranscript(
+ (String)trans.getQualifierByName("ID").getValues().get(0),
+ DatabaseDocument.EXONMODEL);
+
+ if(exons == null || exons.size() < 1)
+ continue;
+
+ if(exons.get(0) instanceof org.gmod.schema.sequence.Feature)
+ return;
+
+ for(int j=0; j<exons.size(); j++)
+ {
+ uk.ac.sanger.artemis.io.Feature embl_exon =
+ (uk.ac.sanger.artemis.io.Feature)exons.get(j);
+
+ Feature exon = (Feature)embl_exon.getUserData();
+
+ if(exon == null)
+ exon = new Feature(embl_exon);
+ exon.addFeatureChangeListener(this);
+
+ if(exon.getEntry() != null)
+ exon.getEntry().addEntryChangeListener(this);
+ }
+ }
+ }
+
+ private void stopListeningAll() throws InvalidRelationException
+ {
+ uk.ac.sanger.artemis.io.Feature embl_gene =
+ (uk.ac.sanger.artemis.io.Feature)chado_gene.getGene();
+ Feature gene = (Feature)embl_gene.getUserData();
+ stopListening(gene);
+
+ List transcripts = chado_gene.getTranscripts();
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ uk.ac.sanger.artemis.io.Feature transcript =
+ (uk.ac.sanger.artemis.io.Feature)transcripts.get(i);
+ Feature trans = (Feature)transcript.getUserData();
+
+ stopListening(trans);
+
+ List exons = chado_gene.getSpliceSitesOfTranscript(
+ (String)trans.getQualifierByName("ID").getValues().get(0),
+ DatabaseDocument.EXONMODEL);
+
+ if(exons == null)
+ continue;
+
+ for(int j=0; j<exons.size(); j++)
+ {
+ uk.ac.sanger.artemis.io.Feature embl_exon =
+ (uk.ac.sanger.artemis.io.Feature)exons.get(j);
+
+ Feature exon = (Feature)embl_exon.getUserData();
+ stopListening(exon);
+ }
+ }
+ }
+
+ class GeneBuilderSelectionChangeListener implements SelectionChangeListener
+ {
+ public void selectionChanged(SelectionChangeEvent event)
+ {
+ viewer.repaint();
+ tree.setSelection(GeneBuilderFrame.this.selection);
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/GeneComponentTree.java b/uk/ac/sanger/artemis/components/genebuilder/GeneComponentTree.java
new file mode 100644
index 0000000..8c8ed18
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/GeneComponentTree.java
@@ -0,0 +1,371 @@
+/* GeneComponentTree.java
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/genebuilder/GeneComponentTree.java,v 1.23 2009-06-12 13:50:35 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DatabaseInferredFeature;
+import uk.ac.sanger.artemis.io.Feature;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.FeatureVector;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+import javax.swing.tree.*;
+import java.util.List;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Tree to display a gene hierarchy.
+ */
+public class GeneComponentTree extends JTree
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+ private ChadoCanonicalGene chado_gene;
+ private GeneBuilderFrame gene_builder;
+ private Selection selection;
+ private GeneTreeSelectionListener selection_listener;
+
+ public GeneComponentTree(final ChadoCanonicalGene chado_gene,
+ final GeneBuilderFrame gene_builder,
+ final Selection selection)
+ {
+ this.chado_gene = chado_gene;
+ this.gene_builder = gene_builder;
+ this.selection = selection;
+
+ final Feature gene = (Feature)chado_gene.getGene();
+ final String gene_id;
+ try
+ {
+ gene_id = (String)gene.getQualifierByName("ID").getValues().get(0);
+ DefaultMutableTreeNode top =
+ new DefaultMutableTreeNode(gene_id);
+
+ createNodes(top, chado_gene);
+ DefaultTreeModel model = new DefaultTreeModel(top);
+ setModel(model);
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+
+ //Listen for when a file is selected
+ selection_listener = new GeneTreeSelectionListener();
+ addTreeSelectionListener(selection_listener);
+ setSelection(selection);
+ }
+
+ /**
+ * Select the tree nodes based on the features selected.
+ * @param selection
+ */
+ protected void setSelection(Selection selection)
+ {
+ removeTreeSelectionListener(selection_listener);
+ FeatureVector features = selection.getAllFeatures();
+ TreePath path[] = new TreePath[features.size()];
+ for(int i=0; i<features.size(); i++)
+ {
+ uk.ac.sanger.artemis.Feature feature =
+ (uk.ac.sanger.artemis.Feature)features.elementAt(i);
+
+ try
+ {
+ String id =
+ (String)feature.getQualifierByName("ID").getValues().get(0);
+ DefaultMutableTreeNode node = getNodeFromName(id);
+ if(node == null)
+ return;
+
+ path[i] = new TreePath(node.getPath());
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ setSelectionPaths(path);
+ addTreeSelectionListener(selection_listener);
+ }
+
+ /**
+ * Build the node hierarchy for this gene, i.e. transcript, exons,
+ * CDS, polypeptide.
+ * @param gene_node
+ * @param chado_gene
+ * @throws InvalidRelationException
+ */
+ private void createNodes(final DefaultMutableTreeNode gene_node,
+ final ChadoCanonicalGene chado_gene)
+ throws InvalidRelationException
+ {
+ List transcripts = chado_gene.getTranscripts();
+
+ Feature transcript;
+ Feature exon;
+ Object protein;
+ String transcript_id;
+ String exon_id;
+ String protein_id;
+ DefaultMutableTreeNode transcript_node;
+ DefaultMutableTreeNode exon_node;
+ DefaultMutableTreeNode protein_node;
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ transcript = (Feature)transcripts.get(i);
+ transcript_id =
+ (String)transcript.getQualifierByName("ID").getValues().get(0);
+ transcript_node = new DefaultMutableTreeNode(transcript_id);
+ gene_node.add(transcript_node);
+
+ // exons
+ List exons = chado_gene.getSplicedFeaturesOfTranscript(transcript_id);
+ if(exons != null)
+ {
+ for(int j = 0; j < exons.size(); j++)
+ {
+ exon = (Feature) exons.get(j);
+
+ if(exon instanceof DatabaseInferredFeature)
+ continue;
+ exon_id = (String) exon.getQualifierByName("ID").getValues().get(0);
+
+ exon_node = new DefaultMutableTreeNode(exon_id);
+ transcript_node.add(exon_node);
+ }
+ }
+
+ // utr & other features
+ List utrs3 = chado_gene.get3UtrOfTranscript(transcript_id);
+ List utrs5 = chado_gene.get5UtrOfTranscript(transcript_id);
+ List others = chado_gene.getOtherFeaturesOfTranscript(transcript_id);
+
+ List utrs = new Vector();
+ if(utrs3 != null)
+ utrs.addAll(utrs3);
+ if(utrs5 != null)
+ utrs.addAll(utrs5);
+ if(others != null)
+ utrs.addAll(others);
+
+ for(int j=0; j<utrs.size(); j++)
+ {
+ Feature utr = (Feature)utrs.get(j);
+ if(utr.getQualifierByName("ID") == null)
+ continue;
+ String utr_id = (String)utr.getQualifierByName("ID").getValues().get(0);
+ transcript_node.add(new DefaultMutableTreeNode(utr_id));
+ }
+
+ // protein
+ protein = chado_gene.getProteinOfTranscript(transcript_id);
+ if(protein == null)
+ continue;
+
+ protein_id =
+ (String)((Feature)protein).getQualifierByName("ID").getValues().get(0);
+
+
+ protein_node = new DefaultMutableTreeNode(protein_id);
+
+ transcript_node.add(protein_node);
+ }
+ }
+
+ /**
+ * Change a node name.
+ * @param old_id the old uniquename of the feature
+ * @param new_id the new uniquename of the feature
+ */
+ protected void changeNode(String old_id, String new_id)
+ {
+ DefaultMutableTreeNode change_node = getNodeFromName(old_id);
+ if(change_node != null)
+ {
+ change_node.setUserObject(new_id);
+ repaint();
+ revalidate();
+ }
+ }
+
+ /**
+ * Get the node in the tree with a given name. If it is not part
+ * of the tree return null.
+ * @param name
+ * @return
+ */
+ private DefaultMutableTreeNode getNodeFromName(final String name)
+ {
+ DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
+
+ if(name.equals((String)root.getUserObject()))
+ return root;
+
+ DefaultMutableTreeNode change_node = searchChildren(root, name);
+ if(change_node != null)
+ return change_node;
+
+ Enumeration root_children = root.children();
+ while(root_children.hasMoreElements())
+ {
+ DefaultMutableTreeNode child =
+ (DefaultMutableTreeNode)root_children.nextElement();
+
+ change_node = searchChildren(child, name);
+ if(change_node != null)
+ return change_node;
+ }
+
+ return null;
+ }
+
+ /**
+ * Delete a node and all its descendents from the tree.
+ * @param id the uniquename of the feature being removed
+ */
+ protected void deleteNode(final String id)
+ {
+ DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
+ deleteChildNode(id,root);
+ }
+
+ /**
+ * Recursively check children of the parent node against the ID
+ * and delete the child that matches the ID.
+ * @param id
+ * @param parentNode
+ */
+ private void deleteChildNode(final String id,
+ final DefaultMutableTreeNode parentNode)
+ {
+ Enumeration root_children = parentNode.children();
+ while(root_children.hasMoreElements())
+ {
+ DefaultMutableTreeNode child =
+ (DefaultMutableTreeNode)root_children.nextElement();
+
+ // check children of this node!!
+ if(child.getSiblingCount() > 0)
+ deleteChildNode(id, child);
+
+ if(id.equals((String)child.getUserObject()))
+ {
+ ((DefaultTreeModel)getModel()).removeNodeFromParent(child);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Add a new node for the selected feature. It uses the Parent
+ * qualifier information to work out where it should be added.
+ * @param feature
+ */
+ protected void addNode(final uk.ac.sanger.artemis.Feature feature)
+ {
+ try
+ {
+ final String parent;
+
+ if(feature.getQualifierByName("Parent") != null)
+ parent = (String)feature.getQualifierByName("Parent").getValues().get(0);
+ else
+ parent = (String)feature.getQualifierByName("Derives_from").getValues().get(0);
+
+ String name =
+ (String)feature.getQualifierByName("ID").getValues().get(0);
+
+ if(getNodeFromName(name) != null)
+ return;
+
+ DefaultMutableTreeNode parentNode = getNodeFromName(parent);
+
+ if(parentNode != null)
+ {
+ ((DefaultTreeModel)getModel()).insertNodeInto(
+ new DefaultMutableTreeNode(name), parentNode,
+ parentNode.getChildCount());
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private DefaultMutableTreeNode searchChildren(
+ final DefaultMutableTreeNode node,
+ final String id)
+ {
+ Enumeration root_children = node.children();
+ while(root_children.hasMoreElements())
+ {
+ DefaultMutableTreeNode child =
+ (DefaultMutableTreeNode)root_children.nextElement();
+
+ if(id.equals((String)child.getUserObject()))
+ return child;
+ }
+ return null;
+ }
+
+ class GeneTreeSelectionListener implements TreeSelectionListener
+ {
+ public void valueChanged(TreeSelectionEvent e)
+ {
+ DefaultMutableTreeNode node =
+ (DefaultMutableTreeNode)GeneComponentTree.this.getLastSelectedPathComponent();
+ if(node == null)
+ return;
+
+ Feature embl_feature =
+ (Feature)chado_gene.getFeatureFromId((String)node.getUserObject());
+
+ final uk.ac.sanger.artemis.Feature feature;
+
+ if(embl_feature.getUserData() == null)
+ feature = new uk.ac.sanger.artemis.Feature(embl_feature);
+ else
+ feature =
+ (uk.ac.sanger.artemis.Feature)embl_feature.getUserData();
+ boolean isSet = true;
+ if(feature.isReadOnly())
+ isSet = false;
+
+ gene_builder.setActiveFeature(feature, isSet);
+ if(selection != null)
+ selection.set(feature);
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/GeneEdit.java b/uk/ac/sanger/artemis/components/genebuilder/GeneEdit.java
new file mode 100644
index 0000000..a4be56d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/GeneEdit.java
@@ -0,0 +1,462 @@
+/*
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.Cursor;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Vector;
+
+
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.UIManager;
+
+import org.gmod.schema.organism.Organism;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.chado.*;
+import uk.ac.sanger.artemis.io.FeatureVector;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.PartialSequence;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.components.Splash;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.components.database.DatabaseEntrySource;
+
+
+/**
+ * Chado data access example code. This searches for features by their
+ * uniquename and returns their properties and attributes.
+ */
+public class GeneEdit
+{
+ /** JDBC DAO */
+ private JdbcDAO jdbcDAO = null;
+
+ /** iBatis DAO */
+ private IBatisDAO connIB = null;
+
+ /** password fields */
+ private JPasswordField pfield;
+
+ private String geneName;
+
+ /**
+ * Standalone gene editing (i.e. outside of Artemis)
+ */
+ public GeneEdit()
+ {
+ try
+ {
+ DatabaseEntrySource entry_source = new DatabaseEntrySource();
+ entry_source.setLocation(true);
+ final String location = entry_source.getLocation();
+ pfield = entry_source.getPfield();
+
+ final GmodDAO dao = getDAO(location);
+ showFeatureSearchPanel(dao, location);
+ }
+ catch(java.net.ConnectException ce)
+ {
+ ce.printStackTrace();
+ }
+ catch(SQLException sqlExp)
+ {
+ JOptionPane.showMessageDialog(null, "SQL Problems...\n"
+ + sqlExp.getMessage(), "SQL Error", JOptionPane.ERROR_MESSAGE);
+ sqlExp.printStackTrace();
+ }
+ }
+
+ public GeneEdit(final String geneName)
+ {
+ DatabaseEntrySource entry_source = new DatabaseEntrySource();
+ this.geneName = geneName;
+
+ boolean promptUser = true;
+ if(System.getProperty("read_only") != null)
+ promptUser = false;
+ entry_source.setLocation(promptUser);
+ final String location = entry_source.getLocation();
+ pfield = entry_source.getPfield();
+
+ if(System.getProperty("show_log") != null)
+ GeneSplash.showLog();
+ openGeneBuilder(null, location, null);
+ }
+
+ /**
+ * Display a window for searching for features.
+ *
+ * @throws java.net.ConnectException
+ * @throws SQLException
+ */
+ private void showFeatureSearchPanel(final GmodDAO dao,
+ final String location)
+ throws java.net.ConnectException, SQLException
+ {
+ int index = location.indexOf('=') + 1;
+ String schema = location.substring(index);
+
+ final List<Organism> schemas = dao.getOrganisms(); //dao.getSchema();
+
+ Vector<String> v_schemas = new Vector<String>(schemas.size());
+ for(int i=0; i<schemas.size(); i++)
+ v_schemas.add( schemas.get(i).getCommonName() );
+
+ v_schemas.add(0, "All");
+
+ final JPanel panel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ final JComboBox schema_list = new JComboBox(v_schemas);
+ schema_list.setSelectedItem(schema);
+
+ final JTextField gene_text = new JTextField(20);
+ gene_text.setText("Smp_000130"); //"SPAC212.04c");
+
+ c.gridx = 0;
+ c.gridy = 0;
+ panel.add(gene_text,c);
+ c.gridx = 1;
+ panel.add(schema_list,c);
+ gene_text.selectAll();
+
+ final GeneSplash frame = new GeneSplash();
+ final JButton findButt = new JButton("OPEN GENE BUILDER");
+ findButt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ GeneEdit.this.geneName = gene_text.getText().trim();
+ final String schema = (String)schema_list.getSelectedItem();
+ List schema_search;
+ if(schema.equalsIgnoreCase("All"))
+ schema_search = schemas;
+ else
+ {
+ schema_search = new Vector();
+ schema_search.add(schema);
+ }
+
+ openGeneBuilder(schema, location, frame);
+ }
+ });
+
+ c.gridx = 1;
+ c.gridy = 1;
+ panel.add(findButt, c);
+
+ frame.getContentPane().add(panel);
+ frame.setJMenuBar(getJMenuBar(dao));
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ private void openGeneBuilder(final String organism,
+ final String location,
+ final GeneSplash frame)
+ {
+ SwingWorker entryWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ if(frame != null)
+ frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ DatabaseDocumentEntry entry = makeEntry(organism,
+ location, pfield);
+
+ if(System.getProperty("read_only") != null)
+ entry.setReadOnly(true);
+ showGeneEditor(organism, geneName, entry);
+
+ if(frame != null)
+ frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ return null;
+ }
+ };
+ entryWorker.start();
+ }
+
+ private DatabaseDocumentEntry makeEntry(final String schema,
+ final String location,
+ final JPasswordField pfield)
+ {
+ DatabaseDocumentEntry db_entry = null;
+ DatabaseDocument doc = new DatabaseDocument(location, pfield,
+ geneName,
+ schema, true);
+ doc.setLazyFeatureLoad(false);
+
+ try
+ {
+ db_entry = new DatabaseDocumentEntry(doc, null);
+ }
+ catch(Exception e)
+ {
+ org.gmod.schema.sequence.Feature chadoFeature =
+ doc.getChadoGeneByAnyCurrentName(geneName);
+ if(chadoFeature != null)
+ {
+ JOptionPane.showMessageDialog(null,
+ geneName+" appears to be a synonym for "+chadoFeature.getUniqueName()+
+ "\nNow opening "+chadoFeature.getUniqueName(),
+ geneName, JOptionPane.INFORMATION_MESSAGE);
+ geneName = chadoFeature.getUniqueName();
+ db_entry = makeEntry(schema, location, pfield);
+ }
+ }
+
+ return db_entry;
+ }
+
+ /**
+ * Build a <code>JMenuBar</code>.
+ *
+ * @return a <code>JMenuBar</code>
+ */
+ public JMenuBar getJMenuBar(final GmodDAO dao)
+ {
+ JMenuBar mbar = new JMenuBar();
+ JMenu file = new JMenu("File");
+ mbar.add(file);
+
+ JMenuItem showLog = new JMenuItem("Show Log Window");
+ showLog.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ GeneSplash.showLog();
+ }
+ });
+ file.add(showLog);
+
+ JMenuItem exit = new JMenuItem("Exit");
+ exit.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ System.exit(0);
+ }
+ });
+ file.add(exit);
+
+ return mbar;
+ }
+
+ /**
+ * Make an entry for a single gene.
+ * @param organism
+ * @param uniquename
+ * @param doc
+ * @param stream_progress_listener
+ * @return
+ */
+ public static DatabaseDocumentEntry makeGeneEntry(final String organism,
+ final String uniquename,
+ final DatabaseDocument doc,
+ final InputStreamProgressListener stream_progress_listener)
+ {
+ DatabaseDocumentEntry db_entry = null;
+ DatabaseDocument newdoc = new DatabaseDocument(doc,
+ uniquename, organism, true, stream_progress_listener);
+ newdoc.setLazyFeatureLoad(false);
+
+ try
+ {
+ db_entry = new DatabaseDocumentEntry(newdoc, null);
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ catch(NullPointerException npe)
+ {
+ JOptionPane.showMessageDialog(null, organism+":"+uniquename+
+ " not found!", "Warning", JOptionPane.WARNING_MESSAGE);
+ }
+ return db_entry;
+ }
+
+
+
+ /**
+ *
+ * @param organism
+ * @param uniqueName
+ * @param dbentry
+ */
+ public static void showGeneEditor(final String organism,
+ final String uniqueName,
+ final DatabaseDocumentEntry dbentry)
+ {
+ DatabaseDocument doc = (DatabaseDocument)dbentry.getDocument();
+ PartialSequence sequence = doc.getChadoSequence(uniqueName);
+ dbentry.setPartialSequence(sequence);
+
+ FeatureVector features = dbentry.getAllFeatures();
+ Feature gff_gene_feature = null;
+
+ SimpleEntryGroup entry_group = new SimpleEntryGroup();
+ try
+ {
+ // create Entry
+ new Entry(dbentry);
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ catch(NoSequenceException e)
+ {
+ e.printStackTrace();
+ }
+
+ for(int i = 0; i < features.size(); i++)
+ {
+ GFFStreamFeature this_embl_feature = (GFFStreamFeature) features.get(i);
+ String this_uniquename = (String) this_embl_feature.getQualifierByName("ID")
+ .getValues().get(0);
+
+ Feature this_feature;
+
+ if(this_embl_feature.getUserData() == null)
+ this_feature = new Feature(this_embl_feature);
+ else
+ this_feature = (Feature)this_embl_feature.getUserData();
+
+ if(uniqueName.equals(this_uniquename))
+ gff_gene_feature = this_feature;
+ }
+
+ Selection selection = new Selection(null);
+ selection.add(gff_gene_feature);
+
+ entry_group.addElement(gff_gene_feature.getEntry());
+
+ ChadoTransactionManager ctm = new ChadoTransactionManager();
+ entry_group.addFeatureChangeListener(ctm);
+ entry_group.addEntryChangeListener(ctm);
+ ctm.setEntryGroup(entry_group);
+
+ if(System.getProperty("basic") == null)
+ new GeneBuilderFrame(gff_gene_feature, entry_group, selection, null, ctm);
+ else
+ new BasicGeneBuilderFrame(gff_gene_feature, entry_group, selection, ctm);
+ }
+
+ /**
+ * Get the data access object (DAO).
+ *
+ * @return data access object
+ */
+ private GmodDAO getDAO(final String location)
+ throws java.net.ConnectException, SQLException
+ {
+ if(System.getProperty("ibatis") == null)
+ {
+ if(jdbcDAO == null)
+ jdbcDAO = new JdbcDAO(location, pfield);
+ return jdbcDAO;
+ }
+ else
+ {
+ if(connIB == null)
+ {
+ System.setProperty("chado", location);
+ connIB = new IBatisDAO(pfield);
+ }
+
+ return connIB;
+ }
+ }
+
+ class GeneSplash extends Splash
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public GeneSplash()
+ {
+ super("Gene Search", "Gene Builder");
+ }
+
+ protected void exit()
+ {
+ }
+
+ }
+
+
+
+ public static void main(String args[])
+ {
+ final javax.swing.plaf.FontUIResource font_ui_resource =
+ Options.getOptions().getFontUIResource();
+
+ java.util.Enumeration<Object> keys = UIManager.getDefaults().keys();
+ while(keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ Object value = UIManager.get(key);
+ if(value instanceof javax.swing.plaf.FontUIResource)
+ UIManager.put(key, font_ui_resource);
+ }
+ if(args.length == 1)
+ new GeneEdit(args[0]);
+ else
+ new GeneEdit();
+
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/GeneEditorPanel.java b/uk/ac/sanger/artemis/components/genebuilder/GeneEditorPanel.java
new file mode 100644
index 0000000..5cf4c1d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/GeneEditorPanel.java
@@ -0,0 +1,205 @@
+/* GeneEditorPanel.java
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+
+import uk.ac.sanger.artemis.components.QualifierTextArea;
+import uk.ac.sanger.artemis.components.genebuilder.cv.CVPanel;
+import uk.ac.sanger.artemis.components.genebuilder.gff.PropertiesPanel;
+import uk.ac.sanger.artemis.components.genebuilder.ortholog.MatchPanel;
+
+
+/**
+ * Panel for display controlled vocabulary terms for Chado
+ */
+public class GeneEditorPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ protected static Color STEEL_BLUE = new Color(25, 25, 112);
+ private static Color LIGHT_STEEL_BLUE = new Color(176, 196, 222);
+
+ private OpenSectionButton coreButton;
+ private OpenSectionButton cvButton;
+ private OpenSectionButton matchButton;
+ private OpenSectionButton propertiesButton;
+ private OpenSectionButton refButton;
+
+ private QualifierTextArea qualifier_text_area;
+ private CVPanel cvForm;
+ private MatchPanel matchForm;
+ private PropertiesPanel propertiesPanel;
+ private ReferencesPanel refPanel;
+
+ /**
+ * Gene editor panel - showing annotation in a single panel.
+ * @param qualifier_text_area
+ * @param cvForm
+ * @param matchForm
+ * @param propertiesPanel
+ */
+ public GeneEditorPanel(final QualifierTextArea qualifier_text_area,
+ final CVPanel cvForm,
+ final ReferencesPanel refPanel,
+ final MatchPanel matchForm,
+ final PropertiesPanel propertiesPanel)
+ {
+ this.qualifier_text_area = qualifier_text_area;
+ this.cvForm = cvForm;
+ this.matchForm = matchForm;
+ this.propertiesPanel = propertiesPanel;
+ this.refPanel = refPanel;
+
+ setLayout( new BoxLayout(this, BoxLayout.PAGE_AXIS) );
+ setBackground(Color.WHITE);
+
+ addDarkSeparator(this);
+ propertiesButton = addOpenClosePanel("Properties", propertiesPanel, this, null);
+ add(propertiesPanel);
+
+ addDarkSeparator(this);
+ coreButton = addOpenClosePanel("Core",qualifier_text_area, this, null);
+ add(qualifier_text_area);
+
+ addDarkSeparator(this);
+ refButton = addOpenClosePanel("References",refPanel, this, null);
+ add(refPanel);
+
+ addDarkSeparator(this);
+ cvButton = addOpenClosePanel("Controlled Vocabulary", cvForm, this,
+ CVPanel.getDescription());
+ add(cvForm);
+
+ addDarkSeparator(this);
+ matchButton = addOpenClosePanel("Match", matchForm, this,
+ MatchPanel.getDescription());
+ add(matchForm);
+
+ add(Box.createVerticalGlue());
+ }
+
+ /**
+ * Open/close the sections if they contain elements or
+ * are empty.
+ */
+ public void updatePanelState()
+ {
+ coreButton.setOpen(!qualifier_text_area.getText().equals(""));
+ cvButton.setOpen(!cvForm.isEmpty());
+ refButton.setOpen(!refPanel.isEmpty());
+ matchButton.setOpen(!matchForm.isEmpty());
+ propertiesButton.setOpen(!propertiesPanel.isEmpty());
+ }
+
+ /**
+ * Add a Separator to a given component
+ * @param comp
+ * @param parent
+ * @param useLightColor
+ */
+ public static JSeparator getSeparator(final JComponent comp,
+ final boolean useLightColor)
+ {
+ final JSeparator separator = new JSeparator();
+
+ if(useLightColor)
+ separator.setForeground(LIGHT_STEEL_BLUE);
+ else
+ separator.setForeground(STEEL_BLUE);
+
+ //separator.setPreferredSize(new Dimension(width,10));
+ // add glue to expand the separator horizontally
+ comp.add(Box.createHorizontalGlue());
+ //comp.add(Box.createVerticalStrut(1));
+ separator.setPreferredSize(
+ new Dimension(comp.getPreferredSize().width,
+ 6));
+ separator.setMaximumSize(new Dimension(2500,8));
+
+ return separator;
+ }
+
+ /**
+ * Add a light Separator to a given component
+ * @param comp
+ * @param parent
+ */
+ public static void addLightSeparator(final JComponent comp)
+ {
+ comp.add(getSeparator(comp, true));
+ }
+
+ /**
+ * Add a dark Separator to a given component
+ * @param comp
+ */
+ public static void addDarkSeparator(final JComponent comp)
+ {
+ comp.add(getSeparator(comp, false));
+ }
+
+ /**
+ * @param name
+ * @param panel
+ * @param tt
+ * @return
+ */
+ protected static OpenSectionButton addOpenClosePanel(final String name,
+ final JComponent panel,
+ final JPanel container,
+ final String tt)
+ {
+ final JPanel bannerPanel = new JPanel();
+
+ if(tt != null)
+ bannerPanel.setToolTipText(tt);
+ bannerPanel.setLayout(new BoxLayout(bannerPanel, BoxLayout.LINE_AXIS));
+ bannerPanel.setBackground(LIGHT_STEEL_BLUE);
+
+ final JLabel nameLabel = new JLabel(name);
+ nameLabel.setForeground(STEEL_BLUE);
+ nameLabel.setFont(nameLabel.getFont().deriveFont(Font.BOLD));
+
+ final OpenSectionButton openButton = new OpenSectionButton("-", panel);
+
+ bannerPanel.add(nameLabel);
+ bannerPanel.add(Box.createHorizontalGlue());
+ bannerPanel.add(openButton);
+ bannerPanel.setPreferredSize(
+ new Dimension(bannerPanel.getPreferredSize().width, 18));
+
+ container.add(bannerPanel);
+ return openButton;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/GeneUtils.java b/uk/ac/sanger/artemis/components/genebuilder/GeneUtils.java
new file mode 100644
index 0000000..bee966d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/GeneUtils.java
@@ -0,0 +1,1559 @@
+/* GeneUtils.java
+ *
+ * This file is part of Artemis
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureDbXRef;
+import org.gmod.schema.sequence.FeaturePub;
+import org.gmod.schema.sequence.FeatureSynonym;
+
+import uk.ac.sanger.artemis.chado.ClusterLazyQualifierValue;
+import uk.ac.sanger.artemis.chado.FeatureForUpdatingResidues;
+import uk.ac.sanger.artemis.chado.FeatureLocLazyQualifierValue;
+import uk.ac.sanger.artemis.components.EditMenu;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.components.SelectionMenu;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.DatabaseInferredFeature;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Feature;
+import uk.ac.sanger.artemis.io.GFFDocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.KeyVector;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierLazyLoading;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.util.ByteBuffer;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.EntryVector;
+import uk.ac.sanger.artemis.FeatureKeyQualifierPredicate;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.GotoEventSource;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+
+
+public class GeneUtils
+{
+ private static Vector<String> hideFeatures = new Vector<String>();
+ private static JCheckBox showObsolete = new JCheckBox("Show Obsolete Features",false);
+ private static String nonCodingTranscripts[] =
+ { "tRNA", "rRNA", "snRNA", "snoRNA", "ncRNA", "scRNA" };
+ private static StringVector featuresToUpdateResidues =
+ Options.getOptions().getOptionValues("sequence_update_features");
+
+ static
+ {
+ hideFeatures.add("polypeptide");
+ hideFeatures.add(DatabaseDocument.TRANSCRIPT);
+ hideFeatures.add("pseudogenic_transcript");
+ }
+
+ /**
+ * Used when a whole sequence is loaded in and the features are loaded
+ * lazily
+ * @param feature
+ */
+ public static void addLazyQualifiers(final GFFStreamFeature feature)
+ {
+ if(feature.isLazyLoaded() || feature.getChadoLazyFeature() == null)
+ return;
+
+ // synonyms
+ final Collection featureSynonyms = feature.getChadoLazyFeature().getFeatureSynonyms();
+
+ final Iterator it = featureSynonyms.iterator();
+ while(it.hasNext())
+ {
+ final FeatureSynonym featureSynonym = (FeatureSynonym) it.next();
+ final String name = featureSynonym.getSynonym().getCvTerm().getName();
+ String value = featureSynonym.getSynonym().getName();
+
+ if(!featureSynonym.isCurrent())
+ value.concat(GFFStreamFeature.encode(";current=false"));
+
+ Qualifier qualifier = feature.getQualifiers().getQualifierByName(name);
+ if(qualifier == null)
+ qualifier = new Qualifier(name, value);
+ else
+ qualifier.addValue(value);
+
+ feature.getQualifiers().setQualifier(qualifier);
+ }
+
+ // dbxrefs
+ if(feature.getQualifierByName("Dbxref") == null)
+ {
+ DbXRef dbxref = feature.getChadoLazyFeature().getDbXRef();
+ if(dbxref != null)
+ {
+ String value = dbxref.getDb().getName() + ":" +
+ dbxref.getAccession();
+ feature.getQualifiers().setQualifier(new Qualifier("Dbxref", value));
+
+ if(feature.isReadOnly() && feature.getKey().equals("polypeptide_domain"))
+ {
+ value= "protein motif:"+value;
+ feature.getQualifiers().setQualifier(new Qualifier("inference", value));
+ }
+ }
+ }
+
+
+ final Collection featureDbXRefs = feature.getChadoLazyFeature().getFeatureDbXRefs();
+ final Iterator it2 = featureDbXRefs.iterator();
+ while(it2.hasNext())
+ {
+ final FeatureDbXRef featureDbXRef = (FeatureDbXRef) it2.next();
+ String value = featureDbXRef.getDbXRef().getDb().getName() + ":" +
+ featureDbXRef.getDbXRef().getAccession();
+
+ Qualifier qualifier = feature.getQualifiers().getQualifierByName("Dbxref");
+ if(qualifier == null)
+ qualifier = new Qualifier("Dbxref", value);
+ else
+ qualifier.addValue(value);
+ feature.getQualifiers().setQualifier(qualifier);
+ }
+
+
+ // feature cvterms (GO, product....)
+ final Collection featureCvTerms = feature.getChadoLazyFeature().getFeatureCvTerms();
+ if(featureCvTerms != null)
+ {
+ final Iterator it3 = featureCvTerms.iterator();
+ while(it3.hasNext())
+ {
+ FeatureCvTerm featureCvTerm = (FeatureCvTerm)it3.next();
+ List featureCvTermDbXRefList = null;
+
+ if(featureCvTerm.getFeatureCvTermDbXRefs() != null)
+ featureCvTermDbXRefList = new Vector(featureCvTerm.getFeatureCvTermDbXRefs());
+
+ List featureCvTermPubList = null;
+
+ if(featureCvTerm.getFeatureCvTermPubs() != null)
+ featureCvTermPubList = new Vector(featureCvTerm.getFeatureCvTermPubs());
+
+ ByteBuffer this_buff = new ByteBuffer();
+ DatabaseDocument.appendControlledVocabulary(this_buff, null, featureCvTerm,
+ featureCvTermDbXRefList,featureCvTermPubList, null, false);
+
+ final String qualifierString = new String(this_buff.getBytes());
+ int ind = qualifierString.indexOf('=');
+ final String name = qualifierString.substring(0, ind);
+ final String value = GFFStreamFeature.decode(
+ qualifierString.substring(ind+1, qualifierString.length()-1));
+
+ Qualifier qualifier = feature.getQualifiers().getQualifierByName(name);
+ if(qualifier == null)
+ qualifier = new Qualifier(name, value);
+ else
+ qualifier.addValue(value);
+ feature.getQualifiers().setQualifier(qualifier);
+ }
+ }
+ // feature pubs - literature
+ final Collection featurePubs = feature.getChadoLazyFeature().getFeaturePubs();
+
+ if(featurePubs != null)
+ {
+ final Iterator it4 = featurePubs.iterator();
+ while(it4.hasNext())
+ {
+ FeaturePub featurePub = (FeaturePub) it4.next();
+
+ Qualifier qualifier = feature.getQualifiers().getQualifierByName(
+ "literature");
+ if(qualifier == null)
+ qualifier = new Qualifier("literature", featurePub.getPub()
+ .getUniqueName());
+ else
+ qualifier.addValue(featurePub.getPub().getUniqueName());
+ feature.getQualifiers().setQualifier(qualifier);
+ }
+ }
+
+ feature.setLazyLoaded(true);
+ }
+
+ /**
+ * Used to reverse complement all the gene model features
+ * @param chadoGene
+ */
+ public static void complementGeneModel(final ChadoCanonicalGene chadoGene)
+ {
+ if(chadoGene == null)
+ return;
+ try
+ {
+ final Feature gene = chadoGene.getGene();
+ final boolean complement = gene.getLocation().isComplement();
+ gene.setLocation(gene.getLocation().getComplement());
+ final Set kids = chadoGene.getChildren(gene);
+ final Iterator it = kids.iterator();
+ while(it.hasNext())
+ {
+ final Feature f = (Feature)it.next();
+ final RangeVector rv = f.getLocation().getRanges();
+ rv.reverse();
+ f.setLocation(new Location(rv, !complement));
+ }
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public static void addSegment(final GFFStreamFeature feature,
+ final RangeVector rangesToAdd,
+ final String transcriptName)
+ throws ReadOnlyException, EntryInformationException
+ {
+ // add new ID
+ final Hashtable id_store = feature.getSegmentRangeStore();
+ String prefix[] = null;
+ Enumeration enum_ids = id_store.keys();
+ while(enum_ids.hasMoreElements())
+ {
+ String id = (String) enum_ids.nextElement();
+ prefix = feature.getPrefix(id, ':');
+ if(prefix[0] != null)
+ break;
+ }
+
+ // USE PREFIX TO CREATE NEW ID
+ RangeVector rv = (RangeVector)feature.getLocation().getRanges().clone();
+ for(int i=0; i<rangesToAdd.size(); i++)
+ {
+ final Range range = (Range) rangesToAdd.elementAt(i);
+ final String ID;
+ if(prefix[0] != null)
+ {
+ int auto_num = feature.getAutoNumber(prefix[0], ':');
+ ID = prefix[0] + ":" + auto_num;
+ feature.getSegmentRangeStore().put(ID, range);
+ }
+ else
+ {
+ String key = feature.getKey().toString();
+ ID = transcriptName + ":" + key + ":1";
+ feature.getSegmentRangeStore().put(ID, range);
+ }
+
+ if(!rv.containsRange(range))
+ rv.add(range);
+ }
+
+ feature.setQualifier(new Qualifier("ID", feature.getSegmentID( rv )));
+ }
+
+ /**
+ * Used when writing the database entry to a file. This routine
+ * forces lazy-loading qualifier values to be read in full.
+ * @param entry
+ * @param parent
+ */
+ public static void lazyLoadAll(final Entry entry, final JFrame parent)
+ {
+ final List lazySimilarityValues = new Vector();
+ final List lazyClusterValues = new Vector();
+ final FeatureVector features = entry.getAllFeatures();
+ // find any lazy values to be loaded
+
+ for(int i=0; i<features.size(); i++)
+ {
+ QualifierVector qualifiers = features.elementAt(i).getQualifiers();
+ for(int j=0; j<qualifiers.size(); j++)
+ {
+ Qualifier qualifier = (Qualifier)qualifiers.get(j);
+ if(qualifier instanceof QualifierLazyLoading &&
+ !((QualifierLazyLoading)qualifier).isAllLazyValuesLoaded())
+ {
+ if( ((QualifierLazyLoading)qualifier).getValue(0) instanceof FeatureLocLazyQualifierValue )
+ lazySimilarityValues.addAll( ((QualifierLazyLoading)qualifier).getLazyValues() );
+ else if( ((QualifierLazyLoading)qualifier).getValue(0) instanceof ClusterLazyQualifierValue )
+ {
+ List lazyValues = ((QualifierLazyLoading)qualifier).getLazyValues();
+ lazyClusterValues.addAll(lazyValues);
+ }
+
+ ((QualifierLazyLoading)qualifier).setForceLoad(true);
+ }
+ }
+ }
+
+ if(lazySimilarityValues.size() > 0 || lazyClusterValues.size() > 0)
+ {
+ int n = JOptionPane.YES_OPTION;
+ if(parent != null)
+ n = JOptionPane.showConfirmDialog(parent,
+ "Load and write to file all qualifers from the database?"+
+ "\nThis may take a few minutes.",
+ "Load All Data",
+ JOptionPane.YES_NO_OPTION);
+
+ if(n == JOptionPane.YES_OPTION)
+ {
+ if(parent != null)
+ parent.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ final DatabaseDocument document =
+ (DatabaseDocument)((DocumentEntry)entry.getEMBLEntry()).getDocument();
+
+ if(lazySimilarityValues.size() > 0)
+ FeatureLocLazyQualifierValue.bulkRetrieve(lazySimilarityValues,document);
+
+ if(lazyClusterValues.size() > 0)
+ ClusterLazyQualifierValue.setClusterFromValueList(lazyClusterValues, document);
+
+ for(int i=0; i<features.size(); i++)
+ {
+ GFFStreamFeature feature = (GFFStreamFeature)(features.elementAt(i).getEmblFeature());
+ if(feature.isReadOnly() &&
+ feature.getKey().equals("polypeptide_domain") &&
+ feature.getChadoLazyFeature() != null)
+ {
+ // load dbxrefs for domains
+ if(feature.getQualifierByName("Dbxref") == null)
+ {
+ DbXRef dbxref = feature.getChadoLazyFeature().getDbXRef();
+ if(dbxref != null)
+ {
+ String value = dbxref.getDb().getName() + ":" +
+ dbxref.getAccession();
+ feature.getQualifiers().setQualifier(new Qualifier("Dbxref", value));
+
+ value= "protein motif:"+value;
+ feature.getQualifiers().setQualifier(new Qualifier("inference", value));
+ }
+ }
+ }
+ }
+ if(parent != null)
+ parent.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ }
+
+ /**
+ * Sorts the elements of the vector using a simple O(n^2) selection
+ * sort.
+ */
+ private static void sort(Vector v)
+ {
+ int smallest;
+
+ for (int i = 0; i < v.size (); ++i)
+ {
+ //find smallest remaining element
+ smallest = i;
+ for(int j = i + 1 ; j < v.size () ; ++j)
+ {
+ if(((String)v.get(j)).compareTo( (String)v.get(smallest)) < 0)
+ smallest = j;
+ }
+ //exchange smallest and i
+ if (smallest != i)
+ {
+ final String tmp = (String)v.get(i);
+ v.setElementAt (v.get(smallest), i);
+ v.setElementAt (tmp, smallest);
+ }
+ }
+ }
+
+
+ /**
+ * Given a collection of features, determine if these should be
+ * shown or hidden in the Artemis display
+ * @param features
+ */
+ public static void defineShowHideGeneFeatures(final FeatureVector features)
+ {
+ final KeyVector keys =
+ features.elementAt(0).getEntry().getEntryInformation().getSortedValidKeys ();
+ final Vector showFeatures = new Vector();
+ for(int i=0; i<keys.size(); i++)
+ {
+ String keyStr = ((Key)keys.get(i)).getKeyString();
+ if( !hideFeatures.contains(keyStr) )
+ showFeatures.add(keyStr);
+ }
+
+ sort(hideFeatures);
+
+ final DefaultListModel showListModel = new DefaultListModel();
+ for(int i=0; i<showFeatures.size(); i++)
+ showListModel.addElement(showFeatures.get(i));
+ final JList displayList = new JList(showListModel);
+
+
+ final DefaultListModel hideListModel = new DefaultListModel();
+ for(int i=0; i<hideFeatures.size(); i++)
+ hideListModel.addElement(hideFeatures.get(i));
+ final JList hideList = new JList(hideListModel);
+
+
+ final JButton hide_butt = new JButton("HIDE");
+ hide_butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ while(!displayList.isSelectionEmpty())
+ {
+ final String hideKey = (String)displayList.getSelectedValue();
+
+ hideFeatures.add(hideKey);
+ if(showFeatures.contains(hideKey))
+ showFeatures.remove(hideKey);
+
+ sort(hideFeatures);
+ hideListModel.add(hideFeatures.indexOf(hideKey), hideKey);
+ showListModel.removeElement(hideKey);
+ }
+ }
+ });
+
+ Box bdown = Box.createVerticalBox();
+ bdown.add(new JLabel("Features Displayed:"));
+ bdown.add(new JScrollPane(displayList));
+ bdown.add(hide_butt);
+
+ final JPanel hideShowPanel = new JPanel(new BorderLayout());
+ hideShowPanel.add(bdown, BorderLayout.CENTER);
+
+
+ final JButton show_butt = new JButton("SHOW");
+ show_butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ while(!hideList.isSelectionEmpty())
+ {
+ final String showKey = (String)hideList.getSelectedValue();
+
+ if(hideFeatures.contains(showKey))
+ hideFeatures.remove(showKey);
+ showFeatures.add(showKey);
+ sort(showFeatures);
+
+ showListModel.add(showFeatures.indexOf(showKey), showKey);
+ hideListModel.removeElement(showKey);
+ }
+ }
+ });
+
+ bdown = Box.createVerticalBox();
+ bdown.add(Box.createVerticalGlue());
+ bdown.add(new JLabel("Features Hidden:"));
+ bdown.add(new JScrollPane(hideList));
+ bdown.add(show_butt);
+ hideShowPanel.add(bdown, BorderLayout.EAST);
+
+ hideShowPanel.add(showObsolete, BorderLayout.SOUTH);
+
+ int select = JOptionPane.showConfirmDialog(null, hideShowPanel,
+ "Gene Model Features Displayed...",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+
+ showHideGeneFeatures(features);
+ }
+
+ /**
+ * Based on the hidenFeatures and showFeatures set the GFFStreamFeatures
+ * visibility
+ * @param features
+ */
+ public static void showHideGeneFeatures(final FeatureVector features)
+ {
+ for(int i=0; i<features.size(); i++)
+ {
+ final Feature feature = features.elementAt(i).getEmblFeature();
+
+ if(feature instanceof GFFStreamFeature)
+ {
+ if(isObsolete((GFFStreamFeature)feature))
+ {
+ if(!showObsolete.isSelected())
+ {
+ ((GFFStreamFeature)feature).setVisible(false);
+ continue;
+ }
+ }
+
+ final String key = feature.getKey().getKeyString();
+ if(hideFeatures.contains(key))
+ ((GFFStreamFeature)feature).setVisible(false);
+ else
+ ((GFFStreamFeature)feature).setVisible(true);
+ }
+ }
+ }
+
+ /**
+ * Determine based on the feature given if a feature is hidden
+ * @param key
+ * @return
+ */
+ public static boolean isHiddenFeature(final String key)
+ {
+ return hideFeatures.contains(key);
+ }
+
+ /**
+ * Test if this feature is obsolete
+ * @param feature
+ * @return
+ */
+ public static boolean isObsolete(final uk.ac.sanger.artemis.io.GFFStreamFeature feature)
+ {
+ Qualifier qualifier = feature.getQualifierByName("isObsolete");
+ if(qualifier == null)
+ return false;
+
+ if( ((String)qualifier.getValues().get(0)).equals("true") )
+ return true;
+
+ return false;
+ }
+
+ /**
+ *
+ */
+ public static Vector<ChadoCanonicalGene> duplicateGeneModel(final JFrame frame,
+ final FeatureVector features_to_duplicate,
+ final EntryGroup entry_group)
+ {
+ final Vector<ChadoCanonicalGene> duplicatedGenes = new Vector<ChadoCanonicalGene>();
+ final Vector<ChadoCanonicalGene> newGenes = new Vector<ChadoCanonicalGene>();
+ for (int i = 0 ; i < features_to_duplicate.size () ; ++i)
+ {
+ final GFFStreamFeature gffFeature =
+ (GFFStreamFeature)features_to_duplicate.elementAt(i).getEmblFeature();
+ if(duplicatedGenes.contains(gffFeature.getChadoGene()))
+ continue;
+
+ duplicatedGenes.add(gffFeature.getChadoGene());
+
+ try
+ {
+ GFFStreamFeature gene = (GFFStreamFeature)gffFeature.getChadoGene().getGene();
+ uk.ac.sanger.artemis.Feature newGeneFeature = ((uk.ac.sanger.artemis.Feature)
+ gene.getUserData()).duplicate (true);
+
+ final ChadoCanonicalGene chadoGene = gffFeature.getChadoGene();
+ final ChadoCanonicalGene newchadoGene = new ChadoCanonicalGene();
+ newGenes.add(newchadoGene);
+ ((GFFStreamFeature)newGeneFeature.getEmblFeature()).setChadoGene(newchadoGene);
+ newchadoGene.setGene(newGeneFeature.getEmblFeature());
+
+ final List<Feature> transcripts = chadoGene.getTranscripts();
+ for(int j=0; j<transcripts.size(); j++) // duplicate transcripts and children
+ {
+ final GFFStreamFeature transcript = (GFFStreamFeature)transcripts.get(j);
+ final String transcriptName = getUniqueName(transcript);
+
+ uk.ac.sanger.artemis.Feature newTranscriptFeature = duplicateFeature(transcript, newchadoGene);
+ newchadoGene.addTranscript(newTranscriptFeature.getEmblFeature());
+ final String newTranscriptName = getUniqueName(newTranscriptFeature.getEmblFeature());
+
+ List<uk.ac.sanger.artemis.Feature> newFeatures=
+ duplicateFeatures(chadoGene.get3UtrOfTranscript(transcriptName), newchadoGene);
+ for(uk.ac.sanger.artemis.Feature utrFeature: newFeatures)
+ newchadoGene.add3PrimeUtr(newTranscriptName, utrFeature.getEmblFeature());
+
+ newFeatures = duplicateFeatures(chadoGene.get5UtrOfTranscript(transcriptName), newchadoGene);
+ for(uk.ac.sanger.artemis.Feature utrFeature: newFeatures)
+ newchadoGene.add5PrimeUtr(newTranscriptName, utrFeature.getEmblFeature());
+
+ newFeatures = duplicateFeatures(chadoGene.getOtherFeaturesOfTranscript(transcriptName), newchadoGene);
+ for(uk.ac.sanger.artemis.Feature otherFeature: newFeatures)
+ newchadoGene.addOtherFeatures(newTranscriptName, otherFeature.getEmblFeature());
+
+ newFeatures = duplicateFeatures(chadoGene.getSplicedFeaturesOfTranscript(transcriptName), newchadoGene);
+ for(uk.ac.sanger.artemis.Feature splicedFeature: newFeatures)
+ newchadoGene.addSplicedFeatures(newTranscriptName, splicedFeature.getEmblFeature());
+
+ uk.ac.sanger.artemis.Feature newProtein =
+ duplicateFeature(chadoGene.getProteinOfTranscript(transcriptName), newchadoGene);
+ if(newProtein != null)
+ newchadoGene.addProtein(newTranscriptName, newProtein.getEmblFeature());
+ }
+ }
+ catch (ReadOnlyException e) {}
+ }
+
+ duplicatedGenes.clear();
+ return newGenes;
+ }
+
+
+ private static List<uk.ac.sanger.artemis.Feature> duplicateFeatures(
+ final List<Feature> featuresOfTranscript,
+ final ChadoCanonicalGene chadoGene)
+ throws ReadOnlyException
+ {
+ final List<uk.ac.sanger.artemis.Feature> newFeatures =
+ new Vector<uk.ac.sanger.artemis.Feature>();
+
+ if(featuresOfTranscript == null)
+ return newFeatures;
+
+ for(int i=0; i<featuresOfTranscript.size(); i++)
+ newFeatures.add(duplicateFeature(
+ (GFFStreamFeature)featuresOfTranscript.get(i), chadoGene));
+ return newFeatures;
+ }
+
+ private static uk.ac.sanger.artemis.Feature duplicateFeature(
+ final Feature feature, final ChadoCanonicalGene chadoGene)
+ throws ReadOnlyException
+ {
+ if(feature == null)
+ return null;
+ uk.ac.sanger.artemis.Feature newFeature =
+ ((uk.ac.sanger.artemis.Feature)feature.getUserData()).duplicate(true);
+ ((GFFStreamFeature)newFeature.getEmblFeature()).setChadoGene(chadoGene);
+ if(isHiddenFeature(newFeature.getKey().getKeyString()))
+ ((GFFStreamFeature)newFeature.getEmblFeature()).setVisible(false);
+ return newFeature;
+ }
+
+ /**
+ * Create gene model from base selection
+ * @param frame
+ * @param selection
+ * @param entry_group
+ * @param goto_event_source
+ */
+ public static void createGeneModel(final JFrame frame,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GotoEventSource goto_event_source)
+ {
+ if(!SelectionMenu.checkForSelectionRange(frame, selection))
+ return;
+ final MarkerRange range = selection.getMarkerRange ();
+ final Entry default_entry = entry_group.getDefaultEntry ();
+
+ if (default_entry == null)
+ {
+ new MessageDialog (frame, "There is no default entry");
+ return;
+ }
+
+ QualifierVector qualifiers = new QualifierVector();
+ final String uniquename = promptForUniquename(entry_group,
+ range.isForwardMarker());
+ final Qualifier qualifier = new Qualifier("ID", uniquename);
+ qualifiers.add(qualifier);
+
+ try
+ {
+ final FeatureVector newFeatures = new FeatureVector();
+ final Location new_location = range.createLocation ();
+ final Key key = new Key("gene");
+ final uk.ac.sanger.artemis.Feature geneFeature =
+ default_entry.createFeature(key, new_location, qualifiers);
+ newFeatures.add(geneFeature);
+
+ final ChadoCanonicalGene chadoGene = new ChadoCanonicalGene();
+ chadoGene.setGene(geneFeature.getEmblFeature());
+ ((uk.ac.sanger.artemis.io.GFFStreamFeature)
+ (geneFeature.getEmblFeature())).setChadoGene(chadoGene);
+
+ // create transcript
+ uk.ac.sanger.artemis.Feature transcript =
+ GeneViewerPanel.createTranscript(chadoGene, entry_group);
+ newFeatures.add(transcript);
+ ((uk.ac.sanger.artemis.io.GFFStreamFeature)
+ (transcript.getEmblFeature())).setChadoGene(chadoGene);
+ final String transcriptId =
+ (String)transcript.getQualifierByName("ID").getValues().get(0);
+
+ // add exon
+ GFFStreamFeature exonFeature =
+ GeneViewerPanel.addExonFeature(chadoGene, entry_group,
+ null, new_location.getTotalRange(), transcriptId, selection,
+ new Key(DatabaseDocument.EXONMODEL), null);
+
+ // add protein
+ uk.ac.sanger.artemis.Feature polypep =
+ GeneViewerPanel.addProteinFeature(chadoGene, entry_group, transcriptId, transcript);
+ newFeatures.add(polypep);
+
+ // add inferred CDS
+ if(DatabaseDocument.CHADO_INFER_CDS)
+ DatabaseInferredFeature.createFeature(transcriptId, exonFeature,
+ chadoGene, entry_group.getDefaultEntry());
+
+ showHideGeneFeatures(newFeatures);
+ selection.clear();
+ selection.add(polypep);
+
+ EditMenu.editSelectedFeatures(entry_group, selection,
+ goto_event_source, polypep, null, null);
+ }
+ catch(ReadOnlyException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ catch(OutOfRangeException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Prompt the user for an ID
+ * @return
+ */
+ public static String promptForUniquename(final EntryGroup entry_group,
+ final boolean is_forward)
+ {
+ final Entry default_entry = entry_group.getDefaultEntry ();
+ String id = null;
+
+ if(default_entry.getEMBLEntry() instanceof
+ uk.ac.sanger.artemis.io.DatabaseDocumentEntry ||
+ default_entry.getEMBLEntry() instanceof
+ uk.ac.sanger.artemis.io.GFFDocumentEntry)
+ {
+ while(id == null ||
+ id.equals("") ||
+ id.equals("to_be_set"))
+ {
+ String msg = "Provide a unique ID ";
+
+ if(!is_forward)
+ msg = msg + "for reverse strand : ";
+ else
+ msg = msg + ": ";
+
+ id = JOptionPane.showInputDialog(null,
+ msg,
+ "ID missing ",
+ JOptionPane.QUESTION_MESSAGE).trim();
+
+ if(!isUniqueID(entry_group, id))
+ {
+ JOptionPane.showMessageDialog(null,
+ "ID "+id+" not unique.\nEnter a unique ID.",
+ "ID Not Unique",
+ JOptionPane.WARNING_MESSAGE);
+ id = null;
+ }
+ }
+ }
+ return id;
+ }
+
+ /**
+ * Prompt the user for an ID and provide a default automated ID
+ * based on the range.
+ * @param entry_group
+ * @param is_forward
+ * @param range
+ * @return
+ */
+ public static String promptForUniquename(final EntryGroup entry_group,
+ final boolean is_forward,
+ final Range range)
+ {
+ final Entry default_entry = entry_group.getDefaultEntry ();
+ String id = null;
+
+ if(default_entry.getEMBLEntry() instanceof
+ uk.ac.sanger.artemis.io.DatabaseDocumentEntry)
+ {
+
+ while(id == null ||
+ id.equals("") ||
+ id.equals("to_be_set"))
+ {
+ String msg = "Provide a unique ID ";
+
+ if(!is_forward)
+ msg = msg + "for reverse strand : ";
+ else
+ msg = msg + ": ";
+
+
+ id = JOptionPane.showInputDialog(null, msg,
+ default_entry.getName()+":"+
+ range.getStart()+".."+
+ range.getEnd());
+
+ if(!isUniqueID(entry_group, id))
+ {
+ JOptionPane.showMessageDialog(null,
+ "ID "+id+" not unique.\nEnter a unique ID.",
+ "ID Not Unique",
+ JOptionPane.WARNING_MESSAGE);
+ id = null;
+ }
+ }
+ }
+ return id;
+ }
+
+
+ /**
+ * Test to ensure ID (chado uniquename) is unique.
+ * @param entry_group
+ * @param id
+ * @return
+ */
+ private static boolean isUniqueID(final EntryGroup entry_group,
+ final String id)
+ {
+ final FeaturePredicate predicate =
+ new FeatureKeyQualifierPredicate(null, "ID", id,
+ false, true);
+ final FeatureVector features = entry_group.getAllFeatures();
+ for(int i=0; i<features.size(); i++)
+ {
+ uk.ac.sanger.artemis.Feature feature = features.elementAt(i);
+ if(predicate.testPredicate(feature))
+ return false;
+
+ }
+ return true;
+ }
+
+ /**
+ * Given an group of entries determine if they contain a database entry
+ * @param entryGroup
+ * @return
+ */
+ public static boolean isDatabaseEntry(final EntryGroup entryGroup)
+ {
+ final EntryVector entries = entryGroup.getActiveEntries();
+
+ for(int i=0; i<entries.size(); i++)
+ {
+ if( entries.elementAt(i).getEMBLEntry() instanceof DatabaseDocumentEntry )
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Given an group of entries determine if they contain a GFF entry
+ * @param entryGroup
+ * @return
+ */
+ public static boolean isGFFEntry(final EntryGroup entryGroup)
+ {
+ final EntryVector entries = entryGroup.getActiveEntries();
+
+ for(int i=0; i<entries.size(); i++)
+ {
+ if( entries.elementAt(i).getEMBLEntry() instanceof GFFDocumentEntry )
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Given a feature determine if it belongs to a database entry
+ * @param entryGroup
+ * @return
+ */
+ public static boolean isDatabaseEntry(final Feature feature)
+ {
+ if( feature.getEntry() instanceof DatabaseDocumentEntry &&
+ ((GFFStreamFeature)feature).getDocumentEntry().getDocument() instanceof DatabaseDocument)
+ return true;
+
+ return false;
+ }
+
+
+ private static void deleteFeature(uk.ac.sanger.artemis.Feature feature)
+ throws ReadOnlyException
+ {
+ if(feature != null && feature.getEntry() != null)
+ feature.removeFromEntry();
+ }
+
+ public static void deleteAllFeature(uk.ac.sanger.artemis.Feature feature,
+ final ChadoCanonicalGene chado_gene) throws ReadOnlyException
+ {
+ deleteAllFeature(feature, chado_gene, true);
+ }
+
+ /**
+ * Delete feature and children in a chado gene model
+ * @param feature
+ * @param chado_gene
+ * @throws ReadOnlyException
+ */
+ public static void deleteAllFeature(uk.ac.sanger.artemis.Feature feature,
+ final ChadoCanonicalGene chado_gene, final boolean updateChadoCanonicalGene) throws ReadOnlyException
+ {
+ Set<Feature> children = chado_gene.getChildren(feature.getEmblFeature());
+ deleteFeature(feature);
+ if(updateChadoCanonicalGene)
+ chado_gene.deleteFeature(feature.getEmblFeature());
+
+ Feature embl_feature;
+ Iterator<Feature> it = children.iterator();
+
+ while(it.hasNext())
+ {
+ embl_feature = it.next();
+ deleteFeature((uk.ac.sanger.artemis.Feature) embl_feature.getUserData());
+ if(updateChadoCanonicalGene)
+ chado_gene.deleteFeature(embl_feature);
+ }
+ }
+
+ /**
+ * Check gene model strands for any inconsistencies
+ * @param chado_gene
+ * @return true is gene model ranges are correct
+ */
+ public static boolean isStrandOK(final ChadoCanonicalGene chado_gene)
+ {
+ boolean isRev = chado_gene.getGene().getLocation().isComplement();
+ final List<Feature> transcripts = chado_gene.getTranscripts();
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ final Feature transcript = (Feature)transcripts.get(i);
+
+ if(isRev ^ transcript.getLocation().isComplement())
+ return false;
+
+ final Feature protein =
+ chado_gene.getProteinOfTranscript(GeneUtils.getUniqueName(transcript));
+ if(protein != null && (isRev ^ protein.getLocation().isComplement()))
+ return false;
+
+ final Set<Feature> children = chado_gene.getChildren(transcript);
+ final Iterator<Feature> it = children.iterator();
+ while(it.hasNext())
+ {
+ final Feature feature = it.next();
+ if(isRev ^ feature.getLocation().isComplement())
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check gene model boundaries for any inconsistencies
+ * @param chado_gene
+ * @return 0 - if consisent
+ * 1 - if transcript start or end is outside gene range
+ * 2 - if child feature of a transcript is outside the transcript range
+ * 3 - if the span of the children features does not match start and end of the transcript
+ * 4 - if the protein range does not match CDS
+ * 5 - if the gene range does not match the largest transcript range
+ */
+ public static int isBoundaryOK(final ChadoCanonicalGene chado_gene)
+ {
+ final Range geneRange = chado_gene.getGene().getLocation().getTotalRange();
+ final List<Feature> transcripts = chado_gene.getTranscripts();
+ int geneStart = Integer.MAX_VALUE;
+ int geneEnd = -1;
+
+ for(Feature transcript: transcripts)
+ {
+ final Range transcriptRange = transcript.getLocation().getTotalRange();
+ int transcriptStart = Integer.MAX_VALUE;
+ int transcriptEnd = -1;
+ int ppStart = Integer.MAX_VALUE;
+ int ppEnd = -1;
+
+ if(transcriptRange.getStart() < geneRange.getStart() ||
+ transcriptRange.getEnd() > geneRange.getEnd())
+ return 1;
+
+ if(transcriptRange.getStart() < geneStart)
+ geneStart = transcriptRange.getStart();
+ if(transcriptRange.getEnd() > geneEnd)
+ geneEnd = transcriptRange.getEnd();
+
+ final Feature protein =
+ chado_gene.getProteinOfTranscript(GeneUtils.getUniqueName(transcript));
+ String proteinName = null;
+ if(protein != null)
+ proteinName = GeneUtils.getUniqueName(protein);
+
+ final Set<Feature> children = chado_gene.getChildren(transcript);
+ final Iterator<Feature> it = children.iterator();
+ while(it.hasNext())
+ {
+ final Feature feature = it.next();
+ final Range childRange = feature.getLocation().getTotalRange();
+ if(childRange.getStart() < transcriptRange.getStart() ||
+ childRange.getEnd() > transcriptRange.getEnd())
+ return 2;
+
+ if(proteinName != null &&
+ GeneUtils.getUniqueName(feature).equals(proteinName))
+ continue;
+
+ if(childRange.getStart() < transcriptStart)
+ transcriptStart = childRange.getStart();
+ if(childRange.getEnd() > transcriptEnd )
+ transcriptEnd = childRange.getEnd();
+
+ String keyStr = feature.getKey().getKeyString();
+ if( (DatabaseDocument.CHADO_INFER_CDS && keyStr.equals("CDS")) ||
+ (!DatabaseDocument.CHADO_INFER_CDS && keyStr.equals(DatabaseDocument.EXONMODEL)) ||
+ feature.getKey().equals("pseudogenic_exon"))
+ {
+ if(childRange.getStart() < ppStart)
+ ppStart = childRange.getStart();
+ if(childRange.getEnd() > ppEnd )
+ ppEnd = childRange.getEnd();
+ }
+ }
+
+ if((transcriptRange.getStart() != transcriptStart && transcriptStart < Integer.MAX_VALUE) ||
+ (transcriptRange.getEnd() != transcriptEnd && transcriptEnd > -1))
+ return 3;
+
+ if(protein != null)
+ {
+ final Range proteinRange = protein.getLocation().getTotalRange();
+ if((proteinRange.getStart() != ppStart && ppStart < Integer.MAX_VALUE) ||
+ (proteinRange.getEnd() != ppEnd && ppEnd > -1))
+ return 4;
+ }
+ }
+
+ // check gene range
+ if((geneRange.getStart() != geneStart && geneStart < Integer.MAX_VALUE) ||
+ (geneRange.getEnd() != geneEnd && geneEnd > -1))
+ return 5;
+
+ return 0;
+ }
+
+ public static void checkGeneBoundary(final ChadoCanonicalGene chado_gene)
+ {
+ checkGeneBoundary(chado_gene, true);
+ }
+
+ protected static Range checkTranscriptBoundary(
+ final uk.ac.sanger.artemis.Feature transcript,
+ final ChadoCanonicalGene chado_gene)
+ {
+ return checkTranscriptBoundary(transcript, chado_gene, true);
+ }
+
+ /**
+ * Adjust transcript and gene boundaries
+ * @param chado_gene
+ */
+ public static void checkGeneBoundary(final ChadoCanonicalGene chado_gene, final boolean changeEmblFeature)
+ {
+ final List<Feature> transcripts = chado_gene.getTranscripts();
+ int gene_start = Integer.MAX_VALUE;
+ int gene_end = -1;
+
+ Range range;
+ for(Feature transcript: transcripts)
+ {
+ range = checkTranscriptBoundary(
+ (uk.ac.sanger.artemis.Feature)transcript.getUserData(), chado_gene, changeEmblFeature);
+ if(range != null && range.getStart() < gene_start)
+ gene_start = range.getStart();
+ if(range != null && range.getEnd() > gene_end)
+ gene_end = range.getEnd();
+ }
+
+ if(gene_end == -1 && gene_start == Integer.MAX_VALUE)
+ return;
+
+ setLocation(chado_gene.getGene(), gene_start, gene_end, changeEmblFeature);
+ }
+
+ /**
+ * Check and adjust transcript boundary
+ * @param transcript
+ * @param chado_gene
+ * @param changeEmblFeature
+ * @return
+ */
+ protected static Range checkTranscriptBoundary(
+ final uk.ac.sanger.artemis.Feature transcript,
+ final ChadoCanonicalGene chado_gene,
+ final boolean changeEmblFeature)
+ {
+ final List transcripts = chado_gene.getTranscripts();
+
+ if(transcripts.contains(transcript.getEmblFeature()))
+ {
+ checkProteinBoundary(transcript.getEmblFeature(), chado_gene, changeEmblFeature);
+
+ final Set children = chado_gene.getChildren(transcript.getEmblFeature());
+ int transcript_start = Integer.MAX_VALUE;
+ int transcript_end = -1;
+
+ final Iterator it = children.iterator();
+ while(it.hasNext())
+ {
+ final Feature feature = (Feature) it.next();
+ final Range range = feature.getLocation().getTotalRange();
+ if(range.getStart() < transcript_start)
+ transcript_start = range.getStart();
+ if(range.getEnd() > transcript_end)
+ transcript_end = range.getEnd();
+ }
+
+ if(transcript_start == Integer.MAX_VALUE ||
+ transcript_end == -1)
+ return null;
+
+ return setLocation(transcript.getEmblFeature(),
+ transcript_start, transcript_end, changeEmblFeature);
+ }
+ else
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript and try again.", "Transcript Selection",
+ JOptionPane.ERROR_MESSAGE);
+ return null;
+ }
+
+ /**
+ * Check and adjust protein boundary
+ * @param transcript
+ * @param chado_gene
+ */
+ private static void checkProteinBoundary(final Feature transcript,
+ final ChadoCanonicalGene chado_gene,
+ final boolean changeEmblFeature)
+ {
+ final String transcriptName = getUniqueName(transcript);
+ final Feature protein = chado_gene.getProteinOfTranscript(transcriptName);
+ if(protein == null)
+ return;
+
+ int pp_start = Integer.MAX_VALUE;
+ int pp_end = -1;
+
+ final List<Feature> dnaFeatures = new Vector<Feature>();
+/* if(chado_gene.get3UtrOfTranscript(transcriptName) != null)
+ dnaFeatures.addAll(chado_gene.get3UtrOfTranscript(transcriptName));
+ if(chado_gene.get5UtrOfTranscript(transcriptName) != null)
+ dnaFeatures.addAll(chado_gene.get5UtrOfTranscript(transcriptName));*/
+
+ List<Feature> exons;
+ if(DatabaseDocument.CHADO_INFER_CDS)
+ exons = chado_gene.getSpliceSitesOfTranscript(transcriptName, "CDS");
+ else
+ exons = chado_gene.getSpliceSitesOfTranscript(transcriptName, DatabaseDocument.EXONMODEL);
+ if(exons != null)
+ dnaFeatures.addAll(exons);
+
+ exons = chado_gene.getSpliceSitesOfTranscript(transcriptName, "pseudogenic_exon");
+ if(exons != null)
+ dnaFeatures.addAll(exons);
+
+ for(Feature dnaFeature: dnaFeatures)
+ {
+ final Range range = dnaFeature.getLocation().getTotalRange();
+ if(range.getStart() < pp_start)
+ pp_start = range.getStart();
+ if(range.getEnd() > pp_end)
+ pp_end = range.getEnd();
+ }
+
+ if(pp_start == Integer.MAX_VALUE || pp_end == -1)
+ return;
+
+ setLocation(protein, pp_start, pp_end, changeEmblFeature);
+ }
+
+ /**
+ * For a feature propagate the uniquename as the prefix for
+ * the associated children.
+ * @param gene
+ * @param newName
+ * @param children
+ */
+ public static void propagateId(final GFFStreamFeature feature,
+ final String newName,
+ final Set children)
+ {
+ final ChadoCanonicalGene gene = feature.getChadoGene();
+ final Iterator it = children.iterator();
+ while(it.hasNext())
+ {
+ final GFFStreamFeature child = (GFFStreamFeature)it.next();
+ final Hashtable segmentHash = child.getSegmentRangeStore();
+
+ final String oldId = getUniqueName(child);
+
+ final Set childrenOfChild = gene.getChildren(child);
+ int index = oldId.lastIndexOf('.');
+
+ if(index == -1)
+ index = oldId.indexOf(':');
+
+ if(index > -1)
+ {
+ final String newId;
+ if( segmentHash != null &&
+ ( segmentHash.size() > 1 ||
+ child.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL)))
+ {
+ final Set idKeys = segmentHash.keySet();
+ final Hashtable newSegmentHash = new Hashtable(idKeys.size());
+ final Iterator itKeys = idKeys.iterator();
+ final Hashtable newIdMapToOldId = new Hashtable(idKeys.size());
+ while(itKeys.hasNext())
+ {
+ String oldKey = (String)itKeys.next();
+ index = oldKey.lastIndexOf('.');
+ if(index == -1)
+ index = oldKey.indexOf(':');
+ final String newKey = newName + oldKey.substring(index);
+ Object range = segmentHash.get(oldKey);
+
+ newSegmentHash.put(newKey, range);
+ newIdMapToOldId.put(newKey, oldKey);
+ }
+ child.setSegmentRangeStore(newSegmentHash);
+ child.setNewIdMapToOldId(newIdMapToOldId);
+ newId = child.getSegmentID(child.getLocation().getRanges());
+ }
+ else
+ newId = newName + oldId.substring(index);
+ try
+ {
+ ((uk.ac.sanger.artemis.Feature) child.getUserData())
+ .setQualifier(new Qualifier("ID", newId));
+ gene.updateUniqueName(oldId, newId, childrenOfChild);
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * Fix parent (Parent and Derives_from) qualifiers. Used when the
+ * uniqueName of the parent has been changed.
+ * @param oldName
+ * @param newName
+ * @param children
+ */
+ public static void fixParentQualifier(final String oldName,
+ final String newName,
+ final Set children)
+ {
+ final Iterator it = children.iterator();
+ while(it.hasNext())
+ {
+ Feature child = (Feature)it.next();
+ QualifierVector qualifiers = child.getQualifiers();
+ if( qualifiers.getQualifierByName("Parent") != null &&
+ ((String)(qualifiers.getQualifierByName("Parent").getValues().get(0))).equals(oldName) )
+ {
+ qualifiers.removeQualifierByName("Parent");
+ qualifiers.setQualifier(new Qualifier("Parent", newName));
+ }
+ else if( qualifiers.getQualifierByName("Derives_from") != null &&
+ ((String)(qualifiers.getQualifierByName("Derives_from").getValues().get(0))).equals(oldName) )
+ {
+ qualifiers.removeQualifierByName("Derives_from");
+ qualifiers.setQualifier(new Qualifier("Derives_from", newName));
+ }
+ }
+ }
+
+ /**
+ * Converts a gene to pseudogene or vice-versa
+ * @param chado_gene
+ * @throws EntryInformationException
+ * @throws ReadOnlyException
+ * @throws OutOfRangeException
+ */
+ public static void convertPseudo(final ChadoCanonicalGene chado_gene)
+ throws ReadOnlyException, EntryInformationException, OutOfRangeException
+ {
+ final Key geneKey = chado_gene.getGene().getKey();
+
+ boolean convertToPseudogene = false;
+
+ uk.ac.sanger.artemis.Feature gene = (uk.ac.sanger.artemis.Feature)chado_gene.getGene().getUserData();
+ if(geneKey.equals("gene"))
+ {
+ gene.set(new Key("pseudogene"), gene.getLocation(), gene.getQualifiers());
+ convertToPseudogene = true;
+ }
+ else if(geneKey.equals("pseudogene"))
+ gene.set(new Key("gene"), gene.getLocation(), gene.getQualifiers());
+ else
+ return;
+
+ final List transcripts = chado_gene.getTranscripts();
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ final uk.ac.sanger.artemis.Feature transcript = (
+ uk.ac.sanger.artemis.Feature)((Feature)transcripts.get(i)).getUserData();
+ final String transcriptName = getUniqueName(transcript.getEmblFeature());
+ final List exons;
+ if(convertToPseudogene)
+ {
+ exons = chado_gene.getSpliceSitesOfTranscript(transcriptName,
+ DatabaseDocument.EXONMODEL);
+ transcript.set(new Key("pseudogenic_transcript"), transcript.getLocation(),
+ transcript.getQualifiers());
+ }
+ else
+ {
+ exons = chado_gene.getSpliceSitesOfTranscript(transcriptName,
+ "pseudogenic_exon");
+ transcript.set(new Key(DatabaseDocument.TRANSCRIPT), transcript.getLocation(), transcript.getQualifiers());
+ }
+
+ if(exons == null)
+ continue;
+
+ for(int j=0; j<exons.size(); j++)
+ {
+ final uk.ac.sanger.artemis.Feature exon = (uk.ac.sanger.artemis.Feature)((Feature)exons.get(j)).getUserData();
+ exon.resetColour();
+ if(convertToPseudogene)
+ exon.set(new Key("pseudogenic_exon"), exon.getLocation(), exon.getQualifiers());
+ else
+ exon.set(new Key(DatabaseDocument.EXONMODEL), exon.getLocation(), exon.getQualifiers());
+ }
+ }
+ }
+
+ private static Range setLocation(final Feature f,
+ final int start,
+ final int end,
+ final boolean changeEmblFeature)
+ {
+ try
+ {
+ final RangeVector ranges = new RangeVector();
+ final Range range = new Range(start, end);
+ ranges.add(range);
+
+ final Location new_location = new Location(ranges,
+ f.getLocation().isComplement());
+
+ if(changeEmblFeature)
+ f.setLocation(new_location);
+ else
+ ((uk.ac.sanger.artemis.Feature)f.getUserData()).setLocation(new_location);
+ return range;
+ }
+ catch (OutOfRangeException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ catch (ReadOnlyException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static String getUniqueName(final Feature feature)
+ {
+ try
+ {
+ return (String)feature.getQualifierByName("ID").getValues().get(0);
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Return an array on non-coding transcripts
+ * @return
+ */
+ public static String[] getNonCodingTranscripts()
+ {
+ return nonCodingTranscripts;
+ }
+
+ /**
+ * Test if the key given is a non-coding transcript key
+ * @param key
+ * @return
+ */
+ public static boolean isNonCodingTranscripts(final Key key)
+ {
+ for(int i=0; i<nonCodingTranscripts.length; i++)
+ if(nonCodingTranscripts[i].equals(key.getKeyString()))
+ return true;
+ return false;
+ }
+
+ public static String deriveResidues(final GFFStreamFeature gffFeature)
+ {
+ final ChadoCanonicalGene chadoGene = gffFeature.getChadoGene();
+
+ boolean isProteinFeature =
+ ((uk.ac.sanger.artemis.Feature)gffFeature.getUserData()).isProteinFeature();
+
+ String residues = null;
+ try
+ {
+ String transcriptName =
+ chadoGene.getTranscriptFromName(GeneUtils.getUniqueName(gffFeature));
+
+ List<Feature> splicedFeatures =
+ chadoGene.getSplicedFeaturesOfTranscript(transcriptName);
+ for (Feature emblFeature: splicedFeatures)
+ {
+ if (emblFeature.getKey().getKeyString().equals(
+ DatabaseDocument.EXONMODEL) ||
+ emblFeature.getKey().getKeyString().equals("pseudogenic_exon"))
+ {
+ uk.ac.sanger.artemis.Feature f =
+ (uk.ac.sanger.artemis.Feature) emblFeature.getUserData();
+ if (!isProteinFeature)
+ residues = f.getTranslationBases();
+ else
+ residues = f.getTranslation().toString();
+ }
+ }
+ }
+ catch (Exception e){ }
+
+ return residues;
+ }
+
+ public static FeatureForUpdatingResidues getFeatureForUpdatingResidues(
+ final GFFStreamFeature gffFeature)
+ {
+ if(!isFeatureToUpdateResidues(gffFeature.getKey().getKeyString()))
+ return null;
+ String residues = deriveResidues(gffFeature);
+ if(residues == null)
+ return null;
+ final FeatureForUpdatingResidues chadoFeature = new FeatureForUpdatingResidues();
+ chadoFeature.setStartBase(0);
+ chadoFeature.setLength(residues.length());
+ chadoFeature.setNewSubSequence(residues);
+ chadoFeature.setResidueUpdate(true);
+
+ if(gffFeature.getQualifierByName("feature_id") != null)
+ chadoFeature.setFeatureId( Integer.parseInt( (String)
+ gffFeature.getQualifierByName("feature_id").getValues().get(0)) );
+ else
+ {
+ chadoFeature.setFeatureId(-1);
+ chadoFeature.setUniqueName(getUniqueName(gffFeature));
+ }
+ chadoFeature.setSeqLen(residues.length());
+ return chadoFeature;
+ }
+
+ /**
+ * Look at the sequence_update_features property in the options
+ * file to see if this is a feature to update residues for.
+ * @param keyStr
+ * @return
+ */
+ public static boolean isFeatureToUpdateResidues(final String keyStr)
+ {
+ if(featuresToUpdateResidues == null)
+ return false;
+
+ return featuresToUpdateResidues.contains(keyStr);
+ }
+
+ public static void main(String args[])
+ {
+ GeneUtils.defineShowHideGeneFeatures(new FeatureVector());
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/GeneViewerPanel.java b/uk/ac/sanger/artemis/components/genebuilder/GeneViewerPanel.java
new file mode 100644
index 0000000..9f29cba
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/GeneViewerPanel.java
@@ -0,0 +1,1716 @@
+/* GeneViewerPanel
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/genebuilder/GeneViewerPanel.java,v 1.85 2009-06-18 14:59:05 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import javax.swing.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.Vector;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.FeatureSegment;
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.LastSegmentException;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.FeatureVector;
+
+import uk.ac.sanger.artemis.components.SegmentBorder;
+import uk.ac.sanger.artemis.io.DatabaseInferredFeature;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Feature;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.sequence.Marker;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+public class GeneViewerPanel extends MapPanel
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ /** pop up menu */
+ private JPopupMenu popup;
+
+ private float fraction;
+
+ private int start;
+
+ private MarkerRange click_range = null;
+
+ /**
+ * The last(FeatureSegment) Marker that the user clicked on. This is used
+ * for dragging the ends of segments.
+ **/
+ private Marker click_segment_marker = null;
+
+ /**
+ * This is true if click_segment_marker is the Marker at the start of
+ * segment false otherwise. The value is only useful if
+ * click_segment_marker is set.
+ **/
+ private boolean click_segment_marker_is_start_marker = false;
+
+ /**
+ * When a FeatureSegment Marker drag starts, this is set to the Marker at
+ * the other end of the segment. This is used to check that the drag has
+ * not move the Marker too far(past the end of the segment).
+ **/
+ private Marker other_end_of_segment_marker = null;
+
+ /** This is a record of the feature last clicked */
+ private uk.ac.sanger.artemis.Feature clicked_feature;
+
+ private Point last_cursor_position;
+
+ /**
+ * The shortcut for Delete Selected Features.
+ **/
+ final static KeyStroke DELETE_FEATURES_KEY =
+ KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+ final static public int DELETE_FEATURES_KEY_CODE = KeyEvent.VK_DELETE;
+
+ final static KeyStroke CREATE_FEATURES_KEY =
+ KeyStroke.getKeyStroke (KeyEvent.VK_C,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+
+ final static public int CREATE_FEATURES_KEY_CODE = KeyEvent.VK_C;
+
+ private GeneBuilderFrame gene_builder;
+
+ public GeneViewerPanel(final ChadoCanonicalGene chado_gene,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final GeneBuilderFrame gene_builder,
+ final JLabel status_line)
+ {
+ this.chado_gene = chado_gene;
+ this.selection = selection;
+ this.gene_builder = gene_builder;
+
+ Dimension dim = new Dimension(400,350);
+ setPreferredSize(dim);
+ setBackground(Color.white);
+
+// Popup menu
+ addMouseListener(new PopupListener());
+ popup = new JPopupMenu();
+ createMenus(popup, entry_group);
+
+ // Listen for mouse motion events so that we can select ranges of bases.
+ addMouseMotionListener(new MouseMotionAdapter()
+ {
+ public void mouseDragged(MouseEvent event)
+ {
+ if(event.isPopupTrigger() || entry_group == null ||
+ event.getButton() == MouseEvent.BUTTON3)
+ return;
+
+ int select_start = (int) ((event.getX() - border) / fraction) + start;
+
+ final Strand strand = ((uk.ac.sanger.artemis.Feature) (chado_gene.getGene()
+ .getUserData())).getStrand();
+
+ if(!strand.isForwardStrand())
+ select_start = strand.getBases().getComplementPosition(select_start);
+
+ if(click_segment_marker != null)
+ {
+ final int new_position;
+ MarkerRange drag_range;
+ try
+ {
+ drag_range = new MarkerRange(strand, select_start, select_start + 1);
+ }
+ catch(OutOfRangeException e1)
+ {
+ e1.printStackTrace();
+ return;
+ }
+
+ if(click_segment_marker_is_start_marker)
+ {
+ // the Marker is at the start of the segment
+ new_position = drag_range.getStart().getPosition();
+
+ // don't go past the other end of the segment
+ if(new_position > other_end_of_segment_marker.getPosition())
+ return;
+ }
+ else
+ {
+ new_position = drag_range.getEnd().getPosition();
+
+ // don't go past the other end of the segment
+ if(new_position < other_end_of_segment_marker.getPosition())
+ return;
+ }
+
+ try
+ {
+ click_segment_marker.setPosition(new_position);
+ gene_builder.setActiveFeature(clicked_feature, false);
+ return;
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected OutOfRangeException");
+ }
+ }
+
+ final MarkerRange selected_range = selection.getMarkerRange();
+
+ try
+ {
+ MarkerRange drag_range = new MarkerRange(strand, select_start,
+ select_start + 1);
+
+ // final MarkerRange new_marker_range;
+ if(selected_range == null || click_range == null)
+ {
+ click_range = drag_range;
+ status_line.setText("");
+ }
+ else
+ {
+ click_range = selected_range.combineRanges(drag_range, true);
+ status_line.setText(selected_range.getRawRange().getStart() + ".."
+ + selected_range.getRawRange().getEnd());
+ }
+
+ last_cursor_position = event.getPoint();
+ selection.setMarkerRange(click_range);
+
+ repaint();
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Create menu for gene editor
+ *
+ * @param menu
+ * @param entry_group
+ */
+ protected void createMenus(JComponent menu,
+ final EntryGroup entry_group)
+ {
+ JMenuItem deleteMenu = new JMenuItem("Delete Selected Features");
+ deleteMenu.setAccelerator(DELETE_FEATURES_KEY);
+ deleteMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ FeatureVector features = selection.getAllFeatures();
+ boolean delete = deleteFeatures(features, chado_gene);
+
+ try
+ {
+ if(delete)
+ {
+ for(int i = 0; i < features.size(); i++)
+ GeneUtils.deleteAllFeature(features.elementAt(i), chado_gene);
+ repaint();
+ }
+ else
+ gene_builder.getFeatureEdit().setObsoleteChanged(true);
+ }
+ catch(ReadOnlyException e)
+ {
+ JOptionPane.showMessageDialog(null,
+ e.getMessage(), "Read Only",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ });
+ menu.add(deleteMenu);
+
+
+ final JMenuItem deleteSegmentMenu = new JMenuItem("Delete Selected Exon");
+ deleteSegmentMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ final FeatureSegmentVector features = selection.getAllSegments();
+
+ if(features == null || features.size() < 1)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select an exon and try again.",
+ "Exon to delete not found!",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ else
+ {
+ for(int i=0; i<features.size(); i++)
+ {
+ final uk.ac.sanger.artemis.Feature f = features.elementAt(i).getFeature();
+ if(!f.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL) &&
+ !f.getKey().getKeyString().equals("pseudogenic_exon") )
+ {
+ JOptionPane.showMessageDialog(null,
+ "Other feature types are selected.\nSelect an exon and try again.",
+ "Select exon to delete!",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ }
+ }
+
+ uk.ac.sanger.artemis.FeatureSegment segment = null;
+ int option = JOptionPane.showConfirmDialog(null,
+ "Delete Selected Exon",
+ "Delete Selected Exon",
+ JOptionPane.OK_CANCEL_OPTION);
+
+ if(option == JOptionPane.CANCEL_OPTION)
+ return;
+ try
+ {
+ uk.ac.sanger.artemis.Feature feature = selection.getAllFeatures().elementAt(0);
+ for(int i = 0; i < features.size(); i++)
+ {
+ segment = features.elementAt(i);
+ segment.removeFromFeature();
+ selection.remove(segment);
+ }
+
+ //selection.add(feature);
+ gene_builder.setActiveFeature(feature, false);
+ repaint();
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(LastSegmentException e)
+ {
+ try
+ {
+ GeneUtils.deleteAllFeature(segment.getFeature(), chado_gene);
+ }
+ catch(ReadOnlyException e1)
+ {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }
+ });
+ menu.add(deleteSegmentMenu);
+
+ menu.add(new JSeparator());
+ final JMenuItem createTranscript = new JMenuItem("Create transcript");
+ createTranscript.setAccelerator(CREATE_FEATURES_KEY);
+ createTranscript.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ createTranscript(chado_gene, entry_group);
+ repaint();
+ }
+ });
+ menu.add(createTranscript);
+
+
+ final JMenuItem duplicateTranscript = new JMenuItem("Duplicate selected transcript");
+ duplicateTranscript.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ duplicateTranscript(entry_group);
+ repaint();
+ }
+ });
+ menu.add(duplicateTranscript);
+
+
+ final JMenu createFeatureMenu = new JMenu("Add to transcript in selected range");
+
+ final JMenuItem createExon = new JMenuItem("exon");
+ createExon.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(last_cursor_position == null)
+ return;
+ Feature transcript = getTranscriptAt(last_cursor_position);
+
+ if(transcript == null)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript to add an exon to and try again.",
+ "Transcript Not Found",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ String uniquename = getQualifier(transcript, "ID");
+
+ final List exons;
+ final Key exonKey;
+ if(chado_gene.getGene().getKey().getKeyString().equals("pseudogene"))
+ {
+ exons = chado_gene.getSpliceSitesOfTranscript(uniquename, "pseudogenic_exon");
+ exonKey = new Key("pseudogenic_exon");
+ }
+ else
+ {
+ exons = chado_gene.getSpliceSitesOfTranscript(uniquename, DatabaseDocument.EXONMODEL);
+ exonKey = new Key(DatabaseDocument.EXONMODEL);
+ /*
+ exons = chado_gene.getSpliceSitesOfTranscript(uniquename, "exon");
+ exonKey = new Key("exon");
+ */
+ }
+
+ GFFStreamFeature embl_exon = null;
+ if(exons != null && exons.size() > 0)
+ embl_exon = (GFFStreamFeature)exons.get(0);
+
+ Range range_selected = selection.getSelectionRange();
+
+ addExonFeature(chado_gene, entry_group, embl_exon,
+ range_selected, uniquename, selection, exonKey, gene_builder);
+ }
+ });
+ createFeatureMenu.add(createExon);
+
+
+
+ JMenuItem createFeature5Utr = new JMenuItem("5'UTR");
+ createFeature5Utr.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ addDnaFeature(last_cursor_position, selection,
+ entry_group, new Key("five_prime_UTR"));
+ }
+ });
+
+
+ JMenuItem createFeature3Utr = new JMenuItem("3'UTR");
+ createFeature3Utr.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ addDnaFeature(last_cursor_position, selection,
+ entry_group, new Key("three_prime_UTR"));
+ }
+ });
+
+
+ JMenuItem createFeatureDna = new JMenuItem("region");
+ createFeatureDna.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ addDnaFeature(last_cursor_position, selection,
+ entry_group, new Key("region"));
+ }
+ });
+
+
+
+ JMenuItem createFeatureProtein = new JMenuItem("polypeptide");
+ createFeatureProtein.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(selection.getAllFeatures().size() < 1)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript and try again.",
+ "Transcript Selection",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ uk.ac.sanger.artemis.Feature transcript = selection.getAllFeatures().elementAt(0);
+ final String transcriptName;
+ try
+ {
+ transcriptName = (String)transcript.getQualifierByName("ID").getValues().get(0);
+ }
+ catch(InvalidRelationException e1)
+ {
+ e1.printStackTrace();
+ return;
+ }
+
+ if(!chado_gene.isTranscript(transcriptName))
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript and try again.",
+ "Transcript Selection",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ addProteinFeature(chado_gene, entry_group, transcriptName, transcript);
+ }
+ });
+ createFeatureMenu.add(createFeatureProtein);
+
+ if(!DatabaseDocument.CHADO_INFER_CDS)
+ {
+ createFeatureMenu.add(createFeature5Utr);
+ createFeatureMenu.add(createFeature3Utr);
+ }
+ createFeatureMenu.add(createFeatureDna);
+
+ menu.add(createFeatureMenu);
+
+ menu.add(new JSeparator());
+ JMenuItem adjustCoords = new JMenuItem("Fix selected transcript coordinates");
+ adjustCoords.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ FeatureVector features = selection.getAllFeatures();
+ if(features.size() != 1)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript and try again.",
+ "Transcript Selection",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ final uk.ac.sanger.artemis.Feature transcript = features.elementAt(0);
+ GeneUtils.checkTranscriptBoundary(transcript, chado_gene);
+ gene_builder.dispose(true);
+ }
+ });
+ menu.add(adjustCoords);
+
+
+ JMenuItem adjustGeneCoords = new JMenuItem("Fix gene model coordinates");
+ adjustGeneCoords.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ GeneUtils.checkGeneBoundary(chado_gene);
+ gene_builder.dispose(true);
+ }
+ });
+ menu.add(adjustGeneCoords);
+
+ menu.add(new JSeparator());
+
+ final JMenuItem convertPsuedoMenu = new JMenuItem("Convert gene to/from pseudogene");
+ convertPsuedoMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ try
+ {
+ GeneUtils.convertPseudo(chado_gene);
+ gene_builder.dispose(true);
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ });
+ menu.add(convertPsuedoMenu);
+ }
+
+ /**
+ * Delete or make features obsolete
+ */
+ protected static Boolean deleteFeatures(FeatureVector features,
+ ChadoCanonicalGene chado_gene)
+ {
+ final String feature_count_string;
+ if (features.size () < 2)
+ feature_count_string = "the selected feature";
+ else
+ feature_count_string = features.size () + " features";
+
+ Box boption = Box.createVerticalBox();
+
+ JCheckBox delete = new JCheckBox("permanently delete",
+ !Options.getOptions().getPropertyTruthValue("set_obsolete_on_delete"));
+ boption.add(new JLabel("Make "+feature_count_string+" obsolete?"));
+ boption.add(delete);
+ int option = JOptionPane.showConfirmDialog(null,
+ boption, "Make obsolete",
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+ if(option == JOptionPane.CANCEL_OPTION)
+ return null;
+ return delete.isSelected();
+ }
+
+ public static uk.ac.sanger.artemis.Feature
+ createTranscript(final ChadoCanonicalGene chadoGene,
+ final EntryGroup entry_group)
+ {
+ return createTranscript(chadoGene, entry_group, chadoGene.getGene().getLocation());
+ }
+
+ public static uk.ac.sanger.artemis.Feature
+ createTranscript(final ChadoCanonicalGene chadoGene,
+ final EntryGroup entry_group,
+ final Location location)
+ {
+ try
+ {
+ String gene_name =
+ (String)chadoGene.getGene().getQualifierByName("ID").getValues().get(0);
+
+ String ID = chadoGene.autoGenerateTanscriptName(DatabaseDocument.TRANSCRIPT);
+
+ QualifierVector qualifiers = new QualifierVector();
+ qualifiers.add(new Qualifier("Parent", gene_name));
+
+ if(ID != null)
+ qualifiers.add(new Qualifier("ID", ID));
+
+ final Key transcriptKey;
+ if(chadoGene.getGene().getKey().getKeyString().equals("pseudogene"))
+ transcriptKey = new Key("pseudogenic_transcript");
+ else
+ transcriptKey = new Key(DatabaseDocument.TRANSCRIPT);
+
+ uk.ac.sanger.artemis.Feature feature = createFeature(
+ location, entry_group, transcriptKey,
+ qualifiers);
+
+ ((GFFStreamFeature)(feature.getEmblFeature())).setChadoGene(chadoGene);
+ chadoGene.addTranscript(feature.getEmblFeature());
+ return feature;
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ /**
+ *
+ */
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2d = (Graphics2D)g;
+ Feature embl_gene = (Feature)chado_gene.getGene();
+ uk.ac.sanger.artemis.Feature gene =
+ (uk.ac.sanger.artemis.Feature)embl_gene.getUserData();
+
+ setFont(uk.ac.sanger.artemis.Options.getOptions().getFont());
+
+ start = embl_gene.getFirstBase();
+ int end = embl_gene.getLastBase();
+ g2d.setColor( gene.getColour() );
+ int ypos = border;
+
+ // draw gene
+ g2d.drawString(gene.getIDString()+
+ ( GeneUtils.isObsolete((GFFStreamFeature)embl_gene) ? " (obsolete)" : "") , border, ypos);
+ drawFeature(g2d, border,
+ getSize().width - border,
+ ypos, gene.getColour(), 1,
+ selection.contains(gene), 2.f,
+ getFontHeight());
+
+ List transcripts = chado_gene.getTranscripts();
+ fraction = (float)(getSize().width - (2*border))/
+ (float)(end-start);
+
+ ypos += border*2;
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ drawTranscriptOnLine(g2d, (Feature)transcripts.get(i),
+ start, end, ypos,
+ fraction, selection, chado_gene,
+ getFontHeight());
+
+ if(i != transcripts.size()-1)
+ ypos += getTranscriptSize();
+ }
+
+ // draw mouse drag selection
+ if(selection.getMarkerRange() != null &&
+ last_cursor_position != null)
+ {
+ Range range = selection.getSelectionRange();
+
+ int ntranscript = (last_cursor_position.y - (border*3))/getTranscriptSize();
+ if(ntranscript < transcripts.size())
+ {
+ int select_start = border+(int)((range.getStart()-start)*fraction);
+ int select_end = border+(int)((range.getEnd()-start)*fraction);
+ ypos = (border*5)+(getTranscriptSize()*ntranscript);
+ drawFeature(g2d, select_start, select_end,
+ ypos, Color.YELLOW, 1.5f,
+ false, 2.f, getFontHeight());
+ }
+ }
+ setPreferredSize(new Dimension(getSize().width, ypos+border));
+ }
+
+ /**
+ * Duplicate the selected transcript and children
+ * @param entry_group
+ */
+ private void duplicateTranscript(final EntryGroup entry_group)
+ {
+ FeatureVector features = selection.getAllFeatures();
+ Feature transcript = null;
+
+ if(features.size() == 1)
+ transcript = features.elementAt(0).getEmblFeature();
+
+ if(transcript == null ||
+ transcript.getKey().getKeyString().indexOf(DatabaseDocument.TRANSCRIPT) == -1)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a single transcript and try again.",
+ "Transcript Selection",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ final uk.ac.sanger.artemis.Feature newTranscript =
+ createTranscript(chado_gene, entry_group, transcript.getLocation());
+ String newTranscriptName = getQualifier(newTranscript.getEmblFeature(), "ID");
+ last_cursor_position = getPointFromTranscriptName(newTranscriptName);
+
+ Set childFeatures = chado_gene.getChildren(transcript);
+ Iterator it = childFeatures.iterator();
+ while(it.hasNext())
+ {
+ Feature f = (Feature)it.next();
+ if(f.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL) ||
+ f.getKey().getKeyString().equals("pseudogenic_exon") )
+ {
+ GFFStreamFeature gff_exon = null;
+ RangeVector ranges = f.getLocation().getRanges();
+
+ for(int i=0; i<ranges.size(); i++)
+ gff_exon = addExonFeature(chado_gene, entry_group, gff_exon,
+ (Range)ranges.get(i), newTranscriptName, selection, f.getKey(), gene_builder);
+ }
+ else if(!f.getKey().equals("polypeptide"))
+ {
+ selection.clear();
+ selection.add((uk.ac.sanger.artemis.Feature) f.getUserData());
+ addDnaFeature(last_cursor_position, selection, entry_group, f.getKey());
+ }
+ }
+
+ addProteinFeature(chado_gene, entry_group, newTranscriptName,
+ newTranscript);
+ }
+
+ /**
+ * Create and return a new Artemis feature.
+ * @param new_location location of the feature
+ * @param entry_group group to create feature in
+ * @param key the new features key
+ * @param qualifiers the new features qualifiers
+ * @return
+ */
+ private static uk.ac.sanger.artemis.Feature createFeature(
+ final Location new_location,
+ final EntryGroup entry_group,
+ final Key key,
+ final QualifierVector qualifiers)
+ {
+ final Entry default_entry = entry_group.getDefaultEntry();
+ uk.ac.sanger.artemis.Feature new_feature = null;
+
+ try
+ {
+ new_feature = default_entry.createFeature(key,
+ new_location, qualifiers);
+ return new_feature;
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ return new_feature;
+ }
+
+
+ /**
+ * Return the closest transcript feature from a given point on the
+ * panel.
+ * @param p
+ * @return
+ */
+ private Feature getTranscriptAt(Point p)
+ {
+ List transcripts = chado_gene.getTranscripts();
+
+ int ntranscript = (p.y - (border*3))/getTranscriptSize();
+ if(ntranscript < transcripts.size())
+ return (Feature)transcripts.get(ntranscript);
+
+ return (Feature)transcripts.get(transcripts.size()-1);
+ }
+
+ /**
+ * Given a point on the panel find the feature drawn at that
+ * location.
+ * @param p
+ * @return
+ */
+ private Object getFeatureAt(Point p)
+ {
+ if(p.y <= border+getFontHeight())
+ return chado_gene.getGene();
+
+ List transcripts = chado_gene.getTranscripts();
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ if(p.y >= (border*3)+(getTranscriptSize()*i) &&
+ p.y <= (border*3)+(getTranscriptSize()*i)+getFontHeight())
+ {
+ return (Feature)transcripts.get(i);
+ }
+ else if(p.y >= (border*3)+(getTranscriptSize()*i)+getFontHeight() &&
+ p.y <= (border*3)+(getTranscriptSize()*(i+1)))
+ {
+ Feature feature = (Feature)transcripts.get(i);
+ String transcript_name = getQualifier(feature, "ID");
+ List splicedFeatures = chado_gene.getSplicedFeaturesOfTranscript(transcript_name);
+
+ if(splicedFeatures != null)
+ {
+ for(int j = 0; j < splicedFeatures.size(); j++)
+ {
+ FeatureSegmentVector segments = ((uk.ac.sanger.artemis.Feature) ((Feature)
+ splicedFeatures.get(j)).getUserData()).getSegments();
+
+ if(segments.size() == 0)
+ return (Feature)splicedFeatures.get(j);
+
+ for(int k = 0; k < segments.size(); k++)
+ {
+ FeatureSegment segment = segments.elementAt(k);
+ Range segment_range = segment.getRawRange();
+
+ int segment_start = border
+ + (int) ((segment_range.getStart() - start) * fraction);
+ int segment_end = border
+ + (int) ((segment_range.getEnd() - start) * fraction);
+ if(p.x >= segment_start && p.x <= segment_end)
+ return segment;
+ }
+ }
+ }
+
+ final List utr_3 = chado_gene.get3UtrOfTranscript(transcript_name);
+ final List utr_5 = chado_gene.get5UtrOfTranscript(transcript_name);
+ final List utrs = new Vector();
+ if(utr_3 != null)
+ utrs.addAll(utr_3);
+ if(utr_5 != null)
+ utrs.addAll(utr_5);
+
+ for(int j = 0; j < utrs.size(); j++)
+ {
+ Feature utr = (Feature) utrs.get(j);
+ Range range = utr.getLocation().getTotalRange();
+ int utr_start = border
+ + (int) ((range.getStart() - start) * fraction);
+ int utr_end = border + (int) ((range.getEnd() - start) * fraction);
+ if(p.x >= utr_start && p.x <= utr_end)
+ return utr;
+ }
+
+
+ // anything else
+ List others = chado_gene.getOtherFeaturesOfTranscript(transcript_name);
+ if(others != null)
+ {
+ for(int j=0; j<others.size(); j++)
+ {
+ Feature other = (Feature)others.get(j);
+ Range range = other.getLocation().getTotalRange();
+ int r_start = border
+ + (int) ((range.getStart() - start) * fraction);
+ int r_end = border
+ + (int) ((range.getEnd() - start) * fraction);
+ if(p.x >= r_start && p.x <= r_end)
+ return other;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the Point for a given transcript name
+ * @param transcriptName
+ * @return
+ */
+ private Point getPointFromTranscriptName(final String transcriptName)
+ {
+ final List transcripts = chado_gene.getTranscripts();
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ String name = GeneUtils.getUniqueName((Feature)transcripts.get(i));
+ if( name.equals(transcriptName) )
+ return new Point(1, (border*3)+(getTranscriptSize()*i));
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Draw the transcript and child features.
+ * @param g2d
+ * @param embl_transcript
+ * @param start
+ * @param end
+ * @param ypos
+ * @param fraction
+ */
+ protected static void drawTranscriptOnLine(Graphics2D g2d, Feature embl_transcript,
+ final int start, final int end, int ypos,
+ float fraction, Selection selection,
+ ChadoCanonicalGene chado_gene,
+ int fontHeight)
+ {
+ BasicStroke stroke = new BasicStroke(48.f);
+ g2d.setStroke(stroke);
+
+ uk.ac.sanger.artemis.Feature transcript =
+ (uk.ac.sanger.artemis.Feature)embl_transcript.getUserData();
+
+ int t_start = border+(int)((embl_transcript.getFirstBase()-start)*fraction);
+ int t_end = border+(int)((embl_transcript.getLastBase()-start)*fraction);
+
+ g2d.setColor( transcript.getColour() );
+
+ g2d.drawString(transcript.getIDString() +
+ ( GeneUtils.isObsolete((GFFStreamFeature)embl_transcript) ? " (obsolete)" : ""), border, ypos);
+ drawFeature(g2d, t_start, t_end,
+ ypos, transcript.getColour(), 1,
+ selection.contains(transcript), 2.f, fontHeight);
+
+ //List exons = chado_gene.getSpliceSitesOfTranscript(
+ // getQualifier( embl_transcript, "ID" ), "exon");
+
+ Set spliceSiteTypes = chado_gene.getSpliceTypes(
+ getQualifier( embl_transcript, "ID" ));
+ ypos += border*2;
+
+ if(spliceSiteTypes != null)
+ {
+ Iterator it = spliceSiteTypes.iterator();
+
+ while(it.hasNext())
+ {
+ final String type = (String)it.next();
+ List splicedFeatures = chado_gene.getSpliceSitesOfTranscript(
+ getQualifier( embl_transcript, "ID" ), type);
+
+ boolean last_segment = false;
+
+ // build from artemis objects
+ for(int i = 0; i < splicedFeatures.size(); i++)
+ {
+ int last_ex_start = 0;
+ int last_ex_end = 0;
+ int last_ypos = 0;
+
+ Feature embl_exon = (Feature) splicedFeatures.get(i);
+ if(embl_exon instanceof DatabaseInferredFeature)
+ continue;
+
+ uk.ac.sanger.artemis.Feature exon = (uk.ac.sanger.artemis.Feature) embl_exon
+ .getUserData();
+
+ RangeVector ranges = exon.getLocation().getRanges();
+ FeatureSegmentVector segments = null;
+
+ try
+ {
+ segments = exon.getSegments();
+ }
+ catch(NullPointerException npe)
+ {
+ }
+
+ float selected_size;
+ for(int j = 0; j < ranges.size(); j++)
+ {
+ Range range = (Range) ranges.get(j);
+
+ int ex_start = border
+ + (int) ((range.getStart() - start) * fraction);
+ int ex_end = border + (int) ((range.getEnd() - start) * fraction);
+
+ if(exon.getColour() != null)
+ g2d.setColor(exon.getColour());
+
+ if(j == ranges.size() - 1)
+ last_segment = true;
+
+ selected_size = 2.f;
+ Color borderColor = Color.BLACK;
+ if(segments != null)
+ {
+ for(int k = 0; k < segments.size(); k++)
+ {
+ FeatureSegment segment = segments.elementAt(k);
+ if(range.equals(segment.getRawRange())
+ && selection.contains(segment))
+ {
+ selected_size = 4.f;
+ borderColor = SegmentBorder.HIGHLIGHT_BORDER_COLOUR;
+ }
+ }
+ }
+
+ drawExons(g2d, ex_start, ex_end, last_ex_start, last_ex_end,
+ last_ypos, 0, ypos, exon.getColour(), borderColor, 1.5f, exon
+ .isForwardFeature(), last_segment,
+ selection.contains(exon), selected_size, fontHeight);
+
+ last_ex_end = ex_end;
+ last_ex_start = ex_start;
+ last_ypos = ypos;
+ }
+ }
+ }
+ }
+
+ // draw utr's
+ String transcript_id = getQualifier( embl_transcript, "ID" );
+ List embl_utr = chado_gene.get3UtrOfTranscript(
+ transcript_id);
+
+ if(embl_utr != null)
+ drawFeatureList(g2d, embl_utr, ypos, selection, fontHeight, start, fraction);
+
+ embl_utr = chado_gene.get5UtrOfTranscript(
+ transcript_id);
+
+ if(embl_utr != null)
+ drawFeatureList(g2d, embl_utr, ypos, selection, fontHeight, start, fraction);
+
+ // draw other transcript child features
+ List embl_other = chado_gene.getOtherFeaturesOfTranscript(transcript_id);
+ if(embl_other != null)
+ drawFeatureList(g2d, embl_other, ypos, selection, fontHeight, start, fraction);
+ }
+
+ /**
+ * Method to draw UTR features
+ * @param g2d
+ * @param embl_utr
+ * @param ypos
+ */
+ private static void drawFeatureList(final Graphics2D g2d,
+ final List feature_list,
+ final int ypos,
+ Selection selection,
+ int fontHeight,
+ int start,
+ float fraction)
+ {
+ for(int i=0; i<feature_list.size(); i++)
+ {
+ Feature embl_feature = (Feature)feature_list.get(i);
+ uk.ac.sanger.artemis.Feature feature =
+ (uk.ac.sanger.artemis.Feature)embl_feature.getUserData();
+ RangeVector ranges = embl_feature.getLocation().getRanges();
+
+ if(feature == null)
+ feature = new uk.ac.sanger.artemis.Feature(embl_feature);
+
+
+ for(int j = 0; j < ranges.size(); j++)
+ {
+ Range range = (Range) ranges.get(j);
+
+ int r_start = border + (int) ((range.getStart() - start) * fraction);
+ int r_end = border + (int) ((range.getEnd() - start) * fraction);
+
+ drawFeature(g2d, r_start, r_end, ypos, feature.getColour(), 1.5f,
+ selection.contains(feature), 2.f, fontHeight);
+ }
+ }
+ }
+
+ /**
+ * Draw frame lines
+ * @param g2d
+ * @param ypos
+ * @param start
+ * @param end
+ * @param fraction
+ */
+ /*private void drawFrameLines(Graphics2D g2d, int ypos,
+ int start, int end, float fraction)
+ {
+ int offset;
+ g2d.setStroke(new BasicStroke(getFontHeight()));
+ for(int k=0; k<8; k++)
+ {
+ offset = (k * getFontHeight() * 2) + (getFontHeight()/2);
+ if(k == 3 || k == 4)
+ g2d.setColor( Color.LIGHT_GRAY );
+ else
+ g2d.setColor(light_grey);
+ g2d.drawLine(border, ypos+offset,
+ (int)((end-start)*fraction)+border, ypos+offset);
+ }
+ }*/
+
+ /**
+ * Draw exon features
+ * @param g2d
+ * @param ex_start
+ * @param ex_end
+ * @param last_ex_start
+ * @param last_ex_end
+ * @param last_ypos
+ * @param offset
+ * @param ypos
+ * @param exon_colour
+ * @param size
+ * @param isForward
+ * @param last_segment
+ * @param selected
+ * @param selected_size
+ */
+ private static void drawExons(Graphics2D g2d, int ex_start, int ex_end,
+ int last_ex_start, int last_ex_end, int last_ypos,
+ int offset, int ypos, Color exon_colour,
+ Color borderColor, float size,
+ boolean isForward, boolean last_segment,
+ boolean selected, float selected_size, int fontHeight)
+ {
+ drawFeature(g2d, ex_start, ex_end, ypos+offset, exon_colour, borderColor,
+ size, selected, selected_size, fontHeight);
+
+ // draw connections
+ if(last_ex_end != 0 ||
+ last_ex_start != 0)
+ {
+ BasicStroke stroke = new BasicStroke(1.f);
+ g2d.setStroke(stroke);
+
+ int ymid;
+ if(last_ypos < ypos+offset)
+ ymid = last_ypos;
+ else
+ ymid = ypos+offset;
+
+ if(isForward)
+ {
+ g2d.drawLine(last_ex_end, last_ypos,
+ last_ex_end+((ex_start-last_ex_end)/2), ymid-fontHeight/2);
+ g2d.drawLine(last_ex_end+((ex_start-last_ex_end)/2), ymid-fontHeight/2,
+ ex_start, ypos+offset);
+ }
+ else
+ {
+ g2d.drawLine(last_ex_start, last_ypos,
+ last_ex_start+((ex_end-last_ex_start)/2), ymid-fontHeight/2);
+ g2d.drawLine(last_ex_start+((ex_end-last_ex_start)/2), ymid-fontHeight/2,
+ ex_end, ypos+offset);
+ }
+ }
+
+ // draw arrow
+ if(last_segment)
+ {
+ BasicStroke stroke = new BasicStroke(1.f);
+ g2d.setStroke(stroke);
+
+ if(isForward)
+ {
+
+ g2d.drawLine(ex_end, ypos,
+ ex_end+fontHeight/2, ypos+(int)((fontHeight*size)/2));
+ g2d.drawLine(ex_end+fontHeight/2, ypos+(int)((fontHeight*size)/2),
+ ex_end, ypos+(int)((fontHeight*size)));
+ }
+ else
+ {
+ g2d.drawLine(ex_start, ypos,
+ ex_start-fontHeight/2, ypos+(int)((fontHeight*size)/2));
+ g2d.drawLine(ex_start-fontHeight/2, ypos+(int)((fontHeight*size)/2),
+ ex_start, ypos+(int)((fontHeight*size)));
+ }
+ }
+ }
+
+
+
+ /**
+ * Get the <code>Color</code> for a feature from its colour attribute.
+ * @param feature
+ * @return
+ */
+ /*private Color getColorFromAttributes(org.gmod.schema.sequence.Feature feature)
+ {
+ List properties = new Vector(feature.getFeatureProps());
+ for(int i=0; i<properties.size(); i++)
+ {
+ FeatureProp property = (FeatureProp)properties.get(i);
+
+ if(property.getCvTerm().getName().equals("colour") ||
+ property.getCvTerm().getName().equals("color") )
+ return Options.getOptions().getColorFromColourNumber(Integer.parseInt(property.getValue()));
+ }
+ return Color.CYAN;
+ }*/
+
+ /**
+ * Get the frame id for a feature segment
+ * @param chado_gene the chado representation of the gene model
+ * @param loc feature location of the feature
+ * @param nexon number of the exon
+ * @param exons List of exons
+ * @return frame id
+ */
+ /*private int getFrameID(ChadoCanonicalGene chado_gene,
+ FeatureLoc loc,
+ int nexon, List exons)
+ {
+ final int position_on_strand;
+
+ if(loc.getStrand().shortValue() == -1)
+ position_on_strand = chado_gene.getSeqlen()-loc.getFmax().intValue();
+ else
+ position_on_strand = loc.getFmin().intValue();
+
+ // this will be 0, 1 or 2 depending on which frame the segment is in
+ final int start_base_modulo =
+ (position_on_strand + getFrameShift(nexon, exons, chado_gene, loc)) % 3;
+
+ if(loc.getStrand().shortValue() == 1)
+ {
+ switch (start_base_modulo)
+ {
+ case 0:
+ return FeatureSegment.FORWARD_FRAME_1;
+ case 1:
+ return FeatureSegment.FORWARD_FRAME_2;
+ case 2:
+ return FeatureSegment.FORWARD_FRAME_3;
+ }
+ }
+ else
+ {
+ switch (start_base_modulo)
+ {
+ case 0:
+ return FeatureSegment.REVERSE_FRAME_1;
+ case 1:
+ return FeatureSegment.REVERSE_FRAME_2;
+ case 2:
+ return FeatureSegment.REVERSE_FRAME_3;
+ }
+ }
+
+ return FeatureSegment.NO_FRAME;
+ }*/
+
+
+ /**
+ * Returns 0, 1 or 2 depending on which translation frame this segment is
+ * in. A frame shift of zero means that the bases should be translated
+ * starting at the start position of this segment, 1 means start
+ * translating one base ahead of the start position and 2 means start
+ * translating two bases ahead of the start position.
+ **/
+ /*private int getFrameShift(int nexon, List exons,
+ ChadoCanonicalGene chado_gene,
+ FeatureLoc loc)
+ {
+ // find the number of bases in the segments before this one
+ int base_count = 0;
+ int direction = 0;
+
+ for(int i = 0; i < exons.size(); ++i)
+ {
+ org.gmod.schema.sequence.Feature this_feature =
+ (org.gmod.schema.sequence.Feature)exons.get(i);
+ FeatureLoc featureLoc = uk.ac.sanger.artemis.util.DatabaseDocument.getFeatureLoc(
+ new Vector(this_feature.getFeatureLocsForFeatureId()), chado_gene.getSrcfeature_id());
+
+ int this_direction;
+ if(featureLoc.getStrand().shortValue() == 1)
+ this_direction = 1;
+ else
+ this_direction = -1;
+
+ if(i == nexon)
+ {
+ if(i != 0 && this_direction != direction)
+ base_count = 0;
+
+ break;
+ }
+ else
+ {
+ if(i == 0)
+ direction = this_direction;
+ else if(this_direction != direction)
+ base_count = 0;
+
+ base_count += featureLoc.getFmax().intValue()-featureLoc.getFmin().intValue();
+ }
+ }
+
+ int codon_start = loc.getPhase().shortValue();
+ int mod_value = (base_count + 3 - codon_start) % 3;
+
+ if(mod_value == 1)
+ return 2;
+ else if(mod_value == 2)
+ return 1;
+ else
+ return 0;
+ }*/
+
+
+ protected static uk.ac.sanger.artemis.Feature addFeature(
+ final Range range,
+ final String transcriptName,
+ String featureName,
+ final boolean isComplement,
+ final boolean isParent,
+ final ChadoCanonicalGene chado_gene,
+ final EntryGroup entry_group,
+ final Key key, final String keyName)
+ throws OutOfRangeException
+ {
+ if(range == null)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Select a range and try again.",
+ "Range Selection",
+ JOptionPane.ERROR_MESSAGE);
+ return null;
+ }
+
+ if(featureName == null)
+ {
+ final String autoId =
+ chado_gene.autoGenerateFeatureName(transcriptName, keyName);
+ featureName = JOptionPane.showInputDialog(null,
+ "Provide a feature unique name : ", autoId);
+
+ if(featureName == null || featureName.equals(""))
+ return null;
+ }
+
+ QualifierVector qualifiers = new QualifierVector();
+
+ qualifiers.add(new Qualifier("ID", featureName.trim()));
+
+ if(isParent)
+ {
+ //key = new Key("region");
+ qualifiers.add(new Qualifier("Parent", transcriptName));
+ }
+ else
+ {
+ //key = new Key("polypeptide");
+ qualifiers.add(new Qualifier("Derives_from", transcriptName));
+ }
+
+ //final Entry default_entry = entry_group.getDefaultEntry();
+ //final Key default_key =
+ // default_entry.getEntryInformation().getDefaultKey();
+
+ uk.ac.sanger.artemis.Feature newFeature = createFeature(
+ new Location(new RangeVector(range), isComplement),
+ entry_group, key,
+ qualifiers);
+
+ if(GeneUtils.isHiddenFeature(key.getKeyString()))
+ ((GFFStreamFeature)(newFeature.getEmblFeature())).setVisible(false);
+
+ ((GFFStreamFeature)(newFeature.getEmblFeature())).setChadoGene(chado_gene);
+
+ if(isParent)
+ chado_gene.addOtherFeatures(transcriptName,
+ newFeature.getEmblFeature());
+ else
+ chado_gene.addProtein(transcriptName,
+ newFeature.getEmblFeature());
+ return newFeature;
+ }
+
+
+ private void addDnaFeature(final Point last_cursor_position,
+ final Selection selection,
+ final EntryGroup entry_group,
+ final Key key)
+ {
+ if(last_cursor_position == null)
+ return;
+
+ final String name;
+ if(key.getKeyString().equals("five_prime_UTR"))
+ name = "5UTR";
+ else if(key.getKeyString().equals("three_prime_UTR"))
+ name = "3UTR";
+ else
+ name = key.getKeyString();
+
+ Feature transcript = getTranscriptAt(last_cursor_position);
+ String transcriptName = getQualifier(transcript, "ID");
+ Range range_selected = selection.getSelectionRange();
+
+
+ try
+ {
+ uk.ac.sanger.artemis.Feature newFeature =
+ addFeature(range_selected, transcriptName, null,
+ transcript.getLocation().isComplement(),
+ true, chado_gene, entry_group, key, name);
+ if(newFeature != null)
+ {
+ selection.clear();
+ selection.add(newFeature);
+ gene_builder.setActiveFeature(newFeature, false);
+ }
+ }
+ catch(OutOfRangeException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public static uk.ac.sanger.artemis.Feature
+ addProteinFeature(final ChadoCanonicalGene chadoGene,
+ final EntryGroup entry_group,
+ final String transcriptName,
+ final uk.ac.sanger.artemis.Feature transcript)
+ {
+ final List exons;
+ if(chadoGene.getGene().getKey().getKeyString().equals("pseudogene"))
+ exons = chadoGene.getSpliceSitesOfTranscript(transcriptName, "pseudogenic_exon");
+ else
+ exons = chadoGene.getSpliceSitesOfTranscript(transcriptName, DatabaseDocument.EXONMODEL);
+
+ final Range range_selected;
+ if(exons != null && exons.size() > 0)
+ range_selected = ((GFFStreamFeature)exons.get(0)).getLocation().getTotalRange();
+ else
+ range_selected = transcript.getLocation().getTotalRange();
+
+ final String pepName = chadoGene.autoGeneratePepName(transcriptName);
+
+ try
+ {
+ return addFeature(range_selected, transcriptName, pepName,
+ transcript.getLocation().isComplement(), false, chadoGene, entry_group,
+ new Key("polypeptide"), "pep");
+ }
+ catch(OutOfRangeException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Create an exons feature
+ * @param feature
+ * @param range
+ * @param transcript_name
+ */
+ public static GFFStreamFeature addExonFeature(
+ final ChadoCanonicalGene chadoGene,
+ final EntryGroup entry_group,
+ final GFFStreamFeature feature, Range range,
+ final String transcript_name,
+ final Selection selection,
+ final Key exonKey,
+ final GeneBuilderFrame gene_builder)
+ {
+ try
+ {
+ if(feature == null)
+ {
+ QualifierVector qualifiers = new QualifierVector();
+ qualifiers.add(new Qualifier("Parent", transcript_name));
+
+ final String ID = chadoGene.autoGenerateSplicedFeatureName(transcript_name);
+
+ if(ID != null)
+ qualifiers.add(new Qualifier("ID", ID));
+
+ uk.ac.sanger.artemis.Feature exon = createFeature(
+ new Location(new RangeVector(range),
+ chadoGene.getGene().getLocation().isComplement()),
+ entry_group, exonKey,
+ qualifiers);
+
+ GFFStreamFeature gff_exon = (GFFStreamFeature)exon.getEmblFeature();
+ chadoGene.addSplicedFeatures(transcript_name, gff_exon);
+ ((GFFStreamFeature)(exon.getEmblFeature())).setChadoGene(chadoGene);
+
+ if(ID != null)
+ {
+ final Hashtable id_range_store = new Hashtable();
+ id_range_store.put(ID, range);
+ gff_exon.setSegmentRangeStore(id_range_store);
+ }
+ return gff_exon;
+ }
+ else
+ {
+ // add new ID
+ final RangeVector ranges = new RangeVector();
+ ranges.add(range);
+ GeneUtils.addSegment(feature, ranges, transcript_name);
+ final QualifierVector old_qualifiers = feature.getQualifiers().copy();
+ ((uk.ac.sanger.artemis.Feature)feature.getUserData()).addSegment(range, old_qualifiers);
+
+ if(gene_builder != null)
+ gene_builder.setActiveFeature((uk.ac.sanger.artemis.Feature)feature.getUserData(), false);
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return feature;
+ }
+
+
+
+
+ private static String getQualifier(Feature feature, String name)
+ {
+ Qualifier qualifier = null;
+ try
+ {
+ qualifier = feature.getQualifierByName(name);
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ if(qualifier == null)
+ return null;
+
+ return (String) (qualifier.getValues().get(0));
+ }
+
+ /**
+ * Popup listener
+ */
+ class PopupListener extends MouseAdapter
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ click_segment_marker = null;
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ maybeShowPopup(e);
+ click_segment_marker = null;
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ {
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ else if(e.getButton() != MouseEvent.BUTTON3 &&
+ e.getClickCount() == 1)
+ {
+ if(selection.getMarkerRange() != null &&
+ e.isShiftDown())
+ return;
+
+ click_range = null;
+
+ if(!e.isShiftDown())
+ selection.clear();
+ Object feature = getFeatureAt(e.getPoint());
+ if(feature == null)
+ return;
+
+ if(e.isShiftDown())
+ {
+ if(feature instanceof Feature)
+ selection.add(
+ (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData());
+ else
+ selection.add((FeatureSegment)feature);
+ }
+ else
+ {
+ if(feature instanceof Feature)
+ {
+ selection.set(
+ (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData());
+
+ boolean isSet = true;
+ if(((Feature)feature).isReadOnly())
+ isSet = false;
+
+ gene_builder.setActiveFeature(
+ (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData(), isSet);
+ }
+ else
+ {
+ selection.set((FeatureSegment)feature);
+ gene_builder.setActiveFeature(((FeatureSegment)feature).getFeature(), true);
+ }
+ }
+
+
+ // the following is used by mouseDragged() to edit feature locations
+ if(feature instanceof Feature)
+ clicked_feature = (uk.ac.sanger.artemis.Feature)((Feature)feature).getUserData();
+ else
+ clicked_feature = ((FeatureSegment)feature).getFeature();
+ click_segment_marker = null;
+
+ if(Options.getOptions().canDirectEdit() &&
+ !clicked_feature.isReadOnly() &&
+ clicked_feature.getEntry().getBases() != null)
+ {
+ // search the feature to find if the start Marker of any of the
+ // segments matches the start Marker of new_click_range or if an end
+ // Marker matches the end Marker of new_click_range
+
+ final FeatureSegmentVector segments = clicked_feature.getSegments();
+
+ for(int k = 0; k < segments.size(); k++)
+ {
+ FeatureSegment this_segment = segments.elementAt(k);
+ Range segment_range = this_segment.getRawRange();
+
+ int segment_start = border
+ + (int) ((segment_range.getStart() - start) * fraction);
+ int segment_end = border
+ + (int) ((segment_range.getEnd() - start) * fraction);
+
+ // allow very ends of features to be dragable
+ if( (e.getPoint().x >= segment_start && e.getPoint().x < segment_start+3 && e.getPoint().x <= segment_end) ||
+ (e.getPoint().x <= segment_end && e.getPoint().x > segment_end-3 && e.getPoint().x >= segment_start) )
+ {
+ int segment_mid = (segment_start+segment_end)/2;
+
+ if((e.getPoint().x < segment_mid && this_segment.isForwardSegment()) ||
+ (e.getPoint().x > segment_mid && !this_segment.isForwardSegment()))
+ {
+ click_segment_marker = this_segment.getStart();
+ click_segment_marker_is_start_marker = true;
+ other_end_of_segment_marker = this_segment.getEnd();
+ }
+ else
+ {
+ click_segment_marker = this_segment.getEnd();
+ click_segment_marker_is_start_marker = false;
+ other_end_of_segment_marker = this_segment.getStart();
+ }
+ }
+ }
+
+ }
+ repaint();
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/JExtendedComboBox.java b/uk/ac/sanger/artemis/components/genebuilder/JExtendedComboBox.java
new file mode 100644
index 0000000..bc32cac
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/JExtendedComboBox.java
@@ -0,0 +1,258 @@
+/* JExtendedComboBox.java
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.text.JTextComponent;
+
+import org.gmod.schema.cv.CvTerm;
+
+
+/**
+ * JComboBox with horizontal scrollbar
+ */
+public class JExtendedComboBox extends JComboBox
+{
+ private static final long serialVersionUID = 1L;
+ public static String SEPARATOR = "SEPARATOR";
+ private int current;
+ private boolean highLightCurrent = false;
+
+ /**
+ * @param str
+ * @param useAutoComplete set true to use auto-complete
+ */
+ public JExtendedComboBox(String str[], final boolean useAutoComplete)
+ {
+ super(str);
+ init(useAutoComplete);
+ }
+
+ /**
+ * @param str
+ * @param useAutoComplete set true to use auto-complete
+ */
+ public JExtendedComboBox(Vector<?> vector, final boolean useAutoComplete)
+ {
+ super(vector);
+ init(useAutoComplete);
+ }
+
+ /**
+ * @param str
+ * @param useAutoComplete set true to use auto-complete
+ */
+ public JExtendedComboBox(final boolean useAutoComplete)
+ {
+ super();
+ init(useAutoComplete);
+ }
+
+ public JExtendedComboBox(String str[])
+ {
+ this(str, false);
+ }
+
+ public JExtendedComboBox(Vector<?> vector)
+ {
+ this(vector, false);
+ }
+
+ /**
+ * Set up renderer, auto-complete and horizontal scroll bar
+ * @param useAutoComplete
+ */
+ private void init(final boolean useAutoComplete)
+ {
+ setRenderer(new ComboBoxRenderer());
+
+ if(useAutoComplete)
+ {
+ setEditable(true);
+ JTextComponent editor = (JTextComponent) getEditor().getEditorComponent();
+ editor.setDocument(new AutoCompleteComboDocument(this));
+ }
+
+ addPopupMenuListener(new ComboPopupMenuLister());
+ //setUI(new ComboUI());
+ }
+
+ class ComboPopupMenuLister implements PopupMenuListener
+ {
+ public void popupMenuCanceled(PopupMenuEvent e){}
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e){}
+
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e)
+ {
+ Object comp = getUI().getAccessibleChild(JExtendedComboBox.this, 0);
+ if (!(comp instanceof JPopupMenu))
+ return;
+
+ JComponent scrollPane = (JComponent) ((JPopupMenu) comp).getComponent(0);
+ if (scrollPane instanceof JScrollPane)
+ {
+ JScrollPane sp = (JScrollPane) scrollPane;
+ sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ }
+ }
+ }
+
+ class ComboBoxRenderer extends JLabel implements ListCellRenderer
+ {
+ private static final long serialVersionUID = 1L;
+ private JSeparator separator;
+
+ public ComboBoxRenderer()
+ {
+ setOpaque(true);
+ setBorder(new EmptyBorder(1, 1, 1, 1));
+ separator = new JSeparator(JSeparator.HORIZONTAL);
+ }
+
+ public Component getListCellRendererComponent(
+ final JList list, final Object value,
+ final int index, final boolean isSelected,
+ final boolean cellHasFocus)
+ {
+ String str;
+ if(value instanceof String || value == null)
+ str = (value == null) ? "" : value.toString();
+ else
+ str = ((CvTerm)value).getName();
+
+ if (SEPARATOR.equals(str))
+ return separator;
+
+ if (isSelected)
+ {
+ setBackground(list.getSelectionBackground());
+ setForeground(list.getSelectionForeground());
+ }
+ else
+ {
+ setBackground(list.getBackground());
+ setForeground(list.getForeground());
+ }
+
+ Font f = list.getFont();
+ if(isHighLightCurrent() && index == getCurrent())
+ f = f.deriveFont(Font.BOLD);
+
+ setFont(f);
+ setText(str);
+ return this;
+ }
+ }
+
+
+ /*public class ComboUI extends BasicComboBoxUI
+ {
+ protected ComboPopup createPopup()
+ {
+ return new BasicComboPopup(comboBox)
+ {
+ private static final long serialVersionUID = 1L;
+
+ protected JScrollPane createScroller()
+ {
+ return new JScrollPane( list, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+ ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED );
+ }
+ };
+ }
+ }*/
+
+ public int getCurrent()
+ {
+ return current;
+ }
+
+ public void setCurrent(int current)
+ {
+ this.current = current;
+ }
+
+ public boolean isHighLightCurrent()
+ {
+ return highLightCurrent;
+ }
+
+ public void setHighLightCurrent(boolean highLightCurrent)
+ {
+ this.highLightCurrent = highLightCurrent;
+ }
+
+ public static void main(String args[])
+ {
+ /*
+ uk.ac.sanger.artemis.components.database.DatabaseEntrySource entry_source =
+ new uk.ac.sanger.artemis.components.database.DatabaseEntrySource();
+ entry_source.setLocation(true);
+ uk.ac.sanger.artemis.util.DatabaseDocument doc = entry_source.getDatabaseDocument();
+ Vector terms = doc.getCvTermsByCvName(
+ uk.ac.sanger.artemis.util.DatabaseDocument.PRODUCTS_TAG_CVNAME);
+ Collections.sort(terms,
+ new uk.ac.sanger.artemis.components.genebuilder.cv.CvTermsComparator());
+ */
+ final String options[] = { "<PREV", "CANCEL", "NEXT>"};
+
+ String[] terms = { "aaaa", "bbbb", "cccc" };
+ JExtendedComboBox term_list = new JExtendedComboBox(terms);
+ term_list.setCurrent(0);
+ term_list.setHighLightCurrent(true);
+
+ Box xbox = Box.createHorizontalBox();
+ xbox.add(term_list);
+
+ Dimension d = new Dimension(70,term_list.getPreferredSize().height);
+ term_list.setPreferredSize(d);
+ term_list.setMaximumSize(d);
+
+ JOptionPane.showOptionDialog(null, xbox,
+ "CV term selection",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ options,
+ options[2]);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/MapPanel.java b/uk/ac/sanger/artemis/components/genebuilder/MapPanel.java
new file mode 100644
index 0000000..05aa9c9
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/MapPanel.java
@@ -0,0 +1,123 @@
+/* MapPanel.java
+ *
+ * created: 2008
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.GradientPaint;
+import java.awt.Graphics2D;
+import java.awt.geom.RoundRectangle2D;
+
+import javax.swing.JPanel;
+
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+
+
+public class MapPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ protected static int border = 15;
+ protected ChadoCanonicalGene chado_gene;
+ protected Selection selection;
+
+ /**
+ * Draw rectangular box for a feature.
+ * @param g2d
+ * @param start start of feature
+ * @param end end of feature
+ * @param ypos y position
+ * @param colour feature colour
+ * @param size parameter to control the height of the feature
+ */
+ protected static void drawFeature(Graphics2D g2d, int start, int end,
+ int ypos, Color colour, float size,
+ boolean selected, float selected_size,
+ int fontHeight)
+ {
+ drawFeature(g2d, start, end, ypos, colour, Color.BLACK, size,
+ selected, selected_size, fontHeight);
+ }
+
+ /**
+ * Draw rectangular box for a feature.
+ * @param g2d
+ * @param start start of feature
+ * @param end end of feature
+ * @param ypos y position
+ * @param colour feature colour
+ * @param size parameter to control the height of the feature
+ */
+ protected static void drawFeature(Graphics2D g2d, int start, int end,
+ int ypos, Color colour, Color borderColor, float size,
+ boolean selected, float selected_size,
+ int fontHeight)
+ {
+ RoundRectangle2D e = new RoundRectangle2D.Float(start, ypos,
+ end-start,
+ fontHeight*size, 0, ypos);
+
+ if(colour == null)
+ colour = Color.BLACK;
+
+ GradientPaint gp = new GradientPaint(start, ypos,
+ colour,
+ start, ypos+( (fontHeight/2) * size ),
+ Color.white, true);
+ g2d.setPaint(gp);
+ g2d.fill(e);
+
+ if(selected)
+ g2d.setStroke(new BasicStroke(selected_size));
+ else
+ g2d.setStroke(new BasicStroke(1.f));
+
+ // draw boundary
+ g2d.setColor(borderColor);
+ g2d.draw(e);
+ }
+
+ protected int getFontHeight()
+ {
+ final FontMetrics fm = this.getFontMetrics(getFont());
+ return fm.getHeight();
+ }
+
+ /**
+ * Macro for getting the size of the transcipt and
+ * exon image.
+ * @return
+ */
+ protected int getTranscriptSize()
+ {
+ return (2 * border) + (getFontHeight() * 3);
+ }
+
+ protected int getViewerBorder()
+ {
+ return border;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/OpenSectionButton.java b/uk/ac/sanger/artemis/components/genebuilder/OpenSectionButton.java
new file mode 100644
index 0000000..5d62cfb
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/OpenSectionButton.java
@@ -0,0 +1,85 @@
+/* OpenSectionButton
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+
+ public class OpenSectionButton extends JButton
+ {
+ private static final long serialVersionUID = 1L;
+ private JComponent panel;
+
+ public OpenSectionButton(final String text, final JComponent panel)
+ {
+ super(text);
+ this.panel = panel;
+
+ Dimension size = new Dimension(55,20);
+ setForeground(GeneEditorPanel.STEEL_BLUE);
+ Font font = getFont();
+ font = font.deriveFont(Font.BOLD);
+ setFont(font);
+ setBorderPainted(false);
+ setOpaque(false);
+ setPreferredSize(size);
+ setMaximumSize(size);
+
+ addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ updateButton();
+ }
+ });
+ }
+
+ private void updateButton()
+ {
+ if(getText().equals("-"))
+ {
+ setText("+");
+ panel.setVisible(false);
+ }
+ else
+ {
+ setText("-");
+ panel.setVisible(true);
+ }
+ }
+
+ public void setOpen(final boolean isOpen)
+ {
+ if(isOpen && getText().equals("-"))
+ return;
+ if(!isOpen && getText().equals("+"))
+ return;
+ updateButton();
+ }
+
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/ProteinMapPanel.java b/uk/ac/sanger/artemis/components/genebuilder/ProteinMapPanel.java
new file mode 100644
index 0000000..550c55a
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/ProteinMapPanel.java
@@ -0,0 +1,569 @@
+/* ProteinMapPanel.java
+ *
+ * created: 2008
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Vector;
+
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.components.QualifierTextArea;
+import uk.ac.sanger.artemis.io.Feature;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierLazyLoading;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class ProteinMapPanel extends MapPanel
+{
+ private static final long serialVersionUID = 1L;
+ public static String[] TMHMM =
+ {
+ "membrane_structure",
+ "non_cytoplasm_location",
+ "non_cytoplasmic_polypeptide_region",
+ "transmembrane",
+ "transmembrane_polypeptide_region",
+ "cytoplasm_location",
+ "cytoplasmic_polypeptide_region"
+ };
+ protected static String GPI_ANCHORED = "GPI_anchor_cleavage_site";
+ //private static String PlasmoAP_SCORE = "PlasmoAP_score";
+ protected static String[] SIGNALP =
+ {
+ "signal_peptide"
+ //"SignalP_prediction",
+ //"signal_peptide_probability",
+ //"signal_anchor_probability"
+ };
+
+ public static String POLYPEPTIDE_DOMAIN = "polypeptide_domain";
+
+ protected static Vector<String> PROTEIN_MAP_ELEMENTS = new Vector<String>();
+ static
+ {
+ Collections.addAll(PROTEIN_MAP_ELEMENTS, TMHMM);
+ Collections.addAll(PROTEIN_MAP_ELEMENTS, SIGNALP);
+ PROTEIN_MAP_ELEMENTS.add(GPI_ANCHORED);
+ //PROTEIN_MAP_ELEMENTS.add(PlasmoAP_SCORE);
+ PROTEIN_MAP_ELEMENTS.add(POLYPEPTIDE_DOMAIN);
+ }
+
+ protected Hashtable<Rectangle, String> toolTipPositions = new Hashtable<Rectangle, String>();
+ protected Feature feature;
+
+ public ProteinMapPanel(final Feature feature,
+ final ChadoCanonicalGene chado_gene,
+ final Selection selection)
+ {
+ this.chado_gene = chado_gene;
+ this.selection = selection;
+ this.feature = feature;
+
+ Dimension dim = new Dimension(400,350);
+ setPreferredSize(dim);
+ setBackground(Color.white);
+ setToolTipText("");
+
+ addMouseMotionListener(new MouseMotionListener()
+ {
+ public void mouseDragged(MouseEvent e) {}
+
+ public void mouseMoved(MouseEvent e)
+ {
+ final String tt = ProteinMapPanel.this.getToolTipText(e);
+ if(tt != null && isDatabaseHyperLink(false,tt))
+ setCursor(new Cursor(Cursor.HAND_CURSOR));
+ else
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+
+ });
+
+ addMouseListener(new MouseAdapter()
+ {
+ public void mouseExited(MouseEvent e)
+ {
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+
+ public void mouseClicked(MouseEvent e)
+ {
+ final String tt = ProteinMapPanel.this.getToolTipText(e);
+ isDatabaseHyperLink(true,tt);
+ }
+ });
+ }
+
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+
+ if(this instanceof BasicProteinMapPanel)
+ return;
+
+ Graphics2D g2d = (Graphics2D)g;
+
+ QualifierVector qualifiers = feature.getQualifiers();
+
+ setFont(uk.ac.sanger.artemis.Options.getOptions().getFont());
+
+ Feature embl_gene = (Feature)chado_gene.getGene();
+ uk.ac.sanger.artemis.Feature gene =
+ (uk.ac.sanger.artemis.Feature)embl_gene.getUserData();
+
+ setFont(uk.ac.sanger.artemis.Options.getOptions().getFont());
+
+ int ypos = border;
+
+ int geneStart = embl_gene.getFirstBase();
+ int geneEnd = embl_gene.getLastBase();
+ float fraction = (float)(getSize().width - (2*border))/
+ (float)(geneEnd-geneStart);
+
+ final List<Feature> transcripts = chado_gene.getTranscripts();
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ Feature transcript = (Feature)transcripts.get(i);
+ String transcriptName = GeneUtils.getUniqueName(transcript);
+
+ Feature protein_embl_feature =
+ (Feature)chado_gene.getProteinOfTranscript(transcriptName);
+
+ if(protein_embl_feature == null)
+ continue;
+
+ uk.ac.sanger.artemis.Feature protein =
+ (uk.ac.sanger.artemis.Feature)protein_embl_feature.getUserData();
+
+ List<Feature> exons = chado_gene.getSpliceSitesOfTranscript(transcriptName,
+ DatabaseDocument.EXONMODEL);
+ if(exons == null || exons.size() == 0)
+ exons = chado_gene.getSpliceSitesOfTranscript(transcriptName,
+ "pseudogenic_exon");
+
+ if(exons == null || exons.size() == 0)
+ continue;
+
+ Feature exon_embl_feature = exons.get(0);
+
+ uk.ac.sanger.artemis.Feature exon =
+ (uk.ac.sanger.artemis.Feature)exon_embl_feature.getUserData();
+
+ int ppLength = exon.getTranslationBasesLength()/3;
+
+ int emblStart = protein_embl_feature.getFirstBase();
+ int emblEnd = protein_embl_feature.getLastBase();
+
+ // draw protein
+ g2d.drawString(protein.getIDString()+
+ ( GeneUtils.isObsolete((GFFStreamFeature)embl_gene) ? " (obsolete)" : "") , border, ypos);
+
+ int ppStart = border+(int)((emblStart-geneStart)*fraction);
+ int ppEnd = border+(int)((emblEnd-geneStart)*fraction);
+
+ drawFeature(g2d, ppStart, ppEnd,
+ ypos, protein.getColour(), 1,
+ selection.contains(gene), 2.f, getFontHeight());
+
+ //record position for tooltip
+ Rectangle r = new Rectangle(ppStart, ypos, ppEnd-ppStart, 1*getFontHeight());
+ toolTipPositions.put(r, protein.getIDString()+" length="+ppLength);
+
+ ypos += border*2;
+
+ ypos = drawDomain(protein_embl_feature,
+ g2d, ypos, ppStart, ppEnd, ppLength);
+
+ if(qualifiers.getQualifierByName(TMHMM[0]) != null)
+ {
+ g2d.drawString("Transmembrane Domains:", ppStart, ypos);
+ drawPrediction(protein_embl_feature,
+ g2d, ypos, ppStart, ppEnd, ppLength, TMHMM);
+ ypos += border * 2;
+ }
+
+ if(qualifiers.getQualifierByName(SIGNALP[0]) != null)
+ {
+ g2d.drawString("SignalP:", ppStart, ypos);
+ drawPrediction(protein_embl_feature,
+ g2d, ypos, ppStart, ppEnd, ppLength,
+ new String[]{ SIGNALP[0] });
+ ypos += border * 2;
+ }
+
+ Qualifier gpiAnchor;
+ if((gpiAnchor=qualifiers.getQualifierByName(GPI_ANCHORED)) != null)
+ {
+ g2d.drawString("GPI anchor cleavage site:", ppStart, ypos);
+ drawGPIArrow(g2d, gpiAnchor, ppStart, ppEnd, ppLength, ypos);
+ }
+
+ if(i != transcripts.size()-1)
+ ypos += border * 2;
+ }
+ setPreferredSize(new Dimension(getSize().width, ypos+border));
+ }
+
+ /**
+ * Draw polypeptide_domain
+ * @param feature
+ * @param g2d
+ * @param ypos
+ * @param ppStart
+ * @param ppEnd
+ * @param ppLength
+ * @return
+ */
+ protected int drawDomain(final Feature feature,
+ final Graphics2D g2d, int ypos,
+ final int ppStart, final int ppEnd, final int ppLength)
+ {
+ float fraction = (float)(ppEnd-ppStart)/
+ (float)(ppLength);
+
+ QualifierVector qualifiers = feature.getQualifiers();
+ for(int i = 0; i < qualifiers.size(); i++)
+ {
+ Qualifier qualifier = (Qualifier) qualifiers.get(i);
+ if(!qualifier.getName().equals(POLYPEPTIDE_DOMAIN))
+ continue;
+
+ if(qualifier instanceof QualifierLazyLoading)
+ ((QualifierLazyLoading) qualifier).setForceLoad(true);
+ StringVector values = qualifier.getValues();
+
+ Collections.sort(values);
+
+ String lastDomain = null;
+ for(int j = 0; j < values.size(); j++)
+ {
+ StringVector parts = StringVector.getStrings((String) values.get(j),
+ ";");
+
+ if(lastDomain == null || !lastDomain.equals((String) parts.get(0)))
+ lastDomain = (String) parts.get(0);
+
+
+ StringBuffer toolTipBuff = new StringBuffer();
+ for(int k = 0; k < parts.size(); k++)
+ toolTipBuff.append((String) parts.get(k)+"\n");
+
+ for(int k = 1; k < parts.size(); k++)
+ {
+ String part = (String) parts.get(k);
+
+ int domainCoords[] = getCoords(part);
+ if(domainCoords == null)
+ continue;
+
+ int start = ppStart + (int) (domainCoords[0] * fraction);
+ int end = ppStart + (int) (domainCoords[1] * fraction);
+
+ if(k==1)
+ g2d.drawString((String) parts.get(0), start, ypos);
+ else
+ g2d.drawString((String) parts.get(1), start, ypos);
+
+ final Color col = Color.GRAY;
+
+ drawFeature(g2d, start, end, ypos, col, 1, false, 2.f, getFontHeight());
+
+ //record position for tooltip
+ Rectangle r = new Rectangle(start, ypos, end-start, 1*getFontHeight());
+ toolTipPositions.put(r, toolTipBuff.toString());
+
+ ypos += border * 2;
+ }
+ }
+ }
+ return ypos;
+ }
+
+ /**
+ * Draw TMHMM
+ * @param feature
+ * @param g2d
+ * @param ypos
+ * @param ppStart
+ * @param ppEnd
+ * @param ppLength
+ */
+ protected void drawPrediction(final Feature feature,
+ final Graphics2D g2d, final int ypos,
+ final int ppStart, final int ppEnd, final int ppLength,
+ final String[] prediction)
+ {
+
+ float fraction = (float)(ppEnd-ppStart)/
+ (float)(ppLength);
+
+ final QualifierVector qualifiers = feature.getQualifiers();
+
+ for(int i=0;i<qualifiers.size(); i++)
+ {
+ Qualifier qualifier = (Qualifier) qualifiers.get(i);
+ String qualifierName = qualifier.getName();
+
+ // predictions
+ for(int j = 0; j < prediction.length; j++)
+ {
+ if(qualifierName.equals(prediction[j]))
+ {
+ if(qualifier instanceof QualifierLazyLoading)
+ ((QualifierLazyLoading) qualifier).setForceLoad(true);
+
+ StringVector values = qualifier.getValues();
+ if(qualifierName.equals(TMHMM[0]))
+ continue;
+ for(int k = 0; k < values.size(); k++)
+ {
+ int coords[] = getCoords(((String)values.get(k)));
+ if(coords == null)
+ continue;
+
+ int start = ppStart+(int)(coords[0]*fraction);
+ int end = ppStart+(int)(coords[1]*fraction);
+
+ final Color col;
+ switch(j)
+ {
+ case 0: col = Color.BLUE; break;
+ case 1: col = Color.LIGHT_GRAY; break;
+ case 2: col = Color.GREEN; break;
+ default: col = Color.YELLOW; break;
+ }
+
+ drawFeature(g2d, start, end,
+ ypos, col, 1, false, 2.f, getFontHeight());
+
+
+ StringVector parts = StringVector.getStrings((String)values.get(k), ";");
+ String tt = qualifierName+" : "+coords[0]+".."+coords[1];
+ for(int l=0; l<parts.size(); l++)
+ if( ((String)parts.get(l)).indexOf("query") < 0 )
+ tt = tt.concat("\n"+(String)parts.get(l));
+ // record position for tooltip
+ Rectangle r = new Rectangle(start, ypos, end-start, 1*getFontHeight());
+ toolTipPositions.put(r, tt);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Draw GPI anchor site
+ * @param g2d
+ * @param gpiAnchor
+ * @param ppStart
+ * @param ppEnd
+ * @param ppLength
+ * @param ypos
+ * @return
+ */
+ protected int drawGPIArrow(final Graphics2D g2d,
+ final Qualifier gpiAnchor,
+ final int ppStart, final int ppEnd, final int ppLength,
+ int ypos)
+ {
+ float fraction = (float)(ppEnd-ppStart)/
+ (float)(ppLength);
+ ((QualifierLazyLoading)gpiAnchor).setForceLoad(true);
+
+ StringVector values = gpiAnchor.getValues();
+ for(int j=0; j<values.size(); j++)
+ {
+ int coords[] = getCoords(((String)values.get(j)));
+ int start = ppStart+(int)(coords[0]*fraction);
+
+ g2d.setStroke(new BasicStroke(2.f));
+ g2d.setColor(Color.RED);
+ g2d.drawLine(start-1,ypos,start-1,ypos+15);
+
+
+ StringVector parts = StringVector.getStrings((String)values.get(j), ";");
+ String tt = gpiAnchor.getName()+" : "+coords[0];
+ for(int l=0; l<parts.size(); l++)
+ if( ((String)parts.get(l)).indexOf("query") < 0 )
+ tt = tt.concat("\n"+(String)parts.get(l));
+ // record position for tooltip
+ Rectangle r = new Rectangle(start-1, ypos, 2, 15);
+ toolTipPositions.put(r, tt);
+ }
+ ypos += border * 2;
+ return ypos;
+ }
+
+ /**
+ * Get coordinates from the qualifier value by looking
+ * for query in the string
+ * @param value
+ * @return
+ */
+ private int[] getCoords(String value)
+ {
+ int coords[] = new int[2];
+
+ int beginIndex = value.indexOf("query ")+6;
+
+ if(beginIndex < 6)
+ return null;
+
+ int endIndex = value.indexOf(';', beginIndex);
+ if(endIndex < 0)
+ endIndex = value.length();
+ value = value.substring(beginIndex, endIndex);
+
+ String splitStr[] = value.split("-");
+
+ coords[0] = Integer.parseInt(splitStr[0]);
+ coords[1] = Integer.parseInt(splitStr[1]);
+ return coords;
+ }
+
+ /**
+ * Determine if the tooltip contains a database link.
+ * @param sendToBrowser if true then the link is opened in a browser
+ * @param tt tooltip text
+ * @return
+ */
+ private boolean isDatabaseHyperLink(final boolean sendToBrowser,
+ final String tt)
+ {
+ if(tt == null)
+ return false;
+
+ boolean isLink = false;
+ final Vector<String> dbs = QualifierTextArea.DATABASES;
+ for(int i=0; i<dbs.size(); i++)
+ {
+ String db = dbs.get(i);
+ int beginIndex;
+ if( (beginIndex=tt.indexOf(db+":")) > -1 )
+ {
+ int endIndex = QualifierTextArea.getEndOfLink(tt, beginIndex);
+ isLink = true;
+ if(sendToBrowser)
+ QualifierTextArea.handleMouseSingleClick(
+ tt.substring(beginIndex, endIndex),
+ ProteinMapPanel.this);
+ else
+ return isLink;
+ }
+ }
+ return isLink;
+ }
+
+ public String getToolTipText(MouseEvent me)
+ {
+ Point p = me.getPoint();
+ Enumeration<Rectangle> rectangles = toolTipPositions.keys();
+ while(rectangles.hasMoreElements())
+ {
+ Rectangle r = rectangles.nextElement();
+ if((r.x <= p.x && r.x+r.width >= p.x) &&
+ (r.y <= p.y && r.y+r.height >= p.y) )
+ return (String) toolTipPositions.get(r);
+ }
+ return null;
+ }
+
+ /**
+ * Check if a feature contains a protein map qualifier. Return a list
+ * of the protein features containing protein map qualifiers.
+ * @param feature
+ * @return
+ */
+ public static List<Feature> getProteinsWithProteinMapElement(final GFFStreamFeature feature)
+ {
+ List<Feature> transcripts = feature.getChadoGene().getTranscripts();
+ List<Feature> proteins = null;
+ if(transcripts != null)
+ {
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ Feature transcript = transcripts.get(i);
+ String transcriptName = GeneUtils.getUniqueName(transcript);
+ Feature protein = feature.getChadoGene().getProteinOfTranscript(transcriptName);
+
+ if(protein != null)
+ {
+ QualifierVector qualifiers = protein.getQualifiers();
+ for(int j=0; j<qualifiers.size(); j++)
+ if(isProteinMapElement((Qualifier)qualifiers.get(j)))
+ {
+ if(proteins == null)
+ proteins = new Vector<Feature>();
+ proteins.add(protein);
+ }
+ }
+ }
+ }
+ return proteins;
+ }
+
+ public static boolean isProteinMapElement(final Qualifier this_qualifier)
+ {
+ final String qualifierName = this_qualifier.getName();
+
+ if(PROTEIN_MAP_ELEMENTS.contains(qualifierName))
+ return true;
+
+ return false;
+ }
+
+ public static QualifierVector getProteinMapQualifiers(uk.ac.sanger.artemis.Feature f)
+ {
+ QualifierVector proteinMapQualifiers = null;
+ QualifierVector qualifiers = f.getQualifiers();
+ for(int i=0; i<qualifiers.size(); i++)
+ {
+ if(isProteinMapElement((Qualifier)qualifiers.get(i)))
+ {
+ if(proteinMapQualifiers == null)
+ proteinMapQualifiers = new QualifierVector();
+ proteinMapQualifiers.addQualifierValues((Qualifier)qualifiers.get(i));
+ }
+ }
+ return proteinMapQualifiers;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/ReferencesPanel.java b/uk/ac/sanger/artemis/components/genebuilder/ReferencesPanel.java
new file mode 100644
index 0000000..80541c0
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/ReferencesPanel.java
@@ -0,0 +1,188 @@
+/* ReferencesPanel.java
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java,v 1.11 2009-08-17 12:50:42 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.components.QualifierTextArea;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class ReferencesPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ private QualifierTextArea literatureTextArea;
+ private QualifierTextArea dbxrefTextArea;
+
+ public ReferencesPanel(final Feature feature)
+ {
+ super(new FlowLayout(FlowLayout.LEFT));
+ updateFromFeature(feature);
+ }
+
+ /**
+ * Return true if this is a literature or dbxref qualifier
+ * @param qualifier
+ * @return
+ */
+ public static boolean isReferenceTag(final Qualifier qualifier)
+ {
+ if(qualifier.getName().equals("literature") ||
+ qualifier.getName().equalsIgnoreCase("Dbxref"))
+ return true;
+ return false;
+ }
+
+ public void updateFromFeature(Feature feature)
+ {
+ super.removeAll();
+ GridBagConstraints c = new GridBagConstraints();
+ JPanel gridPanel = new JPanel(new GridBagLayout());
+ gridPanel.setBackground(Color.white);
+
+ //
+ // literature & dbxref
+ literatureTextArea = new QualifierTextArea();
+ literatureTextArea.setBorder(BorderFactory.createLineBorder(Color.gray));
+ dbxrefTextArea = new QualifierTextArea();
+ dbxrefTextArea.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+ literatureTextArea.getDocument().addDocumentListener(
+ new TextAreaDocumentListener(literatureTextArea));
+
+ dbxrefTextArea.getDocument().addDocumentListener(
+ new TextAreaDocumentListener(dbxrefTextArea));
+
+ final QualifierVector qualifiers = feature.getQualifiers().copy();
+ final StringBuffer litBuffer = new StringBuffer();
+ final StringBuffer dbxrefBuffer = new StringBuffer();
+
+ for(int i = 0 ; i < qualifiers.size(); ++i)
+ {
+ Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(i);
+ if(this_qualifier.getName().equals("literature"))
+ appendToBuffer(this_qualifier.getValues(), litBuffer);
+ else if(this_qualifier.getName().equalsIgnoreCase("Dbxref"))
+ appendToBuffer(this_qualifier.getValues(), dbxrefBuffer);
+ }
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.ipadx = 5;
+ c.ipady = 5;
+ c.anchor = GridBagConstraints.NORTHEAST;
+ c.fill = GridBagConstraints.NONE;
+ JLabel litLab = new JLabel("Literature");
+ litLab.setToolTipText("Comma separated list, e.g. PMID:12345, PMID:56789...");
+ gridPanel.add(litLab,c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.NORTHWEST;
+ gridPanel.add(literatureTextArea,c);
+
+ c.gridx = 0;
+ c.gridy = 1;
+ c.anchor = GridBagConstraints.NORTHEAST;
+ JLabel dbLab = new JLabel("Dbxref");
+ dbLab.setToolTipText("Comma separated list, e.g. UniProtKB:Q9NFB6, ...");
+ gridPanel.add(dbLab,c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.NORTHWEST;
+ gridPanel.add(dbxrefTextArea,c);
+
+ add(gridPanel);
+
+ literatureTextArea.setText(litBuffer.toString()+"\n");
+ dbxrefTextArea.setText(dbxrefBuffer.toString()+"\n");
+ }
+
+ private void appendToBuffer(StringVector qualifierStr, StringBuffer buff)
+ {
+ for (int j = 0; j < qualifierStr.size(); ++j)
+ {
+ String str = (String) qualifierStr.elementAt(j);
+ if(j!=0)
+ buff.append(", "+str);
+ else
+ buff.append(str);
+ }
+ }
+
+ /**
+ * Get the latest (edited) literature/dbxref qualifiers
+ * @return
+ */
+ public QualifierVector getQualifiers()
+ {
+ QualifierVector referenceQualifier = null;
+ String literatureTxt = literatureTextArea.getText().trim();
+
+ if(!literatureTxt.equals(""))
+ {
+ referenceQualifier = new QualifierVector();
+ String[] lits = getValues(literatureTxt);
+ StringVector litValues = new StringVector(lits);
+ Qualifier literature = new Qualifier("literature",litValues);
+ referenceQualifier.setQualifier(literature);
+ }
+
+ String dbxrefTxt = dbxrefTextArea.getText().trim();
+ if(!dbxrefTxt.equals(""))
+ {
+ if(referenceQualifier == null)
+ referenceQualifier = new QualifierVector();
+ String[] dbxrefs = getValues(dbxrefTxt);
+ StringVector dbxrefsValues = new StringVector(dbxrefs);
+ Qualifier dbxrefsQualifier = new Qualifier("Dbxref",dbxrefsValues);
+ referenceQualifier.setQualifier(dbxrefsQualifier);
+ }
+
+ return referenceQualifier;
+ }
+
+ protected boolean isEmpty()
+ {
+ QualifierVector qualifiers = getQualifiers();
+ if(qualifiers == null)
+ return true;
+ return false;
+ }
+
+ private String[] getValues(String txt)
+ {
+ String delim = "[\t\n\f\r,;]";
+ String[] lits = txt.split(delim);
+ for(int i=0; i<lits.length; i++)
+ lits[i] = lits[i].trim();
+ return lits;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/TextAreaDocumentListener.java b/uk/ac/sanger/artemis/components/genebuilder/TextAreaDocumentListener.java
new file mode 100644
index 0000000..425b76e
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/TextAreaDocumentListener.java
@@ -0,0 +1,148 @@
+/* TextAreaDocumentListener
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.genebuilder;
+
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.util.StringTokenizer;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+import uk.ac.sanger.artemis.components.QualifierTextArea;
+
+
+public class TextAreaDocumentListener implements DocumentListener
+{
+ private QualifierTextArea qta;
+
+ public TextAreaDocumentListener(QualifierTextArea qta)
+ {
+ this.qta = qta;
+ setQualifierTextAreaSize();
+ }
+
+ public void insertUpdate(DocumentEvent e)
+ {
+ updateSize(e);
+ }
+
+ public void removeUpdate(DocumentEvent e)
+ {
+ updateSize(e);
+ }
+
+ // Plain text components do not fire these events
+ public void changedUpdate(DocumentEvent e)
+ {
+ }
+
+ private void updateSize(DocumentEvent e)
+ {
+ setQualifierTextAreaSize();
+ }
+
+/* private int getLineCount(Document doc)
+ {
+ // get last visible character's offset
+ int lineEndOffset = 0;
+ int line = 0;
+ // go to end of each line until last character of last line is reached
+ try
+ {
+ while (lineEndOffset < qta.getDocument().getEndPosition().getOffset() && line < 50)
+ {
+ lineEndOffset = Utilities.getRowEnd(qta, lineEndOffset+1);
+ line++;
+ }
+ }
+ catch (BadLocationException e){}
+
+ if(line == 50)
+ {
+ Element root = doc.getDefaultRootElement();
+ line = root.getElementCount();
+ }
+ return line;
+ }*/
+
+ /**
+ * Calculate the number of lines, taking into account line wrapping at
+ * word boundaries (whitespace).
+ * @param fm
+ * @param text
+ * @param width
+ * @return
+ */
+ private int getNumberOfLines(final int width)
+ {
+ String text = qta.getText();
+ String lines[] = text.split("\n");
+ int lineCount = lines.length;
+ for(int i=0; i<lines.length; i++)
+ lineCount += getWrappedLineCount(lines[i], width);
+
+ if(lineCount < 1)
+ lineCount = 1;
+ return lineCount;
+ }
+
+ /**
+ * For a given line count how many times it is wrapped.
+ * @param text
+ * @param width
+ * @return
+ */
+ private int getWrappedLineCount(final String text, final int width)
+ {
+ String delim = " \t\n\f\r";
+ StringTokenizer tok = new StringTokenizer(text, delim, true);
+ FontMetrics fm = qta.getFontMetrics(qta.getFont());
+
+ int lineOffset = 0;
+ int lineNumber = 0;
+ while(tok.hasMoreTokens())
+ {
+ int thisWordLength = fm.stringWidth(tok.nextToken());
+ lineOffset+=thisWordLength;
+ if(lineOffset>width)
+ {
+ lineNumber++;
+ lineOffset = thisWordLength;
+ }
+ }
+ return lineNumber;
+ }
+
+ /**
+ * Set the size from the number of lines.
+ * @param doc
+ */
+ private void setQualifierTextAreaSize()
+ {
+ int lines = getNumberOfLines(qta.getPreferredSize().width);
+ int lineHeight = qta.getFontMetrics(qta.getFont()).getHeight();
+
+ qta.setPreferredSize(new Dimension(qta.getPreferredSize().width,
+ lineHeight * lines));
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/AbstractCvBox.java b/uk/ac/sanger/artemis/components/genebuilder/cv/AbstractCvBox.java
new file mode 100644
index 0000000..a457f87
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/AbstractCvBox.java
@@ -0,0 +1,177 @@
+/* AbstractCvBox.java
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import uk.ac.sanger.artemis.io.QualifierVector;
+
+/**
+ * Component for controlled vocabulary qualifier, e.g
+ * GO, controlled_curation, product
+ */
+abstract class AbstractCvBox
+{
+
+ /**
+ * Strip out the value of a field of interest from a qualifier string
+ *
+ * @param fieldName
+ * @param qualifierString
+ * @return
+ */
+ protected static String getField(final String fieldName, final String qualifierString)
+ {
+ String field = "";
+
+ int ind1 = qualifierString.toLowerCase().indexOf(fieldName.toLowerCase());
+ int ind2 = qualifierString.indexOf(";", ind1);
+
+ int len = fieldName.length();
+
+ if(ind2 > ind1 && ind1 > -1)
+ field = qualifierString.substring(ind1+len,ind2);
+ else if(ind1 > -1)
+ field = qualifierString.substring(ind1+len);
+
+ return field;
+ }
+
+ /**
+ * Strip out the value of a field of interest from a qualifier string
+ * by splitting on '='.
+ * @param fieldName
+ * @param qualifierString
+ * @return
+ */
+ public static String getFieldIgnoreSeparator(final String fieldName, final String qualifierString)
+ {
+ String[] completeValues = qualifierString.split(";[\\S&&[^,;=]]+=");
+ StringBuffer buff = null;
+ for(int i=0; i<completeValues.length; i++)
+ {
+ if(qualifierString.indexOf(fieldName+"="+completeValues[i])>-1)
+ {
+ if(buff == null)
+ buff = new StringBuffer();
+ else
+ buff.append("; ");
+ buff.append(completeValues[i]);
+ }
+ }
+ if(buff == null)
+ return getField(fieldName, qualifierString);
+ return buff.toString();
+ }
+
+ /**
+ * Strip out the value of a field of interest from a qualifier string
+ *
+ * @param fieldName
+ * @param qualifierString
+ * @return
+ */
+ protected String changeField(final String fieldName,
+ final String newFieldStr,
+ String newQualifierString)
+ {
+ int ind1 = newQualifierString.toLowerCase().indexOf(fieldName.toLowerCase());
+ int ind2 = newQualifierString.indexOf(";", ind1);
+
+ int len = fieldName.length();
+
+ if(ind2 > ind1 && ind1 > -1)
+ {
+ if(newFieldStr.equals(""))
+ newQualifierString =
+ newQualifierString.substring(0, ind1) +
+ newQualifierString.substring(ind2);
+ else
+ newQualifierString =
+ newQualifierString.substring(0, ind1+len) +
+ newFieldStr +
+ newQualifierString.substring(ind2);
+ }
+ else if(ind1 > -1)
+ {
+ if(newFieldStr.equals(""))
+ newQualifierString =
+ newQualifierString.substring(0, ind1);
+ else
+ newQualifierString =
+ newQualifierString.substring(0, ind1+len) +
+ newFieldStr;
+ }
+ else
+ {
+ if(!newFieldStr.equals(""))
+ newQualifierString = newQualifierString + ";" +
+ fieldName + newFieldStr;
+ }
+
+ return newQualifierString;
+ }
+
+/* protected String replace(String str, String pattern, String replace)
+ {
+ int s = 0;
+ int e = 0;
+ StringBuffer result = new StringBuffer();
+ while ((e = str.indexOf(pattern, s)) >= 0)
+ {
+ result.append(str.substring(s, e));
+ result.append(replace);
+ s = e+pattern.length();
+ }
+ result.append(str.substring(s));
+ return result.toString();
+ } */
+
+ /**
+ * Get a Date object from a date string in the format 20061129
+ * @param dateStr
+ * @return
+ */
+ protected Date getDate(String dateStr)
+ {
+ Calendar cal = Calendar.getInstance();
+ cal.clear();
+ if(dateStr == null ||
+ dateStr.equals("") ||
+ dateStr.length() != 8)
+ return null;
+
+ int year = Integer.parseInt(dateStr.substring(0,4));
+ int month = Integer.parseInt(dateStr.substring(4, 6))-1;
+ int day = Integer.parseInt(dateStr.substring(6,8));
+
+ cal.set(year,month,day);
+ return cal.getTime();
+ }
+
+ protected abstract boolean isQualifierChanged();
+ protected abstract void updateQualifier(final QualifierVector qv);
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/CVPanel.java b/uk/ac/sanger/artemis/components/genebuilder/cv/CVPanel.java
new file mode 100644
index 0000000..b8f4f5e
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/CVPanel.java
@@ -0,0 +1,815 @@
+/* CVPanel.java
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+import javax.swing.JPanel;
+import javax.swing.JButton;
+
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.genebuilder.GeneEditorPanel;
+
+import org.gmod.schema.cv.CvTerm;
+
+/**
+ * Panel for display controlled vocabulary terms for Chado
+ */
+public class CVPanel extends JPanel
+ implements FeatureChangeListener
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ private QualifierVector cvQualifiers;
+ private Vector<AbstractCvBox> editableComponents;
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(CVPanel.class);
+
+ private JButton hide_show_CC;
+ private JButton hide_show_GO;
+ private Feature feature;
+
+ private DatabaseDocument doc;
+ //used to test if cv panel has contents
+ private boolean empty = true;
+
+ public CVPanel(final Feature feature)
+ {
+ super(new BorderLayout());
+ updateFromFeature(feature);
+ doc = (DatabaseDocument)
+ ((GFFStreamFeature)feature.getEmblFeature()).getDocumentEntry().getDocument();
+ }
+
+ /**
+ * Return true if this is a CV qualifier
+ * @param qualifier
+ * @return
+ */
+ public static boolean isCvTag(final Qualifier qualifier)
+ {
+ return isCvTag(qualifier.getName());
+ }
+
+ /**
+ * Return true if this is a CV qualifier
+ * @param qualifierName
+ * @return
+ */
+ public static boolean isCvTag(String qualifierName)
+ {
+ if(qualifierName.startsWith("/"))
+ qualifierName = qualifierName.substring(1);
+
+ return ChadoTransactionManager.isCvTag(qualifierName);
+ }
+
+ /**
+ * Create CV qualifier components
+ * @return
+ */
+ private Component createCVQualifiersComponent()
+ {
+ empty = true;
+ Vector<String> cv_tags = new Vector<String>();
+ cv_tags.add("GO");
+ cv_tags.add("controlled_curation");
+ cv_tags.add("product");
+ cv_tags.add("class");
+
+ editableComponents = new Vector<AbstractCvBox>();
+
+ final Dimension dimension = new Dimension(100,
+ (new JTextField()).getPreferredSize().height);
+
+ Box cvBox = Box.createVerticalBox();
+
+ Box xBox = Box.createHorizontalBox();
+ JButton addRemove = new JButton("ADD");
+ addRemove.setOpaque(false);
+ addRemove.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ CvTermSelector cvTermSelector = new CvTermSelector();
+ if(cvTermSelector.showOptions(null, doc))
+ {
+ addCvTermQualifier(cvTermSelector.getCvTerm(),
+ cvTermSelector.getCvName(),
+ cvTermSelector.getCv(),
+ cvTermSelector.getEvidenceCode());
+ }
+
+ }
+ });
+ xBox.add(addRemove);
+ JButton lookUp = new JButton("LOOK UP");
+ lookUp.setOpaque(false);
+ lookUp.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ DatabaseDocumentEntry entry =
+ (DatabaseDocumentEntry)feature.getEmblFeature().getEntry();
+ DatabaseDocument doc = (DatabaseDocument)entry.getDocument();
+
+ doc.showCvTermLookUp();
+ }
+ });
+ xBox.add(lookUp);
+
+ JButton addHistory = new JButton("ADD HISTORY");
+ addHistory.setOpaque(false);
+ addHistory.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ addHistoryQualifier();
+ }
+ });
+ if(ChadoTransactionManager.HISTORY_CV != null)
+ xBox.add(addHistory);
+
+ xBox.add(Box.createHorizontalGlue());
+ cvBox.add(xBox);
+
+ Dimension go_dimension = null;
+ Box goBox = null;
+ Box goHeadings = null;
+ for(Qualifier this_qualifier: cvQualifiers)
+ {
+ if(this_qualifier.getName().equals("GO"))
+ {
+ empty = false;
+
+ if(hide_show_GO == null)
+ hide_show_GO = new JButton("-");
+
+ if(goBox == null)
+ goBox = Box.createVerticalBox();
+
+ addHideShowButton(goBox, hide_show_GO);
+
+ if(goHeadings == null)
+ goHeadings = Box.createHorizontalBox();
+
+ final StringVector qualifier_strings = this_qualifier.getValues();
+ for(int i = 0; i < qualifier_strings.size(); ++i)
+ {
+ GoBox go_box = new GoBox(this_qualifier, qualifier_strings.elementAt(i), i,
+ go_dimension, dimension);
+ go_dimension = go_box.getGoDimension();
+ editableComponents.add(go_box);
+
+ xBox = go_box.getBox();
+ xBox.add(Box.createHorizontalGlue());
+ xBox.add(getRemoveButton(this_qualifier, i));
+
+ goBox.add(xBox);
+ }
+
+ // add column headings
+ final JLabel lab = new JLabel("GO terms");
+ lab.setPreferredSize(go_dimension);
+ lab.setFont(lab.getFont().deriveFont(Font.BOLD));
+ goHeadings.add(lab);
+ final JLabel withLabel = new JLabel("WITH/FROM");
+ withLabel.setPreferredSize(dimension);
+ goHeadings.add(withLabel);
+
+ final JLabel dbxrefLabel = new JLabel("Dbxref");
+ dbxrefLabel.setPreferredSize(dimension);
+ goHeadings.add(dbxrefLabel);
+
+ final JLabel evidenceLabel = new JLabel("Evidence");
+ evidenceLabel.setPreferredSize(GoBox.getEvidenceListDimension());
+ goHeadings.add(evidenceLabel);
+
+ final JLabel qualLabel = new JLabel("Qualifier");
+ qualLabel.setPreferredSize(dimension);
+ goHeadings.add(qualLabel);
+ goHeadings.add(new JLabel("Date"));
+
+ goHeadings.add(Box.createHorizontalGlue());
+ goHeadings.add(hide_show_GO);
+
+ if(hide_show_GO.getText().equals("+"))
+ goBox.setVisible(false);
+ }
+ }
+
+ int n = 0;
+ for(Qualifier this_qualifier: cvQualifiers)
+ {
+ if(this_qualifier.getName().equals("product"))
+ {
+ final StringVector qualifier_strings = this_qualifier.getValues();
+ for(int i = 0; i < qualifier_strings.size(); ++i)
+ {
+ empty = false;
+ final ProductBox productBox = new ProductBox(
+ this_qualifier, qualifier_strings.elementAt(i),
+ i, dimension, go_dimension);
+ editableComponents.add(productBox);
+
+ if(qualifier_strings.size() == 1)
+ productBox.getRecommended().setEnabled(false);
+ xBox = productBox.getBox();
+ xBox.add(Box.createHorizontalGlue());
+ xBox.add(getRemoveButton(this_qualifier, i));
+ n++;
+ cvBox.add(productBox.getHeadingsBox());
+ cvBox.add(xBox);
+ }
+ }
+ }
+
+ if(n > 0)
+ GeneEditorPanel.addLightSeparator(cvBox);
+
+ // history field
+ n = 0;
+ for(Qualifier this_qualifier: cvQualifiers)
+ {
+ if(this_qualifier.getName().equals("history"))
+ {
+ final StringVector qualifier_strings = this_qualifier.getValues();
+
+ for(int i = 0; i < qualifier_strings.size(); ++i)
+ {
+ empty = false;
+
+ final HistoryBox historyBox = new HistoryBox(
+ this_qualifier,
+ qualifier_strings.elementAt(i), i,
+ dimension, go_dimension);
+ editableComponents.add(historyBox);
+
+ xBox = historyBox.getBox();
+ xBox.add(Box.createHorizontalGlue());
+ xBox.add(getRemoveButton(this_qualifier, i));
+
+ if(n == 0)
+ {
+ final Box xLabel = Box.createHorizontalBox();
+ JLabel lab = new JLabel("History");
+ lab.setFont(lab.getFont().deriveFont(Font.BOLD));
+ xLabel.add(lab);
+ xLabel.add(Box.createHorizontalGlue());
+ cvBox.add(xLabel);
+ }
+ n++;
+ cvBox.add(xBox);
+ }
+ }
+ }
+
+ if(n > 0)
+ GeneEditorPanel.addLightSeparator(cvBox);
+
+ //
+ if(goBox != null)
+ {
+ cvBox.add(goHeadings);
+ cvBox.add(goBox);
+ GeneEditorPanel.addLightSeparator(cvBox);
+ }
+
+
+ n = 0;
+ for(Qualifier this_qualifier: cvQualifiers)
+ {
+ if(this_qualifier.getName().equals("controlled_curation"))
+ {
+ empty = false;
+
+ final Box yBox = Box.createVerticalBox();
+ if(hide_show_CC == null)
+ hide_show_CC = new JButton("-");
+
+ addHideShowButton(yBox, hide_show_CC);
+
+ if(n == 0)
+ {
+ final Box xLabel = Box.createHorizontalBox();
+ JLabel lab = new JLabel("Controlled Curation");
+ lab.setFont(lab.getFont().deriveFont(Font.BOLD));
+ xLabel.add(lab);
+ xLabel.add(Box.createHorizontalGlue());
+ xLabel.add(hide_show_CC);
+ cvBox.add(xLabel);
+
+
+ final Box xHeadings = Box.createHorizontalBox();
+ yBox.add(xHeadings);
+// add column headings
+ final JLabel termLabel = new JLabel("Term");
+
+ if(go_dimension != null)
+ termLabel.setPreferredSize(
+ new Dimension(go_dimension.width+dimension.width,
+ dimension.height));
+ else
+ termLabel.setPreferredSize(
+ new Dimension(getWidthOfGoField()+dimension.width,
+ dimension.height));
+ xHeadings.add(termLabel);
+
+
+ final JLabel withLabel = new JLabel("WITH/FROM");
+ withLabel.setPreferredSize(dimension);
+ xHeadings.add(withLabel);
+
+ final JLabel dbxrefLabel = new JLabel("Dbxref");
+ dbxrefLabel.setPreferredSize(dimension);
+ xHeadings.add(dbxrefLabel);
+
+ final JLabel evidenceLabel = new JLabel("Evidence");
+ evidenceLabel.setPreferredSize(GoBox.getEvidenceListDimension());
+ xHeadings.add(evidenceLabel);
+
+ final JLabel qualLabel = new JLabel("Qualifier");
+ qualLabel.setPreferredSize(dimension);
+ xHeadings.add(qualLabel);
+
+ final JLabel dateLabel = new JLabel("Date");
+ xHeadings.add(dateLabel);
+
+ xHeadings.add(Box.createHorizontalGlue());
+ }
+
+ final StringVector qualifier_strings = this_qualifier.getValues();
+ for(int i = 0; i < qualifier_strings.size(); ++i)
+ {
+ n++;
+
+ final ControlledCurationBox ccBox = new ControlledCurationBox(
+ this_qualifier,
+ qualifier_strings.elementAt(i), i,
+ dimension, go_dimension);
+ editableComponents.add(ccBox);
+
+ xBox = ccBox.getBox();
+ xBox.add(Box.createHorizontalGlue());
+ xBox.add(getRemoveButton(this_qualifier, i));
+ yBox.add(xBox);
+ yBox.add(Box.createVerticalStrut(2));
+ }
+
+ // add CC rows
+ cvBox.add(yBox);
+ if(hide_show_CC.getText().equals("+"))
+ yBox.setVisible(false);
+ }
+ }
+
+ if(n > 0)
+ GeneEditorPanel.addLightSeparator(cvBox);
+
+ //
+ // RILEY
+ //
+
+ if(go_dimension == null)
+ go_dimension = new JLabel("product ").getPreferredSize();
+ n = 0;
+ for(Qualifier this_qualifier: cvQualifiers)
+ {
+ if(this_qualifier.getName().equals("class"))
+ {
+ empty = false;
+ final StringVector qualifier_strings = this_qualifier.getValues();
+ n++;
+
+ for(int i = 0; i < qualifier_strings.size(); ++i)
+ {
+ xBox = Box.createHorizontalBox();
+ final String qualifierStr = qualifier_strings.elementAt(i);
+
+ JLabel label = new JLabel("class");
+ if(go_dimension != null)
+ label.setPreferredSize(go_dimension);
+ xBox.add(label);
+
+ int index = qualifierStr.indexOf("::");
+ String classStr = qualifierStr.substring(0, index);
+ JLabel termTextField = new JLabel(classStr);
+ termTextField.setOpaque(false);
+
+ termTextField
+ .setToolTipText(DatabaseDocument.getCvTermByCvTermId(
+ Integer.parseInt(qualifierStr.substring(index + 2)), feature.getEmblFeature())
+ .getDefinition());
+ termTextField.setPreferredSize(dimension);
+ termTextField.setMaximumSize(dimension);
+
+ xBox.add(termTextField);
+ xBox.add(Box.createHorizontalGlue());
+ xBox.add(getRemoveButton(this_qualifier, i));
+ cvBox.add(xBox);
+ }
+ }
+ }
+
+ cvBox.add(Box.createVerticalGlue());
+ validate();
+ return cvBox;
+ }
+
+ /**
+ * Add hide/show button to CV section
+ * @param box
+ */
+ private void addHideShowButton(final Box box, final JButton hide_show)
+ {
+ hide_show.setOpaque(false);
+
+ // remove any old listeners
+ ActionListener l[] = hide_show.getActionListeners();
+ if(l != null)
+ for(int i=0;i<l.length;i++)
+ hide_show.removeActionListener(l[i]);
+
+ hide_show.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(hide_show.getText().equals("-"))
+ {
+ hide_show.setText("+");
+ box.setVisible(false);
+ }
+ else
+ {
+ hide_show.setText("-");
+ box.setVisible(true);
+ }
+ }
+ });
+ }
+
+
+ private static int getWidthOfGoField()
+ {
+ JTextField textField = new JTextField();
+ FontMetrics fm = textField.getFontMetrics(textField.getFont());
+ return fm.stringWidth("GO:0001234 [F] ");
+ }
+
+ private JButton getRemoveButton(final Qualifier this_qualifier,
+ final int v_index)
+ {
+ JButton buttRemove = new JButton("X");
+ buttRemove.setOpaque(false);
+ Font font = buttRemove.getFont().deriveFont(Font.BOLD);
+ buttRemove.setFont(font);
+ buttRemove.setToolTipText("REMOVE");
+ buttRemove.setForeground(new Color(139,35,35));
+
+ buttRemove.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ removeCvTerm(this_qualifier.getName(), v_index);
+ }
+ });
+ return buttRemove;
+ }
+
+ public void updateFromFeature(final Feature feature)
+ {
+ this.feature = feature;
+ if(cvQualifiers != null)
+ feature.removeFeatureChangeListener(this);
+ //cvQualifiers = feature.getQualifiers().copy();
+
+ cvQualifiers = new QualifierVector();
+ final QualifierVector qualifiers = feature.getQualifiers();
+ for(int i = 0 ; i < qualifiers.size(); ++i)
+ {
+ Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(i);
+
+ if(this_qualifier.getName().equals("GO"))
+ {
+ final StringVector qualifier_strings = this_qualifier.getValues();
+ // sort by aspect (molecular_function, biological_process, cellular_component)
+ Collections.sort(qualifier_strings, new StringVectorComparator());
+ this_qualifier = new Qualifier("GO", qualifier_strings);
+ }
+
+ if(isCvTag(this_qualifier))
+ cvQualifiers.addElement(this_qualifier.copy());
+ }
+
+ feature.addFeatureChangeListener(this);
+
+ removeAll();
+ add(createCVQualifiersComponent(),
+ BorderLayout.CENTER);
+ }
+
+ public void updateFromQualifiers(final QualifierVector qualifiers)
+ {
+ cvQualifiers = new QualifierVector();
+
+ for(int i = 0 ; i < qualifiers.size(); ++i)
+ {
+ Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(i);
+
+ if(this_qualifier.getName().equals("GO"))
+ {
+ final StringVector qualifier_strings = this_qualifier.getValues();
+ // sort by aspect (molecular_function, biological_process, cellular_component)
+ Collections.sort(qualifier_strings, new StringVectorComparator());
+ this_qualifier = new Qualifier("GO", qualifier_strings);
+ }
+
+ if(isCvTag(this_qualifier))
+ cvQualifiers.addElement(this_qualifier.copy());
+ }
+
+ removeAll();
+ add(createCVQualifiersComponent(),
+ BorderLayout.CENTER);
+ }
+
+ /**
+ * Add a history qualifier
+ */
+ private void addHistoryQualifier()
+ {
+ cvQualifiers = getCvQualifiers();
+ Qualifier cv_qualifier = cvQualifiers.getQualifierByName("history");
+
+ final int index;
+ if(cv_qualifier == null)
+ {
+ cv_qualifier = new Qualifier("history");
+ index = -1;
+ }
+ else
+ index = cvQualifiers.indexOf(cv_qualifier);
+
+ cv_qualifier.addValue(
+ "term="+HistoryBox.getDefaultTerm().getName()+";"+
+ "curatorName="+doc.getUserName()+";"+
+ "date="+ DatePanel.getDate());
+
+ if(index > -1)
+ {
+ cvQualifiers.remove(index);
+ cvQualifiers.add(index, cv_qualifier);
+ }
+ else
+ cvQualifiers.add(cv_qualifier);
+
+ removeAll();
+ add(createCVQualifiersComponent(),
+ BorderLayout.CENTER);
+ revalidate();
+ repaint();
+ }
+
+ /**
+ * Add CvTerm qualifier
+ * @param cvterm
+ * @param go_name
+ * @param cv_type
+ */
+ private void addCvTermQualifier(CvTerm cvterm,
+ final String go_name,
+ String cv_type,
+ final String evidenceCode)
+ {
+ if(!cv_type.equals("GO") &&
+ !cv_type.equals("controlled_curation") &&
+ !cv_type.equals("product") &&
+ !cv_type.equals("class"))
+ cv_type = "controlled_curation";
+
+ cvQualifiers = getCvQualifiers();
+ Qualifier cv_qualifier = cvQualifiers.getQualifierByName(cv_type);
+
+ final int index;
+ if(cv_qualifier == null)
+ {
+ cv_qualifier = new Qualifier(cv_type);
+ index = -1;
+ }
+ else
+ index = cvQualifiers.indexOf(cv_qualifier);
+
+ if(cv_type.equals("GO"))
+ cv_qualifier.addValue("GOid=GO:"+cvterm.getDbXRef().getAccession()+";"+
+ "aspect="+go_name+";"+
+ "term="+cvterm.getName()+";"+
+ "evidence="+ evidenceCode+";"+
+ "date="+ DatePanel.getDate());
+ else if(cv_type.equals("controlled_curation"))
+ cv_qualifier.addValue("term="+cvterm.getName()+";cv="+cvterm.getCv().getName());
+ else if(cv_type.equals("product"))
+ cv_qualifier.addValue("term="+cvterm.getName());
+ else if(cv_type.equals("class"))
+ cv_qualifier.addValue(cvterm.getDbXRef().getAccession()+
+ "::"+cvterm.getCvTermId());
+
+ if(index > -1)
+ {
+ cvQualifiers.remove(index);
+ cvQualifiers.add(index, cv_qualifier);
+ }
+ else
+ cvQualifiers.add(cv_qualifier);
+
+ removeAll();
+ add(createCVQualifiersComponent(),
+ BorderLayout.CENTER);
+ revalidate();
+ repaint();
+ }
+
+ private void removeCvTerm(final String qualifier_name,
+ final int value_index)
+ {
+ cvQualifiers = getCvQualifiers();
+ Qualifier qual = cvQualifiers.getQualifierByName(qualifier_name);
+ StringVector values = qual.getValues();
+ values.remove(value_index);
+
+ cvQualifiers.removeQualifierByName(qualifier_name);
+
+ if(values.size() > 0)
+ {
+ qual = new Qualifier(qualifier_name, values);
+ cvQualifiers.addQualifierValues(qual);
+ }
+
+ removeAll();
+
+ add(createCVQualifiersComponent(),
+ BorderLayout.CENTER);
+ validate();
+ }
+
+
+ /**
+ * Get the latest (edited) controlled vocab qualifiers
+ * @return
+ */
+ public QualifierVector getCvQualifiers()
+ {
+ // check editable components for changes
+
+ if(editableComponents != null)
+ {
+ logger4j.debug("CV checking......");
+ for(int i=0; i<editableComponents.size(); i++)
+ {
+ AbstractCvBox cvBox = (AbstractCvBox)editableComponents.get(i);
+ if(cvBox.isQualifierChanged())
+ {
+ logger4j.debug("CV QUALIFIER CHANGED");
+ cvBox.updateQualifier(cvQualifiers);
+ }
+ }
+ }
+
+ return cvQualifiers;
+ }
+
+ public static String getDescription()
+ {
+ final StringBuffer buff = new StringBuffer();
+ buff.append("Section for controlled vocabulary qualifiers:\n");
+ buff.append("GO, controlled_curation, product, Riley class");
+
+ return buff.toString();
+ }
+
+ /**
+ * Implementation of the FeatureChangeListener interface. We need to
+ * listen to feature change events from the Features in this object so that
+ * we can update the display.
+ * @param event The change event.
+ **/
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ // re-read the information from the feature
+ switch(event.getType())
+ {
+ case FeatureChangeEvent.QUALIFIER_CHANGED:
+ /*if(qualifier_text_area.getText().equals(orig_qualifier_text))
+ updateFromFeature();
+ else
+ {
+ final String message =
+ "warning: the qualifiers have changed outside the editor - " +
+ "view now?";
+
+ final YesNoDialog yes_no_dialog =
+ new YesNoDialog(frame, message);
+
+ if(yes_no_dialog.getResult())
+ new FeatureViewer(getFeature());
+ }*/
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ class StringVectorComparator implements Comparator<String>
+ {
+ public final int compare(String a, String b)
+ {
+ int result;
+
+ String strA = getField("aspect", a);
+ String strB = getField("aspect", b);
+ result = strA.compareTo(strB);
+
+ return result;
+ }
+
+ /**
+ * Strip out the value of a field of interest from a qualifier string
+ *
+ * @param fieldName
+ * @param qualifierString
+ * @return
+ */
+ private String getField(final String fieldName, final String qualifierString)
+ {
+ String field = "";
+
+ int ind1 = qualifierString.toLowerCase().indexOf(fieldName.toLowerCase());
+ int ind2 = qualifierString.indexOf(";", ind1);
+
+ int len = fieldName.length();
+
+ if(ind2 > ind1 && ind1 > -1)
+ field = qualifierString.substring(ind1+len,ind2);
+ else if(ind1 > -1)
+ field = qualifierString.substring(ind1+len);
+
+ return field;
+ }
+ }
+
+ public boolean isEmpty()
+ {
+ return empty;
+ }
+
+ public void setEmpty(boolean empty)
+ {
+ this.empty = empty;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/ControlledCurationBox.java b/uk/ac/sanger/artemis/components/genebuilder/cv/ControlledCurationBox.java
new file mode 100644
index 0000000..dee768e
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/ControlledCurationBox.java
@@ -0,0 +1,298 @@
+/* ControlledCurationBox.java
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JTextField;
+
+import org.gmod.schema.cv.CvTerm;
+
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+class ControlledCurationBox extends AbstractCvBox
+{
+ private Box xBox;
+ private int value_index;
+ private WrapTextArea termTextField;
+ private JTextField withTextField;
+ private JTextField dbxrefTextField;
+ private JExtendedComboBox evidenceList;
+ private JTextField qualfTextField;
+ private DatePanel dateField;
+ private String origQualifierString;
+ private Qualifier origQualifier;
+
+ public ControlledCurationBox(final Qualifier qualifier,
+ final String qualifierString,
+ final int value_index,
+ final Dimension dimension,
+ final Dimension go_dimension)
+ {
+ this.origQualifier = qualifier;
+ this.origQualifierString = qualifierString;
+ this.value_index = value_index;
+ this.xBox = Box.createHorizontalBox();
+
+ //JLabel cclabel = new JLabel("controlled_curation");
+ //if(go_dimension != null)
+ // cclabel.setPreferredSize(go_dimension);
+
+ //xBox.add(cclabel);
+
+ final String term = getField("term=", qualifierString);
+ final String cvName = getField("cv=", qualifierString);
+
+ CvTerm cvTerm = DatabaseDocument.getCvTermByCvPartAndCvTerm(term,cvName);
+ if(cvTerm == null)
+ cvTerm = DatabaseDocument.getCvTermByCvPartAndCvTerm(term,"CC");
+ termTextField = new WrapTextArea(term, go_dimension, dimension.width);
+ termTextField.setOpaque(false);
+ termTextField.setEditable(false);
+ termTextField.setToolTipText(cvTerm.getCv().getName());
+
+ final Dimension d;
+ if(go_dimension != null)
+ {
+ d = new Dimension(go_dimension.width+dimension.width,
+ termTextField.getPreferredSize().height);
+ }
+ else
+ {
+ FontMetrics fm = termTextField.getFontMetrics(termTextField.getFont());
+ int width= fm.stringWidth("GO:0001234 [F] ");
+ d = new Dimension(width+dimension.width,
+ termTextField.getPreferredSize().height);
+ }
+ termTextField.setPreferredSize(d);
+ termTextField.setMaximumSize(d);
+
+ termTextField.setCaretPosition(0);
+ xBox.add(termTextField);
+
+ //
+ String with = getField("with=", qualifierString);
+ withTextField = new JTextField(with);
+ withTextField.setToolTipText("with/from column");
+ withTextField.setPreferredSize(dimension);
+ withTextField.setMaximumSize(dimension);
+ withTextField.setActionCommand("with=");
+ xBox.add(withTextField);
+
+ String dbxref = getField("db_xref=", qualifierString);
+ dbxrefTextField = new JTextField(dbxref);
+ dbxrefTextField.setToolTipText("dbxref column");
+ dbxrefTextField.setPreferredSize(dimension);
+ dbxrefTextField.setMaximumSize(dimension);
+ dbxrefTextField.setActionCommand("db_xref=");
+ xBox.add(dbxrefTextField);
+
+ // feature_cvterm_prop's
+ String evidence = getField("evidence=", qualifierString);
+
+ evidenceList = new JExtendedComboBox(GoBox.evidenceCodes[1]);
+ evidenceList.setOpaque(false);
+ evidenceList.setToolTipText("evidence column");
+ evidenceList.setSelectedIndex( GoBox.getEvidenceIndex(evidence) );
+
+ Dimension d2 = evidenceList.getPreferredSize();
+ d2 = new Dimension(90,(int)d2.getHeight());
+ evidenceList.setPreferredSize(d2);
+ evidenceList.setMaximumSize(d2);
+ evidenceList.setActionCommand("evidence=");
+ xBox.add(evidenceList);
+
+ String qual = getField("qualifier=", qualifierString);
+ qualfTextField = new JTextField(qual);
+ qualfTextField.setToolTipText("qualifier column");
+ qualfTextField.setPreferredSize(dimension);
+ qualfTextField.setMaximumSize(dimension);
+ qualfTextField.setActionCommand("qualifier=");
+ xBox.add(qualfTextField);
+
+
+ dateField = new DatePanel(getField("date=", qualifierString),
+ dimension.height);
+
+ xBox.add(dateField);
+ }
+
+
+
+ protected boolean isQualifierChanged()
+ {
+ String old = getField("with=", origQualifierString);
+ if(!old.equals(withTextField.getText().trim()))
+ return true;
+
+ old = getField("db_xref=", origQualifierString);
+ if(!old.equals(dbxrefTextField.getText().trim()))
+ return true;
+
+ old = getField("evidence=", origQualifierString);
+
+ if(!(old.equals("") && evidenceList.getSelectedIndex() == -1) )
+ if(!old.equals(GoBox.evidenceCodes[2][ evidenceList.getSelectedIndex() ]) &&
+ !old.equals(GoBox.evidenceCodes[0][ evidenceList.getSelectedIndex() ]))
+ return true;
+
+ old = getField("qualifier=", origQualifierString);
+ if(!old.equals(qualfTextField.getText()))
+ return true;
+
+ old = getField("date=", origQualifierString);
+ if(!old.equals(dateField.getText()))
+ return true;
+
+ return false;
+ }
+
+ protected void updateQualifier(QualifierVector qv)
+ {
+ int index = qv.indexOfQualifierWithName(origQualifier.getName());
+ Qualifier oldQualifier = qv.getQualifierByName(origQualifier.getName());
+
+ final String term = getField("term=", origQualifierString);
+
+ StringVector oldValues = oldQualifier.getValues();
+ Vector<Integer> values_index = new Vector<Integer>();
+ for(int i=0; i<oldValues.size(); i++)
+ {
+ String oldValue = (String)oldValues.get(i);
+ String newTerm = getField("term=", oldValue);
+ if(newTerm.equals(term))
+ values_index.add(new Integer(i));
+ }
+
+ if(values_index.size() > 0)
+ {
+ String oldValue = (String) oldValues.get(value_index);
+ String oldTermId = getField("term=", oldValue);
+
+ if(!term.equals(oldTermId))
+ {
+ if(values_index.size() == 1)
+ value_index = ((Integer)values_index.get(0)).intValue();
+ else
+ {
+ final String with = getField("with=", origQualifierString);
+ final String evidence = getField("evidence=", origQualifierString);
+ final String dbxref = getField("dbxref=", origQualifierString);
+ for(int i=0; i<values_index.size(); i++)
+ {
+ int ind = ((Integer)values_index.get(i)).intValue();
+ value_index = ind;
+ String value = (String) oldValues.get(ind);
+
+ if(!with.equals(""))
+ {
+ String thisWith = getField("with=", value);
+ if(thisWith.equals(with))
+ break;
+ }
+
+ if(!dbxref.equals(""))
+ {
+ String thisDbxref = getField("dbxref=", value);
+ if(thisDbxref.equals(dbxref))
+ break;
+ }
+
+ String thisEvidence = getField("evidence=", value);
+ if(thisEvidence.equals(evidence))
+ break;
+ }
+ }
+ }
+ }
+ else
+ value_index = -99;
+
+ if(value_index > -1)
+ oldValues.remove(value_index);
+
+ String updatedQualifierString = updateQualifierString();
+ oldValues.add(value_index, updatedQualifierString);
+
+ origQualifier = new Qualifier(origQualifier.getName(), oldValues);
+ qv.remove(index);
+ qv.add(index, origQualifier);
+ }
+
+ private String updateQualifierString()
+ {
+ String newQualifierString = origQualifierString;
+
+ String old = getField("with=", origQualifierString);
+ if(!old.equals(withTextField.getText().trim()))
+ {
+ newQualifierString = changeField("with=", withTextField.getText().trim(),
+ newQualifierString);
+ }
+
+ old = getField("date=", origQualifierString);
+ if(!old.equals(dateField.getText()))
+ {
+ newQualifierString = changeField("date=", dateField.getText().trim(),
+ newQualifierString);
+ }
+
+ old = getField("db_xref=", origQualifierString);
+ if(!old.equals(dbxrefTextField.getText().trim()))
+ {
+ newQualifierString = changeField("db_xref=", dbxrefTextField.getText().trim(),
+ newQualifierString);
+ }
+
+ old = getField("evidence=", origQualifierString);
+ if(evidenceList.getSelectedIndex() > -1 &&
+ !old.equals(GoBox.evidenceCodes[2][ evidenceList.getSelectedIndex() ]))
+ {
+ newQualifierString = changeField("evidence=", GoBox.evidenceCodes[2][ evidenceList.getSelectedIndex() ],
+ newQualifierString);
+ }
+
+ old = getField("qualifier=", origQualifierString);
+ if(!old.equals(qualfTextField.getText()))
+ {
+ newQualifierString = changeField("qualifier=", qualfTextField.getText().trim(),
+ newQualifierString);
+ }
+
+ return newQualifierString;
+ }
+
+ protected Box getBox()
+ {
+ return xBox;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/CvTermSelector.java b/uk/ac/sanger/artemis/components/genebuilder/cv/CvTermSelector.java
new file mode 100644
index 0000000..f5e8d91
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/CvTermSelector.java
@@ -0,0 +1,489 @@
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Collections;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.gmod.schema.cv.CvTerm;
+
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.database.DatabaseEntrySource;
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+
+
+class CvTermSelector extends JPanel
+ implements ActionListener
+{
+ private static final long serialVersionUID = 1L;
+ private JExtendedComboBox cvCombo;
+ private JExtendedComboBox editableCvCombo;
+ private JComboBox goAspect = new JComboBox(new String[]{ "F", "P", "C" });
+ private JTextField keyWord = new JTextField(20);
+ private JCheckBox ignoreCase = new JCheckBox("Ignore case",false);
+ private JExtendedComboBox termList = new JExtendedComboBox(true);
+ private JExtendedComboBox evidenceList = new JExtendedComboBox(GoBox.evidenceCodes[1]);
+ private GridBagConstraints c = new GridBagConstraints();
+ private int termRow = 0;
+ private Dimension d = new Dimension(500,termList.getPreferredSize().height);
+ private CvTerm cvTerm;
+ private Vector<CvTerm> terms;
+ private String cv;
+ private String evidenceCode;
+
+ // components used when adding a new term to the database
+ private JTextField newTerm;
+ private JTextField definition;
+ private JCheckBox addToAnnotation;
+
+ public CvTermSelector()
+ {
+ super(new GridBagLayout());
+
+ setBackground(Color.white);
+ createComponentToAddCvTerm();
+ }
+
+ /**
+ * Add components to the panel for adding CvTerm's to
+ * the annotation.
+ */
+ private void createComponentToAddCvTerm()
+ {
+ createCvComboBox();
+ int row = 0;
+ c.gridx = 0;
+ c.gridy = row;
+ c.anchor = GridBagConstraints.EAST;
+ add(new JLabel("CV: "), c);
+
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ add(cvCombo, c);
+
+ c.gridy = ++row;
+ c.gridx = 1;
+ add(goAspect, c);
+
+ // keyword
+ keyWord.setSelectionStart(0);
+ keyWord.setSelectionEnd(keyWord.getText().length());
+ keyWord.setSelectedTextColor(Color.blue);
+ keyWord.setMinimumSize(d);
+ keyWord.setPreferredSize(d);
+ keyWord.addActionListener(new ActionListener(){
+ // carry out search when enter key is pressed
+ public void actionPerformed(ActionEvent arg0)
+ {
+ searchCvTerms();
+ }
+ });
+ c.gridy = ++row;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ add(new JLabel("Keywords (or GOid): "),c);
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ add(keyWord,c);
+
+ c.gridy = ++row;
+ c.gridwidth = 1;
+ add(ignoreCase,c);
+
+ // search button
+ c.gridx = 0;
+ c.gridy = ++row;
+ c.gridwidth = 1;
+ JButton search = new JButton("Search");
+ search.setActionCommand("SEARCH");
+ search.addActionListener(this);
+ add(search,c);
+
+ // term selection
+ termList.setPreferredSize(d);
+ termList.setMaximumSize(d);
+ c.gridy = ++row;
+ c.gridwidth = 2;
+ termRow = row;
+ add(termList,c);
+
+ // evidence
+ evidenceList.setSelectedItem("NR \t:: Not Recorded");
+ c.gridx = 0;
+ c.gridy = ++row;
+ c.gridwidth = 2;
+ add(evidenceList,c);
+ }
+
+ /**
+ * Add components to the panel for adding a CvTerm to
+ * the database.
+ */
+ private void createComponentToAddCvTermToDb()
+ {
+ removeAll();
+
+ final java.util.List<String> cvNames =
+ DatabaseDocument.getCvControledCurationNames();
+
+ editableCvCombo =
+ new JExtendedComboBox(new Vector<String>(cvNames));
+ editableCvCombo.addItem(JExtendedComboBox.SEPARATOR);
+ editableCvCombo.addItem(ChadoTransactionManager.PRODUCT_CV);
+ editableCvCombo.setPreferredSize(d);
+
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 1;
+
+ add(new JLabel("Controlled vocabulary:"), c);
+
+ c.gridx = 1;
+ add(new JLabel("Term to add:"), c);
+
+ c.gridy = 1;
+ c.gridx = 0;
+ add(editableCvCombo, c);
+
+ c.gridx = 1;
+ newTerm = new JTextField(20);
+ newTerm.setMinimumSize(newTerm.getPreferredSize());
+ newTerm.setPreferredSize(d);
+ add(newTerm, c);
+
+ c.gridy = 2;
+ c.gridx = 0;
+ add(new JLabel("Definition (optional):"), c);
+
+ c.gridy = 3;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ definition = new JTextField();
+ add(definition, c);
+
+ c.gridy = 4;
+ c.gridwidth = 1;
+ addToAnnotation = new JCheckBox("Add to annotation", false);
+ add(addToAnnotation, c);
+
+ repaint();
+ revalidate();
+ }
+
+ /**
+ * Create the JComboBox of the CV's.
+ */
+ private void createCvComboBox()
+ {
+ cvCombo =
+ new JExtendedComboBox(ChadoTransactionManager.CV_NAME);
+
+ cvCombo.setActionCommand("CV");
+ cvCombo.addActionListener(this);
+ final java.util.List<String> cvNames =
+ DatabaseDocument.getCvControledCurationNames();
+ cvCombo.addItem(JExtendedComboBox.SEPARATOR);
+ for(int i=0; i<cvNames.size(); i++)
+ cvCombo.addItem(cvNames.get(i));
+
+ cvCombo.addItem(JExtendedComboBox.SEPARATOR);
+ cvCombo.addItem(JExtendedComboBox.SEPARATOR);
+
+ final String ADD_TERM = "Add term ...";
+ cvCombo.addItem(ADD_TERM);
+
+ Dimension d = cvCombo.getPreferredSize();
+ d = new Dimension(180,(int)d.getHeight());
+ cvCombo.setPreferredSize(d);
+ }
+
+ public void actionPerformed(ActionEvent e)
+ {
+ if(e.getActionCommand().equals("CV"))
+ {
+ if( ((String)cvCombo.getSelectedItem()).equals("Add term ...") )
+ {
+ createComponentToAddCvTermToDb();
+ return;
+ }
+
+ if( !((String)cvCombo.getSelectedItem()).equals("GO") )
+ {
+ goAspect.setVisible(false);
+ evidenceList.setVisible(false);
+ }
+ else
+ {
+ goAspect.setVisible(true);
+ evidenceList.setVisible(true);
+ }
+ termList.removeAllItems();
+ }
+ else if(e.getActionCommand().equals("SEARCH"))
+ searchCvTerms();
+ }
+
+ /**
+ * Utility to return the name of the CV in the database.
+ * @return
+ */
+ protected String getCvName()
+ {
+ String cvName = (String) cvCombo.getSelectedItem();
+ if (cvName.equals("GO"))
+ {
+ if (((String) goAspect.getSelectedItem()).equals("F"))
+ cvName = "molecular_function";
+ else if (((String) goAspect.getSelectedItem()).equals("P"))
+ cvName = "biological_process";
+ else if (((String) goAspect.getSelectedItem()).equals("C"))
+ cvName = "cellular_component";
+ }
+ else if (cvName.equals("product"))
+ cvName = DatabaseDocument.PRODUCTS_TAG_CVNAME;
+ else if (cvName.equals("controlled_curation"))
+ cvName = DatabaseDocument.CONTROLLED_CURATION_TAG_CVNAME;
+ else if (cvName.equals("class"))
+ cvName = DatabaseDocument.RILEY_TAG_CVNAME;
+ return cvName;
+ }
+
+ /**
+ * Search the CvTerms for the selected CV and keywords.
+ */
+ private void searchCvTerms()
+ {
+ String key = keyWord.getText().trim();
+
+ if (((String) cvCombo.getSelectedItem()).equals("GO") &&
+ Pattern.matches("^\\d{7}$", key))
+ {
+ terms = getTermsForGoId();
+ }
+ else
+ {
+ String cvName = getCvName();
+ terms =
+ DatabaseDocument.getCvterms(key,
+ cvName, ignoreCase.isSelected());
+ Collections.sort(terms, new CvTermsComparator());
+ }
+
+ remove(termList);
+ termList = new JExtendedComboBox(terms, true);
+ termList.setPreferredSize(d);
+ termList.setMaximumSize(d);
+
+ if(((CvTerm)termList.getSelectedItem()).getName().equals("") &&
+ termList.getItemCount() > 2)
+ termList.setSelectedIndex(1);
+ c.gridy = termRow;
+ c.gridwidth = 2;
+ c.gridx = 0;
+ add(termList,c);
+
+ repaint();
+ revalidate();
+ }
+
+ private Vector<CvTerm> getTermsForGoId()
+ {
+ CvTerm cvTerm =
+ DatabaseDocument.getCvtermFromGoId(keyWord.getText().trim());
+
+ terms = new Vector<CvTerm>();
+ if(cvTerm != null)
+ terms.add(cvTerm);
+ return terms;
+ }
+
+ /**
+ * Show the CvTerm selector in an dialog.
+ * @param frame
+ * @param doc
+ * @return
+ */
+ protected boolean showOptions(final JFrame frame, final DatabaseDocument doc)
+ {
+ final JOptionPane optionPane = new JOptionPane(
+ this,
+ JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
+
+ final JDialog dialog = new JDialog(frame, "CV Term Selector", true);
+ dialog.setContentPane(optionPane);
+ dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+
+ optionPane.addPropertyChangeListener(new PropertyChangeListener()
+ {
+ public void propertyChange(PropertyChangeEvent e)
+ {
+ String prop = e.getPropertyName();
+
+ if (dialog.isVisible() && (e.getSource() == optionPane)
+ && (prop.equals(JOptionPane.VALUE_PROPERTY)))
+ {
+ int value = ((Integer) optionPane.getValue()).intValue();
+ if (value == JOptionPane.OK_OPTION)
+ {
+ if (editableCvCombo != null)
+ {
+ cvTerm = addCvTermToDb(doc);
+ if (addToAnnotation.isSelected())
+ {
+ cv = (String) editableCvCombo.getSelectedItem();
+ if (DatabaseDocument.PRODUCTS_TAG_CVNAME.equals(cv))
+ cv = "product";
+ }
+ }
+ else
+ {
+ if (termList == null)
+ cvTerm = null;
+ else
+ cvTerm = getCvTermFromSelectedItem();
+
+ cv = (String) cvCombo.getSelectedItem();
+ if (evidenceList != null && evidenceList.getSelectedIndex() > -1)
+ evidenceCode = GoBox.evidenceCodes[2][evidenceList.getSelectedIndex()];
+ }
+ }
+ dialog.setVisible(false);
+ dialog.dispose();
+ }
+ }
+ });
+ dialog.pack();
+ centerDialog(dialog);
+ dialog.setMinimumSize(getPreferredSize());
+ dialog.setVisible(true);
+
+ int value = ((Integer) optionPane.getValue()).intValue();
+ if(value == JOptionPane.OK_OPTION && cvTerm != null)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Center the given dialog on the screen.
+ * @param dialog
+ */
+ private void centerDialog(JDialog dialog)
+ {
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ final int x_position =(screen.width - dialog.getSize().width) / 2;
+ int y_position =(screen.height - dialog.getSize().height) / 2;
+ if(y_position < 10)
+ y_position = 10;
+
+ dialog.setLocation(new Point(x_position, y_position));
+ }
+
+ /**
+ * Return the CvTerm for a selected item in the list of terms.
+ * @return
+ */
+ private CvTerm getCvTermFromSelectedItem()
+ {
+ if (termList.getSelectedItem() instanceof String)
+ {
+ final String selectedStr = (String) termList.getSelectedItem();
+ for (int i = 0; i < terms.size(); i++)
+ {
+ CvTerm thisCvTerm = (CvTerm) terms.get(i);
+ if (thisCvTerm.getName().equals(selectedStr))
+ return thisCvTerm;
+ }
+ }
+ else
+ return (CvTerm) termList.getSelectedItem();
+ return null;
+ }
+
+ /**
+ * Add the Selected term to the database.
+ * @param doc
+ * @return
+ */
+ private CvTerm addCvTermToDb(final DatabaseDocument doc)
+ {
+ final java.util.List<String> cvNames =
+ DatabaseDocument.getCvControledCurationNames();
+ final String db;
+ if(cvNames.contains((String)editableCvCombo.getSelectedItem()))
+ db = ChadoTransactionManager.CONTROLLED_CURATION_DB;
+ else
+ db = ChadoTransactionManager.PRODUCT_DB;
+
+ cvTerm = ChadoTransactionManager.getCvTerm(
+ newTerm.getText().trim(),
+ (String)editableCvCombo.getSelectedItem(),
+ definition.getText().trim(), db);
+ try
+ {
+ doc.insertCvTerm(cvTerm);
+ return cvTerm;
+ }
+ catch(RuntimeException re)
+ {
+ JOptionPane.showMessageDialog(null, re.getMessage(),
+ "Problems Writing to Database ",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ return null;
+ }
+
+ // getters
+ protected CvTerm getCvTerm()
+ {
+ return cvTerm;
+ }
+
+ protected String getCv()
+ {
+ return cv;
+ }
+
+ protected String getEvidenceCode()
+ {
+ return evidenceCode;
+ }
+
+ public static void main(final String args[])
+ {
+ DatabaseEntrySource src = new DatabaseEntrySource();
+ src.setLocation(true);
+
+ DatabaseDocument doc = new DatabaseDocument(src.getLocation(), src.getPfield());
+ doc.getCvTermsByCvName("molecular_function");
+
+ CvTermSelector cvTermSelector = new CvTermSelector();
+ if(cvTermSelector.showOptions(null, doc))
+ {
+ System.out.println(cvTermSelector.getCvTerm().getName());
+ }
+
+ System.exit(0);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/CvTermsComparator.java b/uk/ac/sanger/artemis/components/genebuilder/cv/CvTermsComparator.java
new file mode 100644
index 0000000..39b8770
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/CvTermsComparator.java
@@ -0,0 +1,38 @@
+/* CvTermsComparator.java
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.util.Comparator;
+
+import org.gmod.schema.cv.CvTerm;
+
+public class CvTermsComparator implements Comparator<CvTerm>
+{
+ public int compare(CvTerm cvTerm1, CvTerm cvTerm2)
+ {
+ return cvTerm1.getName().toLowerCase().compareTo(
+ cvTerm2.getName().toLowerCase() );
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/DatePanel.java b/uk/ac/sanger/artemis/components/genebuilder/cv/DatePanel.java
new file mode 100644
index 0000000..1edf386
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/DatePanel.java
@@ -0,0 +1,134 @@
+/* DatePanel.java
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.swing.JSpinner;
+import javax.swing.SpinnerDateModel;
+import javax.swing.SpinnerListModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+
+/**
+ * Date input panel
+ */
+public class DatePanel extends JSpinner
+{
+ private static final long serialVersionUID = 1L;
+
+ public DatePanel(final String date,
+ final int height)
+ {
+ this(date);
+ setMaximumSize(
+ new Dimension(getPreferredSize().width, height));
+ }
+
+ public DatePanel(final String date)
+ {
+ Date initDate = getDate(date);
+ Calendar calendar = Calendar.getInstance();
+ calendar.add(Calendar.YEAR, -100);
+ Date earliestDate = calendar.getTime();
+ calendar.add(Calendar.YEAR, 200);
+ Date latestDate = calendar.getTime();
+
+ if(initDate != null)
+ {
+ SpinnerDateModel model = new SpinnerDateModel(initDate,
+ earliestDate,
+ latestDate,
+ Calendar.YEAR);
+ setModel(model);
+ setBackground(Color.white);
+ setEditor(new JSpinner.DateEditor(this, "yyyy/MM/dd"));
+ }
+ else
+ {
+ SpinnerListModel model = new SpinnerListModel( new String[] { "", "----/--/--", "" } );
+ setModel(model);
+ setBackground(Color.white);
+ setValue("----/--/--");
+ model.addChangeListener(new ChangeListener()
+ {
+ public void stateChanged(ChangeEvent e)
+ {
+ if(getModel() instanceof SpinnerListModel)
+ {
+ setModel(new SpinnerDateModel());
+ setEditor(new JSpinner.DateEditor(DatePanel.this, "yyyy/MM/dd"));
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Get a Date object from a date string in the format 20061129
+ * @param dateStr
+ * @return
+ */
+ protected Date getDate(String dateStr)
+ {
+ Calendar cal = Calendar.getInstance();
+ cal.clear();
+ if(dateStr == null ||
+ dateStr.equals("") ||
+ dateStr.length() != 8)
+ return null;
+
+ int year = Integer.parseInt(dateStr.substring(0,4));
+ int month = Integer.parseInt(dateStr.substring(4, 6))-1;
+ int day = Integer.parseInt(dateStr.substring(6,8));
+
+ cal.set(year,month,day);
+ return cal.getTime();
+ }
+
+ protected String getText()
+ {
+ if(getModel() instanceof SpinnerDateModel)
+ {
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(
+ "yyyyMMdd");
+ Date date = ((SpinnerDateModel)getModel()).getDate();
+ return sdf.format(date);
+ }
+ return "";
+ }
+
+ public static String getDate()
+ {
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(
+ "yyyyMMdd");
+ Date date = new Date();
+ return sdf.format(date);
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/GoBox.java b/uk/ac/sanger/artemis/components/genebuilder/cv/GoBox.java
new file mode 100644
index 0000000..0ee9457
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/GoBox.java
@@ -0,0 +1,521 @@
+/* GoBox.java
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JTextField;
+
+import org.gmod.schema.cv.CvTerm;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.editor.BrowserControl;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class GoBox extends AbstractCvBox
+{
+ public static String[][] evidenceCodes =
+ {
+ {"EXP", "IC", "IDA", "IEA", "IEP", "IGC",
+ "IBA", "IBD", "IKR", "IRD", "IGI",
+ "IMP", "IPI", "ISA", "ISM", "ISO", "ISS",
+ "NAS", "ND", "RCA", "TAS", "NR" },
+ {"EXP\t:: Inferred from Experiment",
+ "IC \t:: Inferred by Curator",
+ "IDA\t:: Inferred from Direct Assay",
+ "IEA\t:: Inferred from Electronic Annotation",
+ "IEP\t:: Inferred from Expression Pattern",
+ "IGC\t:: Inferred from Genomic Context",
+ "IBA\t:: Inferred from Biological aspect of Ancestor",
+ "IBD\t:: Inferred from Biological aspect of Descendent",
+ "IKR\t:: Inferred from Key Residues",
+ "IRD\t:: Inferred from Rapid Divergence",
+ "IGI\t:: Inferred from Genetic Interaction",
+ "IMP\t:: Inferred from Mutant Phenotype",
+ "IPI\t:: Inferred from Physical Interaction",
+ "ISA\t:: Inferred from Sequence Alignment",
+ "ISM\t:: Inferred from Sequence Model",
+ "ISO\t:: Inferred from Sequence Orthology",
+ "ISS\t:: Inferred from Sequence or Structural Similarity",
+ "NAS\t:: Non-traceable Author Statement",
+ "ND \t:: No biological Data available",
+ "RCA\t:: inferred from Reviewed Computational Analysis",
+ "TAS\t:: Traceable Author Statement",
+ "NR \t:: Not Recorded"},
+ { "Inferred from Experiment",
+ "Inferred by Curator",
+ "Inferred from Direct Assay",
+ "Inferred from Electronic Annotation",
+ "Inferred from Expression Pattern",
+ "Inferred from Genomic Context",
+ "Inferred from Biological aspect of Ancestor",
+ "Inferred from Biological aspect of Descendent",
+ "Inferred from Key Residues",
+ "Inferred from Rapid Divergence",
+ "Inferred from Genetic Interaction",
+ "Inferred from Mutant Phenotype",
+ "Inferred from Physical Interaction",
+ "Inferred from Sequence Alignment",
+ "Inferred from Sequence Model",
+ "Inferred from Sequence Orthology",
+ "Inferred from Sequence or Structural Similarity",
+ "Non-traceable Author Statement",
+ "No biological Data available",
+ "inferred from Reviewed Computational Analysis",
+ "Traceable Author Statement",
+ "Not Recorded"}
+ };
+
+ private Dimension go_dimension;
+ private static Dimension evidenceListDimension;
+
+ private Box xBox;
+ private int value_index;
+ private JTextField withTextField;
+ private JTextField dbxrefTextField;
+ private JExtendedComboBox evidenceList;
+ private JTextField qualfTextField;
+ private DatePanel dateField;
+ private String origQualifierString;
+ private Qualifier origQualifier;
+ private static Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ private static Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+ private static Cursor chand = new Cursor(Cursor.HAND_CURSOR);
+
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(GoBox.class);
+
+ private static String AMIGOURL;
+
+ protected GoBox(final Qualifier qualifier,
+ final String qualifierString,
+ final int value_index,
+ Dimension go_dimension, final Dimension dimension)
+ {
+ this.origQualifier = qualifier;
+ this.origQualifierString = qualifierString;
+ this.go_dimension = go_dimension;
+ this.value_index = value_index;
+ this.xBox = Box.createHorizontalBox();
+
+ String goId = getField("GOid=", qualifierString);
+ final String term = getField("term=", qualifierString);
+ CvTerm cvTerm = getGOCvTerm(term);
+
+ final JLabel goTermField = new JLabel(goId);
+ addGoLabelLiteners(goTermField);
+
+ JLabel goAspect = null;
+ Font font = goTermField.getFont().deriveFont(Font.BOLD);
+
+ if(cvTerm.getCv().getName().indexOf("molecular_function")>-1)
+ {
+ goAspect = new JLabel(" [F] ");
+ goAspect.setForeground(Color.RED);
+ goAspect.setFont(font);
+ }
+ else if(cvTerm.getCv().getName().indexOf("biological_process")>-1)
+ {
+ goAspect = new JLabel(" [P] ");
+ goAspect.setForeground(Color.GREEN);
+ goAspect.setFont(font);
+ }
+ else if(cvTerm.getCv().getName().indexOf("cellular_component")>-1)
+ {
+ goAspect = new JLabel(" [C] ");
+ goAspect.setForeground(Color.BLUE);
+ goAspect.setFont(font);
+ }
+ else
+ {
+ goAspect = new JLabel(" [?] ");
+ goAspect.setForeground(Color.BLACK);
+ goAspect.setFont(font);
+ }
+
+ if(go_dimension == null)
+ this.go_dimension = new Dimension(goTermField.getPreferredSize().width+
+ goAspect.getPreferredSize().width,
+ goTermField.getPreferredSize().height);
+ goTermField.setToolTipText(term);
+ xBox.add(goTermField);
+ xBox.add(goAspect);
+
+ // the WITH column is associated with one or more FeatureCvTermDbXRef
+ String with = getField("with=", qualifierString);
+ withTextField = new JTextField(with);
+ withTextField.setToolTipText("with/from column");
+ withTextField.setPreferredSize(dimension);
+ withTextField.setMaximumSize(dimension);
+ withTextField.setActionCommand("with=");
+ xBox.add(withTextField);
+
+
+ // N.B. for /GO the db_xref is a Pub (for primary pubs)
+ // or FeatureCvTermPub (for others) in /GO
+ String dbxref = getField("db_xref=", qualifierString);
+ dbxrefTextField = new JTextField(dbxref);
+ dbxrefTextField.setToolTipText("dbxref column");
+ dbxrefTextField.setPreferredSize(dimension);
+ dbxrefTextField.setMaximumSize(dimension);
+ dbxrefTextField.setActionCommand("db_xref=");
+ xBox.add(dbxrefTextField);
+
+ // feature_cvterm_prop's
+ String evidence = getField("evidence=", qualifierString);
+
+ evidenceList = new JExtendedComboBox(evidenceCodes[1]);
+ evidenceList.setOpaque(false);
+ evidenceList.setToolTipText("evidence column");
+ evidenceList.setSelectedIndex( getEvidenceIndex(evidence) );
+ evidenceList.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(((String)evidenceList.getSelectedItem()).startsWith("NR \t::"))
+ JOptionPane.showMessageDialog(null,
+ "This evicence code is obsolete:\n"+
+ evidenceList.getSelectedItem(),
+ "Obsolete Evidence Code", JOptionPane.WARNING_MESSAGE);
+ }
+ });
+ evidenceListDimension = evidenceList.getPreferredSize();
+ evidenceListDimension = new Dimension(90,(int)evidenceListDimension.getHeight());
+ evidenceList.setPreferredSize(evidenceListDimension);
+ evidenceList.setMaximumSize(evidenceListDimension);
+ evidenceList.setActionCommand("evidence=");
+ xBox.add(evidenceList);
+
+ String qual = getField("qualifier=", qualifierString);
+ qualfTextField = new JTextField(qual);
+ qualfTextField.setToolTipText("qualifier column");
+ qualfTextField.setPreferredSize(dimension);
+ qualfTextField.setMaximumSize(dimension);
+ qualfTextField.setActionCommand("qualifier=");
+ xBox.add(qualfTextField);
+
+ dateField = new DatePanel( getField("date=", qualifierString),
+ dimension.height);
+ xBox.add(dateField);
+ }
+
+ public static CvTerm getGOCvTerm(String term)
+ {
+ CvTerm cvTerm = DatabaseDocument.getCvTermByCvTermName(term);
+
+ if(cvTerm.getCv().getName().indexOf("molecular_function") < 0 &&
+ cvTerm.getCv().getName().indexOf("biological_process") < 0 &&
+ cvTerm.getCv().getName().indexOf("cellular_component") < 0)
+ {
+ CvTerm thisCvTerm = DatabaseDocument.getCvTermByCvAndCvTerm(term,
+ "molecular_function");
+
+ if(thisCvTerm == null)
+ thisCvTerm = DatabaseDocument.getCvTermByCvAndCvTerm(term,
+ "biological_process");
+
+ if(thisCvTerm == null)
+ thisCvTerm = DatabaseDocument.getCvTermByCvAndCvTerm(term,
+ "cellular_component");
+ if(thisCvTerm != null)
+ cvTerm = thisCvTerm;
+ }
+ return cvTerm;
+ }
+
+ /**
+ * Add GO listeners for opening Amigo.
+ * @param goTermField
+ */
+ private void addGoLabelLiteners(final JLabel goTermField)
+ {
+ setAmigoUrl();
+ goTermField.addMouseListener(new MouseAdapter()
+ {
+ public void mouseClicked(final MouseEvent e)
+ {
+ SwingWorker browserLaunch = new SwingWorker()
+ {
+ public Object construct()
+ {
+ if(e.getClickCount() == 1)
+ {
+ goTermField.setCursor(cbusy);
+ BrowserControl.displayURL((AMIGOURL+goTermField.getText()).replaceFirst("GO:GO:", "GO:"));
+ goTermField.setCursor(cdone);
+ }
+ return null;
+ }
+ };
+ browserLaunch.start();
+ }
+ });
+
+ goTermField.addMouseMotionListener(new MouseMotionAdapter()
+ {
+ public void mouseMoved(MouseEvent e)
+ {
+ goTermField.setCursor(chand);
+ }
+ });
+ }
+
+ /**
+ * Set the Amigo URL for hyperlinking GO terms.
+ */
+ private void setAmigoUrl()
+ {
+ if(AMIGOURL == null)
+ {
+ StringVector dbsLinks = Options.getOptions().getOptionValues("hyperlinks");
+ for(int i=0; i<dbsLinks.size(); i+=2)
+ {
+ if(dbsLinks.get(i).equals("GO"))
+ {
+ AMIGOURL = dbsLinks.get(i+1);
+ return;
+ }
+ }
+ AMIGOURL = "http://amigo.geneontology.org/cgi-bin/amigo/term-details.cgi?term=";
+ }
+ }
+
+ protected static int getEvidenceIndex(String evidence)
+ {
+ for(int i=0; i<evidenceCodes[2].length; i++)
+ {
+ // look for full text or abbreviation of the code
+ if(evidenceCodes[2][i].equalsIgnoreCase(evidence) ||
+ evidenceCodes[0][i].equalsIgnoreCase(evidence))
+ return i;
+ }
+ return -1;
+ }
+
+ protected Dimension getGoDimension()
+ {
+ return go_dimension;
+ }
+
+ protected Box getBox()
+ {
+ return xBox;
+ }
+
+ protected boolean isQualifierChanged()
+ {
+ String old = getField("with=", origQualifierString);
+ if(!old.equals(withTextField.getText().trim()))
+ return true;
+
+ old = getField("db_xref=", origQualifierString);
+ if(!old.equals(dbxrefTextField.getText().trim()))
+ return true;
+
+ old = getField("evidence=", origQualifierString);
+
+ if(evidenceList.getSelectedIndex() > -1 &&
+ !old.equalsIgnoreCase(evidenceCodes[2][ evidenceList.getSelectedIndex() ]))
+ return true;
+
+ old = getField("qualifier=", origQualifierString);
+ if(!old.equals(qualfTextField.getText()))
+ return true;
+
+ old = getField("date=", origQualifierString);
+ if(!old.equals(dateField.getText()))
+ return true;
+
+ return false;
+ }
+
+ protected int getValueIndex()
+ {
+ return value_index;
+ }
+
+ /**
+ * Update the qualifier from the GO form.
+ */
+ protected void updateQualifier(final QualifierVector qv)
+ {
+ int index = qv.indexOfQualifierWithName(origQualifier.getName());
+ Qualifier oldQualifier = qv.getQualifierByName(origQualifier.getName());
+
+ final String goId = getField("GOid=", origQualifierString);
+
+ StringVector oldValues = oldQualifier.getValues();
+ Vector<Integer> values_index = new Vector<Integer>();
+ for(int i=0; i<oldValues.size(); i++)
+ {
+ String oldValue = oldValues.get(i);
+ String newGoId = getField("GOid=", oldValue);
+ if(newGoId.equals(goId))
+ values_index.add(new Integer(i));
+ }
+
+ if(values_index.size() > 0)
+ {
+ String oldValue = oldValues.get(value_index);
+ String oldGoId = getField("GOid=", oldValue);
+
+ if(!goId.equals(oldGoId))
+ {
+ if(values_index.size() == 1)
+ value_index = values_index.get(0).intValue();
+ else
+ {
+ final String with = getField("with=", origQualifierString);
+ final String evidence = getField("evidence=", origQualifierString);
+ final String dbxref = getField("dbxref=", origQualifierString);
+ for(int i=0; i<values_index.size(); i++)
+ {
+ int ind = values_index.get(i).intValue();
+ value_index = ind;
+ String value = oldValues.get(ind);
+
+ if(!with.equals(""))
+ {
+ String thisWith = getField("with=", value);
+ if(thisWith.equals(with))
+ break;
+ }
+
+ if(!dbxref.equals(""))
+ {
+ String thisDbxref = getField("dbxref=", value);
+ if(thisDbxref.equals(dbxref))
+ break;
+ }
+
+ String thisEvidence = getField("evidence=", value);
+ if(thisEvidence.equals(evidence))
+ break;
+ }
+ }
+ }
+ }
+ else
+ value_index = -99;
+
+ if(value_index > -1)
+ oldValues.remove(value_index);
+
+ String updatedQualifierString = updateQualifierString();
+ logger4j.debug(origQualifierString);
+ logger4j.debug(updatedQualifierString);
+ oldValues.add(value_index, updatedQualifierString);
+
+ origQualifier = new Qualifier(origQualifier.getName(), oldValues);
+ qv.remove(index);
+ qv.add(index, origQualifier);
+ }
+
+ private String updateQualifierString()
+ {
+ String newQualifierString = origQualifierString;
+
+ String old = getField("with=", origQualifierString);
+ if(!old.equals(withTextField.getText().trim()))
+ newQualifierString = changeField("with=", withTextField.getText().trim(),
+ newQualifierString);
+
+ old = getField("db_xref=", origQualifierString);
+ if(!old.equals(dbxrefTextField.getText().trim()))
+ newQualifierString = changeField("db_xref=", dbxrefTextField.getText().trim(),
+ newQualifierString);
+
+ old = getField("evidence=", origQualifierString);
+ if(!old.equals(evidenceCodes[2][ evidenceList.getSelectedIndex() ]))
+ newQualifierString = changeField("evidence=", evidenceCodes[2][ evidenceList.getSelectedIndex() ],
+ newQualifierString);
+
+ old = getField("qualifier=", origQualifierString);
+ if(!old.equals(qualfTextField.getText()))
+ newQualifierString = changeField("qualifier=", qualfTextField.getText().trim(),
+ newQualifierString);
+
+ old = getField("date=", origQualifierString);
+ if(!old.equals(dateField.getText()))
+ newQualifierString = changeField("date=", dateField.getText().trim(),
+ newQualifierString);
+ return newQualifierString;
+ }
+
+ protected static Dimension getEvidenceListDimension()
+ {
+ if(evidenceListDimension == null)
+ {
+ JExtendedComboBox evidenceList = new JExtendedComboBox(evidenceCodes[1]);
+ evidenceListDimension = evidenceList.getPreferredSize();
+ evidenceListDimension = new Dimension(80,(int)evidenceListDimension.getHeight());
+ }
+ return evidenceListDimension;
+ }
+
+ /**
+ * Given the string:
+ * aspect=F;GOid=GO:0003674;term=molecular_function;evidence=No biological Data available
+ * return the string:
+ * aspect=F;GOid=GO:0003674;term=molecular_function;evidence=ND
+ * @param goText
+ * @return
+ */
+ public static String getEvidenceCodeGoTextFromText(String goText)
+ {
+ final String oldEvidence = getField("evidence=", goText);
+ String newEvidence = oldEvidence;
+
+ for(int i=0; i<evidenceCodes[2].length; i++)
+ {
+ if(evidenceCodes[2][i].equalsIgnoreCase(oldEvidence.toLowerCase()))
+ {
+ newEvidence = evidenceCodes[0][i];
+ break;
+ }
+ }
+
+ if(!oldEvidence.equals(newEvidence))
+ goText = goText.replaceAll(oldEvidence, newEvidence);
+ return goText;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/HistoryBox.java b/uk/ac/sanger/artemis/components/genebuilder/cv/HistoryBox.java
new file mode 100644
index 0000000..1af0e8f
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/HistoryBox.java
@@ -0,0 +1,334 @@
+/* HistoryBox
+ *
+ * created: 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JTextField;
+
+import org.gmod.schema.cv.CvTerm;
+
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.QualifierTextArea;
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+import uk.ac.sanger.artemis.components.genebuilder.TextAreaDocumentListener;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class HistoryBox extends AbstractCvBox
+{
+ private Box xBox;
+ private int value_index;
+ private JExtendedComboBox termCombo;
+ private JTextField curatorNameField;
+ private QualifierTextArea qualfTextField;
+ private DatePanel dateField;
+ private String origQualifierString;
+ private Qualifier origQualifier;
+
+ /**
+ * Display history qualifiers
+ * @param qualifier
+ * @param qualifierString
+ * @param value_index
+ * @param dimension
+ * @param go_dimension
+ */
+ public HistoryBox(final Qualifier qualifier,
+ final String qualifierString,
+ final int value_index,
+ final Dimension dimension,
+ final Dimension go_dimension)
+ {
+ this.origQualifier = qualifier;
+ this.origQualifierString = qualifierString;
+ this.value_index = value_index;
+ this.xBox = Box.createHorizontalBox();
+
+ Box yBox = Box.createVerticalBox();
+
+ Box lineBox = Box.createHorizontalBox();
+ final String term = getField("term=", qualifierString);
+
+ termCombo =
+ new JExtendedComboBox(getCvTermStrings());
+
+ final Dimension d;
+ if(go_dimension != null)
+ {
+ d = new Dimension(go_dimension.width+dimension.width,
+ termCombo.getPreferredSize().height);
+ }
+ else
+ {
+ FontMetrics fm = termCombo.getFontMetrics(termCombo.getFont());
+ int width= fm.stringWidth("GO:0001234 [F] ");
+ d = new Dimension(width+dimension.width,
+ termCombo.getPreferredSize().height);
+ }
+ termCombo.setPreferredSize(d);
+ termCombo.setMaximumSize(d);
+
+ termCombo.setSelectedItem(term);
+ lineBox.add(termCombo);
+
+ // feature_cvterm_prop's
+ Dimension dimension2 = new Dimension(d.width*2, d.height);
+ String qual = getField("curatorName=", qualifierString);
+ curatorNameField = new JTextField(qual);
+ curatorNameField.setToolTipText("qualifier column");
+ curatorNameField.setPreferredSize(dimension2);
+ curatorNameField.setMaximumSize(dimension2);
+ curatorNameField.setActionCommand("curatorName=");
+ curatorNameField.setCaretPosition(0);
+ lineBox.add(curatorNameField);
+
+ dateField = new DatePanel(getField("date=", qualifierString),
+ dimension.height);
+
+ lineBox.add(dateField);
+ lineBox.add(Box.createHorizontalGlue());
+ yBox.add(lineBox);
+
+ lineBox = Box.createHorizontalBox();
+ lineBox.add(Box.createHorizontalStrut(5));
+ Dimension dimension4 = new Dimension(
+ termCombo.getPreferredSize().width+
+ curatorNameField.getPreferredSize().width+
+ dateField.getPreferredSize().width-5, dimension.height*20);
+ qual = getFieldIgnoreSeparator("qualifier", qualifierString);
+ qualfTextField = new QualifierTextArea();
+ qualfTextField.setUseHyperlinks(false);
+ qualfTextField.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+ qualfTextField.setText(qual);
+ qualfTextField.setToolTipText("qualifier column");
+ qualfTextField.setPreferredSize(dimension4);
+ qualfTextField.setMaximumSize(dimension4);
+ qualfTextField.getDocument().addDocumentListener(
+ new TextAreaDocumentListener(qualfTextField));
+ qualfTextField.setCaretPosition(0);
+ lineBox.add(qualfTextField);
+ lineBox.add(Box.createHorizontalGlue());
+ yBox.add(lineBox);
+
+ xBox.add(yBox);
+ }
+
+ protected boolean isQualifierChanged()
+ {
+ String old = getField("term=", origQualifierString);
+ if(!old.equals(termCombo.getSelectedItem()))
+ return true;
+
+ old = getFieldIgnoreSeparator("qualifier", origQualifierString);
+ if(!old.equals(qualfTextField.getText()))
+ return true;
+
+ old = getField("date=", origQualifierString);
+ if(!old.equals(dateField.getText()))
+ return true;
+
+ old = getField("curatorName=", origQualifierString);
+ if(!old.equals(curatorNameField.getText()))
+ return true;
+
+ return false;
+ }
+
+ public static boolean contains(StringVector oldQualValues, String newQualString)
+ {
+ for(int i=0; i<oldQualValues.size(); i++)
+ {
+ String oldQualString = (String) oldQualValues.get(i);
+ String oldStr = getField("term=", oldQualString);
+ String newStr = getField("term=", newQualString);
+ if(!oldStr.equals(newStr))
+ continue;
+
+ oldStr = getFieldIgnoreSeparator("qualifier", oldQualString);
+ newStr = getFieldIgnoreSeparator("qualifier", newQualString);
+ if(!oldStr.equals(newStr))
+ continue;
+
+ oldStr = getField("date=", oldQualString);
+ newStr = getField("date=", newQualString);
+ if(!oldStr.equals(newStr))
+ continue;
+
+ oldStr = getField("curatorName=", oldQualString);
+ newStr = getField("curatorName=", newQualString);
+ if(oldStr.equals(newStr))
+ return true;
+ }
+
+ return false;
+ }
+
+ protected void updateQualifier(QualifierVector qv)
+ {
+ int index = qv.indexOfQualifierWithName(origQualifier.getName());
+ Qualifier oldQualifier = qv.getQualifierByName(origQualifier.getName());
+
+ final String term = getField("term=", origQualifierString);
+
+ StringVector oldValues = oldQualifier.getValues();
+ Vector<Integer> values_index = new Vector<Integer>();
+ for(int i=0; i<oldValues.size(); i++)
+ {
+ String oldValue = (String)oldValues.get(i);
+ String newTerm = getField("term=", oldValue);
+ if(newTerm.equals(term))
+ values_index.add(new Integer(i));
+ }
+
+ if(values_index.size() > 0)
+ {
+ String oldValue = (String) oldValues.get(value_index);
+ String oldTermId = getField("term=", oldValue);
+
+ if(!term.equals(oldTermId))
+ {
+ if(values_index.size() == 1)
+ value_index = ((Integer)values_index.get(0)).intValue();
+ else
+ {
+ final String with = getField("with=", origQualifierString);
+ final String evidence = getField("evidence=", origQualifierString);
+ final String dbxref = getField("dbxref=", origQualifierString);
+ for(int i=0; i<values_index.size(); i++)
+ {
+ int ind = ((Integer)values_index.get(i)).intValue();
+ value_index = ind;
+ String value = (String) oldValues.get(ind);
+
+ if(!with.equals(""))
+ {
+ String thisWith = getField("with=", value);
+ if(thisWith.equals(with))
+ break;
+ }
+
+ if(!dbxref.equals(""))
+ {
+ String thisDbxref = getField("dbxref=", value);
+ if(thisDbxref.equals(dbxref))
+ break;
+ }
+
+ String thisEvidence = getField("evidence=", value);
+ if(thisEvidence.equals(evidence))
+ break;
+ }
+ }
+ }
+ }
+ else
+ value_index = -99;
+
+ if(value_index > -1)
+ oldValues.remove(value_index);
+
+ String updatedQualifierString = updateQualifierString();
+ oldValues.add(value_index, updatedQualifierString);
+
+ origQualifier = new Qualifier(origQualifier.getName(), oldValues);
+ qv.remove(index);
+ qv.add(index, origQualifier);
+ }
+
+ private String updateQualifierString()
+ {
+ String newQualifierString = origQualifierString;
+ String old = getField("term=", origQualifierString);
+ if(!old.equals(dateField.getText()))
+ {
+ newQualifierString = changeField("term=", (String) termCombo.getSelectedItem(),
+ newQualifierString);
+ }
+
+ old = getField("date=", origQualifierString);
+ if(!old.equals(dateField.getText()))
+ {
+ newQualifierString = changeField("date=", dateField.getText().trim(),
+ newQualifierString);
+ }
+
+ old = getField("curatorName=", origQualifierString);
+ if(!old.equals(curatorNameField.getText().trim()))
+ {
+ newQualifierString = changeField("curatorName=", curatorNameField.getText().trim(),
+ newQualifierString);
+ }
+
+ old = getFieldIgnoreSeparator("qualifier", origQualifierString);
+ if(!old.equals(qualfTextField.getText()))
+ {
+ newQualifierString = newQualifierString.replaceAll(old, "");
+ newQualifierString = newQualifierString.replaceAll("qualifier=[^;]+", "");
+ newQualifierString = newQualifierString.replaceAll("qualifier=", "");
+ newQualifierString = newQualifierString.replaceAll("[;]{2,}", ";");
+ newQualifierString += ";qualifier="+qualfTextField.getText().trim();
+ }
+
+ return newQualifierString;
+ }
+
+ protected Box getBox()
+ {
+ return xBox;
+ }
+
+ protected static Vector<CvTerm> getCvTerms()
+ {
+ return DatabaseDocument.getCvterms("", ChadoTransactionManager.HISTORY_CV, false);
+ }
+
+ public static Vector<String> getCvTermStrings()
+ {
+ Vector<CvTerm> cvTerms = getCvTerms();
+ Vector<String> cvTermStr = new Vector<String>();
+ for(int i=0; i<cvTerms.size(); i++)
+ {
+ CvTerm cvTerm = cvTerms.get(i);
+ cvTermStr.add(cvTerm.getName());
+ }
+ return cvTermStr;
+ }
+
+ public static CvTerm getDefaultTerm()
+ {
+ CvTerm cvterm =
+ (CvTerm) DatabaseDocument.getCvterms("", ChadoTransactionManager.HISTORY_CV, false).get(0);
+ return cvterm;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/ProductBox.java b/uk/ac/sanger/artemis/components/genebuilder/cv/ProductBox.java
new file mode 100644
index 0000000..bc027a5
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/ProductBox.java
@@ -0,0 +1,261 @@
+/* ProductBox.java
+ *
+ * created: 2008
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.awt.Dimension;
+import java.awt.Font;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.components.Splash;
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.StringVector;
+
+/**
+ * Product display box.
+ *
+ * Where there are multiple products associated with a feature assume
+ * the product is the recommended product unless rank=1 which means it
+ * is an alternative product.
+ */
+class ProductBox extends AbstractCvBox
+{
+ private Box xBox;
+ private int value_index;
+ private WrapTextArea termTextField;
+ private JTextField withTextField;
+ private JTextField dbxrefTextField;
+ private JExtendedComboBox evidenceList;
+ private JCheckBox recommended = new JCheckBox();
+ private String origQualifierStr;
+ private Qualifier origQualifier;
+ private Box xHeadings = Box.createHorizontalBox();
+
+ public ProductBox(final Qualifier qualifier,
+ final String qualifierStr,
+ final int value_index,
+ final Dimension dimension,
+ final Dimension go_dimension)
+ {
+ this.origQualifier = qualifier;
+ this.origQualifierStr = qualifierStr;
+ this.value_index = value_index;
+ this.xBox = Box.createHorizontalBox();
+
+ String term = getField("term=", qualifierStr);
+
+ // this may not be stored as a CV (product_cv=no?)
+ if(term.equals(""))
+ term = qualifierStr;
+
+ termTextField = new WrapTextArea(term, go_dimension, dimension.width*2);
+
+ xBox.add(termTextField);
+
+ // the WITH column is associated with one or more FeatureCvTermDbXRef
+ String with = getField("with=", qualifierStr);
+ withTextField = new JTextField(with);
+ withTextField.setToolTipText("with/from column");
+ withTextField.setPreferredSize(dimension);
+ withTextField.setMaximumSize(dimension);
+ withTextField.setActionCommand("with=");
+
+ xBox.add(withTextField);
+
+ // N.B. for /GO the db_xref is a Pub (for primary pubs)
+ // or FeatureCvTermPub (for others) in /GO
+ String dbxref = getField("db_xref=", qualifierStr);
+ dbxrefTextField = new JTextField(dbxref);
+ dbxrefTextField.setToolTipText("dbxref column");
+ dbxrefTextField.setPreferredSize(dimension);
+ dbxrefTextField.setMaximumSize(dimension);
+ dbxrefTextField.setActionCommand("db_xref=");
+
+ xBox.add(dbxrefTextField);
+
+ // feature_cvterm_prop's
+ String evidence = getField("evidence=", qualifierStr);
+
+ evidenceList = new JExtendedComboBox(GoBox.evidenceCodes[1]);
+ evidenceList.setOpaque(false);
+ evidenceList.setToolTipText("evidence column");
+ evidenceList.setSelectedIndex( GoBox.getEvidenceIndex(evidence) );
+
+ Dimension de = evidenceList.getPreferredSize();
+ de = new Dimension(90,(int)de.getHeight());
+ evidenceList.setPreferredSize(de);
+ evidenceList.setMaximumSize(de);
+ evidenceList.setActionCommand("evidence=");
+ xBox.add(evidenceList);
+
+ // check box for recommended product
+ final String rank = getField("rank=", qualifierStr);
+ recommended.setSelected( (rank.length() == 0 || rank.equals("0")) ? true : false );
+ evidenceList.setActionCommand("rank=");
+ xBox.add(recommended);
+
+ if (value_index == 0)
+ {
+ JLabel lab = new JLabel("Product");
+ lab.setFont(lab.getFont().deriveFont(Font.BOLD));
+ xHeadings.add(lab);
+
+ xHeadings.add(Box.createRigidArea(new Dimension(termTextField
+ .getPreferredSize().width - lab.getPreferredSize().width, 0)));
+ JLabel withLabel = new JLabel("WITH/FROM");
+ withLabel.setPreferredSize(dimension);
+ withLabel.setMaximumSize(dimension);
+ xHeadings.add(withLabel);
+
+ JLabel dbxrefLabel = new JLabel("Dbxref");
+ dbxrefLabel.setPreferredSize(dimension);
+ dbxrefLabel.setMaximumSize(dimension);
+ xHeadings.add(dbxrefLabel);
+
+ xHeadings.add(Box.createHorizontalStrut(de.width));
+ JLabel recommendedLabel = new JLabel("Recommended");
+ recommendedLabel.setPreferredSize(dimension);
+ recommendedLabel.setMaximumSize(dimension);
+ xHeadings.add(recommendedLabel);
+
+ xHeadings.add(Box.createHorizontalGlue());
+ }
+ }
+
+ /**
+ * Check if this qualifier has been changed
+ */
+ protected boolean isQualifierChanged()
+ {
+ String old = getField("with=", origQualifierStr);
+ if(!old.equals(withTextField.getText().trim()))
+ return true;
+
+ old = getField("db_xref=", origQualifierStr);
+ if(!old.equals(dbxrefTextField.getText().trim()))
+ return true;
+
+ old = getField("evidence=", origQualifierStr);
+ if(evidenceList.getSelectedIndex() > -1 &&
+ !old.equalsIgnoreCase(GoBox.evidenceCodes[2][ evidenceList.getSelectedIndex() ]))
+ return true;
+
+ // test rank change for recommended/alternative product
+ if(recommended.isEnabled())
+ {
+ old = getField("rank=", origQualifierStr);
+ if( !recommended.isSelected() && (old.equals("0") || old.equals("")) )
+ return true;
+ else if(recommended.isSelected() && !old.equals("0"))
+ return true;
+ }
+ return false;
+ }
+
+ protected void updateQualifier(QualifierVector qv)
+ {
+ Qualifier oldQualifier = qv.getQualifierByName(origQualifier.getName());
+ StringVector values = oldQualifier.getValues();
+
+ values.remove(value_index);
+ String updatedQualifierString = updateQualifierString();
+
+ Splash.logger4j.debug(origQualifierStr);
+ Splash.logger4j.debug(updatedQualifierString);
+ values.add(value_index, updatedQualifierString);
+
+ int index = qv.indexOfQualifierWithName(origQualifier.getName());
+
+ origQualifier = new Qualifier(origQualifier.getName(), values);
+ qv.remove(index);
+ qv.add(index, origQualifier);
+ }
+
+ private String updateQualifierString()
+ {
+ String newQualifierString = origQualifierStr;
+
+ String old = getField("with=", origQualifierStr);
+ if(!old.equals(withTextField.getText().trim()))
+ {
+ newQualifierString = changeField("with=", withTextField.getText().trim(),
+ newQualifierString);
+ }
+
+ old = getField("db_xref=", origQualifierStr);
+ if(!old.equals(dbxrefTextField.getText().trim()))
+ {
+ newQualifierString = changeField("db_xref=", dbxrefTextField.getText().trim(),
+ newQualifierString);
+ }
+
+ old = getField("evidence=", origQualifierStr);
+ if(evidenceList.getSelectedIndex() > -1 &&
+ !old.equals(GoBox.evidenceCodes[2][ evidenceList.getSelectedIndex() ]))
+ {
+ newQualifierString = changeField("evidence=",
+ GoBox.evidenceCodes[2][ evidenceList.getSelectedIndex() ],
+ newQualifierString);
+ }
+
+ // test rank change
+ old = getField("rank=", origQualifierStr);
+ if( !recommended.isSelected() )
+ {
+ if(old.equals("0") || old.equals(""))
+ newQualifierString = changeField("rank=", "1", newQualifierString);
+ }
+ else
+ {
+ if(recommended.isEnabled() && !old.equals("0") && !old.equals(""))
+ newQualifierString = changeField("rank=", "0", newQualifierString);
+ }
+
+ return newQualifierString;
+ }
+
+ protected Box getBox()
+ {
+ return xBox;
+ }
+
+ protected Box getHeadingsBox()
+ {
+ return xHeadings;
+ }
+
+ /**
+ * @return the recommended
+ */
+ protected JCheckBox getRecommended()
+ {
+ return recommended;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/cv/WrapTextArea.java b/uk/ac/sanger/artemis/components/genebuilder/cv/WrapTextArea.java
new file mode 100644
index 0000000..35f4264
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/cv/WrapTextArea.java
@@ -0,0 +1,101 @@
+/* WrapTextArea.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+package uk.ac.sanger.artemis.components.genebuilder.cv;
+
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.util.StringTokenizer;
+
+import javax.swing.JTextArea;
+
+ /**
+ * Text component used for product & controlled_curation qualifier value.
+ */
+ class WrapTextArea extends JTextArea
+ {
+ private static final long serialVersionUID = 1L;
+ private int labelWidth;
+
+ public WrapTextArea(final String text,
+ final Dimension go_dimension,
+ int width)
+ {
+ super(text);
+ setOpaque(false);
+ setEditable(false);
+ setLineWrap(true);
+ setWrapStyleWord(true);
+ FontMetrics fm = getFontMetrics(getFont());
+
+
+ if(go_dimension != null)
+ labelWidth = go_dimension.width;
+ else
+ labelWidth= fm.stringWidth("GO:0001234 [F] ");
+
+ width = labelWidth+width;
+ int rows = getNumberOfLines(fm, text, width);
+ final Dimension d = new Dimension(width, (int) (getRowHeight()*rows) );
+ setPreferredSize(d);
+ setMaximumSize(d);
+ }
+
+ /**
+ * Calculate the number of lines, taking into account line wrapping at
+ * word boundaries (whitespace).
+ * @param fm
+ * @param text
+ * @param width
+ * @return
+ */
+ private int getNumberOfLines(FontMetrics fm, final String text, final int width)
+ {
+ String delim = " \t\n\f\r";
+ StringTokenizer tok = new StringTokenizer(text, delim, true);
+ //final String words[] = text.split("\\s");
+ int lineOffset = 0;
+ int lineNumber = 1;
+ int w2 = (fm.stringWidth(" ")+1)/2;
+
+ while(tok.hasMoreTokens())
+ {
+ int thisWordLength = fm.stringWidth(tok.nextToken());
+ lineOffset+=thisWordLength;
+ if(lineOffset>=width-w2)
+ {
+ lineNumber++;
+ lineOffset = thisWordLength;
+ }
+ }
+
+ /*int stringWidth = fm.stringWidth(text);
+ int rows = Math.round((stringWidth/width)+.5f);*/
+ return lineNumber;
+ }
+
+ protected int getLabelWidth()
+ {
+ return labelWidth;
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/gff/AddButton.java b/uk/ac/sanger/artemis/components/genebuilder/gff/AddButton.java
new file mode 100644
index 0000000..9b6d8ba
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/gff/AddButton.java
@@ -0,0 +1,57 @@
+/* AddButton
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.genebuilder.gff;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.SwingConstants;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.SoftBevelBorder;
+
+class AddButton extends JButton
+{
+ private static final long serialVersionUID = 1L;
+
+ public AddButton(ActionListener addAction, String tt)
+ {
+ super("+");
+
+ setToolTipText(tt);
+ setVerticalTextPosition(SwingConstants.TOP);
+ setHorizontalTextPosition(SwingConstants.LEFT);
+
+ setForeground(new Color(0, 100, 0));
+ setFont(getFont().deriveFont(Font.BOLD, 16.f));
+
+ setBorder(new SoftBevelBorder(BevelBorder.RAISED));
+ setOpaque(false);
+
+ Dimension size = new Dimension(16, 20);
+ setPreferredSize(size);
+ setMaximumSize(size);
+
+ addActionListener(addAction);
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/genebuilder/gff/BasicPropertiesPanel.java b/uk/ac/sanger/artemis/components/genebuilder/gff/BasicPropertiesPanel.java
new file mode 100644
index 0000000..bf504ef
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/gff/BasicPropertiesPanel.java
@@ -0,0 +1,125 @@
+/* BasicPropertiesPanel
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java,v 1.11 2009-08-17 12:50:42 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder.gff;
+
+import java.awt.FlowLayout;
+import java.util.List;
+
+import javax.swing.Box;
+import javax.swing.JPanel;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.components.genebuilder.BasicGeneBuilderFrame;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+
+public class BasicPropertiesPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ private PropertiesPanel genePropPanel;
+ private PropertiesPanel transcriptPropPanel;
+ private PropertiesPanel exonPropPanel;
+ private PropertiesPanel pepPropPanel;
+ private Feature gene;
+
+ public BasicPropertiesPanel(final ChadoCanonicalGene chadoGene,
+ BasicGeneBuilderFrame gbFrame)
+ {
+ super(new FlowLayout(FlowLayout.LEFT));
+
+ Box yBox = Box.createVerticalBox();
+ gene = (Feature) chadoGene.getGene().getUserData();
+ genePropPanel = new PropertiesPanel(gene, true, false, true, false);
+ genePropPanel.makeBorder();
+ yBox.add(genePropPanel);
+
+ Feature transcript = gbFrame.getSelectedTranscriptFeature();
+ transcriptPropPanel = new PropertiesPanel(transcript, true, false, false, false);
+ transcriptPropPanel.makeBorder();
+ yBox.add(transcriptPropPanel);
+
+ String transcriptName = GeneUtils.getUniqueName(transcript.getEmblFeature());
+
+ List<uk.ac.sanger.artemis.io.Feature> exons =
+ chadoGene.getSpliceSitesOfTranscript(transcriptName, DatabaseDocument.EXONMODEL);
+ if(exons != null)
+ {
+ Feature exon = (Feature) exons.get(0).getUserData();
+ exonPropPanel = new PropertiesPanel(exon, false, false, false, false);
+ exonPropPanel.makeBorder();
+ yBox.add(exonPropPanel);
+ }
+
+ uk.ac.sanger.artemis.io.Feature pep =
+ chadoGene.getProteinOfTranscript(transcriptName);
+
+ if(pep != null)
+ {
+ Feature protein = (Feature) pep.getUserData();
+ pepPropPanel = new PropertiesPanel(protein, true, false, false, false);
+ pepPropPanel.makeBorder();
+ yBox.add(pepPropPanel);
+ }
+
+ add(yBox);
+ }
+
+ public QualifierVector getProteinProperties(Feature feature)
+ {
+ return pepPropPanel.getGffQualifiers(feature);
+ }
+
+ public QualifierVector getGeneProperties(Feature feature)
+ {
+ return genePropPanel.getGffQualifiers(feature);
+ }
+
+ public QualifierVector getTranscriptProperties(Feature feature)
+ {
+ return transcriptPropPanel.getGffQualifiers(feature);
+ }
+
+ public QualifierVector getExonProperties(Feature feature)
+ {
+ return exonPropPanel.getGffQualifiers(feature);
+ }
+
+ public void updateObsoleteSettings()
+ {
+ if(!genePropPanel.obsoleteChanged)
+ return;
+
+ genePropPanel.obsoleteChanged = false;
+ PropertiesPanel.updateObsoleteSettings((GFFStreamFeature)gene.getEmblFeature());
+ }
+
+ public void setObsoleteChanged(boolean obs, FeatureVector features)
+ {
+ PropertiesPanel.updateObsoleteSettings((GFFStreamFeature)gene.getEmblFeature());
+ //obsoleteField.setSelected(obsoleteChanged);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java b/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java
new file mode 100644
index 0000000..a02fe75
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java
@@ -0,0 +1,993 @@
+/* GffPanel.java
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/genebuilder/gff/PropertiesPanel.java,v 1.11 2009-08-17 12:50:42 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder.gff;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.border.Border;
+
+import org.gmod.schema.cv.CvTerm;
+
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class PropertiesPanel extends JPanel
+ implements FeatureChangeListener
+{
+ private static final long serialVersionUID = 1L;
+ private QualifierVector gffQualifiers;
+ private JTextField uniquenameTextField;
+ private JTextField primaryNameTextField;
+ private JCheckBox obsoleteField;
+ private JCheckBox partialField5prime;
+ private JCheckBox partialField3prime;
+
+ private Feature feature;
+ /** controls if this panel is automatically closed or open */
+ private boolean empty = true;
+ /** track if feature isObsolete flag has changed */
+ protected boolean obsoleteChanged = false;
+ private boolean partialChanged = false;
+
+ private ButtonGroup phaseButtonGroup = null;
+
+ private boolean showNames = true;
+ private boolean showParent = true;
+ private boolean showOptions = true;
+ private boolean showTimeLastModified = true;
+
+ static
+ {
+ MultiLineToolTipUI.initialize();
+ }
+
+ public PropertiesPanel(final Feature feature)
+ {
+ this(feature, true, true, true, true);
+ }
+
+ public PropertiesPanel(final Feature feature,
+ boolean showNames,
+ boolean showParent,
+ boolean showOptions,
+ boolean showTimeLastModified)
+ {
+ super(new FlowLayout(FlowLayout.LEFT));
+ this.showNames = showNames;
+ this.showOptions = showOptions;
+ this.showParent = showParent;
+ this.showTimeLastModified = showTimeLastModified;
+
+ setBackground(Color.WHITE);
+ updateFromFeature(feature);
+ }
+
+ /**
+ * Return true if this is a CV qualifier
+ * @param qualifier
+ * @return
+ */
+ public static boolean isPropertiesTag(final Qualifier qualifier, final Feature feature)
+ {
+ if(qualifier.getName().equals("ID") ||
+ qualifier.getName().equals("Name") ||
+ qualifier.getName().equals("feature_id") ||
+ qualifier.getName().equals("Parent") ||
+ qualifier.getName().equals("Derives_from") ||
+ qualifier.getName().equals("feature_relationship_rank") ||
+ qualifier.getName().equals("timelastmodified") ||
+ qualifier.getName().equals("isObsolete") ||
+ qualifier.getName().equals("Start_range") ||
+ qualifier.getName().equals("End_range") ||
+ qualifier.getName().equals("codon_start") ||
+ ChadoTransactionManager.isSynonymTag(qualifier.getName(),
+ (GFFStreamFeature)feature.getEmblFeature()))
+ return true;
+ return false;
+ }
+
+ private Component createGffQualifiersComponent()
+ {
+ empty = true;
+ GridBagConstraints c = new GridBagConstraints();
+ JPanel gridPanel = new JPanel(new GridBagLayout());
+ gridPanel.setBackground(Color.white);
+
+ if(showNames)
+ {
+ addNames(c, gridPanel);
+ addSynonyms(c, gridPanel);
+ }
+
+ if(showParent)
+ {
+ addParent(c, gridPanel, "Parent");
+ addParent(c, gridPanel, "Derives_from");
+ }
+
+ // phase of translation wrt / codon_start
+ if(feature.getEntry().getEntryInformation().isValidQualifier(
+ feature.getKey(), "codon_start"))
+ addPhaseComponent(c, gridPanel);
+ else
+ phaseButtonGroup = null;
+
+ // partial/obsolete options
+ if(showOptions)
+ addOptions(c, gridPanel);
+
+ // add buttons and timelastmodified
+ if(showTimeLastModified)
+ addTimeLastModified(c, gridPanel);
+ return gridPanel;
+ }
+
+ /**
+ * Add uniquename and name to the panel.
+ * @param c
+ * @param gridPanel
+ */
+ private void addNames(GridBagConstraints c, JPanel gridPanel)
+ {
+ Qualifier idQualifier = gffQualifiers.getQualifierByName("ID");
+ Qualifier nameQualifier = gffQualifiers.getQualifierByName("Name");
+
+ final String uniquename = idQualifier.getValues().get(0);
+ uniquenameTextField = new JTextField(uniquename);
+ uniquenameTextField.setPreferredSize(calcPreferredMaxTextFieldWidth());
+ uniquenameTextField.setCaretPosition(0);
+
+ JLabel idField = new JLabel("ID ");
+ idField.setFont(getFont().deriveFont(Font.BOLD));
+ idField.setHorizontalAlignment(SwingConstants.RIGHT);
+ idField.setPreferredSize(calcPreferredLabelWidth());
+
+ c.gridx = 0;
+ c.gridy++;
+ c.ipadx = 5;
+ c.anchor = GridBagConstraints.EAST;
+ gridPanel.add(idField, c);
+ c.gridx = 1;
+ c.ipadx = 0;
+ c.anchor = GridBagConstraints.WEST;
+ gridPanel.add(uniquenameTextField, c);
+
+ Qualifier featIdQualifier = gffQualifiers.getQualifierByName("feature_id");
+ if (featIdQualifier != null)
+ {
+ Qualifier timeQualifier = gffQualifiers.getQualifierByName("timelastmodified");
+ String time = null;
+ if (timeQualifier != null)
+ time = timeQualifier.getValues().get(0);
+
+ String parent = getParentString();
+ String tt = "feature_id=" +
+ featIdQualifier.getValues().get(0) +
+ (parent == null ? "" : "\n"+parent)+
+ (time == null ? "" : "\n"+time);
+
+ idField.setToolTipText(tt);
+ uniquenameTextField.setToolTipText(tt);
+ }
+
+ if(feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL))
+ uniquenameTextField.setEditable(false);
+
+ if (!feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL))
+ {
+ primaryNameTextField = new JTextField();
+ primaryNameTextField.setPreferredSize(calcPreferredMaxTextFieldWidth());
+
+ if (nameQualifier != null)
+ {
+ primaryNameTextField.setText((String) nameQualifier.getValues().get(0));
+ empty = false;
+ }
+
+ c.gridx = 2;
+ c.ipadx = 5;
+ c.anchor = GridBagConstraints.EAST;
+ JLabel lab = new JLabel(" Name");
+ lab.setFont(getFont().deriveFont(Font.BOLD));
+ gridPanel.add(lab, c);
+ c.gridx = 3;
+ c.ipadx = 0;
+ c.anchor = GridBagConstraints.WEST;
+ gridPanel.add(primaryNameTextField, c);
+ }
+
+ ActionListener addAction = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ addSynonym();
+ }
+ };
+
+ AddButton addSynonymButton = new AddButton(addAction, "Add Synonym");
+ c.gridx = 4;
+ gridPanel.add(addSynonymButton, c);
+ }
+
+ /**
+ * Get the parent feature name.
+ * @return
+ */
+ private String getParentString()
+ {
+ Qualifier parentQual = gffQualifiers.getQualifierByName("Parent");
+ if(parentQual != null &&
+ parentQual.getValues().size() == 1)
+ return "Parent: "+parentQual.getValues().get(0);
+
+ Qualifier derivesFromQual = gffQualifiers.getQualifierByName("Derives_from");
+ if(derivesFromQual != null &&
+ derivesFromQual.getValues().size() == 1)
+ return "Derives from: "+derivesFromQual.getValues().get(0);
+
+ return null;
+ }
+
+ /**
+ * Add Parent or Derives_from.
+ * @param c
+ * @param gridPanel
+ */
+ private void addParent(GridBagConstraints c,
+ JPanel gridPanel,
+ String parentName)
+ {
+ Qualifier parentQualifier = gffQualifiers.getQualifierByName(parentName);
+ if(parentQualifier != null &&
+ parentQualifier.getValues().size() == 1)
+ {
+ JLabel parentField = new JLabel(parentName);
+ parentField.setFont(getFont().deriveFont(Font.BOLD));
+ c.gridx = 0;
+ c.gridy++;
+ c.ipadx = 5;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.EAST;
+ gridPanel.add(parentField, c);
+
+ JTextField parent = new JTextField(" "+ parentQualifier.getValues().get(0));
+ parent.setPreferredSize(calcPreferredMaxTextFieldWidth());
+ parent.setBorder(BorderFactory.createEmptyBorder());
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ gridPanel.add(parent, c);
+ return;
+ }
+ }
+
+ /**
+ * Add synonyms to the panel.
+ * @param c
+ * @param gridPanel
+ * @param nrows
+ */
+ private void addSynonyms(GridBagConstraints c, JPanel gridPanel)
+ {
+ for(Qualifier qualifier: gffQualifiers)
+ {
+ if( ChadoTransactionManager.isSynonymTag(qualifier.getName(),
+ (GFFStreamFeature)feature.getEmblFeature()) &&
+ isSystematicId(qualifier.getName()))
+ {
+ addSynonymComponent(qualifier, c, gridPanel);
+ }
+ }
+
+ for(Qualifier qualifier: gffQualifiers)
+ {
+ if( ChadoTransactionManager.isSynonymTag(qualifier.getName(),
+ (GFFStreamFeature)feature.getEmblFeature()) &&
+ !isSystematicId(qualifier.getName()))
+ {
+ addSynonymComponent(qualifier, c, gridPanel);
+ }
+ }
+ }
+
+
+ /**
+ * Add partial and obsolete options to the panel.
+ * @param c
+ * @param gridPanel
+ */
+ private void addOptions(GridBagConstraints c, JPanel gridPanel)
+ {
+ final Qualifier isPartialQualfier5;
+ final Qualifier isPartialQualfier3;
+ if(feature.isForwardFeature())
+ {
+ isPartialQualfier5 = gffQualifiers.getQualifierByName("Start_range");
+ isPartialQualfier3 = gffQualifiers.getQualifierByName("End_range");
+ }
+ else
+ {
+ isPartialQualfier3 = gffQualifiers.getQualifierByName("Start_range");
+ isPartialQualfier5 = gffQualifiers.getQualifierByName("End_range");
+ }
+
+ Box optionsBox = Box.createHorizontalBox();
+ partialField5prime = new JCheckBox("partial 5'",
+ ( isPartialQualfier5 != null ) ? true : false);
+ Dimension d = calcPreferred(partialField5prime.getPreferredSize().width);
+ partialField5prime.setPreferredSize(d);
+ partialField5prime.setOpaque(false);
+ partialField5prime.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ checkPartial();
+ }
+ });
+ optionsBox.add(partialField5prime);
+
+ partialField3prime = new JCheckBox("partial 3'",
+ ( isPartialQualfier3 != null ) ? true : false);
+ partialField3prime.setPreferredSize(d);
+ partialField3prime.setOpaque(false);
+ partialField3prime.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ checkPartial();
+ }
+ });
+ optionsBox.add(partialField3prime);
+
+ Qualifier obsoleteQual = gffQualifiers.getQualifierByName("isObsolete");
+ obsoleteField = new JCheckBox("obsolete", (obsoleteQual == null ? false :
+ Boolean.parseBoolean(obsoleteQual.getValues().get(0))));
+ obsoleteField.setPreferredSize(calcPreferred(obsoleteField.getPreferredSize().width));
+ obsoleteField.setOpaque(false);
+ obsoleteField.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int result = JOptionPane.showConfirmDialog(
+ PropertiesPanel.this, "Change this feature to be "+
+ (obsoleteField.isSelected() ? "obsolete!" : "not obsolete!"),
+ "Change obsolete option", JOptionPane.OK_CANCEL_OPTION);
+ if(result == JOptionPane.CANCEL_OPTION)
+ obsoleteField.setSelected(!obsoleteField.isSelected());
+ }
+ });
+ optionsBox.add(obsoleteField);
+
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.gridy++;
+ gridPanel.add(optionsBox, c);
+ c.gridwidth = 1;
+ }
+
+ private void addTimeLastModified(GridBagConstraints c, JPanel gridPanel)
+ {
+ Qualifier timeQualifier = gffQualifiers.getQualifierByName("timelastmodified");
+ if (timeQualifier != null)
+ {
+ JLabel timeLabel = new JLabel(timeQualifier.getValues().get(0));
+ timeLabel.setEnabled(false);
+ timeLabel.setToolTipText("time last modified");
+
+ c.gridy++;
+ c.gridx = 4;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.anchor = GridBagConstraints.EAST;
+ gridPanel.add(timeLabel,c);
+ }
+ }
+
+ public void updateFromFeature(final Feature feature)
+ {
+ this.feature = feature;
+ removeAll();
+ if(gffQualifiers != null)
+ feature.removeFeatureChangeListener(this);
+ gffQualifiers = feature.getQualifiers().copy();
+
+ gffQualifiers = new QualifierVector();
+ final QualifierVector qualifiers = feature.getQualifiers();
+ for(Qualifier qualifier: qualifiers)
+ {
+ if(isPropertiesTag(qualifier, feature))
+ gffQualifiers.addElement(qualifier.copy());
+ }
+
+ feature.addFeatureChangeListener(this);
+ add(createGffQualifiersComponent());
+ revalidate();
+ repaint();
+ }
+
+ /**
+ * Get the latest (edited) property qualifiers
+ * @return
+ */
+ public QualifierVector getGffQualifiers(final Feature feature)
+ {
+ // check editable components for changes
+ Qualifier idQualifier = gffQualifiers.getQualifierByName("ID");
+ if(showNames &&
+ !idQualifier.getValues().get(0).equals(uniquenameTextField.getText()))
+ {
+ if(!uniquenameTextField.getText().equals(""))
+ {
+ final String newName = uniquenameTextField.getText().trim();
+ final String oldName = ((String) (idQualifier.getValues().get(0))).trim();
+ gffQualifiers.remove(idQualifier);
+ idQualifier = new Qualifier("ID", newName);
+ gffQualifiers.addElement(idQualifier);
+
+ GFFStreamFeature gffFeature = (GFFStreamFeature)feature.getEmblFeature();
+ if(gffFeature.getChadoGene() != null)
+ {
+ Set<uk.ac.sanger.artemis.io.Feature> children =
+ gffFeature.getChadoGene().getChildren(gffFeature);
+ gffFeature.getChadoGene().updateUniqueName(oldName, newName, children);
+ }
+ }
+ }
+
+ if(!feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL))
+ {
+ Qualifier nameQualifier = gffQualifiers.getQualifierByName("Name");
+ if( (nameQualifier != null &&
+ !nameQualifier.getValues().get(0).equals(primaryNameTextField.getText())) ||
+ (primaryNameTextField != null && !primaryNameTextField.getText().equals("")))
+ {
+ gffQualifiers.remove(nameQualifier);
+ final String newName = primaryNameTextField.getText().trim();
+ nameQualifier = new Qualifier("Name", newName);
+ gffQualifiers.addElement(nameQualifier);
+ }
+ }
+
+ if(phaseButtonGroup != null)
+ {
+ String selectionCmd = phaseButtonGroup.getSelection().getActionCommand();
+ Qualifier phaseQualifier = gffQualifiers.getQualifierByName("codon_start");
+ if(phaseQualifier == null)
+ {
+ if(!selectionCmd.equals(""))
+ {
+ phaseQualifier = new Qualifier("codon_start", selectionCmd);
+ gffQualifiers.addElement(phaseQualifier);
+ }
+ }
+ else
+ {
+ String oldPhase = phaseQualifier.getValues().get(0);
+ if(!oldPhase.equals(phaseButtonGroup))
+ {
+ gffQualifiers.remove(phaseQualifier);
+ if(!selectionCmd.equals(""))
+ {
+ phaseQualifier = new Qualifier("codon_start", selectionCmd);
+ gffQualifiers.addElement(phaseQualifier);
+ }
+ }
+ }
+ }
+
+ Qualifier isObsoleteQualifier = gffQualifiers.getQualifierByName("isObsolete");
+ if(isObsoleteQualifier != null)
+ {
+ if(showOptions)
+ {
+ String isObsoleteOld = isObsoleteQualifier.getValues().get(0);
+ String isObsoleteNew = Boolean.toString(obsoleteField.isSelected());
+
+ if (!isObsoleteNew.equals(isObsoleteOld))
+ {
+ gffQualifiers.remove(isObsoleteQualifier);
+ isObsoleteQualifier = new Qualifier("isObsolete", isObsoleteNew);
+ gffQualifiers.addElement(isObsoleteQualifier);
+ obsoleteChanged = true;
+ }
+ }
+ }
+
+ Qualifier isPartial5primeQualifier;
+ if(feature.isForwardFeature())
+ isPartial5primeQualifier = gffQualifiers.getQualifierByName("Start_range");
+ else
+ isPartial5primeQualifier = gffQualifiers.getQualifierByName("End_range");
+ if(isPartial5primeQualifier != null)
+ {
+ if(showOptions && !partialField5prime.isSelected())
+ {
+ gffQualifiers.remove(isPartial5primeQualifier);
+ partialChanged = true;
+ }
+ }
+ else if(showOptions && partialField5prime.isSelected())
+ {
+ if(feature.isForwardFeature())
+ gffQualifiers.addElement(new Qualifier("Start_range",".,."));
+ else
+ gffQualifiers.addElement(new Qualifier("End_range",".,."));
+ partialChanged = true;
+ }
+
+ Qualifier isPartial3primeQualifier;
+ if(feature.isForwardFeature())
+ isPartial3primeQualifier = gffQualifiers.getQualifierByName("End_range");
+ else
+ isPartial3primeQualifier = gffQualifiers.getQualifierByName("Start_range");
+ if(isPartial3primeQualifier != null)
+ {
+ if(showOptions && !partialField3prime.isSelected())
+ {
+ gffQualifiers.remove(isPartial3primeQualifier);
+ partialChanged = true;
+ }
+ }
+ else if(showOptions && partialField3prime.isSelected())
+ {
+ if(feature.isForwardFeature())
+ gffQualifiers.addElement(new Qualifier("End_range",".,."));
+ else
+ gffQualifiers.addElement(new Qualifier("Start_range",".,."));
+ partialChanged = true;
+ }
+
+ return gffQualifiers;
+ }
+
+ /**
+ * If the partial/isObsolete qualifier for this feature has been changed this
+ * method updates the partial/isObsolete qualifier of the children feature.
+ */
+ public void updateSettings()
+ {
+ if(partialChanged)
+ updatePartialSettings((GFFStreamFeature)feature.getEmblFeature());
+ if(!obsoleteChanged)
+ return;
+
+ obsoleteChanged = false;
+ updateObsoleteSettings((GFFStreamFeature)feature.getEmblFeature());
+ }
+
+ public static void updateObsoleteSettings(GFFStreamFeature gffFeature)
+ {
+ Qualifier isObsoleteQualifier = gffFeature.getQualifierByName("isObsolete");
+ String isObsoleteNew = isObsoleteQualifier.getValues().get(0);
+ gffFeature.setVisible(!isObsoleteNew.equals("true"));
+
+ if(gffFeature.getChadoGene() == null)
+ return;
+ Set<uk.ac.sanger.artemis.io.Feature> children =
+ gffFeature.getChadoGene().getChildren(gffFeature);
+
+ if(children.size() > 0)
+ {
+ Qualifier idQualifier = gffFeature.getQualifierByName("ID");
+ int select = JOptionPane.showConfirmDialog(null,
+ "Make children of "+idQualifier.getValues().get(0)+"\n"+
+ (isObsoleteNew.equals("true") ? "obsolete?" : "not obsolete?"),
+ "Update Children",
+ JOptionPane.YES_NO_OPTION);
+
+ if(select == JOptionPane.YES_OPTION)
+ {
+ try
+ {
+ Iterator<uk.ac.sanger.artemis.io.Feature> it = children.iterator();
+ while(it.hasNext())
+ {
+ GFFStreamFeature gffChildFeature = (GFFStreamFeature)it.next();
+ Feature f = (Feature)gffChildFeature.getUserData();
+ f.setQualifier(new Qualifier("isObsolete", isObsoleteNew));
+ gffChildFeature.setVisible(true);
+
+ if(isObsoleteNew.equals("true") ||
+ GeneUtils.isHiddenFeature( gffChildFeature.getKey().getKeyString() ))
+ gffChildFeature.setVisible(false);
+ }
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * Change partial settings of child featuers.
+ * @param gffFeature
+ */
+ private void updatePartialSettings(GFFStreamFeature gffFeature)
+ {
+ if(!partialChanged)
+ return;
+ partialChanged = false;
+
+ if(gffFeature.getChadoGene() == null)
+ return;
+ Qualifier fminQualifier = gffFeature.getQualifierByName("Start_range");
+ Qualifier fmaxQualifier = gffFeature.getQualifierByName("End_range");
+ ChadoCanonicalGene chadoGene = gffFeature.getChadoGene();
+ Set<uk.ac.sanger.artemis.io.Feature> children = chadoGene.getChildren(gffFeature);
+
+ if(children.size() > 0)
+ {
+ int select = JOptionPane.showConfirmDialog(null,
+ "Update partial setting on child features?",
+ "Update Children", JOptionPane.YES_NO_OPTION);
+ if(select != JOptionPane.YES_OPTION)
+ return;
+ try
+ {
+ Iterator<uk.ac.sanger.artemis.io.Feature> it = children.iterator();
+ while(it.hasNext())
+ {
+ final GFFStreamFeature gffChildFeature = (GFFStreamFeature)it.next();
+ final Feature f = (Feature)gffChildFeature.getUserData();
+ final String keyStr = f.getKey().getKeyString();
+
+ if(keyStr.equals("five_prime_UTR") || keyStr.equals("three_prime_UTR"))
+ {
+ String fName = chadoGene.getQualifier(gffChildFeature, "ID");
+ boolean isFwd = !f.getLocation().isComplement();
+ if(fName != null && !chadoGene.isFirstUtr(fName, isFwd))
+ continue;
+
+ if( (keyStr.equals("five_prime_UTR") && isFwd) ||
+ (keyStr.equals("three_prime_UTR") && !isFwd) )
+ {
+ if(fminQualifier != null)
+ f.setQualifier(new Qualifier("Start_range",".,."));
+ else
+ f.removeQualifierByName("Start_range");
+ }
+ else
+ {
+ if(fmaxQualifier != null)
+ f.setQualifier(new Qualifier("End_range",".,."));
+ else
+ f.removeQualifierByName("End_range");
+ }
+ }
+ else
+ {
+ if(fminQualifier != null)
+ f.setQualifier(new Qualifier("Start_range",".,."));
+ else
+ f.removeQualifierByName("Start_range");
+ if(fmaxQualifier != null)
+ f.setQualifier(new Qualifier("End_range",".,."));
+ else
+ f.removeQualifierByName("End_range");
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Partial settings are made on the gene or transcript features.
+ * Provide a warning if this is not the case.
+ */
+ private void checkPartial()
+ {
+ String keyStr = feature.getKey().getKeyString();
+ if(keyStr.equals("polypeptide") ||
+ keyStr.equals("CDS") ||
+ keyStr.equals("pseudogenic_exon"))
+ {
+ JOptionPane.showMessageDialog(null,
+ "Partial settings should be updated on the transcript\n"+
+ "or gene feature not a "+keyStr+". Please make this change\n"+
+ "on the transcript or gene feature now.",
+ "Error", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+
+ private boolean isSystematicId(final String synonymType)
+ {
+ return (synonymType.indexOf("systematic_id") > -1);
+ }
+
+ private void removeSynonym(String synonymName, String qualifierValue)
+ {
+ int select = JOptionPane.showConfirmDialog(null,
+ "Delete "+qualifierValue+"?",
+ "Select synonym type", JOptionPane.OK_CANCEL_OPTION);
+
+ if(select != JOptionPane.OK_OPTION)
+ return;
+
+ StringVector values =
+ gffQualifiers.getQualifierByName(synonymName).getValues();
+
+ if(values.size()==1)
+ gffQualifiers.removeQualifierByName(synonymName);
+ else
+ {
+ int index = gffQualifiers.indexOfQualifierWithName(synonymName);
+ values.remove(qualifierValue);
+ gffQualifiers.remove(index);
+ gffQualifiers.add(index, new Qualifier(synonymName, values));
+ }
+
+ removeAll();
+ add(createGffQualifiersComponent());
+ revalidate();
+ repaint();
+ }
+
+ private void addSynonym()
+ {
+ final Vector<CvTerm> synonyms = DatabaseDocument.getCvterms("",
+ ChadoTransactionManager.SYNONYM_TAG_CVNAME, false);
+ final JExtendedComboBox list = new JExtendedComboBox(synonyms);
+ final String options[] = { "CANCEL", "NEXT>"};
+
+ int select = JOptionPane.showOptionDialog(null, list,
+ "Select synonym type",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null, options, options[1]);
+
+ if(select == 0)
+ return;
+
+ Box xBox = Box.createHorizontalBox();
+ final String synonymName = ((CvTerm)list.getSelectedItem()).getName();
+ final JLabel name = new JLabel( synonymName );
+ xBox.add(name);
+
+ final JTextField newSynonym = new JTextField(15);
+ xBox.add(newSynonym);
+
+ final JCheckBox current = new JCheckBox("make current", true);
+ xBox.add(current);
+
+ select = JOptionPane.showConfirmDialog(null, xBox,
+ "Input name", JOptionPane.OK_CANCEL_OPTION);
+
+ if(select == JOptionPane.CANCEL_OPTION || newSynonym.getText().equals(""))
+ return;
+
+ Qualifier synonymQualifier = gffQualifiers.getQualifierByName(synonymName);
+
+ String newSynonymValue = newSynonym.getText();
+ if(!current.isSelected())
+ newSynonymValue = newSynonymValue + ";current=false";
+
+ if(synonymQualifier == null)
+ {
+ synonymQualifier = new Qualifier(synonymName, newSynonymValue);
+ gffQualifiers.add(synonymQualifier);
+ }
+ else
+ synonymQualifier.addValue(newSynonymValue);
+
+ final StringVector newValues = synonymQualifier.getValues();
+ int index = gffQualifiers.indexOfQualifierWithName(synonymName);
+ if(index == -1)
+ gffQualifiers.setQualifier(new Qualifier(synonymName,newValues));
+ else
+ {
+ gffQualifiers.remove(index);
+ gffQualifiers.add(index, new Qualifier(synonymName,newValues));
+ }
+ removeAll();
+ add(createGffQualifiersComponent());
+ revalidate();
+ }
+
+ /**
+ * Add codon_start component to the properties panel
+ * @param c
+ * @param gridPanel
+ */
+ private void addPhaseComponent(final GridBagConstraints c, final JPanel gridPanel)
+ {
+ phaseButtonGroup = new ButtonGroup();
+
+ JRadioButton phaseNone = new JRadioButton("Default", true);
+ phaseNone.setOpaque(false);
+ phaseNone.setActionCommand("");
+ phaseButtonGroup.add(phaseNone);
+ int codon_start = feature.getCodonStart();
+
+ Box xBox = Box.createHorizontalBox();
+ c.gridx = 0;
+ c.gridy++;
+ c.anchor = GridBagConstraints.EAST;
+ c.fill = GridBagConstraints.NONE;
+ c.ipadx = 5;
+
+ JLabel lab = new JLabel("Codon Start");
+ lab.setFont(getFont().deriveFont(Font.BOLD));
+ gridPanel.add(lab, c);
+
+ Qualifier qualifierCodonStart = gffQualifiers.getQualifierByName("codon_start");
+ for(int i=1; i<4; i++)
+ {
+ String s = Integer.toString(i);
+ JRadioButton phase = new JRadioButton(s);
+ phase.setOpaque(false);
+ phase.setActionCommand(s);
+ phaseButtonGroup.add(phase);
+ if(qualifierCodonStart != null && i == codon_start)
+ {
+ empty = false;
+ phase.setSelected(true);
+ }
+ xBox.add(phase);
+ }
+ xBox.add(phaseNone);
+ xBox.add(Box.createHorizontalGlue());
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ c.ipadx = 0;
+ c.gridwidth = 2;
+ gridPanel.add(xBox, c);
+ c.gridwidth = 1;
+ }
+
+ /**
+ * Add the synonym components
+ * @param qualifier
+ * @param c
+ * @param gridPanel
+ */
+ private void addSynonymComponent(final Qualifier qualifier,
+ final GridBagConstraints c,
+ final JPanel gridPanel)
+ {
+ empty = false;
+ final StringVector values = qualifier.getValues();
+
+ String name = qualifier.getName();
+ if(name.equals("previous_systematic_id"))
+ name = "prev_sys_id";
+ final JLabel sysidField = new JLabel(name+" ");
+ sysidField.setFont(getFont().deriveFont(Font.BOLD));
+ sysidField.setHorizontalAlignment(SwingConstants.RIGHT);
+ sysidField.setPreferredSize(calcPreferredLabelWidth());
+
+ c.gridx = 0;
+ c.gridy++;
+ c.ipadx = 5;
+ c.anchor = GridBagConstraints.EAST;
+ gridPanel.add(sysidField, c);
+
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+
+ Box synBox = Box.createHorizontalBox();
+ for (final String val: values)
+ {
+ String strs[] = val.split(";");
+ JLabel syn = new JLabel(" "+ strs[0] + ";");
+ syn.setPreferredSize(calcPreferred(syn.getPreferredSize().width));
+
+ if (strs.length > 1 && strs[1].indexOf("current=false") > -1)
+ syn.setEnabled(false);
+ synBox.add(syn);
+
+ ActionListener removeAction = new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ removeSynonym(qualifier.getName(), val);
+ }
+ };
+ synBox.add(new RemoveButton(removeAction));
+ }
+ c.gridwidth = GridBagConstraints.REMAINDER;
+
+ gridPanel.add(synBox, c);
+ c.gridwidth = 1;
+ }
+
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ updateFromFeature(event.getFeature());
+ }
+
+ public boolean isEmpty()
+ {
+ return empty;
+ }
+
+ public void setObsoleteChanged(boolean obsoleteChanged)
+ {
+ obsoleteField.setSelected(obsoleteChanged);
+ }
+
+ private Dimension calcPreferredLabelWidth()
+ {
+ int maxLabelWidth = new JLabel("prev_sys_id ").getPreferredSize().width;
+ return calcPreferred(maxLabelWidth);
+ }
+
+ private Dimension calcPreferredMaxTextFieldWidth()
+ {
+ int maxLabelWidth = new JLabel("previous_systematic_id ").getPreferredSize().width;
+ return calcPreferred(maxLabelWidth);
+ }
+
+ private Dimension calcPreferred(int w)
+ {
+ FontMetrics fm = getFontMetrics(getFont());
+ int preferredHeight = fm.getHeight()+fm.getDescent()+4;
+ Dimension d = super.getPreferredSize();
+ d.height = preferredHeight;
+ d.width = w;
+ return d;
+ }
+
+ protected void makeBorder()
+ {
+ Border grayline = BorderFactory.createLineBorder(Color.gray);
+ setBorder(BorderFactory.createTitledBorder(grayline,
+ feature.getKey().getKeyString()));
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/gff/RemoveButton.java b/uk/ac/sanger/artemis/components/genebuilder/gff/RemoveButton.java
new file mode 100644
index 0000000..180adab
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/gff/RemoveButton.java
@@ -0,0 +1,55 @@
+/* RemoveButton
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder.gff;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.SwingConstants;
+
+class RemoveButton extends JButton
+{
+ private static final long serialVersionUID = 1L;
+
+ public RemoveButton(ActionListener removeAction)
+ {
+ super("X");
+
+ setVerticalTextPosition(SwingConstants.TOP);
+ setHorizontalTextPosition(SwingConstants.LEFT);
+
+ setForeground(Color.red);
+ setFont(getFont().deriveFont(Font.BOLD, 9.f));
+ setBorder(BorderFactory.createEmptyBorder());
+ setOpaque(false);
+
+ Dimension size = new Dimension(9, 20);
+ setPreferredSize(size);
+ setMaximumSize(size);
+
+ addActionListener(removeAction);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/ortholog/AbstractMatchTable.java b/uk/ac/sanger/artemis/components/genebuilder/ortholog/AbstractMatchTable.java
new file mode 100644
index 0000000..e07d394
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/ortholog/AbstractMatchTable.java
@@ -0,0 +1,774 @@
+/* AbstractMatchTable.java
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder.ortholog;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Insets;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.swing.DefaultCellEditor;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.TransferHandler;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.text.JTextComponent;
+
+import org.gmod.schema.sequence.FeatureLoc;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.components.ArtemisMain;
+import uk.ac.sanger.artemis.components.EntryEdit;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.components.Utilities;
+import uk.ac.sanger.artemis.components.genebuilder.GeneEdit;
+import uk.ac.sanger.artemis.editor.BrowserControl;
+import uk.ac.sanger.artemis.editor.DataCollectionPane;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.InputStreamProgressEvent;
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+abstract class AbstractMatchTable
+{
+ protected boolean isChanged = false;
+ protected JTable table;
+ protected QualifierVector origQualifiers;
+ private JLabel status_line;
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(AbstractMatchTable.class);
+ protected abstract String updateQualifierString(int row);
+
+ /**
+ * Determine if qualifiers have changed
+ */
+ protected boolean isQualifierChanged()
+ {
+ return isChanged;
+ }
+
+ /**
+ * Override this if there are multiple qualifier types in the table
+ * e.g. for ortholog, paralog
+ * @param qualifierName
+ * @param row
+ * @return
+ */
+ protected boolean isRowOfType(final String qualifierName, final int row)
+ {
+ return true;
+ }
+
+ /**
+ * Override this method if there are multiple qualifier types in the table
+ * e.g. for ortholog, paralog. See OrthoParalogTable.getOtherValues().
+ * @param origQualifier
+ * @return
+ */
+ protected StringVector getOtherValues(final Qualifier origQualifier)
+ {
+ StringVector values = origQualifier.getValues();
+ values.removeAllElements();
+ return values;
+ }
+
+ /**
+ * Update the qualifiers from the entries in the table
+ * @param qv
+ */
+ protected void updateQualifier(final QualifierVector qv)
+ {
+ for(int i=0; i<origQualifiers.size(); i++)
+ {
+ Qualifier origQualifier = (Qualifier) origQualifiers.elementAt(i);
+ StringVector values = getOtherValues(origQualifier);
+
+ logger4j.debug("AbstractMatchTable.updateQualifier() new value:\n");
+ for(int j = 0; j < getTable().getRowCount(); j++)
+ {
+ if(isRowOfType(origQualifier.getName(), j))
+ {
+ String updatedQualifierString = updateQualifierString(j);
+ values.add(updatedQualifierString);
+ logger4j.debug(updatedQualifierString);
+ }
+ }
+ logger4j.debug("\n");
+
+ if(values.size() < 1)
+ qv.remove(origQualifier);
+ else
+ {
+ int index = qv.indexOfQualifierWithName(origQualifier.getName());
+ origQualifier = new Qualifier(origQualifier.getName(), values);
+ qv.remove(index);
+ qv.add(index, origQualifier);
+ }
+ }
+ }
+
+ public void setTable(JTable table)
+ {
+ this.table = table;
+ }
+
+ protected JTable getTable()
+ {
+ return table;
+ }
+
+ /**
+ * Get the column index from the column name
+ * @param columnName
+ * @return
+ */
+ protected int getColumnIndex(final String columnName)
+ {
+ try
+ {
+ int modelColumnIndex = getTable().getColumn(columnName).getModelIndex();
+ return getTable().convertColumnIndexToView(modelColumnIndex);
+ }
+ catch(IllegalArgumentException e)
+ {
+ return -1;
+ }
+ }
+
+
+ /**
+ * Strip out the value of a field of interest from a qualifier string
+ *
+ * @param fieldName
+ * @param qualifierString
+ * @return
+ */
+ protected static String getField(final String fieldName, final String qualifierString)
+ {
+ String field = "";
+
+ int ind1 = qualifierString.toLowerCase().indexOf(fieldName.toLowerCase());
+ int ind2 = qualifierString.indexOf(";", ind1);
+
+ int len = fieldName.length();
+
+ if(ind2 > ind1 && ind1 > -1)
+ field = qualifierString.substring(ind1+len,ind2);
+ else if(ind1 > -1)
+ field = qualifierString.substring(ind1+len);
+
+ if(field.startsWith("="))
+ field = field.substring(1);
+ return field;
+ }
+
+ protected void openArtemis(final DatabaseDocument doc, final int selectedRow)
+ {
+ int columnIndex = getColumnIndex(OrthoParalogTable.GENE_COL);
+
+ String geneRef = (String)getTable().getValueAt(selectedRow, columnIndex);
+ final String gene[] = geneRef.split(":");
+
+ final org.gmod.schema.sequence.Feature geneFeature = doc.getFeatureByUniquename(gene[1]);
+
+ Collection featureLocs = geneFeature.getFeatureLocsForFeatureId();
+ Iterator it = featureLocs.iterator();
+ final FeatureLoc featureLoc = (FeatureLoc)it.next();
+
+ final JFrame progressFrame = progressReading();
+
+ SwingWorker readWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ try
+ {
+ int start = featureLoc.getFmin().intValue()-10000;
+ if(start <= 0)
+ start = 1;
+ Range range = new Range(start,featureLoc.getFmax().intValue()+10000);
+ final DatabaseDocument newDoc = new DatabaseDocument(
+ doc, gene[0], geneFeature, range,
+ stream_progress_listener);
+ newDoc.setLazyFeatureLoad(false);
+
+ DatabaseDocumentEntry db_entry = new DatabaseDocumentEntry(newDoc, null);
+ Bases bases = new Bases(db_entry.getSequence());
+ Entry entry = new Entry(bases, db_entry);
+
+ final EntryEdit new_entry_edit = ArtemisMain.makeEntryEdit(entry);
+ new_entry_edit.getGotoEventSource().gotoBase(featureLoc.getFmin().intValue());
+ new_entry_edit.setVisible(true);
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ catch(OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public void finished()
+ {
+ progressFrame.dispose();
+ }
+ };
+ readWorker.start();
+ }
+
+ /**
+ * Sets the preferred, min & max width of the column specified by columnIndex.
+ * The column will be just wide enough to show the column head and the widest
+ * cell in the column. margin pixels are added to the left and right
+ * @param table
+ * @param columnIndex
+ * @param margin
+ */
+ protected void packColumn(JTable table, int columnIndex, int margin)
+ {
+ DefaultTableColumnModel colModel = (DefaultTableColumnModel)table.getColumnModel();
+ TableColumn col = colModel.getColumn(columnIndex);
+ int width = 0;
+ int maxWidth;
+
+ // Get width of column header
+ TableCellRenderer renderer = col.getHeaderRenderer();
+ if(renderer == null)
+ renderer = table.getTableHeader().getDefaultRenderer();
+
+ Component comp = renderer.getTableCellRendererComponent(
+ table, col.getHeaderValue(), false, false, 0, 0);
+ //width = comp.getPreferredSize().width;
+
+
+ String text = ((JLabel)comp).getText();
+
+ Font font = comp.getFont();
+ FontMetrics fontMetrics = comp.getFontMetrics ( font );
+
+ width = SwingUtilities.computeStringWidth ( fontMetrics, text );
+
+ // Get maximum width of column data
+ for(int r=0; r<table.getRowCount(); r++)
+ {
+ renderer = table.getCellRenderer(r, columnIndex);
+ comp = renderer.getTableCellRendererComponent(
+ table, table.getValueAt(r, columnIndex), false, false, r, columnIndex);
+
+ if(comp instanceof JLabel)
+ text = ((JLabel)comp).getText();
+ else
+ text = ((JTextComponent)comp).getText();
+
+ font = comp.getFont();
+ fontMetrics = comp.getFontMetrics ( font );
+
+ if(text == null)
+ continue;
+ maxWidth = SwingUtilities.computeStringWidth ( fontMetrics, text );
+ // maxWidth = comp.getPreferredSize().width;
+ width = Math.max(width, maxWidth);
+ }
+
+ // Add margin
+ width += 2*margin;
+
+ // Set the width
+ col.setPreferredWidth(width);
+ col.setMaxWidth(width);
+ col.setMinWidth(width);
+ }
+
+ protected class CellEditing extends DefaultCellEditor
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+ public CellEditing(JTextField textField)
+ {
+ super(textField);
+ }
+
+ public boolean stopCellEditing()
+ {
+ isChanged = true;
+ return super.stopCellEditing();
+ }
+ }
+
+ /**
+ *
+ */
+ protected class ButtonEditor extends DefaultCellEditor
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+ protected JButton buttRemove;
+ private boolean isPushed;
+ private int selectedRow;
+ private Color fgColor = new Color(139,35,35);
+ private DefaultTableModel tableModel;
+ private String text;
+ private DatabaseDocument doc;
+
+ public ButtonEditor(JCheckBox checkBox, final DefaultTableModel tableModel,
+ final String text, final DatabaseDocument doc)
+ {
+ super(checkBox);
+ this.tableModel = tableModel;
+ this.text = text;
+ this.doc = doc;
+
+ buttRemove = new JButton(text);
+ buttRemove.setBorderPainted(false);
+ buttRemove.setOpaque(false);
+ Font font = buttRemove.getFont().deriveFont(Font.BOLD);
+ buttRemove.setFont(font);
+ buttRemove.setForeground(fgColor);
+
+ Dimension size = new Dimension(20,20);
+ buttRemove.setPreferredSize(size);
+ buttRemove.setMaximumSize(size);
+
+ buttRemove.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ fireEditingStopped();
+ }
+ });
+ }
+
+ public Component getTableCellEditorComponent(JTable table, Object value,
+ boolean isSelected, int row, int column)
+ {
+ if (isSelected)
+ {
+ buttRemove.setForeground(fgColor);
+ buttRemove.setBackground(table.getSelectionBackground());
+ }
+ else
+ {
+ buttRemove.setForeground(fgColor);
+ buttRemove.setBackground(table.getBackground());
+ }
+
+ selectedRow = row;
+ isPushed = true;
+ return buttRemove;
+ }
+
+ public Object getCellEditorValue()
+ {
+ if(isPushed)
+ {
+ if(text.equals("X"))
+ {
+ tableModel.removeRow(selectedRow);
+ isChanged = true;
+ }
+ else
+ openArtemis(doc, selectedRow);
+ //return null;
+ }
+ isPushed = false;
+ return text;
+ }
+
+ public boolean stopCellEditing()
+ {
+ isPushed = false;
+ return super.stopCellEditing();
+ }
+
+ protected void fireEditingStopped()
+ {
+ try
+ {
+ super.fireEditingStopped();
+ }
+ catch(ArrayIndexOutOfBoundsException e){}
+ }
+ }
+
+ /**
+ * Let the user know the progress of reading from the database
+ */
+ private JFrame progressReading()
+ {
+ final JFrame fread = new JFrame();
+ fread.setUndecorated(true);
+ status_line = new JLabel("Loading ... ");
+ status_line.setBackground(Color.white);
+ status_line.setOpaque(true);
+ fread.getContentPane().add(status_line);
+
+ fread.pack();
+ Utilities.centreFrame(fread);
+ fread.setVisible(true);
+ return fread;
+ }
+
+ /**
+ *
+ */
+ protected class LinkEditor extends DefaultCellEditor
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+ protected JButton linkButton = new JButton();
+ private boolean isPushed;
+ private Color fgLinkColor = Color.BLUE;
+ private DatabaseDocument doc;
+
+ public LinkEditor(JCheckBox checkBox, final DefaultTableModel tableModel,
+ DatabaseDocument doc)
+ {
+ super(checkBox);
+ this.doc = doc;
+
+ linkButton.setBorderPainted(false);
+ linkButton.setOpaque(true);
+
+ linkButton.setHorizontalAlignment(SwingConstants.LEFT);
+ linkButton.setVerticalAlignment(SwingConstants.TOP);
+ linkButton.setMargin(new Insets(0,1,0,1));
+
+ linkButton.addMouseListener(new MouseListener()
+ {
+
+ public void mouseClicked(MouseEvent e)
+ {
+ if(e.getClickCount() == 2 &&
+ !e.isShiftDown() && !e.isPopupTrigger())
+ fireEditingStopped();
+ }
+
+ public void mouseEntered(MouseEvent e)
+ {
+ }
+
+ public void mouseExited(MouseEvent e)
+ {
+ }
+
+ public void mousePressed(MouseEvent e)
+ {
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ }
+ });
+ }
+
+ public Component getTableCellEditorComponent(JTable table, Object value,
+ boolean isSelected, int row, int column)
+ {
+ linkButton.setActionCommand((String)value);
+ String gene[] = ((String)value).split(":");
+ if(doc == null)
+ linkButton.setText((String)value);
+ else
+ linkButton.setText(gene[1]);
+
+ if (isSelected)
+ {
+ linkButton.setForeground(fgLinkColor);
+ linkButton.setBackground(table.getSelectionBackground());
+ }
+ else
+ {
+ linkButton.setForeground(fgLinkColor);
+ linkButton.setBackground(table.getBackground());
+ }
+
+ isPushed = true;
+ return linkButton;
+ }
+
+ public Object getCellEditorValue()
+ {
+ String link = linkButton.getText();
+ if(isPushed)
+ {
+ if(doc == null)
+ {
+ // open in default browser
+ String srscmd = DataCollectionPane.getSrsSite()+"/wgetz?-e+["+link+"]";
+
+ // link to uniprot accession
+ int ind;
+ if( (ind = srscmd.indexOf("UniProt:")) > -1)
+ srscmd = srscmd.substring(0,ind+7)+"-acc:"+
+ srscmd.substring(ind+8);
+
+ if(srscmd.indexOf("ebi.ac.uk") > -1)
+ srscmd = srscmd + "+-vn+2";
+
+ BrowserControl.displayURL(srscmd);
+ }
+ else
+ {
+ // open gene editor for this gene link
+ linkButton.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ final String reference[] = linkButton.getActionCommand().split(":");
+ DatabaseDocumentEntry entry = GeneEdit.makeGeneEntry(
+ reference[0], reference[1], doc, stream_progress_listener);
+
+ if(entry != null)
+ {
+ //entry.setReadOnly(true);
+ GeneEdit.showGeneEditor(reference[0], reference[1], entry);
+ }
+ }
+ //isChanged = true;
+ linkButton.setCursor(Cursor.getDefaultCursor());
+ return linkButton.getActionCommand();
+ }
+ isPushed = false;
+
+ if(doc == null)
+ return link;
+ else
+ return linkButton.getActionCommand();
+ }
+
+ public boolean stopCellEditing()
+ {
+ isPushed = false;
+ return super.stopCellEditing();
+ }
+
+ protected void fireEditingStopped()
+ {
+ try
+ {
+ super.fireEditingStopped();
+ }
+ catch(ArrayIndexOutOfBoundsException e){}
+ }
+ }
+
+ //
+ //http://java.sun.com/docs/books/tutorial/uiswing/dnd/intro.html
+ //
+ protected abstract class StringTransferHandler extends TransferHandler
+ {
+ protected abstract String exportString(JComponent c);
+ protected abstract void importString(JComponent c, String str);
+ protected abstract void cleanup(JComponent c, boolean remove);
+
+ protected Transferable createTransferable(JComponent c)
+ {
+ return new StringSelection(exportString(c));
+ }
+
+ public int getSourceActions(JComponent c)
+ {
+ return MOVE;
+ }
+
+ public boolean importData(JComponent c, Transferable t)
+ {
+ if(canImport(c, t.getTransferDataFlavors()))
+ {
+ try
+ {
+ String str = (String) t.getTransferData(DataFlavor.stringFlavor);
+ importString(c, str);
+ return true;
+ }
+ catch(UnsupportedFlavorException ufe){}
+ catch(IOException ioe){}
+ }
+ return false;
+ }
+
+ protected void exportDone(JComponent c, Transferable data, int action)
+ {
+ cleanup(c, action == MOVE);
+ }
+
+ public boolean canImport(JComponent c, DataFlavor[] flavors)
+ {
+ for(int i = 0; i < flavors.length; i++)
+ {
+ if(DataFlavor.stringFlavor.equals(flavors[i]))
+ return true;
+ }
+ return false;
+ }
+ }
+
+ protected class TableTransferHandler extends StringTransferHandler
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+ private int[] rows = null;
+ private int addIndex = -1; //Location where items were added
+ private int addCount = 0; //Number of items added.
+
+ protected String exportString(JComponent c)
+ {
+ JTable table = (JTable) c;
+ rows = table.getSelectedRows();
+ int colCount = table.getColumnCount();
+
+ StringBuffer buff = new StringBuffer();
+
+ for(int i = 0; i < rows.length; i++)
+ {
+ for(int j = 0; j < colCount; j++)
+ {
+ Object val = table.getValueAt(rows[i], j);
+ buff.append(val == null ? "" : val.toString());
+ if(j != colCount - 1)
+ buff.append(",");
+ }
+ if(i != rows.length - 1)
+ buff.append("\n");
+ }
+ return buff.toString();
+ }
+
+ protected void importString(JComponent c, String str)
+ {
+ JTable target = (JTable) c;
+ DefaultTableModel model = (DefaultTableModel) target.getModel();
+ int index = target.getSelectedRow();
+
+ //Prevent the user from dropping data back on itself.
+ //For example, if the user is moving rows #4,#5,#6 and #7 and
+ //attempts to insert the rows after row #5, this would
+ //be problematic when removing the original rows.
+ //So this is not allowed.
+ if(rows != null && index >= rows[0] - 1 && index <= rows[rows.length - 1])
+ {
+ rows = null;
+ return;
+ }
+
+ int max = model.getRowCount();
+ if(index < 0)
+ index = max;
+ else
+ {
+ index++;
+ if(index > max)
+ index = max;
+ }
+ addIndex = index;
+ String[] values = str.split("\n");
+ addCount = values.length;
+ int colCount = target.getColumnCount();
+ for(int i = 0; i < values.length && i < colCount; i++)
+ model.insertRow(index++, values[i].split(","));
+ }
+
+ protected void cleanup(JComponent c, boolean remove)
+ {
+ JTable source = (JTable) c;
+ if(remove && rows != null)
+ {
+ DefaultTableModel model = (DefaultTableModel) source.getModel();
+
+ //If we are moving items around in the same table, we
+ //need to adjust the rows accordingly, since those
+ //after the insertion point have moved.
+ if(addCount > 0)
+ {
+ for(int i = 0; i < rows.length; i++)
+ {
+ if(rows[i] > addIndex)
+ rows[i] += addCount;
+ }
+ }
+ for(int i = rows.length - 1; i >= 0; i--)
+ model.removeRow(rows[i]);
+ }
+ rows = null;
+ addCount = 0;
+ addIndex = -1;
+ }
+ }
+
+ /**
+ * An InputStreamProgressListener used to update the error label with the
+ * current number of chars read.
+ **/
+ protected final InputStreamProgressListener stream_progress_listener =
+ new InputStreamProgressListener()
+ {
+ public void progressMade(final InputStreamProgressEvent event)
+ {
+ final int char_count = event.getCharCount();
+ if(char_count == -1)
+ status_line.setText("");
+ else
+ status_line.setText("chars read so far: " + char_count);
+ }
+
+ public void progressMade(String progress)
+ {
+ status_line.setText(progress);
+ }
+ };
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/ortholog/MatchPanel.java b/uk/ac/sanger/artemis/components/genebuilder/ortholog/MatchPanel.java
new file mode 100644
index 0000000..6094be0
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/ortholog/MatchPanel.java
@@ -0,0 +1,845 @@
+/* OrthologPanel.java
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/genebuilder/ortholog/MatchPanel.java,v 1.31 2009-05-27 15:00:10 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder.ortholog;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.sequence.FeatureCvTerm;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.PartialSequence;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierLazyLoading;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.SwingWorker;
+import uk.ac.sanger.artemis.components.genebuilder.GeneEdit;
+import uk.ac.sanger.artemis.components.genebuilder.GeneEditorPanel;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.genebuilder.JExtendedComboBox;
+
+/**
+ * For similarity, orthologue, paralogue qualifiers
+ */
+public class MatchPanel extends JPanel
+ implements FeatureChangeListener
+{
+ private static final long serialVersionUID = 1L;
+ private QualifierVector matchQualifiers;
+ private static Vector databases;
+ private SimilarityTable similarityTable;
+ private OrthoParalogTable orthoparaLogTable;
+ private OrthoParalogTable clusterTable;
+
+ private Vector editableComponents;
+ private JButton hide_show_ortho;
+ private JButton hide_show_cluster;
+ private JButton hide_show_sim;
+ public static String ORTHOLOG = "orthologous_to";
+ public static String PARALOG = "paralogous_to";
+ //public static String CLUSTER = "cluster";
+ public static String SIMILARITY = "similarity";
+
+ private static String[] SO_CLUSTER_NAMES = {
+ ORTHOLOG,
+ PARALOG,
+ // CLUSTER,
+ SIMILARITY };
+ private DocumentEntry entry;
+
+ // used to test if match panel has contents
+ private boolean empty = true;
+ private static boolean LOADING = false;
+ private Feature feature;
+
+ public MatchPanel(final Feature feature,
+ final DocumentEntry entry)
+ {
+ super(new BorderLayout());
+ this.entry = entry;
+ this.feature = feature;
+ }
+
+ /**
+ * Return true if this is a Ortholog qualifier
+ * @param qualifier
+ * @return
+ */
+ public static boolean isMatchTag(final Qualifier qualifier)
+ {
+ return isMatchTag(qualifier.getName());
+ }
+
+
+ /**
+ * Return true if this is a match qualifier
+ * @param qualifierName
+ * @return
+ */
+ public static boolean isMatchTag(final String qualifierName)
+ {
+ for(int i=0; i<SO_CLUSTER_NAMES.length;i++)
+ if(qualifierName.equals(SO_CLUSTER_NAMES[i]) ||
+ qualifierName.startsWith("/"+SO_CLUSTER_NAMES[i]+"="))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Return true if this is a cluster, ortholog, paralog qualifier
+ * @param qualifierName
+ * @return
+ */
+ public static boolean isClusterTag(final String qualifierName)
+ {
+ for(int i=0; i<SO_CLUSTER_NAMES.length-1;i++)
+ if(qualifierName.equals(SO_CLUSTER_NAMES[i]) ||
+ qualifierName.startsWith("/"+SO_CLUSTER_NAMES[i]+"="))
+ return true;
+ return false;
+ }
+
+
+ /**
+ * Create components for ortholog, paralog, cluster and similarity
+ * @param feature
+ * @return
+ */
+ private Component createMatchQualifiersComponent(final Feature feature)
+ {
+ empty = true;
+ editableComponents = new Vector();
+ final Qualifier orthoQualifier = matchQualifiers.getQualifierByName(ORTHOLOG);
+ final Qualifier paraQualifier = matchQualifiers.getQualifierByName(PARALOG);
+ final Qualifier simQualifier = matchQualifiers.getQualifierByName(SIMILARITY);
+
+ //DocumentEntry entry = (DocumentEntry)feature.getEmblFeature().getEntry();
+ final DatabaseDocument doc = (DatabaseDocument)entry.getDocument();
+ if(databases == null)
+ {
+ databases = (Vector) doc.getOrganismNames();
+ }
+
+ //
+ // ortholog / paralog / cluster
+ Box matchVerticalBox = Box.createVerticalBox();
+ JButton addOrthoButton = new JButton("ADD ORTHOLOG/PARALOG");
+ addOrthoButton.setOpaque(false);
+ addOrthoButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ addOrthoParalog(feature, doc);
+ }
+ });
+ Box xBox = Box.createHorizontalBox();
+ xBox.add(addOrthoButton);
+ xBox.add(Box.createHorizontalGlue());
+ matchVerticalBox.add(xBox);
+
+
+ ///
+ if(orthoQualifier != null || paraQualifier != null)
+ {
+ empty = false;
+ if(orthoQualifier != null && orthoQualifier instanceof QualifierLazyLoading)
+ ((QualifierLazyLoading)orthoQualifier).setForceLoad(true);
+
+ if(paraQualifier != null && paraQualifier instanceof QualifierLazyLoading)
+ ((QualifierLazyLoading)paraQualifier).setForceLoad(true);
+
+ if(OrthoParalogTable.hasOrthoParlaog(orthoQualifier, paraQualifier,
+ (GFFStreamFeature)feature.getEmblFeature()))
+ {
+ if(hide_show_ortho == null)
+ hide_show_ortho = new JButton("-");
+
+ orthoparaLogTable = new OrthoParalogTable(doc, orthoQualifier,
+ paraQualifier, feature, false);
+
+ if(orthoparaLogTable.getTable().getRowCount() > 0)
+ {
+ addHideShowButton(orthoparaLogTable.getTable(), hide_show_ortho);
+ xBox.add(hide_show_ortho);
+ editableComponents.add(orthoparaLogTable);
+
+ Box horizontalBox = Box.createHorizontalBox();
+ horizontalBox.add(orthoparaLogTable.getTable().getTableHeader());
+ horizontalBox.add(Box.createHorizontalGlue());
+ matchVerticalBox.add(horizontalBox);
+
+
+ horizontalBox = Box.createHorizontalBox();
+ horizontalBox.add(orthoparaLogTable.getTable());
+ horizontalBox.add(Box.createHorizontalGlue());
+ matchVerticalBox.add(horizontalBox);
+ }
+ }
+
+ //
+ // clusters
+ //
+ if(OrthoParalogTable.hasCluster(orthoQualifier, paraQualifier,
+ (GFFStreamFeature)feature.getEmblFeature()))
+ {
+ empty = false;
+ if(OrthoParalogTable.hasOrthoParlaog(orthoQualifier, paraQualifier,
+ (GFFStreamFeature)feature.getEmblFeature()))
+ GeneEditorPanel.addLightSeparator(matchVerticalBox);
+
+ if(hide_show_cluster == null)
+ hide_show_cluster = new JButton("-");
+
+ clusterTable = new OrthoParalogTable(doc, orthoQualifier,
+ paraQualifier, feature, true);
+ addHideShowButton(clusterTable.getTable(), hide_show_cluster);
+ Box horizontalBox = Box.createHorizontalBox();
+ horizontalBox.add(Box.createHorizontalGlue());
+ horizontalBox.add(hide_show_cluster);
+ matchVerticalBox.add(horizontalBox);
+ editableComponents.add(clusterTable);
+
+ horizontalBox = Box.createHorizontalBox();
+ horizontalBox.add(clusterTable.getTable().getTableHeader());
+ horizontalBox.add(Box.createHorizontalGlue());
+ matchVerticalBox.add(horizontalBox);
+
+ horizontalBox = Box.createHorizontalBox();
+ horizontalBox.add(clusterTable.getTable());
+ horizontalBox.add(Box.createHorizontalGlue());
+ matchVerticalBox.add(horizontalBox);
+ }
+ }
+
+
+ //
+ // similarity
+ GeneEditorPanel.addLightSeparator(matchVerticalBox);
+
+ /*
+ JButton addSimButton = new JButton("ADD SIMILARITY");
+ addSimButton.setOpaque(false);
+ addSimButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ JTextField accession = new JTextField(15);
+
+ Box yBox = Box.createHorizontalBox();
+ yBox.add(accession);
+
+ int select = JOptionPane.showConfirmDialog(null,
+ yBox, "Add Similarity",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+
+ add(SIMILARITY, accession.getText().trim(), feature);
+ }
+ });
+ */
+
+
+ if(simQualifier != null)
+ {
+ xBox = Box.createHorizontalBox();
+ final JLabel simLabel = new JLabel("Similarity:");
+ simLabel.setFont( simLabel.getFont().deriveFont(Font.BOLD ));
+ xBox.add(simLabel);
+ xBox.add(Box.createHorizontalGlue());
+ matchVerticalBox.add(xBox);
+
+ empty = false;
+ if(simQualifier instanceof QualifierLazyLoading)
+ ((QualifierLazyLoading)simQualifier).setForceLoad(true);
+
+ similarityTable = new SimilarityTable(simQualifier,doc);
+ if(hide_show_sim == null)
+ hide_show_sim = new JButton("-");
+ addHideShowButton(similarityTable.getTable(), hide_show_sim);
+ editableComponents.add(similarityTable);
+
+ xBox.add(similarityTable.getInfoLevelButton());
+ xBox.add(hide_show_sim);
+ matchVerticalBox.add(xBox);
+ matchVerticalBox.add(similarityTable.getTable().getTableHeader());
+ matchVerticalBox.add(similarityTable.getTable());
+ }
+
+ return matchVerticalBox;
+ }
+
+ /**
+ * Add an ortholog or paralog to the table
+ * @param feature
+ */
+ private void addOrthoParalog(final Feature feature,
+ final DatabaseDocument doc)
+ {
+ JExtendedComboBox dbs = new JExtendedComboBox(databases);
+ JTextField geneField = new JTextField(15);
+ JRadioButton ortho = new JRadioButton(ORTHOLOG, true);
+ JRadioButton para = new JRadioButton(PARALOG, false);
+ ButtonGroup group = new ButtonGroup();
+ group.add(ortho);
+ group.add(para);
+
+ Box xBox = Box.createVerticalBox();
+ Box yBoxRef = Box.createHorizontalBox();
+ yBoxRef.add(dbs);
+ yBoxRef.add(geneField);
+ yBoxRef.add(Box.createHorizontalGlue());
+ xBox.add(yBoxRef);
+
+ Box yBoxType = Box.createHorizontalBox();
+ yBoxType.add(ortho);
+ yBoxType.add(para);
+ yBoxType.add(Box.createHorizontalGlue());
+ xBox.add(yBoxType);
+
+ boolean found = false;
+ JComboBox polypepList = null;
+ String uniqueName = null;
+ String label = "Add Ortholog/Paralog";
+ int select;
+ Vector polypeptides = new Vector();
+ while(!found)
+ {
+ select = JOptionPane.showConfirmDialog(null, xBox,
+ label, JOptionPane.OK_CANCEL_OPTION);
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+
+ try
+ {
+ uniqueName = geneField.getText().trim();
+ polypeptides = doc.getPolypeptideFeatures(uniqueName);
+
+ if(polypeptides == null || polypeptides.size() == 0)
+ polypeptides = doc.getPartOfFeatures(uniqueName);
+
+ final Vector polypeptideNames = new Vector();
+
+ for(int i=0; i<polypeptides.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature ppfeature =
+ (org.gmod.schema.sequence.Feature)polypeptides.get(i);
+
+ if(!ppfeature.getOrganism().getCommonName().equals(dbs.getSelectedItem()))
+ JOptionPane.showMessageDialog(null, "Found in "+
+ ppfeature.getOrganism().getCommonName(), "Organism Mismatch",
+ JOptionPane.WARNING_MESSAGE);
+
+ polypeptideNames.add(ppfeature.getUniqueName());
+ }
+
+ polypepList = new JComboBox(polypeptideNames);
+ found = true;
+ }
+ catch(NullPointerException npe)
+ {
+ found = false;
+ label = "Gene : "+uniqueName+" not found! Try again!";
+ }
+ }
+
+ Box yBoxPeptide = Box.createHorizontalBox();
+ yBoxPeptide.add(polypepList);
+ yBoxPeptide.add(new JLabel("Add annotation to selected feature"));
+ yBoxPeptide.add(Box.createHorizontalGlue());
+ xBox.add(yBoxPeptide);
+
+ // if there are existing links the optionally transfer annotation link
+ final Qualifier orthoQualifier = matchQualifiers.getQualifierByName(ORTHOLOG);
+ final Qualifier paraQualifier = matchQualifiers.getQualifierByName(PARALOG);
+ JCheckBox transferToList = null;
+ if(orthoQualifier != null || paraQualifier != null)
+ {
+ transferToList =
+ new JCheckBox("Add to the existing list of ortho/paralogs", false);
+ yBoxPeptide = Box.createHorizontalBox();
+ yBoxPeptide.add(transferToList);
+ yBoxPeptide.add(Box.createHorizontalGlue());
+ xBox.add(yBoxPeptide);
+ }
+
+ select = JOptionPane.showConfirmDialog(null,
+ xBox, "Add Ortholog/Paralog",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+
+ final String type;
+ if(ortho.isSelected())
+ type = MatchPanel.ORTHOLOG;
+ else
+ type = MatchPanel.PARALOG;
+
+ int rank = 0;
+ if(orthoparaLogTable != null)
+ rank = orthoparaLogTable.getTable().getRowCount();
+
+ // find product
+ String product = getProductFromFeatures(polypeptides,
+ (String)polypepList.getSelectedItem());
+
+ String qualifierStr = ((String)dbs.getSelectedItem())+":"+
+ uniqueName+" link="+
+ polypepList.getSelectedItem()+" type="+
+ type+"; rank="+rank;
+
+ if(product != null)
+ qualifierStr = qualifierStr.concat("; product="+product);
+
+ if(ortho.isSelected())
+ add(ORTHOLOG, qualifierStr, feature);
+ else
+ add(PARALOG, qualifierStr, feature);
+
+ if(transferToList != null && transferToList.isSelected())
+ {
+ try
+ {
+ addToExistingList((DatabaseDocumentEntry) feature.getEntry().getEMBLEntry(),
+ (String)dbs.getSelectedItem()+":"+uniqueName,
+ (String)polypepList.getSelectedItem(),
+ (String)dbs.getSelectedItem());
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Add a link to any existing ortho/paralog genes.
+ * @param db_entry
+ * @param orthoOrParaLog new ortholog/paralog gene (e.g. Pfalciparum:PFA0010c)
+ * @param orthoOrParaLogLink this is the name of the feature (usualy polypep)
+ * that stores the ortholog/paralog link (e.g. Pfalciparum:PFA0010c:pep)
+ * @param organismCommonName
+ * @throws ReadOnlyException
+ * @throws EntryInformationException
+ */
+ private void addToExistingList(
+ final DatabaseDocumentEntry db_entry,
+ final String orthoOrParaLog,
+ final String orthoOrParaLogLink,
+ final String organismCommonName)
+ throws ReadOnlyException, EntryInformationException
+ {
+ DatabaseDocument doc = (DatabaseDocument) db_entry.getDocument();
+ String TYPES[] = { ORTHOLOG, PARALOG };
+ for(int i=0; i<TYPES.length; i++)
+ {
+ final Qualifier qualifier = matchQualifiers.getQualifierByName(TYPES[i]);
+ if(qualifier != null)
+ addToList(doc, qualifier, orthoOrParaLog,
+ orthoOrParaLogLink, organismCommonName);
+ }
+ }
+
+ /**
+ * Add a link to any existing ortho/paralog genes.
+ * @param doc
+ * @param thisQualifier
+ * @param orthoOrParaLog
+ * @param orthoOrParaLogLink
+ * @throws ReadOnlyException
+ * @throws EntryInformationException
+ */
+ private void addToList(final DatabaseDocument doc,
+ final Qualifier thisQualifier,
+ final String orthoOrParaLog,
+ final String orthoOrParaLogLink,
+ final String organismCommonName)
+ throws ReadOnlyException, EntryInformationException
+ {
+ StringVector values = thisQualifier.getValues();
+
+ for (int i = 0; i < values.size(); i++)
+ {
+ String link = AbstractMatchTable.getField("link", (String) values.get(i));
+ link = link.split(" ")[0];
+ if(link.equals(orthoOrParaLogLink))
+ continue;
+
+ DatabaseDocumentEntry newDbEntry = GeneEdit.makeGeneEntry(null,
+ link, doc, null);
+
+ if (newDbEntry == null)
+ continue;
+
+ char[] c = new char[1];
+ PartialSequence ps = new PartialSequence(c, 100, 0, null, null);
+ newDbEntry.setPartialSequence(ps);
+ Entry entry = null;
+ try
+ {
+ entry = new Entry(newDbEntry);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ SimpleEntryGroup entry_group = new SimpleEntryGroup();
+ entry_group.addElement(entry);
+
+ ChadoTransactionManager ctm = new ChadoTransactionManager();
+ entry_group.addFeatureChangeListener(ctm);
+ ctm.setEntryGroup(entry_group);
+
+
+ FeatureVector features = entry_group.getAllFeatures();
+ Feature existingLinkedFeature = features.elementAt(0);
+
+ org.gmod.schema.sequence.Feature f =
+ ((DatabaseDocument)newDbEntry.getDocument()).getFeatureByUniquename(
+ GeneUtils.getUniqueName(existingLinkedFeature.getEmblFeature()));
+
+ final String TYPE;
+ if(f.getOrganism().getCommonName().equals(organismCommonName))
+ TYPE = PARALOG;
+ else
+ TYPE = ORTHOLOG;
+
+ int rank;
+ QualifierVector qualifiers = existingLinkedFeature.getQualifiers();
+ Qualifier qualifier = qualifiers.getQualifierByName(TYPE);
+
+ if(qualifier == null || qualifier.getValues().size() < 1)
+ rank = 0;
+ else
+ {
+ ((QualifierLazyLoading)qualifier).setForceLoad(true);
+ rank = qualifier.getValues().size();
+ }
+ String qualifierStr = orthoOrParaLog+" link="+orthoOrParaLogLink+
+ " type="+TYPE+"; rank="+rank;
+
+ int index;
+ if(qualifier == null)
+ {
+ qualifier = new Qualifier(TYPE);
+ index = -1;
+ }
+ else
+ index = qualifiers.indexOf(qualifier);
+
+
+ StringVector sv = qualifier.getValues();
+ if(sv == null)
+ sv = new StringVector();
+ sv.add(qualifierStr);
+
+ existingLinkedFeature.setQualifier(new Qualifier(TYPE, sv));
+ // ADD SQL TO EXISTING ChadoTransactionManager?
+ ChadoTransactionManager.commit((DatabaseDocument) newDbEntry
+ .getDocument(), false, ctm);
+
+ entry_group.removeFeatureChangeListener(ctm);
+ }
+ }
+
+ /**
+ * Return the product for the feature with the given uniquename
+ * from a list of features.
+ * @param polypeptides
+ * @param selectedPolyPetideName
+ * @return
+ */
+ private String getProductFromFeatures(final Vector features,
+ final String uniqueName)
+ {
+ for(int i=0; i<features.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature ppfeature =
+ (org.gmod.schema.sequence.Feature)features.get(i);
+ if(ppfeature.getUniqueName().equals(uniqueName))
+ {
+ Collection fc = ppfeature.getFeatureCvTerms();
+ Iterator it = fc.iterator();
+ while(it.hasNext())
+ {
+ FeatureCvTerm featureCvTerm = (FeatureCvTerm)it.next();
+ CvTerm cvTerm = featureCvTerm.getCvTerm();
+
+ if(cvTerm.getCv().getName().equals(
+ uk.ac.sanger.artemis.chado.ChadoTransactionManager.PRODUCT_CV))
+ {
+ return cvTerm.getName();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Add hide/show button
+ * @param box
+ */
+ private void addHideShowButton(final JTable table, final JButton hide_show)
+ {
+ hide_show.setOpaque(false);
+
+ // remove any old listeners
+ ActionListener l[] = hide_show.getActionListeners();
+ if(l != null)
+ for(int i=0;i<l.length;i++)
+ hide_show.removeActionListener(l[i]);
+
+ hide_show.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(hide_show.getText().equals("-"))
+ {
+ hide_show.setText("+");
+ table.setVisible(false);
+ table.getTableHeader().setVisible(false);
+ }
+ else
+ {
+ hide_show.setText("-");
+ table.setVisible(true);
+ table.getTableHeader().setVisible(true);
+ }
+ }
+ });
+ }
+
+ /**
+ * Update ortho/paralogs for a feature
+ * @param feature
+ */
+ public synchronized void updateFromFeature(final Feature feature)
+ {
+ if (matchQualifiers != null)
+ feature.removeFeatureChangeListener(MatchPanel.this);
+
+ matchQualifiers = new QualifierVector();
+ final QualifierVector qualifiers = feature.getQualifiers();
+ for (int i = 0; i < qualifiers.size(); ++i)
+ {
+ final Qualifier qualifier = (Qualifier) qualifiers.elementAt(i);
+ if (isMatchTag(qualifier))
+ matchQualifiers.addElement(qualifier.copy());
+ }
+
+ if(matchQualifiers.size() < 1)
+ LOADING = false;
+ else
+ LOADING = true;
+
+ SwingWorker createMatchWorker = new SwingWorker()
+ {
+ public Object construct()
+ {
+ removeAll();
+ final JLabel loadingData = new JLabel("LOADING DATA...");
+ loadingData.setForeground(Color.red);
+ add(loadingData, BorderLayout.NORTH);
+
+ feature.addFeatureChangeListener(MatchPanel.this);
+ Component matchComponent = createMatchQualifiersComponent(feature);
+ LOADING = false;
+
+ add(matchComponent);
+
+ remove(loadingData);
+ repaint();
+ revalidate();
+ return null;
+ }
+ };
+ createMatchWorker.start();
+ }
+
+ public void updateFromQualifiers(final QualifierVector qualfiers,
+ final Feature feature)
+ {
+ removeAll();
+ matchQualifiers = qualfiers;
+ add(createMatchQualifiersComponent(feature));
+ repaint();
+ revalidate();
+ }
+
+ /**
+ * Add ortholog/paralog
+ * @param name ortholog or paralog
+ * @param value
+ * @param feature
+ */
+ private void add(final String name, final String value, final Feature feature)
+ {
+ final int index;
+
+ Qualifier qualifier = matchQualifiers.getQualifierByName(name);
+
+ if(qualifier == null)
+ {
+ qualifier = new Qualifier(name);
+ index = -1;
+ }
+ else
+ index = matchQualifiers.indexOf(qualifier);
+
+ StringVector sv = qualifier.getValues();
+ if(sv == null)
+ sv = new StringVector();
+ sv.add(value);
+
+ qualifier = new Qualifier(name, sv);
+ if(index > -1)
+ {
+ matchQualifiers.remove(index);
+ matchQualifiers.add(index, qualifier);
+ }
+ else
+ matchQualifiers.add(qualifier);
+
+ removeAll();
+ add(createMatchQualifiersComponent(feature));
+ repaint();
+ revalidate();
+ }
+
+
+ /**
+ * Get the latest (edited) controlled vocab qualifiers
+ * @return
+ */
+ public QualifierVector getMatchQualifiers()
+ {
+ if(editableComponents != null)
+ {
+ for(int i=0; i<editableComponents.size(); i++)
+ {
+ AbstractMatchTable matchTable = (AbstractMatchTable)editableComponents.get(i);
+ //System.out.println("CHECKING MATCHES "+i);
+ if(matchTable.isQualifierChanged())
+ {
+ //System.out.println("UPDATING MATCHES "+i);
+ matchTable.updateQualifier(matchQualifiers);
+ }
+ }
+ }
+ return matchQualifiers;
+ }
+
+ public List<String> getGeneNameList()
+ {
+ return getGeneNameList(false);
+ }
+
+ /**
+ * Get a list of the gene names from the ortholog table or cluster table.
+ * @param cluster if true then retrieve names from the cluster table
+ * @return
+ */
+ public List<String> getGeneNameList(boolean cluster)
+ {
+ final OrthoParalogTable table;
+ if(orthoparaLogTable == null || orthoparaLogTable.getTable().getRowCount()<1 || cluster)
+ {
+ if(clusterTable == null || clusterTable.getTable().getRowCount()<1)
+ return null;
+ table = clusterTable;
+ }
+ else
+ table = orthoparaLogTable;
+
+ int columnIndex = table.getColumnIndex(OrthoParalogTable.GENE_COL);
+ List<String> geneNames = new Vector<String>(table.getTable().getRowCount());
+ for(int row=0; row<table.getTable().getRowCount(); row++)
+ {
+ String name[] =
+ ((String)table.getTable().getValueAt(row, columnIndex)).split(":");
+ geneNames.add(name[name.length-1]);
+ }
+
+ return geneNames;
+ }
+
+ public static String getDescription()
+ {
+ return "Ortholog/Paralog/Similarity";
+ }
+
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ updateFromFeature(event.getFeature());
+ }
+
+ public boolean isEmpty()
+ {
+ if(LOADING)
+ return false;
+ return empty;
+ }
+
+ public void setEmpty(boolean empty)
+ {
+ this.empty = empty;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/ortholog/OrthoParalogTable.java b/uk/ac/sanger/artemis/components/genebuilder/ortholog/OrthoParalogTable.java
new file mode 100644
index 0000000..a84457e
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/ortholog/OrthoParalogTable.java
@@ -0,0 +1,1106 @@
+/* OrthoParalogTable.java
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder.ortholog;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableModel;
+
+import org.gmod.schema.sequence.FeatureLoc;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.chado.ArtemisUtils;
+import uk.ac.sanger.artemis.chado.ClusterLazyQualifierValue;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.InvalidRelationException;
+import uk.ac.sanger.artemis.io.PartialSequence;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierLazyLoading;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class OrthoParalogTable extends AbstractMatchTable
+{
+ private static int NUMBER_COLUMNS = 10;
+ private Vector rowData = new Vector();
+ private Vector tableData = new Vector(NUMBER_COLUMNS);
+ private JTable table;
+ private JButton infoLevelButton = new JButton("Details");
+ private JPopupMenu popupMenu = new JPopupMenu();
+ private boolean showCluster;
+
+ //
+ // column headings
+ protected final static String CLUSTER_NAME_COL = "Cluster";
+ protected final static String MATCH_NAME_COL = "Match";
+ protected final static String ROW_TYPE_HIDE_COL = "Term";
+ protected final static String ROW_TYPE_COL = "Type";
+ protected final static String PROGRAM_COL = "Program";
+ protected final static String ORGANISM_COL = "Organism";
+ protected final static String PRODUCT_COL = "Product";
+ protected final static String GENE_COL = "Gene";
+ protected final static String LINK_COL = "Link";
+ protected final static String VIEW_BUTTON_COL = "View";
+ protected final static String REMOVE_BUTTON_COL = "";
+
+
+ /**
+ * Contruct a component for an ortholog or paralog line
+ * @param doc
+ * @param orthologQualifier
+ * @param paralogQualifier
+ * @param feature
+ * @param showCluster
+ */
+ protected OrthoParalogTable(final DatabaseDocument doc,
+ final Qualifier orthologQualifier,
+ final Qualifier paralogQualifier,
+ final Feature feature,
+ final boolean showCluster)
+ {
+ this.origQualifiers = new QualifierVector();
+ this.showCluster = showCluster;
+
+ if(orthologQualifier != null)
+ this.origQualifiers.add(orthologQualifier);
+ if(paralogQualifier != null)
+ this.origQualifiers.add(paralogQualifier);
+
+ createPopupMenu(doc, feature);
+
+ infoLevelButton.setOpaque(false);
+ tableData.setSize(NUMBER_COLUMNS);
+
+ tableData.setElementAt(CLUSTER_NAME_COL,0);
+ tableData.setElementAt(MATCH_NAME_COL,1);
+ tableData.setElementAt(ROW_TYPE_HIDE_COL,2);
+ if(showCluster)
+ tableData.setElementAt(PROGRAM_COL,3);
+ else
+ tableData.setElementAt(ROW_TYPE_COL,3);
+ tableData.setElementAt(ORGANISM_COL,4);
+ tableData.setElementAt(GENE_COL,5);
+ tableData.setElementAt(LINK_COL,6);
+ tableData.setElementAt(PRODUCT_COL,7);
+ tableData.setElementAt(VIEW_BUTTON_COL,8);
+ tableData.setElementAt(REMOVE_BUTTON_COL,9);
+
+ // add row data
+ int columnIndex;
+
+ for(int i=0; i<origQualifiers.size(); i++)
+ {
+ final Vector qualifierValuesToDelete = new Vector();
+
+ final Qualifier origQualifier = (Qualifier) origQualifiers.elementAt(i);
+
+ // ensure the gene name is loaded as well
+ if(origQualifier instanceof QualifierLazyLoading)
+ {
+ List lazyValues = ((QualifierLazyLoading)origQualifier).getLazyValues();
+ for(int j=0; j<lazyValues.size(); j++)
+ {
+ ClusterLazyQualifierValue lazyValue = (ClusterLazyQualifierValue)lazyValues.get(j);
+
+ if(!lazyValue.isLazyLoaded())
+ lazyValue.setLoadGeneName(true);
+ }
+ }
+
+ final StringVector values = origQualifier.getValues();
+
+ // sort by their rank value
+ Collections.sort(values, new OrthoParalogValueComparator());
+
+ for(int j = 0; j < values.size(); j++)
+ {
+ StringVector rowStr = StringVector.getStrings((String) values.get(j),
+ ";");
+
+ if(rowStr.size() < 1)
+ {
+ qualifierValuesToDelete.add(values.get(j));
+ continue;
+ }
+ if( (ArtemisUtils.getString(rowStr, "cluster_name=").equals("") && !showCluster) ||
+ (!ArtemisUtils.getString(rowStr, "cluster_name=").equals("") && showCluster) )
+ processRowData(rowStr, rowData, origQualifier.getName());
+ }
+
+ values.removeAll(qualifierValuesToDelete);
+ }
+
+ table = new JTable(rowData, tableData);
+ setTable(table);
+
+ // set hand cursor
+ table.addMouseMotionListener( new MouseMotionAdapter()
+ {
+ private Cursor handCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+ public void mouseMoved(MouseEvent e)
+ {
+ int col = table.columnAtPoint(e.getPoint());
+ final String colName = table.getColumnName(col);
+
+ if(colName.equals(GENE_COL) || colName.equals(REMOVE_BUTTON_COL))
+ table.setCursor(handCursor);
+ else
+ table.setCursor(Cursor.getDefaultCursor());
+ }
+ });
+
+ table.addMouseListener(new MouseAdapter()
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ showPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ showPopup(e);
+ }
+
+ private void showPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ popupMenu.show(e.getComponent(), e.getX(), e.getY());
+ }
+ });
+
+ final TableColumn[] hideColumns = new TableColumn[4];
+
+ hideColumns[0] = table.getColumn(ROW_TYPE_HIDE_COL);
+ hideColumns[1] = table.getColumn(MATCH_NAME_COL);
+ if(showCluster)
+ hideColumns[2] = table.getColumn(REMOVE_BUTTON_COL);
+ else
+ hideColumns[2] = table.getColumn(CLUSTER_NAME_COL);
+ hideColumns[3] = table.getColumn(PRODUCT_COL);
+
+ for(int i=0; i<hideColumns.length; i++)
+ {
+ if(i == 3 && !showCluster)
+ continue;
+ hideColumns[i].setMinWidth(0);
+ hideColumns[i].setMaxWidth(0);
+ }
+
+ table.setColumnSelectionAllowed(false);
+ table.setRowSelectionAllowed(true);
+ table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ table.setDragEnabled(true);
+ table.setTransferHandler(new TableTransferHandler());
+
+ TableModel tableModel = table.getModel();
+ // remove button column
+ TableColumn col;
+ if(!showCluster)
+ {
+ col = table.getColumn(REMOVE_BUTTON_COL);
+ col.setMinWidth(35);
+ col.setMaxWidth(40);
+ col.setPreferredWidth(40);
+ }
+
+ final OrthologRenderer renderer = new OrthologRenderer();
+
+ for(columnIndex = 0; columnIndex <tableModel.getColumnCount();
+ columnIndex++)
+ {
+ col = table.getColumnModel().getColumn(columnIndex);
+ col.setCellRenderer(renderer);
+ col.setCellEditor(new CellEditing(new JTextField()));
+ }
+
+ packColumn(table, getColumnIndex(GENE_COL), 4);
+ packColumn(table, getColumnIndex(LINK_COL), 4);
+ packColumn(table, getColumnIndex(ORGANISM_COL), 4);
+ packColumn(table, getColumnIndex(VIEW_BUTTON_COL), 4);
+
+ if(showCluster)
+ {
+ packColumn(table, getColumnIndex(CLUSTER_NAME_COL), 4);
+ packColumn(table, getColumnIndex(PROGRAM_COL), 4);
+ }
+ else
+ {
+ packColumn(table, getColumnIndex(ROW_TYPE_COL), 4);
+ packColumn(table, getColumnIndex(PRODUCT_COL), 4);
+ }
+
+ // remove JButton column
+ col = table.getColumn(REMOVE_BUTTON_COL);
+ col.setCellEditor(new ButtonEditor(new JCheckBox(),
+ (DefaultTableModel)table.getModel(), "X", doc));
+
+ // remove JButton column
+ col = table.getColumn(VIEW_BUTTON_COL);
+ col.setCellEditor(new ButtonEditor(new JCheckBox(),
+ (DefaultTableModel)table.getModel(), "VIEW", doc));
+
+ // orthologue link
+ col = table.getColumn(GENE_COL);
+ col.setCellEditor(new LinkEditor(new JCheckBox(),
+ (DefaultTableModel)table.getModel(), doc));
+ }
+
+ /**
+ * Parse the qualifier string into row data
+ * @param rowStr
+ * @param rowData
+ * @param qualifierName
+ */
+ private void processRowData(final StringVector rowStr,
+ final Vector rowData,
+ final String qualifierName)
+ {
+ final String orthoparalogs[] = ((String) rowStr.get(0)).split(",");
+
+ String clusterName = "";
+ if(rowStr.size() > 1)
+ {
+ clusterName = ArtemisUtils.getString(rowStr, "cluster_name=");
+ if(!clusterName.equals(""))
+ clusterName = clusterName.substring(13);
+ }
+
+ String matchName = "";
+ if(rowStr.size() > 1)
+ {
+ matchName = ArtemisUtils.getString(rowStr, "match_name=");
+ if(!matchName.equals(""))
+ matchName = matchName.substring(11);
+ }
+
+ String program = "";
+ if(rowStr.size() > 1)
+ {
+ program = ArtemisUtils.getString(rowStr, "program=");
+ if(!program.equals(""))
+ program = program.substring(8);
+ }
+
+ String product = "";
+ if(rowStr.size() > 1)
+ {
+ product = ArtemisUtils.getString(rowStr, "product=");
+ if(!product.equals(""))
+ product = product.substring(8);
+ }
+
+ int columnIndex;
+ for(int k = 0; k < orthoparalogs.length; k++)
+ {
+ Vector thisRowData = new Vector(NUMBER_COLUMNS);
+
+ String geneNameAndLinkAndType[] = orthoparalogs[k].split("link=");
+ String linkAndType[] = geneNameAndLinkAndType[1].split("type=");
+ String gene[] = geneNameAndLinkAndType[0].trim().split(":");
+
+ thisRowData.setSize(NUMBER_COLUMNS);
+
+ columnIndex = tableData.indexOf(ORGANISM_COL);
+ thisRowData.setElementAt(gene[0], columnIndex);
+
+ columnIndex = tableData.indexOf(GENE_COL);
+ thisRowData.setElementAt(geneNameAndLinkAndType[0].trim(), columnIndex);
+
+ columnIndex = tableData.indexOf(LINK_COL);
+ thisRowData.setElementAt(linkAndType[0].trim(), columnIndex);
+
+ columnIndex = tableData.indexOf(CLUSTER_NAME_COL);
+ thisRowData.setElementAt(clusterName, columnIndex);
+
+ columnIndex = tableData.indexOf(MATCH_NAME_COL);
+ thisRowData.setElementAt(matchName, columnIndex);
+
+ columnIndex = tableData.indexOf(ROW_TYPE_HIDE_COL);
+ thisRowData.setElementAt(qualifierName, columnIndex);
+
+ columnIndex = tableData.indexOf(PRODUCT_COL);
+ thisRowData.setElementAt(product, columnIndex);
+
+ columnIndex = tableData.indexOf(ROW_TYPE_COL);
+
+ if(columnIndex > -1)
+ {
+ final String symbol;
+ if(linkAndType[1].trim().equals(MatchPanel.ORTHOLOG))
+ symbol = "O";
+ else
+ symbol = "P";
+ thisRowData.setElementAt(symbol, columnIndex);
+ }
+
+ columnIndex = tableData.indexOf(PROGRAM_COL);
+ if(columnIndex > -1)
+ thisRowData.setElementAt(program, columnIndex);
+
+ rowData.add(thisRowData);
+ }
+ }
+
+ /**
+ * Find if this contains any clusters
+ * @return
+ */
+ protected static boolean hasCluster(final Qualifier orthoQualifier,
+ final Qualifier paraQualifier,
+ final GFFStreamFeature feature)
+ {
+ if(hasClusterOrOrthoParalog(true, orthoQualifier, feature))
+ return true;
+ return hasClusterOrOrthoParalog(true, paraQualifier, feature);
+ }
+
+ /**
+ * Find if this contains any ortholog or paralog
+ * @return
+ */
+ protected static boolean hasOrthoParlaog(final Qualifier orthoQualifier,
+ final Qualifier paraQualifier,
+ final GFFStreamFeature feature)
+ {
+ if(hasClusterOrOrthoParalog(false, orthoQualifier, feature))
+ {
+ forceBulkLoad(paraQualifier, feature);
+ return true;
+ }
+ return hasClusterOrOrthoParalog(false, paraQualifier, feature);
+ }
+
+ private static boolean hasClusterOrOrthoParalog(final boolean lookForCluster,
+ final Qualifier qualifier,
+ final GFFStreamFeature feature)
+ {
+ if(qualifier == null)
+ return false;
+
+ forceBulkLoad(qualifier, feature);
+
+ StringVector values = qualifier.getValues();
+
+ for(int j = 0; j < values.size(); j++)
+ {
+ StringVector rowStr = StringVector.getStrings((String) values.get(j),
+ ";");
+
+ if( (!ArtemisUtils.getString(rowStr, "cluster_name=").equals("") && lookForCluster) ||
+ (ArtemisUtils.getString(rowStr, "cluster_name=").equals("") && !lookForCluster) )
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * For long lists of ortho/paralogs this speeds-up their loading
+ * @param qualifier
+ * @param feature
+ */
+ private static void forceBulkLoad(final Qualifier qualifier,
+ final GFFStreamFeature feature)
+ {
+ if(qualifier instanceof QualifierLazyLoading &&
+ !((QualifierLazyLoading)qualifier).isAllLazyValuesLoaded())
+ {
+ List values = ((QualifierLazyLoading)qualifier).getLazyValues();
+ final DatabaseDocument document =
+ (DatabaseDocument)((DocumentEntry)feature.getEntry()).getDocument();
+ ClusterLazyQualifierValue.setClusterFromValueList(values, document);
+ }
+ }
+
+ /**
+ * Find the parent feature
+ * @param feature
+ * @return
+ */
+ private Feature getParentFeature(final Feature feature)
+ {
+ final QualifierVector qualifiers = feature.getQualifiers();
+
+
+ String featureId = null;
+ for(int i=0; i<qualifiers.size(); i++)
+ {
+ Qualifier qualifier = (Qualifier)qualifiers.elementAt(i);
+ if(qualifier.getName().equalsIgnoreCase("Derives_from") ||
+ qualifier.getName().equalsIgnoreCase("Parent"))
+ {
+ featureId = (String) qualifier.getValues().get(0);
+ break;
+ }
+ }
+ FeatureNamePredicate predicate = new FeatureNamePredicate(featureId);
+ final uk.ac.sanger.artemis.FeatureVector features = feature.getEntry().getAllFeatures();
+ for(int i=0; i<features.size(); i++)
+ {
+ Feature thisFeature = features.elementAt(i);
+ if(predicate.testPredicate(thisFeature))
+ return thisFeature;
+ }
+ return null;
+ }
+
+
+ /**
+ * Create the popup menu for the table
+ *
+ */
+ private void createPopupMenu(final DatabaseDocument doc,
+ final Feature feature)
+ {
+ JMenuItem showSequenceMenu = new JMenuItem("Show selected sequences");
+ popupMenu.add(showSequenceMenu);
+ showSequenceMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Cursor orginalCursor = table.getCursor();
+ table.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ showAlignmentEditor(feature, doc, false);
+ table.setCursor(orginalCursor);
+ }
+ });
+
+
+ JMenuItem showAASequenceMenu = new JMenuItem("Show selected amino acid sequences");
+ popupMenu.add(showAASequenceMenu);
+ showAASequenceMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Cursor orginalCursor = table.getCursor();
+ table.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ showAlignmentEditor(feature, doc, true);
+ table.setCursor(orginalCursor);
+ }
+ });
+
+ JMenuItem openMenu = new JMenuItem("Open selected in Artemis");
+ popupMenu.add(openMenu);
+ openMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Cursor orginalCursor = table.getCursor();
+ table.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ int selectedRows[] = getTable().getSelectedRows();
+
+ if(selectedRows.length > 1)
+ {
+ int select = JOptionPane.showConfirmDialog(null,
+ "Open all selected sequences in seperate Artemis windows?",
+ "Open Artemis x"+selectedRows.length, JOptionPane.OK_CANCEL_OPTION);
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+ }
+ for(int i=0; i<selectedRows.length; i++)
+ openArtemis(doc,selectedRows[i]);
+ table.setCursor(orginalCursor);
+ }
+ });
+ }
+
+ /**
+ * Display this feature and selected features in the table in
+ * Jemboss alignment editor.
+ * @param feature
+ * @param doc
+ * @param showPeptideSequence
+ */
+ private void showAlignmentEditor(final Feature feature,
+ final DatabaseDocument doc,
+ final boolean showPeptideSequence)
+ {
+ final int[] rows = table.getSelectedRows();
+ final int orthoColumn = getColumnIndex(GENE_COL);
+ final Vector seqs = new Vector();
+
+ // find the gene feature
+ Feature gene = feature;
+ if(!feature.getKey().equals("gene"))
+ {
+ gene = getParentFeature(feature);
+ if(!gene.getKey().equals("gene"))
+ gene = getParentFeature(gene);
+ }
+
+ // find the exons for the gene
+ if(gene != null)
+ {
+ ChadoCanonicalGene chadoGene = ((GFFStreamFeature)gene.getEmblFeature()).getChadoGene();
+ StringBuffer buffer = new StringBuffer();
+ try
+ {
+ String transcriptName =
+ chadoGene.getTranscriptFromName((String) feature.getQualifierByName("ID").getValues().get(0));
+
+ List exons = chadoGene.getSplicedFeaturesOfTranscript(transcriptName);
+
+ for (int i = 0 ; i < exons.size () ; ++i)
+ {
+ final uk.ac.sanger.artemis.io.Feature this_feature =
+ (uk.ac.sanger.artemis.io.Feature)exons.get(i);
+ buffer.append (((Feature)this_feature.getUserData()).getBases ());
+ }
+
+ final String seqStr;
+ if(showPeptideSequence)
+ seqStr = AminoAcidSequence.getTranslation (buffer.toString(), true).toString();
+ else
+ seqStr = buffer.toString();
+
+ final String sysName = gene.getSystematicName();
+ seqs.add(new org.emboss.jemboss.editor.Sequence(sysName, seqStr));
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ for(int i=0; i<rows.length; i++)
+ {
+ String ortho = (String)table.getValueAt(rows[i], orthoColumn);
+ final String reference[] = ortho.split(":");
+ DatabaseDocument newdoc = new DatabaseDocument(doc,
+ reference[0], reference[1], true, stream_progress_listener);
+
+ try
+ {
+ // gene sequence
+ PartialSequence sequence = newdoc.getChadoSequence(reference[1]);
+
+ // cds featureloc's
+ List featureLocs =
+ newdoc.getCdsFeatureLocsByPeptideName(
+ (String)table.getValueAt(rows[i], getColumnIndex(LINK_COL)));
+
+ //
+ int phase = 0;
+ final StringBuffer sequenceBuffer = new StringBuffer();
+ if(featureLocs != null)
+ {
+ for(int j = 0; j < featureLocs.size(); j++)
+ {
+ FeatureLoc featureLoc = (FeatureLoc) featureLocs.get(j);
+ if(featureLoc.getPhase() != null)
+ phase = featureLoc.getPhase().intValue();
+ char[] subSeq = sequence.getCharSubSequence(
+ (featureLoc.getFmin().intValue() + 1),
+ featureLoc.getFmax().intValue());
+ sequenceBuffer.append(subSeq);
+ }
+ }
+ else
+ {
+ char[] subSeq = sequence.getSequence();
+ sequenceBuffer.append(subSeq);
+ if(sequence.getPhase() != null)
+ phase = sequence.getPhase().intValue();
+ }
+
+ final String seqStr;
+ if(showPeptideSequence)
+ seqStr = AminoAcidSequence.getTranslation(
+ sequenceBuffer.toString().substring(phase), true).toString();
+ else
+ seqStr = new String(sequenceBuffer.toString());
+
+ seqs.add(new org.emboss.jemboss.editor.Sequence(ortho, seqStr));
+ }
+ catch(NullPointerException npe)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Cannot get the sequence for "+ortho,
+ "Warning", JOptionPane.WARNING_MESSAGE);
+ npe.printStackTrace();
+ }
+ }
+
+ org.emboss.jemboss.editor.AlignJFrame ajFrame =
+ new org.emboss.jemboss.editor.AlignJFrame(seqs);
+ ajFrame.setVisible(true);
+ }
+
+ /**
+ * Called by AbstractMatchTable.updateQualifier()
+ */
+ protected String updateQualifierString(final int row)
+ {
+ final String type;
+ if( ((String)getTable().getValueAt(row, getColumnIndex(ROW_TYPE_COL))).equals("O") )
+ type = MatchPanel.ORTHOLOG;
+ else
+ type = MatchPanel.PARALOG;
+
+ StringBuffer orthologStr = new StringBuffer(
+ (String)getTable().getValueAt(row, getColumnIndex(GENE_COL))+
+ " link="+
+ (String)getTable().getValueAt(row, getColumnIndex(LINK_COL))+
+ " type="+type); // ortholog link
+ orthologStr.append(";");
+
+ String clusterName = (String)getTable().getValueAt(row, getColumnIndex(CLUSTER_NAME_COL));
+ if(clusterName != null && !clusterName.equals(""))
+ orthologStr.append("cluster_name="+clusterName+ ";" ); // cluster name
+
+ String product = (String)getTable().getValueAt(row, getColumnIndex(PRODUCT_COL));
+ if(product != null && !product.equals(""))
+ orthologStr.append("product="+product+ ";" );
+
+
+ String matchName = (String)getTable().getValueAt(row, getColumnIndex(MATCH_NAME_COL));
+ if(matchName != null && !matchName.equals(""))
+ orthologStr.append("match_name="+matchName+ ";" ); // match name
+
+ orthologStr.append("rank="+row);
+
+ return orthologStr.toString();
+ }
+
+ /**
+ * Override AbstractMatchTables.getOtherValues().
+ * Removes all values apart from those that are not displayed in
+ * the table, i.e. depending on whether a cluster table is displayed
+ * or the ortholog/paralog table.
+ * @param origQualifier
+ * @return
+ */
+ protected StringVector getOtherValues(final Qualifier origQualifier)
+ {
+ StringVector values = origQualifier.getValues();
+ Vector removeValues = new Vector();
+ for(int j=0; j<values.size(); j++)
+ {
+ String value = (String) values.elementAt(j);
+ if( (value.indexOf("cluster_name=") > -1 && showCluster) ||
+ (value.indexOf("cluster_name=") < 0 && !showCluster) )
+ removeValues.add(value);
+ }
+ values.removeAll(removeValues);
+
+ return values;
+ }
+
+ /**
+ * Returns true if the qualifier name matches the row type, e.g.
+ * orthologous_to / paralogous_to
+ * @param qualifierName
+ * @param row
+ * @return
+ */
+ protected boolean isRowOfType(String qualifierName, int row)
+ {
+ String rowType = (String)getTable().getValueAt(row, getColumnIndex(ROW_TYPE_HIDE_COL));
+
+ if(rowType.equals(qualifierName))
+ return true;
+ return false;
+ }
+
+ /**
+ * Check whether ortholog/paralog qualifier string exists in a StringVector for that qualifier.
+ * If the StringVector contains the hit, description return true.
+ * @param qualStr
+ * @param qualStringVector
+ * @return
+ */
+ public static boolean containsStringInStringVector(final String qualStr,
+ final StringVector qualStringVector)
+ {
+ final StringVector orth1 = StringVector.getStrings(qualStr, ";");
+ final String clusterName1 = ArtemisUtils.getString(orth1, "cluster");
+ //final String rank1 = ArtemisUtils.getString(orth1, "rank");
+ String value1 = (String)orth1.get(0);
+ int index;
+
+ if(!clusterName1.equals("")) // if a part of a cluster
+ {
+ final String clusterElements1[] = value1.split(", ");
+ final List findAll = Arrays.asList(clusterElements1);
+
+ for(int i=0; i<findAll.size(); i++)
+ {
+ final String findMe = (String)findAll.get(i);
+ boolean found = false;
+ for(int j=0; j<qualStringVector.size(); j++)
+ {
+ String thisStr = (String)qualStringVector.get(j);
+ StringVector orth2 = StringVector.getStrings(thisStr, ";");
+ final String clusterName2 = ArtemisUtils.getString(orth2, "cluster_name");
+ if(!clusterName1.equals(clusterName2))
+ continue;
+
+ String value2 = (String)orth2.get(0);
+ if((index = value2.indexOf('=')) > -1)
+ value2 = value2.substring(index+1);
+ String clusterElements2[] = value2.split(", ");
+
+ final List searchList = Arrays.asList(clusterElements2);
+
+ if(searchList.contains(findMe))
+ found = true;
+ }
+ if(!found)
+ return false;
+ }
+ return true;
+ }
+
+ for(int i=0; i<qualStringVector.size(); i++)
+ {
+ String thisStr = (String)qualStringVector.get(i);
+
+ StringVector orth2 = StringVector.getStrings(thisStr, ";");
+
+ if(orth1.size() != orth2.size())
+ continue;
+
+ String value2 = (String)orth2.get(0);
+ if((index = value2.indexOf('=')) > -1)
+ value2 = value2.substring(index+1);
+
+ if(!clusterName1.equals("") && !ArtemisUtils.getString(orth2, "cluster_name").equals(""))
+ System.out.println(value1+" ==> "+value2);
+ // ortholog/paralog/cluster
+ if(value1.indexOf(value2) < 0 &&
+ value2.indexOf(value1) < 0 )
+ continue;
+
+ // cluster name
+ final String clusterName2 = ArtemisUtils.getString(orth2, "cluster_name");
+ if(!clusterName1.equals(clusterName2))
+ continue;
+
+ // rank
+ /*
+ final String rank2 = ArtemisUtils.getString(orth2, "rank");
+ if(!rank1.equals(rank2))
+ continue;
+ */
+
+ // description
+ /*
+ if( orth1.size() > 1 && orth2.size() > 1 &&
+ !((String)orth1.get(1)).equals((String)orth2.get(1)) )
+ continue;
+ */
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Renderer for the Ortholog cells
+ */
+ private class OrthologRenderer extends DefaultTableCellRenderer
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+ private int minHeight = -1;
+
+ private final JLabel gene = new JLabel();
+ private final JLabel link = new JLabel();
+ private final JLabel type = new JLabel();
+ private final JLabel program = new JLabel();
+ private final JLabel symbol = new JLabel();
+ private final JLabel organism = new JLabel();
+ private final JLabel product = new JLabel();
+ private final JTextArea descriptionTextArea = new JTextArea();
+ private final JLabel clusterName = new JLabel();
+ private final JLabel matchName = new JLabel();
+ private final JLabel buttRemove = new JLabel("X");
+ private final JLabel buttView = new JLabel("VIEW");
+ private Color fgColor = new Color(139,35,35);
+ private Color fgLinkColor = Color.BLUE;
+
+ public OrthologRenderer()
+ {
+ gene.setForeground(Color.BLUE);
+ gene.setOpaque(true);
+
+ clusterName.setOpaque(true);
+ organism.setOpaque(true);
+ link.setOpaque(true);
+ product.setOpaque(true);
+
+ descriptionTextArea.setLineWrap(true);
+ descriptionTextArea.setWrapStyleWord(true);
+
+ buttRemove.setOpaque(true);
+ buttRemove.setText("X");
+
+ Font font = getFont().deriveFont(Font.BOLD);
+ buttRemove.setFont(font);
+ buttRemove.setToolTipText("REMOVE");
+ buttRemove.setHorizontalAlignment(SwingConstants.CENTER);
+
+ buttView.setOpaque(true);
+ buttView.setFont(font);
+ buttView.setHorizontalAlignment(SwingConstants.CENTER);
+
+ symbol.setOpaque(true);
+ symbol.setFont(font);
+ symbol.setHorizontalAlignment(SwingConstants.CENTER);
+ }
+
+
+ public Component getTableCellRendererComponent(
+ JTable table,
+ Object value,
+ boolean isSelected,
+ boolean hasFocus,
+ final int row,
+ final int column)
+ {
+ Component c = null;
+ String text = null;
+ if(value != null)
+ text = (String)value;
+ Dimension dim;
+
+ TableColumn tableCol;
+ if(column == getColumnIndex(GENE_COL))
+ {
+ String geneStr[] = text.split(":");
+ if(geneStr.length > 1)
+ gene.setText(geneStr[1]);
+
+ if(isSelected)
+ {
+ gene.setForeground(fgLinkColor);
+ gene.setBackground(table.getSelectionBackground());
+ }
+ else
+ {
+ gene.setForeground(fgLinkColor);
+ gene.setBackground(UIManager.getColor("Button.background"));
+ }
+
+ c = gene;
+ }
+ else if(column == getColumnIndex(ORGANISM_COL))
+ {
+ organism.setText(text);
+ c = organism;
+ }
+ else if(column == getColumnIndex(PRODUCT_COL))
+ {
+ product.setText(text);
+ c = product;
+ }
+ else if(column == getColumnIndex(LINK_COL))
+ {
+ link.setText(text);
+ c = link;
+ }
+ else if(column == getColumnIndex(CLUSTER_NAME_COL))
+ {
+ clusterName.setText(text);
+
+ tableCol = table.getColumnModel().getColumn(column);
+ clusterName.setSize(tableCol.getWidth(), table
+ .getRowHeight(row));
+
+ dim = clusterName.getPreferredSize();
+ minHeight = Math.max(minHeight, dim.height);
+ c = clusterName;
+ }
+ else if(column == getColumnIndex(MATCH_NAME_COL))
+ {
+ matchName.setText(text);
+ c = matchName;
+ }
+ else if(column == getColumnIndex(ROW_TYPE_HIDE_COL))
+ {
+ type.setText(text);
+ c = type;
+ }
+ else if(column == getColumnIndex(PROGRAM_COL))
+ {
+ program.setText(text);
+ c = program;
+ }
+ else if(column == getColumnIndex(ROW_TYPE_COL))
+ {
+ symbol.setText(text);
+
+ if(text.equals("O"))
+ symbol.setForeground(fgColor);
+ else
+ symbol.setForeground(Color.GREEN);
+ tableCol = table.getColumnModel().getColumn(column);
+ symbol.setSize(tableCol.getWidth(), table
+ .getRowHeight(row));
+
+ dim = symbol.getPreferredSize();
+ minHeight = Math.max(minHeight, dim.height);
+ c = symbol;
+ }
+ else if(column == getColumnIndex(VIEW_BUTTON_COL))
+ {
+ if(isSelected)
+ {
+ buttView.setForeground(fgColor);
+ buttView.setBackground(table.getSelectionBackground());
+ }
+ else
+ {
+ buttView.setForeground(fgColor);
+ buttView.setBackground(UIManager.getColor("Button.background"));
+ }
+ c = buttView;
+ }
+ else if(column == getColumnIndex(REMOVE_BUTTON_COL))
+ {
+ if(isSelected)
+ {
+ buttRemove.setForeground(fgColor);
+ buttRemove.setBackground(table.getSelectionBackground());
+ }
+ else
+ {
+ buttRemove.setForeground(fgColor);
+ buttRemove.setBackground(UIManager.getColor("Button.background"));
+ }
+ c = buttRemove;
+ }
+ else
+ {
+ throw new RuntimeException("invalid column! " + column +
+ " " + text);
+ }
+
+ // adjust row height for columns with multiple lines
+ if(column < 3)
+ {
+ if(table.getRowHeight(row) < minHeight)
+ table.setRowHeight(row, minHeight);
+
+ minHeight = -1;
+ }
+
+ // highlight on selection
+ if(isSelected)
+ c.setBackground(table.getSelectionBackground());
+ else
+ c.setBackground(Color.white);
+
+ return c;
+ }
+ }
+
+ public class OrthoParalogValueComparator implements Comparator
+ {
+ public int compare(Object o1, Object o2)
+ {
+ final String value1 = (String)o1;
+ final String value2 = (String)o2;
+
+ StringVector values1 = StringVector.getStrings((String)value1, ";");
+ String rank1 = ArtemisUtils.getString(values1, "rank");
+ if(!rank1.equals(""))
+ {
+ if(rank1.startsWith("rank=") || rank1.startsWith("rank "))
+ rank1 = rank1.substring(5);
+ }
+ else
+ rank1 = "0";
+
+ StringVector values2 = StringVector.getStrings((String)value2, ";");
+ String rank2 = ArtemisUtils.getString(values2, "rank");
+ if(!rank2.equals(""))
+ {
+ if(rank2.startsWith("rank=") || rank2.startsWith("rank "))
+ rank2 = rank2.substring(5);
+ }
+ else
+ rank2 = "0";
+
+
+ return (new Integer(rank1)).compareTo(new Integer(rank2));
+ }
+ }
+
+ public class FeatureNamePredicate implements FeaturePredicate
+ {
+ private String uniqueName;
+
+ public FeatureNamePredicate(final String uniqueName)
+ {
+ this.uniqueName = uniqueName;
+ }
+
+ public boolean testPredicate(Feature feature)
+ {
+ try
+ {
+ String featureId = (String)feature.getQualifierByName("ID").getValues().get(0);
+ if(featureId.equals(uniqueName))
+ return true;
+ }
+ catch(InvalidRelationException e){}
+
+ return false;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/genebuilder/ortholog/SimilarityTable.java b/uk/ac/sanger/artemis/components/genebuilder/ortholog/SimilarityTable.java
new file mode 100644
index 0000000..23625fe
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/genebuilder/ortholog/SimilarityTable.java
@@ -0,0 +1,671 @@
+/* Similarityox.java
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.genebuilder.ortholog;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.util.Vector;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableModel;
+
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class SimilarityTable extends AbstractMatchTable
+{
+ private int NUMBER_COLUMNS = 13;
+ private Vector rowData = new Vector();
+ private Vector tableData = new Vector(NUMBER_COLUMNS);
+
+ private JButton infoLevelButton = new JButton("Details");
+
+
+ //
+ // column headings
+ final static String ORGANISM_COL = "Organism";
+ final static String HIT_COL = "Hit";
+ final static String HIT_DBXREF_COL = "DbXRef";
+ final static String DESCRIPTION_COL = "Description";
+ final static String EVALUE_COL = "E-value";
+ final static String LENGTH_COL = "Length";
+ final static String ID_COL = "ID";
+ final static String QUERY_COL = "Query";
+ final static String SUBJECT_COL = "Subject";
+ final static String SCORE_COL = "Score";
+ final static String OVERLAP_COL = "Overlap";
+ final static String METHOD_COL = "Method";
+ final static String REMOVE_BUTTON_COL = "";
+
+
+ /**
+ * Contruct a component for a similarity line
+ * @param similarity
+ * @param similarityString
+ */
+ protected SimilarityTable(final Qualifier simQualifier,
+ final DatabaseDocument doc)
+ {
+ this.origQualifiers = new QualifierVector();
+ this.origQualifiers.add(simQualifier);
+
+ infoLevelButton.setOpaque(false);
+ infoLevelButton.setHorizontalAlignment(SwingConstants.LEFT);
+ tableData.setSize(NUMBER_COLUMNS);
+
+ tableData.setElementAt(ORGANISM_COL,0);
+ tableData.setElementAt(HIT_COL,1);
+ tableData.setElementAt(HIT_DBXREF_COL,2);
+ tableData.setElementAt(DESCRIPTION_COL,3);
+ tableData.setElementAt(EVALUE_COL,4);
+ tableData.setElementAt(LENGTH_COL,5);
+ tableData.setElementAt(ID_COL,6);
+ tableData.setElementAt(QUERY_COL,7);
+ tableData.setElementAt(SUBJECT_COL,8);
+ tableData.setElementAt(SCORE_COL,9);
+ tableData.setElementAt(OVERLAP_COL,10);
+ tableData.setElementAt(METHOD_COL,11);
+ tableData.setElementAt(REMOVE_BUTTON_COL,12);
+
+ // add rows of similarity
+ StringVector sims = simQualifier.getValues();
+ for(int i=0; i<sims.size(); i++)
+ rowData.add(getRowData((String)sims.get(i), tableData));
+
+ JTable similarityTable = new JTable(rowData, tableData);
+ setTable(similarityTable);
+
+ // set hand cursor
+ similarityTable.addMouseMotionListener( new MouseMotionAdapter()
+ {
+ private Cursor handCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+ public void mouseMoved(MouseEvent e)
+ {
+ int col = table.columnAtPoint(e.getPoint());
+
+ String colName = table.getColumnName(col);
+
+ if(colName.equals(HIT_COL) || colName.equals(HIT_DBXREF_COL) ||
+ colName.equals(REMOVE_BUTTON_COL))
+ table.setCursor(handCursor);
+ else
+ table.setCursor(Cursor.getDefaultCursor());
+ }
+ });
+
+
+ similarityTable.setColumnSelectionAllowed(false);
+ similarityTable.setRowSelectionAllowed(true);
+
+ packColumn(similarityTable, getColumnIndex(LENGTH_COL), 4);
+ packColumn(similarityTable, getColumnIndex(EVALUE_COL), 4);
+ packColumn(similarityTable, getColumnIndex(ID_COL), 4);
+ packColumn(similarityTable, getColumnIndex(HIT_COL), 6);
+ packColumn(similarityTable, getColumnIndex(HIT_DBXREF_COL), 6);
+
+ final TableColumn[] hideColumns = new TableColumn[5];
+ hideColumns[0] = similarityTable.getColumn(QUERY_COL);
+ hideColumns[1] = similarityTable.getColumn(SUBJECT_COL);
+ hideColumns[2] = similarityTable.getColumn(SCORE_COL);
+ hideColumns[3] = similarityTable.getColumn(OVERLAP_COL);
+ hideColumns[4] = similarityTable.getColumn(METHOD_COL);
+
+ for(int i=0; i<hideColumns.length; i++)
+ {
+ hideColumns[i].setMinWidth(0);
+ hideColumns[i].setMaxWidth(0);
+ }
+
+ infoLevelButton.addActionListener(new ActionListener()
+ {
+ private boolean show = true;
+ public void actionPerformed(ActionEvent e)
+ {
+ // change the column size
+ for(int i=0; i<hideColumns.length; i++)
+ {
+ if(show)
+ packColumn(getTable(), getColumnIndex(
+ (String) hideColumns[i].getHeaderValue()), 2);
+ else
+ {
+ hideColumns[i].setMinWidth(0);
+ hideColumns[i].setMaxWidth(0);
+ }
+ }
+ show = !show;
+
+ if(infoLevelButton.getText().equals("Details"))
+ infoLevelButton.setText("Hide Details");
+ else
+ infoLevelButton.setText("Details");
+ }
+ });
+
+ TableModel tableModel = getTable().getModel();
+ // remove button column
+ TableColumn col = getTable().getColumn(REMOVE_BUTTON_COL);
+ col.setMinWidth(35);
+ col.setMaxWidth(40);
+ col.setPreferredWidth(40);
+
+ final SimilarityRenderer renderer = new SimilarityRenderer();
+
+ for(int columnIndex = 0; columnIndex <tableModel.getColumnCount();
+ columnIndex++)
+ {
+ col = getTable().getColumnModel().getColumn(columnIndex);
+ col.setCellRenderer(renderer);
+ col.setCellEditor(new CellEditing(new JTextField()));
+ }
+
+
+ col = getTable().getColumn(HIT_COL);
+ col.setCellEditor(new LinkEditor(new JCheckBox(),
+ (DefaultTableModel)getTable().getModel(), null));
+
+ col = getTable().getColumn(HIT_DBXREF_COL);
+ col.setCellEditor(new LinkEditor(new JCheckBox(),
+ (DefaultTableModel)getTable().getModel(), null));
+
+ // remove JButton column
+ col = getTable().getColumn(REMOVE_BUTTON_COL);
+ col.setCellEditor(new ButtonEditor(new JCheckBox(),
+ (DefaultTableModel)getTable().getModel(), "X", doc));
+ }
+
+ /**
+ * Build a vector of the row data
+ * @param similarityString
+ * @return
+ */
+ private Vector getRowData(String similarityString,
+ final Vector tableData)
+ {
+ Vector row = new Vector(NUMBER_COLUMNS);
+ row.setSize(NUMBER_COLUMNS);
+
+ if(similarityString.startsWith("\""))
+ similarityString = similarityString.substring(1);
+ if(similarityString.endsWith("\""))
+ similarityString = similarityString.substring(0,similarityString.length()-1);
+
+ StringVector sim = StringVector.getStrings(similarityString, ";");
+
+ // organism
+ if(sim.size() >=3)
+ {
+ int columnIndex = tableData.indexOf(ORGANISM_COL);
+ row.setElementAt(((String)sim.get(2)).trim(), columnIndex);
+ }
+
+ // hit
+ if(sim.size() >=2)
+ {
+ int columnIndex = tableData.indexOf(HIT_COL);
+
+ String hit = ((String)sim.get(1)).trim();
+
+ if(hit.startsWith("with="))
+ hit = hit.substring(5);
+
+ final String hits[] = hit.split(" ");
+
+ row.setElementAt(hits[0], columnIndex);
+
+ if(hits.length > 1)
+ {
+ // dbxref
+ columnIndex = tableData.indexOf(HIT_DBXREF_COL);
+
+ if(hits[1].startsWith("(") && hits[1].endsWith(")"))
+ hits[1] = hits[1].substring(1,hits[1].length()-1);
+
+ row.setElementAt(hits[1], columnIndex);
+ }
+ }
+
+ // description
+ if(sim.size() >=4)
+ {
+ int columnIndex = tableData.indexOf(DESCRIPTION_COL);
+ row.setElementAt(((String)sim.get(3)).trim(), columnIndex);
+ }
+
+ // e-value
+ String evalueString;
+ if( !(evalueString=getField("E()=", similarityString)).equals("") )
+ {
+ int columnIndex = tableData.indexOf(EVALUE_COL);
+ row.setElementAt(evalueString, columnIndex);
+ }
+
+ // length
+ String lenString;
+ if( !(lenString=getField("length=", similarityString).trim()).equals("") )
+ {
+ int columnIndex = tableData.indexOf(LENGTH_COL);
+ row.setElementAt(lenString, columnIndex);
+ }
+ else if( !(lenString=getField("length", similarityString).trim()).equals("") )
+ {
+ int columnIndex = tableData.indexOf(LENGTH_COL);
+ row.setElementAt(lenString, columnIndex);
+ }
+
+ String ungappedId;
+ if( !(ungappedId=getField("ungapped id", similarityString)).equals("") )
+ {
+ int columnIndex = tableData.indexOf(ID_COL);
+ row.setElementAt(ungappedId, columnIndex);
+ }
+
+ String query;
+ if( !(query=getField("query", similarityString).trim()).equals("") )
+ {
+ int columnIndex = tableData.indexOf(QUERY_COL);
+ row.setElementAt(query, columnIndex);
+ }
+
+ String subject;
+ if( !(subject=getField("subject", similarityString).trim()).equals("") )
+ {
+ int columnIndex = tableData.indexOf(SUBJECT_COL);
+ row.setElementAt(subject, columnIndex);
+ }
+
+ String score;
+ if( !(score=getField("score=", similarityString)).equals("") )
+ {
+ int columnIndex = tableData.indexOf(SCORE_COL);
+ row.setElementAt(score, columnIndex);
+ }
+
+ String overlap;
+ if( !(overlap=getField("overlap=", similarityString)).equals("") )
+ {
+ int columnIndex = tableData.indexOf(OVERLAP_COL);
+ row.setElementAt(overlap, columnIndex);
+ }
+ else if(similarityString.indexOf("overlap;") > -1)
+ {
+ overlap = null;
+ for(int i=0;i<sim.size(); i++)
+ {
+ String val = (String)sim.get(i);
+ if( val.endsWith("overlap") )
+ {
+ overlap = val;
+ break;
+ }
+ }
+ if(overlap != null)
+ {
+ int columnIndex = tableData.indexOf(OVERLAP_COL);
+ row.setElementAt(overlap, columnIndex);
+ }
+ }
+
+ int columnIndex = tableData.indexOf(METHOD_COL);
+ row.setElementAt(((String)sim.get(0)).trim(), columnIndex);
+ return row;
+ }
+
+
+ /**
+ * Button to show/hide columns
+ * @return
+ */
+ public JButton getInfoLevelButton()
+ {
+ return infoLevelButton;
+ }
+
+
+ /**
+ * Called by AbstractMatchTable.updateQualifier()
+ */
+ protected String updateQualifierString(final int row)
+ {
+ StringBuffer similarityStr = new StringBuffer(
+ (String)getTable().getValueAt(row, getColumnIndex(METHOD_COL)) ); // method
+ similarityStr.append(";");
+ similarityStr.append(
+ (String)getTable().getValueAt(row, getColumnIndex(HIT_COL)) ); // hit
+
+ // dbxref
+ String dbxref = (String)getTable().getValueAt(row, getColumnIndex(HIT_DBXREF_COL));
+ if(!dbxref.equals(""))
+ similarityStr.append(" ("+dbxref+")");
+
+ similarityStr.append(";");
+ similarityStr.append(
+ (String)getTable().getValueAt(row, getColumnIndex(ORGANISM_COL)) ); // organism
+ similarityStr.append(";");
+ similarityStr.append(
+ (String)getTable().getValueAt(row, getColumnIndex(DESCRIPTION_COL)) ); // description
+ similarityStr.append(";");
+ similarityStr.append("length "+
+ (String)getTable().getValueAt(row, getColumnIndex(LENGTH_COL)) ); // length
+ similarityStr.append(";");
+ similarityStr.append("E()="+
+ (String)getTable().getValueAt(row, getColumnIndex(EVALUE_COL)) ); // evalue
+ similarityStr.append(";");
+
+ if(getTable().getValueAt(row, getColumnIndex(SCORE_COL)) != null)
+ {
+ similarityStr.append("score="+
+ (String)getTable().getValueAt(row, getColumnIndex(SCORE_COL)) ); // score
+ similarityStr.append(";");
+ }
+
+ similarityStr.append("query "+
+ (String)getTable().getValueAt(row, getColumnIndex(QUERY_COL)) ); // query
+ similarityStr.append(";");
+ similarityStr.append("subject "+
+ (String)getTable().getValueAt(row, getColumnIndex(SUBJECT_COL)) ); // subject
+ similarityStr.append(";");
+
+ if(getTable().getValueAt(row, getColumnIndex(ID_COL)) != null)
+ {
+ similarityStr.append("ungapped id="+
+ (String)getTable().getValueAt(row, getColumnIndex(ID_COL)) ); // ungapped id
+ similarityStr.append(";");
+ }
+
+ if(getTable().getValueAt(row, getColumnIndex(OVERLAP_COL)) != null)
+ similarityStr.append("overlap="+
+ (String)getTable().getValueAt(row, getColumnIndex(OVERLAP_COL)) ); // overlap
+
+ return similarityStr.toString();
+ }
+
+ /**
+ * Check whether s qualifier string exists in a StringVector for that qualifier.
+ * If the StringVector contains the hit, organism, description & e-value then
+ * return true.
+ * @param qualStr
+ * @param qualStringVector
+ * @return
+ */
+ public static boolean containsStringInStringVector(final String qualStr,
+ final StringVector qualStringVector)
+ {
+ StringVector sim1 = StringVector.getStrings(qualStr, ";");
+ for(int i=0; i<qualStringVector.size(); i++)
+ {
+ String thisStr = (String)qualStringVector.get(i);
+
+ StringVector sim2 = StringVector.getStrings(thisStr, ";");
+
+ // hit
+ if( !((String)sim1.get(1)).equals((String)sim2.get(1)) )
+ continue;
+
+ // organism
+ if( !((String)sim1.get(2)).equals((String)sim2.get(2)) )
+ continue;
+
+ // description
+ if( !((String)sim1.get(3)).equals((String)sim2.get(3)) )
+ continue;
+
+ // e-value
+ final String evalueString1 = getField("E()=", qualStr);
+ final String evalueString2 = getField("E()=", thisStr);
+ if( !(evalueString1.equals(evalueString2)) )
+ continue;
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Renderer for the Similarity cells
+ */
+ private class SimilarityRenderer extends DefaultTableCellRenderer
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+ private int minHeight = -1;
+
+ private final JLabel hit = new JLabel();
+ private final JLabel hit_dbxref = new JLabel();
+ private final JLabel evalue = new JLabel();
+ private final JLabel length = new JLabel();
+ private final JTextArea organismTextArea = new JTextArea();
+ private final JTextArea descriptionTextArea = new JTextArea();
+ private final JLabel ungappedId = new JLabel();
+ private final JLabel queryCoord = new JLabel();
+ private final JLabel subjCoord = new JLabel();
+ private final JLabel score = new JLabel();
+ private final JLabel overlap = new JLabel();
+ private final JLabel method = new JLabel();
+ private final JLabel buttRemove = new JLabel("X");
+ private Color fgColor = new Color(139,35,35);
+ private Color fgLinkColor = Color.BLUE;
+
+ public SimilarityRenderer()
+ {
+ evalue.setHorizontalAlignment(SwingConstants.RIGHT);
+ evalue.setVerticalAlignment(SwingConstants.TOP);
+ evalue.setOpaque(true);
+ length.setHorizontalAlignment(SwingConstants.RIGHT);
+ length.setVerticalAlignment(SwingConstants.TOP);
+ length.setOpaque(true);
+ ungappedId.setHorizontalAlignment(SwingConstants.RIGHT);
+ ungappedId.setVerticalAlignment(SwingConstants.TOP);
+ ungappedId.setOpaque(true);
+ queryCoord.setHorizontalAlignment(SwingConstants.RIGHT);
+ queryCoord.setVerticalAlignment(SwingConstants.TOP);
+ queryCoord.setOpaque(true);
+ subjCoord.setHorizontalAlignment(SwingConstants.RIGHT);
+ subjCoord.setVerticalAlignment(SwingConstants.TOP);
+ subjCoord.setOpaque(true);
+ score.setHorizontalAlignment(SwingConstants.RIGHT);
+ score.setVerticalAlignment(SwingConstants.TOP);
+ score.setOpaque(true);
+ overlap.setHorizontalAlignment(SwingConstants.RIGHT);
+ overlap.setVerticalAlignment(SwingConstants.TOP);
+ overlap.setOpaque(true);
+ method.setHorizontalAlignment(SwingConstants.RIGHT);
+ method.setVerticalAlignment(SwingConstants.TOP);
+ method.setOpaque(true);
+
+ organismTextArea.setLineWrap(true);
+ organismTextArea.setWrapStyleWord(true);
+
+ hit.setHorizontalAlignment(SwingConstants.CENTER);
+ hit.setVerticalAlignment(SwingConstants.TOP);
+ hit.setOpaque(true);
+ hit_dbxref.setHorizontalAlignment(SwingConstants.CENTER);
+ hit_dbxref.setVerticalAlignment(SwingConstants.TOP);
+ hit_dbxref.setOpaque(true);
+
+ descriptionTextArea.setLineWrap(true);
+ descriptionTextArea.setWrapStyleWord(true);
+
+ buttRemove.setOpaque(true);
+ buttRemove.setText("X");
+
+ Font font = getFont().deriveFont(Font.BOLD);
+ buttRemove.setFont(font);
+ buttRemove.setToolTipText("REMOVE");
+ buttRemove.setHorizontalAlignment(SwingConstants.CENTER);
+ buttRemove.setVerticalAlignment(SwingConstants.TOP);
+ }
+
+
+ public Component getTableCellRendererComponent(
+ JTable table,
+ Object value,
+ boolean isSelected,
+ boolean hasFocus,
+ final int row,
+ final int column)
+ {
+ Component c = null;
+ String text = null;
+ if(value != null)
+ text = (String)value;
+ Dimension dim;
+
+ TableColumn tableCol;
+ if(column == getColumnIndex(ORGANISM_COL))
+ {
+ organismTextArea.setText(text);
+
+ tableCol = table.getColumnModel().getColumn(column);
+ organismTextArea.setSize(tableCol.getWidth(), table.getRowHeight(row));
+
+ dim = organismTextArea.getPreferredSize();
+ minHeight = Math.max(minHeight, dim.height);
+
+ c = organismTextArea;
+ }
+ else if(column == getColumnIndex(HIT_COL))
+ {
+ hit.setText(text);
+ hit.setForeground(fgLinkColor);
+
+ c = hit;
+ }
+ else if(column == getColumnIndex(HIT_DBXREF_COL))
+ {
+ hit_dbxref.setText(text);
+ hit_dbxref.setForeground(fgLinkColor);
+
+ c = hit_dbxref;
+ }
+ else if(column == getColumnIndex(DESCRIPTION_COL))
+ {
+ descriptionTextArea.setText(text);
+
+ tableCol = table.getColumnModel().getColumn(column);
+ descriptionTextArea.setSize(tableCol.getWidth(), table
+ .getRowHeight(row));
+
+ dim = descriptionTextArea.getPreferredSize();
+ minHeight = Math.max(minHeight, dim.height);
+ c = descriptionTextArea;
+ }
+ else if(column == getColumnIndex(EVALUE_COL))
+ {
+ evalue.setText(text);
+ c = evalue;
+ }
+ else if(column == getColumnIndex(LENGTH_COL))
+ {
+ length.setText(text);
+ c = length;
+ }
+ else if(column == getColumnIndex(ID_COL))
+ {
+ ungappedId.setText(text);
+ c = ungappedId;
+ }
+ else if(column == getColumnIndex(QUERY_COL))
+ {
+ queryCoord.setText(text);
+ c = queryCoord;
+ }
+ else if(column == getColumnIndex(SUBJECT_COL))
+ {
+ subjCoord.setText(text);
+ c = subjCoord;
+ }
+ else if(column == getColumnIndex(SCORE_COL))
+ {
+ score.setText(text);
+ c = score;
+ }
+ else if(column == getColumnIndex(OVERLAP_COL))
+ {
+ overlap.setText(text);
+ c = overlap;
+ }
+ else if(column ==getColumnIndex(METHOD_COL))
+ {
+ method.setText(text);
+ c = method;
+ }
+ else if(column == getColumnIndex(REMOVE_BUTTON_COL))
+ {
+ if(isSelected)
+ {
+ buttRemove.setForeground(fgColor);
+ buttRemove.setBackground(table.getSelectionBackground());
+
+ }
+ else
+ {
+ buttRemove.setForeground(fgColor);
+ buttRemove.setBackground(UIManager.getColor("Button.background"));
+ }
+ c = buttRemove;
+ }
+ else
+ {
+ throw new RuntimeException("invalid column! " + column +
+ " " + text);
+ }
+
+ // adjust row height for columns with multiple lines
+ if(column < 4)
+ {
+ if(table.getRowHeight(row) < minHeight)
+ table.setRowHeight(row, minHeight);
+
+ minHeight = -1;
+ }
+
+ // highlight on selection
+ if(isSelected)
+ c.setBackground(table.getSelectionBackground());
+ else
+ c.setBackground(Color.white);
+
+ return c;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/AbstractVCFReader.java b/uk/ac/sanger/artemis/components/variant/AbstractVCFReader.java
new file mode 100644
index 0000000..b49845f
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/AbstractVCFReader.java
@@ -0,0 +1,400 @@
+/* AbstractReader
+ *
+ * created: July 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.variant;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.components.variant.BCFReader.BCFReaderIterator;
+
+public abstract class AbstractVCFReader
+{
+ private boolean vcf_v4 = false;
+
+ protected String[] sampleNames;
+ protected abstract String[] getSeqNames();
+ protected abstract String getFileName();
+ protected int nsamples = -1;
+
+ private BCFReaderIterator bcfIterator = null;
+ private TabixReader.Iterator tabixIterator = null;
+ private String header;
+
+ /**
+ * Read and return the next record.
+ * @param chr sequence name
+ * @param sbeg start base
+ * @param send end base
+ * @return
+ * @throws IOException
+ */
+ public VCFRecord getNextRecord(String chr, int sbeg, int send) throws IOException
+ {
+ VCFRecord record;
+ if(this instanceof BCFReader)
+ {
+ if(bcfIterator == null)
+ bcfIterator = ((BCFReader)this).query(chr, sbeg, send);
+
+ record = bcfIterator.next();
+ if(record == null)
+ bcfIterator = null;
+ }
+ else
+ {
+ if(tabixIterator == null)
+ {
+ try
+ {
+ tabixIterator = ((TabixReader)this).query(chr+":"+sbeg+"-"+send);
+ }
+ catch(ArrayIndexOutOfBoundsException aob)
+ {
+ System.err.println(chr+":"+sbeg+"-"+send+" not found in "+((TabixReader)this).getFileName());
+ }
+ }
+ if(tabixIterator == null)
+ return null;
+
+ String s = tabixIterator.next();
+ if(s == null)
+ {
+ tabixIterator = null;
+ return null;
+ }
+ record = VCFRecord.parse(s, getNumberOfSamples());
+
+ if(record == null)
+ tabixIterator = null;
+ }
+ return record;
+ }
+
+
+ protected static int readInt(final InputStream is) throws IOException {
+ byte[] buf = new byte[4];
+ is.read(buf);
+ return ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN).getInt();
+ }
+
+
+ protected static float readFloat(final InputStream is) throws IOException {
+ byte[] buf = new byte[4];
+ is.read(buf);
+ return ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN).getFloat();
+ }
+
+ protected static long readLong(final InputStream is) throws IOException {
+ byte[] buf = new byte[8];
+ is.read(buf);
+ return ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN).getLong();
+ }
+
+ protected String getName()
+ {
+ if(getFileName() == null)
+ return null;
+ File f = new File(getFileName());
+ return f.getName();
+ }
+
+ /**
+ * Export VCF file
+ * @param manualHash
+ * @param vcfFileName
+ * @param writer
+ * @param vcfView
+ * @param features
+ * @throws IOException
+ */
+ protected static void write(final Map<String, Boolean> manualHash,
+ final String vcfFileName,
+ final int vcfIndex,
+ Writer writer,
+ VCFview vcfView,
+ FeatureVector features) throws IOException
+ {
+ // FILTER LINES
+ if(IOUtils.isBCF(vcfFileName))
+ {
+ BCFReader reader = new BCFReader(vcfFileName);
+ // FIX for old style BAM files
+ AbstractVCFReader readers[] = vcfView.getVcfReaders();
+ for(int i=0; i<readers.length; i++)
+ {
+ if(readers[i].getFileName().equals(vcfFileName))
+ reader.newBCF = ((BCFReader)readers[i]).newBCF;
+ }
+
+ // add header
+ String hdr = replaceFilterLines(reader.headerToString(), FilteredPanel.getHeader());
+ writer.write( hdr );
+
+ int sbeg = 0;
+ int send = Integer.MAX_VALUE;
+ VCFRecord record;
+
+ while( (record = reader.nextRecord(null, sbeg, send)) != null)
+ {
+ int basePosition = record.getPos() + vcfView.getSequenceOffset(record.getChrom());
+ VCFFilter.setFilterString(manualHash, record, vcfView, basePosition, features, reader, vcfIndex);
+ writer.write(record.toString()+"\n");
+ }
+ writer.close();
+ reader.close();
+ return;
+ }
+
+ final TabixReader tr = new TabixReader(vcfFileName);
+ String line;
+ boolean headerEnd = true;
+ final StringBuffer buffHeader = new StringBuffer();
+
+ while ((line = tr.readLine()) != null)
+ {
+ if(line.startsWith("##"))
+ {
+ if(!line.startsWith("##FILTER"))
+ writer.write(line+'\n');
+ buffHeader.append(line+'\n');
+ continue;
+ }
+ else if(headerEnd)
+ {
+ writer.write(FilteredPanel.getHeader());
+ headerEnd = false;
+ }
+
+ if(line.startsWith("#"))
+ {
+ writer.write(line+'\n');
+ buffHeader.append(line+'\n');
+ tr.setHeader(buffHeader.toString());
+ continue;
+ }
+
+ VCFRecord record = VCFRecord.parse(line, tr.getNumberOfSamples());
+ int basePosition = record.getPos() + vcfView.getSequenceOffset(record.getChrom());
+ VCFFilter.setFilterString(manualHash, record, vcfView, basePosition, features, tr, vcfIndex);
+ writer.write(record.toString()+'\n');
+ }
+ writer.close();
+ }
+
+ /**
+ * Return the header with the new Filter lines.
+ * @param hdrLines
+ * @param filterLines
+ * @return
+ */
+ private static String replaceFilterLines(final String hdrLines, final String filterLines)
+ {
+ final StringBuffer buff = new StringBuffer();
+ final BufferedReader readerStr = new BufferedReader(new StringReader(hdrLines));
+ String str;
+ try
+ {
+ while ((str = readerStr.readLine()) != null)
+ {
+ if (!str.startsWith("##Filter="))
+ {
+ if(str.startsWith("#CHROM"))
+ {
+ buff.append(filterLines);
+ buff.append(str+"\n");
+ }
+ else
+ buff.append(str+"\n");
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ return buff.toString();
+ }
+
+ /**
+ * @return the vcf_v4
+ */
+ protected boolean isVcf_v4()
+ {
+ return vcf_v4;
+ }
+
+ /**
+ * @param vcfV4 the vcf_v4 to set
+ */
+ protected void setVcf_v4(boolean vcfV4)
+ {
+ vcf_v4 = vcfV4;
+ }
+
+ protected void setHeader(String header)
+ {
+ this.header = header;
+ }
+
+ protected String getHeader()
+ {
+ return header;
+ }
+
+ protected int getNumberOfSamples()
+ {
+ if(nsamples < 1)
+ {
+ nsamples = 1;
+ BufferedReader reader = new BufferedReader(new StringReader(header));
+ try
+ {
+ String ln;
+ while ((ln = reader.readLine()) != null)
+ {
+ if (ln.startsWith("#CHROM"))
+ {
+ int index = ln.indexOf("FORMAT");
+ if(index > -1)
+ {
+ sampleNames = ln.substring(index+7).trim().split("[ \\t]");
+ nsamples = sampleNames.length;
+ }
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ System.err.println("Problem calculating the number of samples.");
+ }
+ }
+ return nsamples;
+ }
+
+ protected String[] getSampleNames()
+ {
+ return sampleNames;
+ }
+
+ protected List<HeaderLine> getFORMAT()
+ {
+ return getListOfLines("FORMAT");
+ }
+
+ protected List<HeaderLine> getFILTER()
+ {
+ return getListOfLines("FILTER");
+ }
+
+ protected List<HeaderLine> getINFO()
+ {
+ return getListOfLines("INFO");
+ }
+
+ /**
+ * Return a list of the lines in the header. Each line is represented as a hash of
+ * the key value pairs.
+ * @param lineType
+ * @return
+ */
+ private List<HeaderLine> getListOfLines(String lineType)
+ {
+ List<HeaderLine> listOfType = new Vector<HeaderLine>();
+
+ try
+ {
+ BufferedReader reader = new BufferedReader(new StringReader(getHeader()));
+ String str;
+ while ((str = reader.readLine()) != null)
+ {
+ String origLine = new String(str);
+ if (str.startsWith("##"+lineType))
+ {
+ listOfType.add(new HeaderLine(origLine, lineType, getLineHash(lineType, str)));
+ }
+ }
+ }
+ catch (IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ return listOfType;
+ }
+
+ protected static Hashtable<String, String> getLineHash(String lineType, String str)
+ {
+ Hashtable<String, String> hash = new Hashtable<String, String>();
+ hash.put("lineType", lineType);
+ str = str.substring(lineType.length() + 4, str.length()-1);
+
+ String parts[] = str.split(",");
+ for(int i=0; i<parts.length; i++)
+ {
+ if(!parts[i].startsWith("Description"))
+ {
+ String thisPart[] = parts[i].split("=");
+ if(thisPart.length == 2)
+ {
+ hash.put(thisPart[0], thisPart[1]);
+ }
+ }
+ else if(parts[i].startsWith("Description"))
+ {
+ String thisPart[] = parts[i].split("=");
+
+ if(thisPart.length == 2)
+ {
+ //search for closing quote
+ while(thisPart[0].equals("Description") &&
+ thisPart[1].startsWith("\"") &&
+ thisPart[1].indexOf("\"",2) == -1 &&
+ i+1 < parts.length)
+ {
+ thisPart[1] = thisPart[1] + "," + parts[++i];
+ }
+
+ if(thisPart[1].startsWith("\""))
+ thisPart[1] = thisPart[1].substring(1);
+ if(thisPart[1].endsWith("\""))
+ thisPart[1] = thisPart[1].substring(0,thisPart[1].length()-1);
+ hash.put(thisPart[0], thisPart[1]);
+ }
+ }
+ }
+ return hash;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/BCFReader.java b/uk/ac/sanger/artemis/components/variant/BCFReader.java
new file mode 100644
index 0000000..42c1d24
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/BCFReader.java
@@ -0,0 +1,632 @@
+/*
+ * created: 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.variant;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.List;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import uk.ac.sanger.artemis.util.FTPSeekableStream;
+
+import net.sf.samtools.util.BlockCompressedInputStream;
+
+class BCFReader extends AbstractVCFReader
+{
+ public static final int TAD_LIDX_SHIFT = 13; // linear index shift
+ private static Pattern formatPattern = Pattern.compile("[^0-9]+");
+ private BlockCompressedInputStream is;
+ private InputStream indexFileStream;
+ private List<BCFIndex> idx;
+
+ // header information and names
+ private String[] seqNames;
+
+ private String metaData;
+ private String fileName;
+
+ //
+ // Nasty work around for backward compatibility with old BCF files.
+ // Assume new BCF file unless ArrayIndexOutOfBoundsException thrown.
+ // The newer BCF files have:
+ // SP type of long
+ protected boolean newBCF = true;
+
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(BCFReader.class);
+
+ /**
+ * @param bcf BCF file or url
+ * @throws IOException
+ */
+ public BCFReader(String bcf) throws IOException
+ {
+ if(bcf.startsWith("http"))
+ {
+ URL bcfURL = new URL(bcf);
+ is = new BlockCompressedInputStream(bcfURL);
+ indexFileStream = new URL(bcf+".bci").openStream();
+ fileName = bcfURL.getFile();
+ }
+ else if(bcf.startsWith("ftp"))
+ {
+ URL bcfURL = new URL(bcf);
+ FTPSeekableStream fss = new FTPSeekableStream(bcfURL);
+ is = new BlockCompressedInputStream(fss);
+ indexFileStream = new URL(bcf+".bci").openStream();
+ fileName = bcfURL.getFile();
+ }
+ else
+ {
+ File bcfFile = new File(bcf);
+ is = new BlockCompressedInputStream(bcfFile);
+ indexFileStream = new FileInputStream(new File(bcf+".bci"));
+ fileName = bcfFile.getAbsolutePath();
+ }
+ idx = loadIndex();
+ readHeader();
+ logger4j.debug(bcf);
+ }
+
+ protected void seek(long off) throws IOException
+ {
+ is.seek(off);
+ }
+
+ protected VCFRecord nextRecord(String chr, int beg, int end) throws IOException
+ {
+ try
+ {
+ VCFRecord bcfRecord = readVCFRecord();
+ if(chr != null && !bcfRecord.getChrom().equals(chr))
+ return null;
+
+ if(bcfRecord.getPos() >= beg && bcfRecord.getPos() <= end)
+ return bcfRecord;
+ else if(bcfRecord.getPos() < beg)
+ {
+ while( (bcfRecord = readVCFRecord()).getPos() <= beg )
+ {
+ if(chr != null && !bcfRecord.getChrom().equals(chr))
+ return null;
+
+ if(bcfRecord.getPos() >= beg && bcfRecord.getPos() <= end)
+ return bcfRecord;
+ }
+ if(bcfRecord.getPos() >= beg && bcfRecord.getPos() <= end)
+ return bcfRecord;
+ }
+ }
+ catch(ArrayIndexOutOfBoundsException ae)
+ {
+ if(newBCF)
+ {
+ newBCF = false;
+ logger4j.debug("This looks like an old style BCF: "+fileName);
+ }
+ else
+ throw ae;
+ }
+ catch(Exception e)
+ {
+ if(is.read() != -1) // eof
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ protected void close() throws IOException
+ {
+ is.close();
+ indexFileStream.close();
+ }
+
+ private void readHeader() throws IOException
+ {
+ byte[] magic = new byte[4];
+ is.read(magic);
+
+ String line = new String(magic);
+ if(!line.equals("BCF\4"))
+ {
+ throw new IOException("Not BCF format.");
+ }
+
+ // sequence names
+ seqNames = getArray(readInt(is));
+
+ // sample names
+ sampleNames = getArray(readInt(is));
+ nsamples = sampleNames.length;
+
+ int len = readInt(is);
+ byte meta[] = new byte[len];
+ is.read(meta);
+
+ StringBuffer buff = new StringBuffer();
+ for(int i=0; i<meta.length; i++)
+ {
+ // expecting null terminated
+ if(meta[i] == 0 && i == meta.length-1)
+ continue;
+ buff.append((char)meta[i]);
+ }
+
+ metaData = buff.toString();
+ }
+
+ protected String headerToString()
+ {
+ StringBuffer head = new StringBuffer();
+ head.append("##fileformat=VCFv4.0\n");
+ head.append(metaData);
+ head.append("#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT\t");
+
+ for(int i=0; i<sampleNames.length; i++)
+ head.append(sampleNames[i]+" ");
+ return head.toString();
+ }
+
+ /**
+ * Given the length of the concatenated names, that are NULL padded
+ * construct a list of the Strings.
+ * @param len
+ * @return
+ * @throws IOException
+ */
+ private String[] getArray(int len) throws IOException
+ {
+ byte b[] = new byte[len];
+ is.read(b);
+
+ List<String> names = new Vector<String>();
+ StringBuffer buff = new StringBuffer();
+ for(int i=0; i<b.length; i++)
+ {
+ if(b[i] != 0)
+ buff.append((char)b[i]);
+ else if(buff.length() > 0)
+ {
+ names.add(buff.toString());
+ buff = new StringBuffer();
+ }
+ }
+
+ String[] arr = new String[names.size()];
+ for(int i=0; i< arr.length; i++)
+ arr[i] = names.get(i);
+ return arr;
+ }
+
+ private VCFRecord readVCFRecord() throws IOException
+ {
+ VCFRecord bcfRecord = new VCFRecord();
+ bcfRecord.setChrom( seqNames[readInt(is)] );
+ bcfRecord.setPos ( readInt(is)+1 );
+ bcfRecord.setQuality( readFloat(is) );
+
+ int slen = readInt(is);
+ if(slen > 5000)
+ return bcfRecord;
+ byte[] str = new byte[slen];
+ is.read(str);
+
+ getParts(str, bcfRecord);
+
+ if(formatPattern.matcher(bcfRecord.getFormat()).matches())
+ {
+ int n_alleles;
+ if(bcfRecord.getAlt().toString().equals(":")) // non-variant
+ {
+ n_alleles = 1;
+ bcfRecord.setAlt(".");
+ }
+ else
+ n_alleles = bcfRecord.getAlt().getNumAlleles();
+ int nc = (int) (n_alleles * ((float)(((float)n_alleles+1.f)/2.f)));
+
+ //String fmts[] = VCFRecord.COLON_PATTERN.split( bcfRecord.getFormat() );
+
+ final int nfmt = VCFRecord.countOccurrences(bcfRecord.getFormat(), ':')+1;
+ final String fmts[] = VCFRecord.split(bcfRecord.getFormat() , ":", nfmt);
+
+ bcfRecord.setGenoTypeData( new String[nsamples][fmts.length] );
+
+ for(int i=0; i<fmts.length; i++)
+ {
+ int nb = getByteSize(fmts[i],nc);
+ str = new byte[nb];
+ is.read(str);
+
+ if(fmts[i].equals("GT"))
+ {
+ for(int j=0; j<nsamples; j++)
+ bcfRecord.getGenoTypeData()[j][i] = getGTString(str[j]);
+ }
+ else if(fmts[i].equals("PL"))
+ {
+ String pls[] = getPLString(str, nsamples);
+ for(int j=0; j<nsamples; j++)
+ bcfRecord.getGenoTypeData()[j][i] = pls[j];
+ }
+ else if(fmts[i].equals("DP")||fmts[i].equals("SP")||fmts[i].equals("GQ"))
+ {
+ for(int j=0; j<nsamples; j++)
+ bcfRecord.getGenoTypeData()[j][i] = Integer.toString(byteToInt(str[j]));
+ }
+ else
+ {
+ bcfRecord.getGenoTypeData()[0][i] = new String(str);
+ }
+ }
+ }
+
+
+ return bcfRecord;
+ }
+
+ /**
+ * Make a string from the byte array (expanding NULL padding) to
+ * determine the parts:- ID+REF+ALT+FILTER+INFO+FORMAT.
+ * @param b
+ * @return
+ */
+ private void getParts(final byte[] b, final VCFRecord bcfRecord)
+ {
+ int index = 0;
+ final StringBuilder buff = new StringBuilder();
+
+ for(int i=0; i<b.length; i++)
+ {
+ if(b[i] == 0 && (i == 0 || b[i-1] == 0) )
+ {
+ switch( index )
+ {
+ case 0: bcfRecord.setID("."); break;
+ case 1: bcfRecord.setRef("."); break;
+ case 2: bcfRecord.setAlt(":"); break; // this looks like a non-variant site
+ case 3: bcfRecord.setFilter("."); break;
+ case 4: bcfRecord.setInfo("."); break;
+ case 5: bcfRecord.setFormat("."); break;
+ default: return;
+ }
+ index++;
+ }
+ if(b[i] == 0)
+ continue;
+
+ buff.setLength(0);
+ buff.append((char)b[i]);
+ while(i < b.length-1 && b[i+1] != 0)
+ buff.append((char)b[++i]);
+
+ switch( index )
+ {
+ case 0: bcfRecord.setID(buff.toString()); break;
+ case 1: bcfRecord.setRef(buff.toString()); break;
+ case 2: bcfRecord.setAlt(buff.toString()); break;
+ case 3: bcfRecord.setFilter(buff.toString()); break;
+ case 4: bcfRecord.setInfo(buff.toString()); break;
+ case 5: bcfRecord.setFormat(buff.toString()); break;
+ default: return;
+ }
+ index++;
+ }
+ }
+
+
+ /*private void getParts(final byte[] b, final VCFRecord bcfRecord)
+ {
+ final StringBuilder buff = new StringBuilder();
+ for(int i=0; i<b.length; i++)
+ {
+ if(i == 0 && b[i] == 0)
+ buff.append(". ");
+ else if(b[i] == 0 && b[i-1] == 0) // this looks like a non-variant site
+ buff.append(" : ");
+ else if(b[i] == 0)
+ buff.append(" ");
+ else
+ buff.append((char)b[i]);
+ }
+
+ int ind = 0;
+ int lastInd = 0;
+ int i = 0;
+ final int len = buff.length();
+
+ while((ind = buff.indexOf(" ", ind)) > -1)
+ {
+ switch( i )
+ {
+ case 0: bcfRecord.setID(buff.substring(lastInd, ind)); break;
+ case 1: bcfRecord.setRef(buff.substring(lastInd, ind)); break;
+ case 2: bcfRecord.setAlt(buff.substring(lastInd, ind)); break;
+ case 3: bcfRecord.setFilter(buff.substring(lastInd, ind)); break;
+ case 4: bcfRecord.setInfo(buff.substring(lastInd, ind)); break;
+ case 5: bcfRecord.setFormat(buff.substring(lastInd, ind)); break;
+ default: return;
+ }
+
+ ind++;
+ if(ind < len && buff.charAt(ind) == ' ')
+ ind++;
+ lastInd = ind;
+ i++;
+ }
+ }*/
+
+ /**
+ * DP uint16 t[n] Read depth
+ * GL float[n*x] Log10 likelihood of data; x = m(m+1)/2 , m = #{alleles}
+ * GT uint8 t[n] phase<<6 | allele1<<3 | allele2
+ * GQ uint8 t[n] Genotype quality
+ * HQ uint8 t[n*2] Haplotype quality
+ * PL uint8 t[n*x] Phred-scaled likelihood of data
+ * misc int32 t+char* NULL padded concatenated strings (integer equal to the length)
+ * @param tag
+ * @param nsamples
+ * @param nc
+ * @return
+ */
+ private int getByteSize(String tag, int nc)
+ {
+ if(tag.equals("DP")) // Read depth
+ return 2*nsamples; // uint16_t[n]
+ else if(tag.equals("GL")) // Log10 likelihood
+ return 4*nsamples*nc; // float[nsamples*x]
+ else if(tag.equals("GT")) // phase<<6 | allele1<<3 | allele2
+ return nsamples; // uint8_t[n]
+ else if(tag.equals("GQ")) // Genotype quality
+ return nsamples; // uint8_t[n]
+ else if(tag.equals("HQ")) // Haplotype quality
+ return 2*nsamples; // uint8_t[n*2]
+ else if(tag.equals("PL")) // Phred-scaled likelihood
+ return nsamples*nc; // uint8_t[n*x]
+ else if(tag.equals("SP")) //
+ {
+ if(newBCF) // type changed in bcftools
+ return 4*nsamples; // uint32_t[n]
+ else
+ return nsamples; // uint8_t[n]
+ }
+ else // misc
+ return 4*nsamples; // uint32_t+char*
+ }
+
+ /**
+ * Phred scaled likelihood of data.
+ * @param b
+ * @param nsamples
+ * @return
+ */
+ private String[] getPLString(byte[] b, int nsamples)
+ {
+ String pls[] = new String[nsamples];
+ int nb = b.length / nsamples;
+ int cnt = 0;
+
+ for(int i=0; i<nsamples; i++)
+ {
+ StringBuffer buff = new StringBuffer();
+ for(int j=0;j<nb; j++)
+ {
+ buff.append(byteToInt(b[cnt]));
+ if(j<nb-1)
+ buff.append(",");
+ cnt++;
+ }
+ pls[i] = buff.toString();
+ }
+ return pls;
+ }
+
+ /**
+ * GT genotype, allele values separated by / or |, i.e.
+ * unphased or phased.
+ * @param b
+ * @return
+ */
+ private String getGTString(byte b)
+ {
+ return ((b >> 3 & 7) + ( ((b >> 6 & 1 )== 1 ) ? "|" : "/") + (b & 7));
+ }
+
+ private int byteToInt(byte b)
+ {
+ return (int)(b & 0xFF);
+ }
+
+ protected List<BCFIndex> loadIndex() throws IOException
+ {
+ BlockCompressedInputStream is = new BlockCompressedInputStream(indexFileStream);
+ byte[] magic = new byte[4];
+ is.read(magic);
+
+ if(!new String(magic).equals("BCI\4"))
+ System.err.println("Not a BCF index file:: "+new String(magic));
+
+ int n = readInt(is);
+ List<BCFIndex> idx = new Vector<BCFIndex>(n);
+
+ for(int i=0; i<n; i++)
+ {
+ BCFIndex idx2 = new BCFIndex();
+ idx2.n = readInt(is);
+ idx2.index2_offset = new long[idx2.n];
+
+ for(int j=0; j<idx2.n; j++)
+ idx2.index2_offset[j] = readLong(is);
+
+ idx.add(idx2);
+ }
+ return idx;
+ }
+
+ protected int getSeqIndex(String chr)
+ {
+ for(int i=0; i<seqNames.length; i++)
+ {
+ if(seqNames[i].equals(chr))
+ return i;
+ }
+
+ return -1;
+ }
+
+ protected long queryIndex(int tid, int beg)
+ {
+ long min_off = -1;
+ if (beg < 0)
+ beg = 0;
+
+ long offset[] = idx.get(tid).index2_offset;
+ int i;
+
+ try
+ {
+ for(i = beg>>TAD_LIDX_SHIFT; i < idx.get(tid).n && offset[i] == 0; ++i);
+ min_off = (i == idx.get(tid).n)? offset[idx.get(tid).n-1] : offset[i];
+ }
+ catch(ArrayIndexOutOfBoundsException e)
+ {
+ return offset[offset.length-1];
+ }
+ return min_off;
+ }
+
+ protected String getMetaData()
+ {
+ return metaData;
+ }
+
+ protected String[] getSeqNames()
+ {
+ return seqNames;
+ }
+
+ protected String getFileName()
+ {
+ return fileName;
+ }
+
+ protected BCFReaderIterator query(String chr, int sbeg, int send) throws IOException
+ {
+ return new BCFReaderIterator(chr, sbeg, send);
+ }
+
+ protected class BCFReaderIterator
+ {
+ private String chr;
+ private int sbeg;
+ private int send;
+ private int count = 0;
+
+ protected BCFReaderIterator(String chr, int sbeg, int send)
+ {
+ this.chr = chr;
+ this.sbeg = sbeg;
+ this.send = send;
+ }
+
+ private boolean seekPosition() throws IOException
+ {
+ int bid = getSeqIndex(chr);
+ if(bid < 0)
+ {
+ VCFview.logger4j.debug(chr+" NOT FOUND");
+ return false;
+ }
+ long off = queryIndex(bid, sbeg);
+ seek(off);
+ return true;
+ }
+
+ public VCFRecord next() throws IOException
+ {
+ if(count == 0 && !seekPosition())
+ return null;
+
+ count+=1;
+ return nextRecord(chr, sbeg, send);
+ }
+ }
+
+
+ public static void main(String args[])
+ {
+ try
+ {
+ int sbeg = 0;
+ int send = Integer.MAX_VALUE;
+ String chr = null;
+ if(args.length > 1)
+ {
+ String parts[] = args[1].split(":");
+ chr = parts[0];
+
+ String rgn[] = parts[1].split("-");
+ sbeg = Integer.parseInt(rgn[0]);
+ send = Integer.parseInt(rgn[1]);
+ }
+
+ BCFReader reader = new BCFReader(args[0]);
+ int bid = 0;
+ if(chr != null)
+ bid = reader.getSeqIndex(chr);
+
+ long off = reader.queryIndex(bid, sbeg);
+ reader.seek(off);
+
+ System.out.println(reader.headerToString());
+ VCFRecord bcfRecord;
+ while( (bcfRecord = reader.nextRecord(chr, sbeg, send)) != null )
+ {
+ if(chr != null && bcfRecord.getChrom().equals(chr))
+ System.out.println(bcfRecord.toString());
+ else
+ break;
+ }
+
+ reader.close();
+ }
+ catch (IOException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+}
+
+class BCFIndex
+{
+ int n;
+ long index2_offset[];
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/CDSFeature.java b/uk/ac/sanger/artemis/components/variant/CDSFeature.java
new file mode 100644
index 0000000..3e89be7
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/CDSFeature.java
@@ -0,0 +1,64 @@
+package uk.ac.sanger.artemis.components.variant;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+
+class CDSFeature
+{
+ protected boolean isFwd;
+ protected RangeVector ranges;
+ protected int firstBase;
+ protected int lastBase;
+ protected int intronlength = 0;
+ protected String bases;
+ protected Feature feature;
+ protected Range lastRange = null;
+
+ public CDSFeature(boolean isFwd,
+ RangeVector ranges,
+ int firstBase,
+ int lastBase,
+ String bases)
+ {
+ this.isFwd = isFwd;
+ this.ranges = ranges;
+ this.firstBase = firstBase;
+ this.lastBase = lastBase;
+ this.bases = bases;
+ }
+
+ public CDSFeature(Feature feature)
+ {
+ this.isFwd = feature.isForwardFeature();
+ this.ranges = feature.getLocation().getRanges();
+ this.firstBase = feature.getRawFirstBase();
+ this.lastBase = feature.getRawLastBase();
+ this.bases = feature.getBases();
+ this.feature = feature;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ final String sep = "\t";
+ sb.append(isFwd);
+ sb.append(sep);
+ sb.append(firstBase);
+ sb.append(sep);
+ sb.append(lastBase);
+ sb.append(sep);
+ String b = bases;
+ if (b.length() > 10)
+ b = b.substring(0, 10);
+ sb.append(b);
+ sb.append(sep);
+ sb.append(ranges.size());
+ sb.append(sep);
+ for (int i = 0; i < ranges.size(); i++) {
+ Range r = (Range) ranges.get(i);
+ sb.append(r.getStart()+"-"+r.getEnd());
+ sb.append(sep);
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/FeatureContigPredicate.java b/uk/ac/sanger/artemis/components/variant/FeatureContigPredicate.java
new file mode 100644
index 0000000..a382373
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/FeatureContigPredicate.java
@@ -0,0 +1,48 @@
+/* FeatureContigPredicate
+ *
+ * created: July 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.variant;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeaturePredicate;
+
+public class FeatureContigPredicate implements FeaturePredicate
+{
+ private String contigName;
+ public FeatureContigPredicate(String contigName)
+ {
+ this.contigName = contigName;
+ }
+
+ public boolean testPredicate(Feature feature)
+ {
+ String id = feature.getIDString().trim();
+ if(id.equals(contigName))
+ return true;
+
+ // allow for description in the ID
+ if(id.startsWith(contigName+" "))
+ return true;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/FilteredPanel.java b/uk/ac/sanger/artemis/components/variant/FilteredPanel.java
new file mode 100644
index 0000000..7d9c402
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/FilteredPanel.java
@@ -0,0 +1,197 @@
+/*
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.variant;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.Border;
+
+ class FilteredPanel extends JPanel
+ {
+ private static final long serialVersionUID = 1L;
+ private static Hashtable<String, RecordFilter> filters = new Hashtable<String, RecordFilter>();
+ private static List<HeaderLine> hdrFltLines;
+ private static List<String> hdrFltLinesIDs;
+
+ private Box hdrFilterBox = Box.createVerticalBox();
+ private Box userFilterBox = Box.createVerticalBox();
+
+ FilteredPanel(final List<HeaderLine> hdrFltLines)
+ {
+ FilteredPanel.hdrFltLines = hdrFltLines;
+ setLayout(new BorderLayout());
+ Border raisedbevel = BorderFactory.createRaisedBevelBorder();
+ Border loweredbevel = BorderFactory.createLoweredBevelBorder();
+
+ setBorder(BorderFactory.createCompoundBorder(
+ raisedbevel, loweredbevel));
+ setBackground(Color.white);
+
+ Box titleBox = Box.createHorizontalBox();
+ JLabel filterLabel = new JLabel("Filter Overview:");
+ filterLabel.setFont(filterLabel.getFont().deriveFont(Font.BOLD));
+ titleBox.add(filterLabel);
+ titleBox.add(Box.createHorizontalGlue());
+ add(titleBox, BorderLayout.NORTH);
+
+ Box ftYBox = Box.createVerticalBox();
+ add(ftYBox, BorderLayout.CENTER);
+ ftYBox.add(userFilterBox, BorderLayout.CENTER);
+ ftYBox.add(hdrFilterBox, BorderLayout.SOUTH);
+ ftYBox.add(Box.createVerticalGlue());
+
+ addHeaderFilters();
+ }
+
+ private void addHeaderFilters()
+ {
+ if(hdrFltLines.size() > 0)
+ {
+ JLabel lab = new JLabel("");
+ Dimension d = new Dimension(150, lab.getPreferredSize().height);
+
+ // SHOW CURRENT FILTERS
+ for (int i = 0; i < hdrFltLines.size(); i++)
+ {
+ final Box hdrFilterLine = Box.createHorizontalBox();
+ final HeaderLine line = hdrFltLines.get(i);
+ lab = new JLabel(line.getID());
+ lab.setPreferredSize(d);
+ hdrFilterLine.add(lab);
+
+ JLabel des = new JLabel(line.getDescription());
+ hdrFilterLine.add(des);
+
+ hdrFilterLine.add(Box.createHorizontalStrut(10));
+ JButton remove = new JButton("X");
+ remove.setBackground(Color.red);
+ remove.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ hdrFilterBox.remove(hdrFilterLine);
+ hdrFltLines.remove(line);
+ hdrFltLinesIDs = null;
+ repaint();
+ }
+ });
+ hdrFilterLine.add(remove);
+
+ hdrFilterLine.add(Box.createHorizontalGlue());
+ hdrFilterBox.add(hdrFilterLine);
+ }
+
+ hdrFilterBox.add(Box.createVerticalGlue());
+ }
+ }
+
+ protected void updateFilters()
+ {
+ userFilterBox.removeAll();
+ final Enumeration<String> enumFilter = filters.keys();
+ JLabel lab = new JLabel("");
+ Dimension d = new Dimension(150, lab.getPreferredSize().height);
+ while(enumFilter.hasMoreElements())
+ {
+ String id = enumFilter.nextElement();
+ RecordFilter filter = filters.get(id);
+
+ Box filterLine = Box.createHorizontalBox();
+ lab = new JLabel(id+" ");
+ lab.setPreferredSize(d);
+ filterLine.add(lab);
+
+ filterLine.add(new JLabel(filter.toString()));
+ filterLine.add(Box.createHorizontalGlue());
+ userFilterBox.add(filterLine);
+ }
+ revalidate();
+ repaint();
+ }
+
+ protected static List<HeaderLine> getHeaderLineFilters()
+ {
+ return hdrFltLines;
+ }
+
+ protected static List<String> getHeaderLineFiltersIDs()
+ {
+ if(hdrFltLinesIDs == null)
+ {
+ hdrFltLinesIDs = new Vector<String>();
+ for(HeaderLine ln: hdrFltLines)
+ hdrFltLinesIDs.add(ln.getID());
+ }
+ return hdrFltLinesIDs;
+ }
+
+ protected static String getHeader()
+ {
+ StringBuffer buff = new StringBuffer();
+ for(HeaderLine ln: hdrFltLines)
+ buff.append(ln.toString()+"\n");
+
+ Enumeration<String> filterStr = filters.keys();
+ while(filterStr.hasMoreElements())
+ {
+ RecordFilter recFilter = filters.get(filterStr.nextElement());
+ buff.append("##FILTER=<ID=");
+ if(recFilter.getHeaderLine().getHeaderType() == HeaderLine.FORMAT_LINE)
+ buff.append("sample");
+ buff.append(recFilter.getHeaderLine().getID());
+ buff.append(",Description=\"");
+ buff.append(recFilter.toString());
+ buff.append("\">\n");
+ }
+ return buff.toString();
+ }
+
+ protected void addFilter(final String ID, final HeaderLine hLine, final int number)
+ {
+ filters.put(ID, new RecordFilter(hLine, number));
+ }
+
+ protected void removeFilter(final String ID)
+ {
+ filters.remove(ID);
+ }
+
+ protected static Hashtable<String, RecordFilter> getFilters()
+ {
+ return filters;
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/GraphPanel.java b/uk/ac/sanger/artemis/components/variant/GraphPanel.java
new file mode 100644
index 0000000..a5a746c
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/GraphPanel.java
@@ -0,0 +1,420 @@
+/*
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.variant;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.text.DecimalFormat;
+
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JTextField;
+
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.components.alignment.LineAttributes;
+import uk.ac.sanger.artemis.components.variant.BCFReader.BCFReaderIterator;
+
+public class GraphPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+ protected boolean autoWinSize = true;
+ protected int userWinSize = 1;
+ private static LineAttributes lines[];
+ protected JPopupMenu popup = new JPopupMenu();
+ private VCFview vcfView;
+
+ private static String TYPES[] = { "SNP", "DP", "SIMILARITY" };
+ private int type = 0; // 0 - SNP; 1 - DP; 2 - SIM
+
+ public GraphPanel(final VCFview vcfView)
+ {
+ super();
+ this.vcfView = vcfView;
+ setBackground(Color.white);
+ initPopupMenu(this);
+ }
+
+ /**
+ * Override
+ */
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+
+ float pixPerBase = vcfView.getPixPerBaseByWidth();
+ int sbeg = vcfView.getBaseAtStartOfView();
+ int send = sbeg+vcfView.getBasesInView();
+
+ int windowSize;
+ if(autoWinSize)
+ {
+ windowSize = (vcfView.getBasesInView()/300);
+ userWinSize = windowSize;
+ }
+ else
+ windowSize = userWinSize;
+ if(windowSize < 1)
+ windowSize = 1;
+
+ int nBins = Math.round((send-sbeg+1.f)/windowSize);
+ int max = 0;
+
+ AbstractVCFReader readers[] = vcfView.getVcfReaders();
+ FeatureVector features = vcfView.getCDSFeaturesInRange(sbeg, send);
+
+ if(type == 2) // base similarity
+ {
+ int plot[][] = new int[readers.length][nBins];
+ for (int i = 0; i < readers.length; i++)
+ for (int j = 0; j < nBins; j++)
+ plot[i][j] = 0;
+
+ for(int i=0; i<readers.length; i++)
+ max = countAll(i, readers[i], sbeg, send, windowSize, nBins, features, plot[i], max);
+ lines = getLineAttributes(readers.length);
+
+ for(int i=0; i<readers.length; i++)
+ plot(g2, lines[i].getLineColour(), windowSize, plot[i], pixPerBase, max, i);
+ }
+ else
+ {
+ int plot[] = new int[nBins];
+ for(int i=0; i<plot.length; i++)
+ plot[i] = 0;
+
+ for(int i=0; i<readers.length; i++)
+ max = countAll(i, readers[i], sbeg, send, windowSize, nBins, features, plot, max);
+
+ plot(g2, Color.red, windowSize, plot, pixPerBase, max, -1);
+ }
+
+ DecimalFormat df;
+ final String maxStr;
+ if(type == 2)
+ {
+ if(max == 0)
+ return;
+ df = new DecimalFormat("0.#");
+ maxStr = df.format(100 - ((float)max/(float)windowSize *100.f));
+ }
+ else
+ {
+ df = new DecimalFormat("0.0###");
+ maxStr = TYPES[type] + ": " + df.format((float)max/(float)windowSize);
+ }
+ FontMetrics fm = getFontMetrics(getFont());
+ g2.setColor(Color.black);
+
+ int xpos = getWidth() - fm.stringWidth(maxStr) - 8;
+ g2.drawString(maxStr, xpos,
+ (type == 2 ? getHeight()-fm.getHeight() : fm.getHeight()));
+ }
+
+ protected static LineAttributes[] getLineAttributes(int nsize)
+ {
+ if(lines == null)
+ lines = LineAttributes.init(nsize);
+ else if(lines.length < nsize)
+ {
+ LineAttributes tmpLines[] = LineAttributes.init(nsize);
+ for(int i=0;i<lines.length;i++)
+ tmpLines[i] = lines[i];
+ lines = tmpLines;
+ }
+ return lines;
+ }
+
+ private void plot(Graphics2D g2, Color c, int windowSize, int plot[], float pixPerBase, int max, int vcfIndex)
+ {
+ g2.setColor(c);
+ g2.setStroke(new BasicStroke(1.f));
+
+ if(windowSize == 1 && type != 2)
+ {
+ GeneralPath shape = new GeneralPath();
+ shape.moveTo(0,getHeight());
+
+ for(int i=0; i<plot.length; i++)
+ {
+ float xpos1 = ((i*windowSize) )*pixPerBase;
+ float xpos2 = ((i*windowSize) + windowSize)*pixPerBase;
+ if(xpos2-xpos1 < 1) // never less than a pixel wide
+ xpos2 = xpos1+1;
+
+ shape.lineTo(xpos1,getHeight());
+ shape.lineTo(xpos1,
+ getHeight() - (((float)plot[i]/(float)max)*getHeight()));
+ shape.lineTo(xpos2,
+ getHeight() - (((float)plot[i]/(float)max)*getHeight()));
+
+ shape.lineTo(xpos2,getHeight());
+ }
+
+ shape.lineTo(getWidth(),getHeight());
+ g2.fill(shape);
+ }
+ else
+ {
+ float hgt = getHeight()*.95f;
+ g2.translate(0, (type == 2 ? 2 : hgt));
+
+ for(int i=1; i<plot.length; i++)
+ {
+ int x0 = (int) (((i*windowSize) - windowSize/2.f)*pixPerBase);
+ int x1 = (int) ((((i+1)*windowSize) - windowSize/2.f)*pixPerBase);
+ int y0 = -(int) (((float)plot[i-1]/(float)max)*hgt*.95);
+ int y1 = -(int) (((float)plot[i]/(float)max)*hgt*.95);
+
+ if(type == 2)
+ {
+ y0 = -y0;
+ y1 = -y1;
+ }
+ g2.drawLine(x0, y0, x1, y1);
+ }
+
+ g2.translate(0, (type == 2 ? -2 : -hgt));
+ }
+ }
+
+ private int countAll(int vcfIndex, AbstractVCFReader reader, int sbeg, int send, int windowSize,
+ int nBins, FeatureVector features, int plot[], int max)
+ {
+ if(vcfView.isConcatenate())
+ {
+ String[] contigs = reader.getSeqNames();
+ for(int j=0; j<contigs.length; j++)
+ {
+ int offset = vcfView.getSequenceOffset(contigs[j]);
+ int nextOffset;
+ if(j<contigs.length-1)
+ nextOffset = vcfView.getSequenceOffset(contigs[j+1]);
+ else
+ nextOffset = vcfView.seqLength;
+
+ if( (offset >= sbeg && offset < send) ||
+ (offset < sbeg && sbeg < nextOffset) )
+ {
+ int thisStart = sbeg - offset;
+ if(thisStart < 1)
+ thisStart = 1;
+ int thisEnd = send - offset;
+
+ max = count(vcfIndex, reader, contigs[j], thisStart, thisEnd, windowSize, nBins, features, plot, max);
+ }
+ }
+ return max;
+ }
+ else
+ return count(vcfIndex, reader, vcfView.getChr(), sbeg, send, windowSize, nBins, features, plot, max);
+ }
+
+ private int count(int vcfIndex, AbstractVCFReader reader, String chr, int sbeg, int send, int windowSize,
+ int nBins, FeatureVector features, int plot[], int max)
+ {
+ if(reader instanceof BCFReader)
+ {
+ try
+ {
+ BCFReader bcfReader = (BCFReader)reader;
+ BCFReaderIterator it = bcfReader.query(chr, sbeg, send);
+
+ VCFRecord record;
+ while((record = it.next()) != null)
+ {
+ max = calc(vcfIndex, record, features, reader, windowSize, nBins, plot, max);
+ }
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ else
+ {
+ TabixReader.Iterator iter =
+ ((TabixReader)reader).query(chr+":"+sbeg+"-"+send); // get the iterator
+ if (iter == null)
+ return max;
+ try
+ {
+ String s;
+ while ((s = iter.next()) != null)
+ {
+ VCFRecord record = VCFRecord.parse(s, reader.getNumberOfSamples());
+ max = calc(vcfIndex, record, features, reader, windowSize, nBins, plot, max);
+ }
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return max;
+ }
+
+ private int calc(int vcfIndex, VCFRecord record, FeatureVector features,
+ AbstractVCFReader reader, int windowSize, int nBins, int plot[], int max)
+ {
+ int pos = record.getPos() + vcfView.getSequenceOffset(record.getChrom());
+ if(!vcfView.showVariant(record, features, pos, reader, -1, vcfIndex))
+ return max;
+
+ int bin = (int)((pos-vcfView.getBaseAtStartOfView()) / windowSize);
+ if(bin < 0 || bin > nBins-1)
+ return max;
+
+ switch (type)
+ {
+ case 0:
+ plot[bin]+=1;
+ break;
+ case 1:
+ int dp = 0;
+ try
+ {
+ dp = Integer.parseInt(record.getInfoValue("DP"));
+ }
+ catch(Exception e){ e.printStackTrace(); }
+ plot[bin]+=dp;
+ break;
+ case 2:
+ plot[bin]+=1;
+ break;
+ }
+
+ if(plot[bin] > max)
+ max = plot[bin];
+
+ return max;
+ }
+
+ private void initPopupMenu(final JPanel graphPanel)
+ {
+ final JMenuItem setScale = new JMenuItem("Set the Window Size...");
+ setScale.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ GridBagLayout gridbag = new GridBagLayout();
+ GridBagConstraints c = new GridBagConstraints();
+ JPanel pane = new JPanel(gridbag);
+ final JTextField newWinSize = new JTextField(Integer.toString(userWinSize), 10);
+ final JLabel lab = new JLabel("Window size:");
+ c.gridy = 0;
+ pane.add(lab, c);
+ pane.add(newWinSize, c);
+
+ final JCheckBox autoSet = new JCheckBox("Automatically set window size", false);
+ autoSet.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ lab.setEnabled(!autoSet.isSelected());
+ newWinSize.setEnabled(!autoSet.isSelected());
+ }
+ });
+ c.gridy = 1;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ pane.add(autoSet, c);
+
+ String window_options[] = { "OK", "Cancel" };
+ int select = JOptionPane.showOptionDialog(null, pane, "Window Size",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+ window_options, window_options[0]);
+
+ if (select == 1)
+ return;
+ autoWinSize = autoSet.isSelected();
+ try
+ {
+ userWinSize = Integer.parseInt(newWinSize.getText().trim());
+ }
+ catch (NumberFormatException nfe)
+ {
+ return;
+ }
+ graphPanel.repaint();
+ }
+ });
+ popup.add(setScale);
+
+ addMouseListener(new PopupListener());
+ }
+
+ /**
+ * @param type the type to set
+ */
+ protected void setType(int type)
+ {
+ this.type = type;
+ }
+
+ /**
+ * Popup menu listener
+ */
+ class PopupListener extends MouseAdapter
+ {
+ JMenuItem gotoMateMenuItem;
+ JMenuItem showDetails;
+
+ public void mouseClicked(MouseEvent e)
+ {
+ }
+
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ {
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/HeaderLine.java b/uk/ac/sanger/artemis/components/variant/HeaderLine.java
new file mode 100644
index 0000000..9fd02a5
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/HeaderLine.java
@@ -0,0 +1,146 @@
+/*
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.variant;
+
+import java.util.Hashtable;
+
+
+public class HeaderLine
+{
+ private int headerType;
+ private String ID;
+ private String type;
+ private String description;
+ private int number = 1;
+ private String numberString = null;
+ private String headerTypeStr;
+ private String origLine;
+ private boolean isFlag = false;
+
+ protected static final String[] filterFlagStr = new String[]{
+ "INFO", "FORMAT", "FILTER", "FILTER_QUAL", "FILTER_NV_FLAG",
+ "FILTER_OVERLAP_FLAG", "FILTER_MULTALL_FLAG",
+ "FILTER_INS", "FILTER_DEL",
+ "FILTER_NONSYN", "FILTER_SYN", "FILTER_MANUAL", "FILTER_HOMOZYG"};
+ protected static final int INFO_LINE = 0;
+ protected static final int FORMAT_LINE = 1;
+ protected static final int FILTER_LINE = 2;
+ protected static final int FILTER_QUAL = 3;
+ protected static final int FILTER_NV_FLAG = 4;
+ protected static final int FILTER_OVERLAP_FLAG = 5;
+ protected static final int FILTER_MULTALL_FLAG = 6;
+ protected static final int FILTER_INS = 7;
+ protected static final int FILTER_DEL = 8;
+ protected static final int FILTER_NONSYN = 9;
+ protected static final int FILTER_SYN = 10;
+ protected static final int FILTER_MANUAL = 11;
+ protected static final int FILTER_HOMOZYG = 12;
+
+ public HeaderLine(final String origLine, String headerTypeStr, Hashtable<String, String> lineHash)
+ {
+ this.origLine = origLine;
+ this.ID = lineHash.get("ID");
+ this.type = lineHash.get("Type");
+ this.description = lineHash.get("Description");
+ this.headerTypeStr = headerTypeStr;
+
+ for(int i=0; i<filterFlagStr.length; i++)
+ {
+ if(headerTypeStr.equals(filterFlagStr[i]))
+ headerType = i;
+ }
+
+ if(type !=null && type.equals("Flag"))
+ isFlag = true;
+
+ String numStr = lineHash.get("Number");
+ if (numStr != null)
+ {
+ try
+ {
+ number = Integer.parseInt(numStr);
+ }
+ catch(NumberFormatException e)
+ {
+ // '.' - number of possible values varies, is unknown, or is unbounded
+ // 'A' - one value per alternate allele
+ // 'G' - one value for each possible genotype (more relevant to the FORMAT tags)
+ numberString = numStr;
+ number = 1;
+ }
+ }
+ }
+
+ /**
+ * Type of line : FORMAT, INFO, FILTER
+ * @return
+ */
+ protected int getHeaderType()
+ {
+ return headerType;
+ }
+
+ protected String getHeaderTypeStr()
+ {
+ return headerTypeStr;
+ }
+
+ protected String getID()
+ {
+ return ID;
+ }
+
+ protected String getType()
+ {
+ return type;
+ }
+
+ protected boolean isFlag()
+ {
+ return isFlag;
+ }
+
+ protected int getNumber()
+ {
+ return number;
+ }
+
+ protected String getDescription()
+ {
+ return description;
+ }
+
+ /**
+ *
+ * @return
+ */
+ protected String getNumberString()
+ {
+ return numberString;
+ }
+
+ public String toString()
+ {
+ return origLine;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/IOUtils.java b/uk/ac/sanger/artemis/components/variant/IOUtils.java
new file mode 100644
index 0000000..9d5f2c0
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/IOUtils.java
@@ -0,0 +1,1507 @@
+/* IOUtils
+ *
+ * created: 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.variant;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureEnumeration;
+import uk.ac.sanger.artemis.FeatureKeyQualifierPredicate;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.FeaturePredicateConjunction;
+import uk.ac.sanger.artemis.FeatureSegment;
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.components.FileViewer;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.components.SequenceViewer;
+import uk.ac.sanger.artemis.components.StickyFileChooser;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.EntryInformationException;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.RemoteFileDocument;
+
+import net.sf.samtools.util.BlockCompressedInputStream;
+
+class IOUtils
+{
+
+ private static int MAXIMUM_SELECTED_FEATURES = 25;
+ private static int SEQUENCE_LINE_BASE_COUNT = 60;
+
+ /**
+ * Write filtered uncompressed VCF. Uses the filter in VCFview to
+ * determine if variants are written.
+ * @param manualHash
+ * @param vcfFileName
+ * @param vcfView
+ * @param features
+ * @param nfiles
+ * @return
+ */
+ protected static File writeVCF(final Map<String, Boolean> manualHash,
+ final String vcfFileName,
+ final int vcfIndex,
+ final VCFview vcfView,
+ final FeatureVector features,
+ final int nfiles)
+ {
+ try
+ {
+ File filterFile = getFile(vcfFileName, nfiles, ".filter", null);
+ if(filterFile == null)
+ return null;
+ FileWriter writer = new FileWriter(filterFile);
+ AbstractVCFReader.write(manualHash, vcfFileName, vcfIndex, writer, vcfView, features);
+
+ return filterFile;
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static File getFile(final String vcfFileName, final int nfiles,
+ final String suffix, final JComponent comp) throws IOException
+ {
+ if(nfiles > 1)
+ {
+ if(vcfFileName.startsWith("http"))
+ {
+ int ind = vcfFileName.lastIndexOf('/')+1;
+ return new File(vcfFileName.substring(ind)+suffix);
+ }
+ else
+ return new File(vcfFileName+suffix);
+ }
+
+ final StickyFileChooser file_dialog = new StickyFileChooser();
+ file_dialog.setSelectedFile(new File(vcfFileName+suffix));
+ file_dialog.setDialogTitle("Choose save file ...");
+ file_dialog.setDialogType(JFileChooser.SAVE_DIALOG);
+ if(comp != null)
+ file_dialog.setAccessory(comp);
+
+ final int status = file_dialog.showSaveDialog(null);
+
+ if(status != JFileChooser.APPROVE_OPTION ||
+ file_dialog.getSelectedFile() == null)
+ return null;
+
+ return file_dialog.getSelectedFile();
+ }
+
+ /**
+ * Export as a VCF based on the filtering applied in the VCFview.
+ * @param manualHash
+ * @param vcfFiles
+ * @param vcfView
+ */
+ protected static void export(final Map<String, Boolean> manualHash,
+ final List<String> vcfFiles,
+ final VCFview vcfView)
+ {
+ // get all CDS features that do not have the /pseudo or /pseudogene qualifier
+ final FeatureVector features = getFeatures(
+ new FeaturePredicateConjunction(
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
+ FeaturePredicateConjunction.AND), vcfView.getEntryGroup());
+
+ String filterFiles = "";
+ for(int i=0; i<vcfFiles.size(); i++)
+ {
+ File filterFile = IOUtils.writeVCF(manualHash, vcfFiles.get(i), i, vcfView, features, vcfFiles.size());
+ if(filterFile == null)
+ return;
+ filterFiles += filterFile.getAbsolutePath()+"\n";
+ }
+
+ new MessageDialog (null, "Saved Files", filterFiles, false);
+ }
+
+ /**
+ * Export all variant sites to a multiple fasta file.
+ * @param vcfView
+ */
+ protected static void exportVariantFasta(final VCFview vcfView)
+ {
+ final EntryGroup entryGroup = vcfView.getEntryGroup();
+ final String name = entryGroup.getActiveEntries().elementAt(0).getName();
+ final File newfile = new File(
+ getBaseDirectoryFromEntry(entryGroup.getActiveEntries().elementAt(0)),
+ name);
+
+ try
+ {
+ final File f = getFile(newfile.getAbsolutePath(), 1, ".fasta", null);
+ if(f == null)
+ return;
+
+ final FileOutputStream fos = new FileOutputStream(f);
+ exportVariantFasta(vcfView,
+ new PrintWriter(fos),
+ entryGroup.getSequenceLength(),
+ entryGroup.getAllFeatures(),
+ entryGroup.getBases());
+ fos.close();
+ }
+ catch(IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ }
+
+ /**
+ * Export all variant sites to a multiple fasta file.
+ * @param vcfView
+ * @param pw
+ * @param length
+ * @param features
+ * @param bases
+ */
+ protected static void exportVariantFasta(final VCFview vcfView,
+ final PrintWriter pw,
+ final int length,
+ final FeatureVector features,
+ final Bases bases)
+ {
+ try
+ {
+ int ntotalSamples = 0;
+ for (int i = 0; i < vcfView.getVcfReaders().length; i++)
+ ntotalSamples += vcfView.getVcfReaders()[i].getNumberOfSamples();
+
+ final Writer[] writer = new Writer[ntotalSamples+1];
+ final File[] tmpFiles = new File[ntotalSamples+1];
+
+ for (int i = 0; i < vcfView.getVcfReaders().length; i++)
+ {
+ final String names[] = vcfView.getVcfReaders()[i].sampleNames;
+ for(int j=0; j<names.length; j++)
+ {
+ final String fn = (names[j].equals("") ? (j+1)+"_sample" : names[j].replaceAll("[/\\:]", "_"));
+ tmpFiles[i+j] = File.createTempFile(fn, "art");
+ writer[i+j] = new FileWriter( tmpFiles[i+j] );
+ writer[i+j].write(">"+fn);
+ }
+ }
+
+ // include reference bases
+ final String refName = vcfView.getEntryGroup().getActiveEntries().elementAt(0).getName();
+ tmpFiles[ntotalSamples] = File.createTempFile("ref", "art");
+ writer[ntotalSamples] = new FileWriter( tmpFiles[ntotalSamples] );
+ writer[ntotalSamples].write(">"+refName);
+
+
+ final int MAX_BASE_CHUNK = (10000/ntotalSamples)*SEQUENCE_LINE_BASE_COUNT;
+ final HashMap<Integer, VCFRecord> records[] = new HashMap[MAX_BASE_CHUNK];
+ int baseCount = 0;
+
+ // write variant sites to tmp files
+ for(int i=0; i<length; i+=MAX_BASE_CHUNK)
+ {
+ int start = i+1;
+ int end = i+MAX_BASE_CHUNK;
+ if(end > length)
+ end = length;
+
+ storeVCFRecords(vcfView, records, start, end);
+ baseCount = writeVariants(vcfView, records, writer, features,
+ ntotalSamples, start, end, bases, baseCount);
+ }
+
+ for(int i=0; i<writer.length; i++)
+ writer[i].close();
+
+ // concatenate the single fasta files into a multiple fasta file
+ for (int i = 0; i < tmpFiles.length; i++)
+ {
+ final BufferedReader br = new BufferedReader(new FileReader(tmpFiles[i].getPath()));
+ String line;
+ while ( (line = br.readLine()) != null)
+ pw.println(line);
+ br.close();
+ tmpFiles[i].delete();
+ }
+ pw.close();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * For a given range store the VFFRecord in a Hashtable.
+ * @param vcfView
+ * @param records
+ * @param start
+ * @param end
+ * @throws IOException
+ */
+ private static void storeVCFRecords(final VCFview vcfView,
+ final HashMap<Integer, VCFRecord> records[],
+ final int start,
+ final int end) throws IOException
+ {
+
+ for (int i = 0; i < vcfView.getVcfReaders().length; i++)
+ records[i] = new HashMap<Integer, VCFRecord> ();
+
+ for (int i = 0; i < vcfView.getVcfReaders().length; i++)
+ {
+ AbstractVCFReader reader = vcfView.getVcfReaders()[i];
+ if(vcfView.isConcatenate())
+ {
+ String[] contigs = reader.getSeqNames();
+ for(int j=0; j<contigs.length; j++)
+ {
+ int offset = vcfView.getSequenceOffset(contigs[j]);
+ int nextOffset;
+ if(j<contigs.length-1)
+ nextOffset = vcfView.getSequenceOffset(contigs[j+1]);
+ else
+ nextOffset = vcfView.seqLength;
+
+ if( (start >= offset && start <= nextOffset) ||
+ (end >= offset && end <= nextOffset) )
+ {
+ int thisStart = start - offset;
+ if(thisStart < 1)
+ thisStart = 1;
+ loadRecords(records[i], reader, contigs[j], thisStart, end - offset, offset);
+ }
+ }
+ }
+ else
+ loadRecords(records[i], reader, vcfView.getChr(), start, end, 0);
+ }
+ }
+
+ private static void loadRecords(HashMap<Integer, VCFRecord> records,
+ final AbstractVCFReader reader,
+ final String contig,
+ final int start,
+ final int end,
+ final int offset) throws IOException
+ {
+ VCFRecord record;
+ while((record = reader.getNextRecord(contig, start, end)) != null)
+ {
+ if(records == null)
+ records = new HashMap<Integer, VCFRecord> ();
+ records.put(record.getPos()+offset, record);
+ }
+ }
+
+ /**
+ * Write variant positions.
+ * @param vcfView
+ * @param records
+ * @param writer
+ * @param features
+ * @param ntotalSamples
+ * @param start
+ * @param end
+ * @param bases
+ * @param bc
+ * @return
+ * @throws IOException
+ * @throws OutOfRangeException
+ */
+ private static int writeVariants(final VCFview vcfView,
+ final HashMap<Integer, VCFRecord> records[],
+ final Writer writer[],
+ final FeatureVector features,
+ final int ntotalSamples,
+ final int start,
+ final int end,
+ final Bases bases,
+ int bc) throws IOException, OutOfRangeException
+ {
+ final char basesC[] = bases.getSubSequenceC(new Range(start, end), Bases.FORWARD);
+ for (int i = start; i < end; i++)
+ {
+ int thisSample = 0;
+ final String[] thisBase = new String[ntotalSamples+1];
+
+ boolean seenSNP = false;
+ int insertionLength = 0;
+
+ // loop over each VCF file
+ for (int j = 0; j < vcfView.getVcfReaders().length; j++)
+ {
+ AbstractVCFReader reader = vcfView.getVcfReaders()[j];
+ VCFRecord record = records[j].get(i);
+
+ if(record == null)
+ continue;
+
+ boolean vcf_v4 = reader.isVcf_v4();
+ int nsamples = reader.getNumberOfSamples();
+ // loop over each sample
+ for(int k=0; k<nsamples; k++)
+ {
+ // look at each type of variant
+ if(vcfView.showVariant(record, features, i, reader, k, j) )
+ {
+ if(record.getAlt().isDeletion(vcf_v4))
+ {
+ // note: do not write out if just deletion
+ thisBase[thisSample] = "-";
+
+ /*if( thisBase[ntotalSamples] == null ||
+ thisBase[ntotalSamples].length() < record.getRef().length() )
+ {
+ thisBase[ntotalSamples] = record.getRef();
+ if(!record.getAlt().toString().equals("."))
+ thisBase[thisSample] = record.getAlt().toString();
+ else
+ thisBase[thisSample] = "";
+
+ int padLength = thisBase[ntotalSamples].length() - thisBase[thisSample].length();
+ for(int ipad=0; ipad<padLength; ipad++)
+ thisBase[thisSample] += "-";
+ }*/
+ }
+ else if(record.getAlt().isInsertion(vcf_v4))
+ {
+ String in = record.getAlt().toString();
+ if(in.startsWith("I"))
+ in = in.substring(1);
+ thisBase[thisSample] = in;
+ if(in.length() > insertionLength)
+ insertionLength = in.length();
+ seenSNP = true;
+
+ if( (thisBase[ntotalSamples] == null ||
+ thisBase[ntotalSamples].length() < record.getRef().length()) &&
+ in.toLowerCase().startsWith(record.getRef().toLowerCase()))
+ thisBase[ntotalSamples] = record.getRef();
+ }
+ else if(record.getAlt().isMultiAllele(k))
+ {
+ String base = MultipleAlleleVariant.getIUBCode(record);
+ if(base != null)
+ {
+ thisBase[thisSample] = base;
+ seenSNP = true;
+ }
+ }
+ else if(record.getAlt().isNonVariant())
+ {
+ thisBase[thisSample] = ".";
+ }
+ else
+ {
+ if(record.getAlt().toString().length() == record.getRef().length() )
+ {
+ thisBase[thisSample] = record.getAlt().toString();
+ seenSNP = true;
+
+ if(thisBase[ntotalSamples] == null ||
+ thisBase[ntotalSamples].length() < record.getRef().length())
+ thisBase[ntotalSamples] = record.getRef();
+ }
+ }
+ }
+ else
+ thisBase[thisSample] = "N"; // filtered out
+
+ thisSample++;
+ }
+ }
+
+ if(seenSNP)
+ {
+ // look-up reference base
+ if(thisBase[ntotalSamples] == null)
+ thisBase[ntotalSamples] = String.valueOf( basesC[i-start] );
+
+ int remainder = 0;
+ for(int j=0; j<thisBase.length; j++)
+ {
+ if(thisBase[j] != null)
+ {
+ for(int k=0; k<thisBase[j].length(); k++)
+ {
+ remainder = (bc+k)%SEQUENCE_LINE_BASE_COUNT;
+ if(remainder == 0)
+ writer[j].write(System.getProperty("line.separator"));
+ writer[j].write(thisBase[j].charAt(k));
+ }
+ }
+ else
+ {
+ remainder = bc%SEQUENCE_LINE_BASE_COUNT;
+ if(remainder == 0)
+ writer[j].write(System.getProperty("line.separator"));
+ writer[j].write("N");
+ }
+
+ if(insertionLength > 0)
+ {
+ int ins;
+ if(thisBase[j] != null)
+ ins = insertionLength-thisBase[j].length();
+ else
+ ins = insertionLength-1;
+
+ int rem = remainder+1;
+ for(int k=0; k<ins; k++)
+ {
+ remainder = (rem+k)%SEQUENCE_LINE_BASE_COUNT;
+ if(remainder == 0)
+ writer[j].write(System.getProperty("line.separator"));
+ writer[j].write("-");
+ }
+ }
+ }
+
+ if(insertionLength > 0)
+ bc+=insertionLength;
+ else
+ bc++;
+ }
+ }
+
+ return bc;
+ }
+
+
+ /**
+ * Write out FASTA for a selected base range
+ * @param vcfView
+ * @param selection
+ * @param view
+ */
+ protected static void exportFastaByRange(
+ final VCFview vcfView,
+ final Selection selection,
+ final boolean view,
+ Writer writer)
+ {
+ if(selection.getMarkerRange() == null)
+ {
+ JOptionPane.showMessageDialog(null,
+ "No base range selected.",
+ "Warning", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ AbstractVCFReader vcfReaders[] = vcfView.getVcfReaders();
+ MarkerRange marker = selection.getMarkerRange();
+ Range range = marker.getRawRange();
+ String fastaFiles = "";
+
+ EntryGroup entryGroup = vcfView.getEntryGroup();
+ String name = entryGroup.getActiveEntries().elementAt(0).getName();
+ int sbeg = range.getStart();
+ int send = range.getEnd();
+
+ StringBuffer buffSeq = null;
+ try
+ {
+ final JCheckBox useNs = new JCheckBox("Use N for filtered out sites", true);
+ useNs.setToolTipText("Mask filtered sites.");
+ final JCheckBox useMask = new JCheckBox("Use N for sites without non-variant", true);
+ useMask.setToolTipText("Mask sites that are not confirmed by a non-variant record.");
+ Box yBox = Box.createVerticalBox();
+ yBox.add(useNs);
+ yBox.add(useMask);
+ if(writer == null)
+ JOptionPane.showMessageDialog(null, yBox, "Options", JOptionPane.INFORMATION_MESSAGE);
+ else
+ useMask.setSelected(false);
+
+ if(!view && writer == null)
+ {
+ File newfile = new File(
+ getBaseDirectoryFromEntry(entryGroup.getActiveEntries().elementAt(0)),
+ name);
+
+ File f = getFile(newfile.getAbsolutePath(), 1, ".fasta", null);
+ if(f == null)
+ return;
+ writer = new FileWriter(f);
+ fastaFiles += f.getAbsolutePath()+"\n";
+ }
+ else
+ buffSeq = new StringBuffer();
+
+ Bases bases = entryGroup.getSequenceEntry().getBases();
+ // reference
+ writeOrViewRange(null, -1, sbeg, send, writer, buffSeq,
+ marker, bases, name, vcfView, entryGroup, useNs.isSelected(), useMask.isSelected());
+
+ // vcf sequences
+ for (int i = 0; i < vcfReaders.length; i++)
+ writeOrViewRange(vcfReaders[i], i, sbeg, send, writer, buffSeq,
+ marker, bases, name, vcfView, entryGroup, useNs.isSelected(), useMask.isSelected());
+
+ if(writer != null)
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+
+ if(!view)
+ {
+ if(writer instanceof FileWriter)
+ new MessageDialog (null, "Saved Files", fastaFiles, false);
+ }
+ else
+ {
+ FileViewer viewer = new FileViewer ("Feature base viewer for selected range: " +
+ sbeg+":"+send+(marker.isForwardMarker() ? "" : " reverse"), true, false, true);
+ viewer.getTextPane().setText(buffSeq.toString());
+ }
+ }
+
+ /**
+ * Write the FASTA sequence out for the given features for each of the
+ * VCF/BCF files.
+ * @param vcfView
+ * @param features
+ * @param view
+ */
+ protected static void exportFasta(final VCFview vcfView,
+ final FeatureVector features,
+ final boolean view,
+ Writer writer)
+ {
+ if(features.size () < 1)
+ {
+ JOptionPane.showMessageDialog(null,
+ "No features selected.",
+ "Warning", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ if(view && features.size () > MAXIMUM_SELECTED_FEATURES)
+ new MessageDialog (null,
+ "warning: only viewing the sequences for " +
+ "the first " + MAXIMUM_SELECTED_FEATURES +
+ " selected features");
+
+ String suffix = ".fasta";
+ if(features.size() == 1)
+ suffix = "."+features.elementAt(0).getIDString()+suffix;
+
+ String fastaFiles = "";
+ final AbstractVCFReader vcfReaders[] = vcfView.getVcfReaders();
+
+ final JCheckBox single = new JCheckBox("Single FASTA", true);
+ final JCheckBox combineFeats = new JCheckBox("Combine feature sequences", true);
+ final JCheckBox useNs = new JCheckBox("Use N for filtered out sites", true);
+ useNs.setToolTipText("Mask filtered sites.");
+ final JCheckBox useMask = new JCheckBox("Use N for sites without non-variant", true);
+ useMask.setToolTipText("Mask sites that are not confirmed by a non-variant record.");
+
+ if(writer != null)
+ useMask.setSelected(false);
+
+ Box yBox = Box.createVerticalBox();
+ if(!view && vcfReaders.length > 1)
+ yBox.add(single);
+ yBox.add(combineFeats);
+ yBox.add(useNs);
+ yBox.add(useMask);
+
+ final String name = vcfView.getEntryGroup().getActiveEntries().elementAt(0).getName();
+ try
+ {
+ if(!view && writer == null)
+ {
+ File newfile = new File(
+ getBaseDirectoryFromEntry(vcfView.getEntryGroup().getActiveEntries().elementAt(0)),
+ name);
+ File f = getFile(newfile.getAbsolutePath(), 1, suffix, yBox);
+ if(f == null)
+ return;
+ writer = new FileWriter(f);
+ fastaFiles += f.getAbsolutePath()+"\n";
+ }
+ else if(writer == null)
+ JOptionPane.showMessageDialog(null, yBox, "View Option(s)", JOptionPane.INFORMATION_MESSAGE);
+
+ // reference sequence
+ StringBuffer buff = new StringBuffer();
+ for (int j = 0; j < features.size() && (!view || j < MAXIMUM_SELECTED_FEATURES); j++)
+ {
+ Feature f = features.elementAt(j);
+ buff.append( f.getBases() );
+ if(!combineFeats.isSelected())
+ {
+ writeOrView(null, f, writer, buff, "");
+ buff = new StringBuffer();
+ }
+ }
+ if(combineFeats.isSelected())
+ writeOrView(null, null, writer, buff, name);
+ if(writer != null && !single.isSelected())
+ writer.close();
+
+ //
+ for (int i = 0; i < vcfReaders.length; i++)
+ {
+ if(!view && !single.isSelected())
+ {
+ File f = getFile(vcfReaders[i].getFileName(), vcfReaders.length, suffix, null);
+ writer = new FileWriter(f);
+ fastaFiles += f.getAbsolutePath()+"\n";
+ }
+ buff = new StringBuffer();
+
+ for (int j = 0; j < features.size() && (!view || j < MAXIMUM_SELECTED_FEATURES); j++)
+ {
+ Feature f = features.elementAt(j);
+ FeatureSegmentVector segs = f.getSegments();
+
+ for(int k=0; k<segs.size(); k++)
+ {
+ FeatureSegment seg = segs.elementAt(k);
+ int sbeg = seg.getRawRange().getStart();
+ int send = seg.getRawRange().getEnd();
+ buff.append( getAllBasesInRegion(vcfReaders[i], i, sbeg, send, seg.getBases(),
+ features, vcfView, f.isForwardFeature(), useNs.isSelected(), useMask.isSelected()) );
+ }
+
+ if(!combineFeats.isSelected())
+ {
+ writeOrView(vcfReaders[i], f, writer, buff, "");
+ buff = new StringBuffer();
+ }
+ }
+
+ if(combineFeats.isSelected())
+ writeOrView(vcfReaders[i], null, writer, buff, "");
+
+ if(writer != null && !single.isSelected())
+ writer.close();
+ }
+
+ if(writer != null && single.isSelected())
+ writer.close();
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ if(!view && writer instanceof FileWriter)
+ new MessageDialog (null, "Saved Files", fastaFiles, false);
+ }
+
+ private static StringBuffer getHeader(AbstractVCFReader reader,
+ MarkerRange marker, String seqName,
+ int sbeg, int send)
+ {
+ StringBuffer header = new StringBuffer();
+ if(reader != null)
+ header.append(reader.getName()).append(" ");
+ header.append(seqName).append(" ");
+ header.append(sbeg).append(":").append(send);
+ if(marker != null)
+ header.append((marker.isForwardMarker() ? "" : " reverse"));
+ return header;
+ }
+
+ private static void writeOrViewRange(AbstractVCFReader reader,
+ final int vcfIndex,
+ int sbeg, int send,
+ Writer writer, StringBuffer buffSeq,
+ MarkerRange marker, Bases bases,
+ String name,
+ VCFview vcfView,
+ final EntryGroup entryGroup,
+ final boolean useNs,
+ final boolean useMask) throws IOException, OutOfRangeException
+ {
+ int direction = ( marker.isForwardMarker() ? Bases.FORWARD : Bases.REVERSE);
+ int length = send-sbeg+1;
+ int MAX_BASE_CHUNK = 2000*SEQUENCE_LINE_BASE_COUNT;
+ String basesStr;
+ StringBuffer header = getHeader(reader, marker, name, sbeg, send);
+ int linePos = 0;
+
+ for(int i=0; i<length; i+=MAX_BASE_CHUNK)
+ {
+ int sbegc = sbeg+i;
+ int sendc = sbeg+i+MAX_BASE_CHUNK-1;
+ if(i+MAX_BASE_CHUNK-1 > length)
+ sendc = send;
+
+ int sbegc_raw = sbegc;
+ int sendc_raw = sendc;
+ if(direction == Bases.REVERSE)
+ {
+ sendc = bases.getLength () - sbegc_raw + 1;
+ sbegc = bases.getLength () - sendc_raw + 1;
+ }
+
+ MarkerRange m = new MarkerRange(marker.getStrand(), sbegc, sendc);
+ basesStr = bases.getSubSequence(m.getRange(), direction);
+ FeatureVector features = entryGroup.getFeaturesInRange(m.getRange());
+ //System.out.println((reader == null ? "" : reader.getName())+" "+sbegc+".."+sendc);
+ if(reader != null)
+ basesStr = getAllBasesInRegion(reader, vcfIndex, sbegc_raw, sendc_raw, basesStr,
+ features, vcfView, marker.isForwardMarker(), useNs, useMask);
+ else
+ basesStr = basesStr.toUpperCase();
+
+ linePos = writeOrView(writer, header, basesStr, buffSeq, linePos);
+ header = null;
+ }
+ }
+
+ private static int writeOrView(Writer writer,
+ StringBuffer header,
+ String basesStr,
+ StringBuffer buff,
+ int linePos) throws IOException
+ {
+ if(writer == null) // sequence viewer
+ {
+ if(header != null)
+ buff.append(">").append(header.toString()).append("\n");
+ wrapString(basesStr, buff);
+ }
+ else // write to file
+ return writeSequence(writer, header, basesStr, linePos);
+
+ return 0;
+ }
+
+ /**
+ * Construct a header and write or view the sequence.
+ * @param reader
+ * @param f
+ * @param writer
+ * @param buff
+ * @throws IOException
+ */
+ private static void writeOrView(AbstractVCFReader reader, Feature f,
+ Writer writer, StringBuffer buff, String hdr)
+ throws IOException
+ {
+ StringBuffer header = new StringBuffer(hdr);
+ final String basesStr;
+
+ if(reader != null)
+ {
+ header.append(reader.getName()).append(" ");
+ basesStr = buff.toString();
+ }
+ else
+ basesStr = buff.toString().toUpperCase();
+
+ if(f != null)
+ {
+ header.append(f.getSystematicName()).append(" ");
+ header.append(f.getIDString()).append(" ");
+ final String product = f.getProductString();
+ header.append( (product == null ? "undefined product" : product) );
+ header.append(" ").append(f.getWriteRange());
+ }
+
+ if(writer == null) // sequence viewer
+ {
+ SequenceViewer viewer =
+ new SequenceViewer ("Feature base viewer for feature(s)", false);
+ viewer.setSequence(">"+header.toString(), basesStr);
+ }
+ else // write to file
+ writeSequence(writer, header, basesStr, 0);
+ }
+
+ /**
+ * For a given VCF file change the sequence in a range and return the
+ * base sequence as a string.
+ * @param reader
+ * @param vcfIndex
+ * @param sbeg
+ * @param send
+ * @param basesStr
+ * @param features
+ * @param vcfView
+ * @param isFwd
+ * @param useNs
+ * @param useMask
+ * @return
+ * @throws IOException
+ */
+ private static String getAllBasesInRegion(final AbstractVCFReader reader,
+ final int vcfIndex,
+ final int sbeg,
+ final int send,
+ String basesStr,
+ final FeatureVector features,
+ final VCFview vcfView,
+ final boolean isFwd,
+ final boolean useNs,
+ final boolean useMask) throws IOException
+ {
+ if(vcfView.isConcatenate())
+ {
+ String[] contigs = reader.getSeqNames();
+ for(int j=0; j<contigs.length; j++)
+ {
+ int offset = vcfView.getSequenceOffset(contigs[j]);
+ int nextOffset;
+ if(j<contigs.length-1)
+ nextOffset = vcfView.getSequenceOffset(contigs[j+1]);
+ else
+ nextOffset = vcfView.seqLength;
+
+ if( (offset >= sbeg && offset < send) ||
+ (offset < sbeg && sbeg < nextOffset) )
+ {
+ int thisStart = sbeg - offset;
+ if(thisStart < 1)
+ thisStart = 1;
+ int thisEnd = send - offset;
+ basesStr = getBasesInRegion(reader, vcfIndex, contigs[j], thisStart, thisEnd,
+ basesStr, features, vcfView, isFwd, useNs, useMask);
+ }
+ }
+ }
+ else
+ basesStr = getBasesInRegion(reader, vcfIndex, vcfView.getChr(), sbeg, send,
+ basesStr, features, vcfView, isFwd, useNs, useMask);
+
+ return basesStr;
+ }
+
+ /**
+ * For a given VCF file change the sequence in a range and return the
+ * base sequence as a string.
+ * @param reader
+ * @param vcfIndex
+ * @param chr
+ * @param sbeg
+ * @param send
+ * @param basesStr
+ * @param features
+ * @param vcfView
+ * @param isFwd
+ * @param useNs
+ * @param useMask
+ * @return
+ * @throws IOException
+ */
+ private static String getBasesInRegion(final AbstractVCFReader reader,
+ final int vcfIndex,
+ final String chr,
+ int sbeg,
+ final int send,
+ String basesStr,
+ final FeatureVector features,
+ final VCFview vcfView,
+ final boolean isFwd,
+ final boolean useNs,
+ final boolean useMask) throws IOException
+ {
+ boolean vcf_v4 = reader.isVcf_v4();
+ int len = basesStr.length();
+ int baseNum = sbeg;
+ try
+ {
+ VCFRecord record;
+ while ((record = reader.getNextRecord(chr, sbeg, send)) != null)
+ {
+ //
+ // mask regions with N where there are no records
+ if(useMask && baseNum < record.getPos())
+ basesStr = maskSites(sbeg, record.getPos(), baseNum, isFwd, basesStr);
+ baseNum = record.getPos()+1;
+
+ int basePosition = record.getPos() + vcfView.getSequenceOffset(record.getChrom());
+ if(vcfView.showVariant(record, features, basePosition, reader, -1, vcfIndex) )
+ basesStr = getSeqsVariation(record, basesStr, sbeg, isFwd, vcf_v4);
+ else if(useNs && isSNPorNonVariant(record))
+ {
+ int position = record.getPos()-sbeg;
+ if(!isFwd)
+ position = basesStr.length()-position-1;
+ basesStr = basesStr.substring(0, position) + 'n' +
+ basesStr.substring(position+1);
+ }
+
+ // adjust for insertions
+ if(basesStr.length() > len)
+ {
+ sbeg -= (basesStr.length()-len);
+ len = basesStr.length();
+ }
+ }
+ }
+ catch(NullPointerException e)
+ {
+ System.err.println(chr+":"+sbeg+"-"+send+"\n"+e.getMessage());
+ }
+
+ if(useMask && baseNum-sbeg < len)
+ basesStr = maskSites(sbeg, len+sbeg, baseNum, isFwd, basesStr);
+
+ return basesStr;
+ }
+
+ /**
+ * Mask sequence sites
+ * @param sbeg
+ * @param endMask
+ * @param baseNum
+ * @param isFwd
+ * @param basesStr
+ * @return
+ */
+ private static String maskSites(final int sbeg, final int endMask,
+ final int baseNum, final boolean isFwd, String basesStr)
+ {
+ for(int i=baseNum; i<endMask; i++)
+ {
+ int position = i-sbeg;
+ if(!isFwd)
+ position = basesStr.length()-position-1;
+ basesStr = basesStr.substring(0, position) + 'n' +
+ basesStr.substring(position+1);
+ }
+ return basesStr;
+ }
+
+
+ protected static void countVariants(final VCFview vcfView,
+ final FeatureVector features) throws IOException
+ {
+ if(features.size () < 1)
+ {
+ JOptionPane.showMessageDialog(null,
+ "No features selected.",
+ "Warning", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ String[] columnNames = {
+ "VCF", "Name", "Variant", "Non-variant", "Deletion", "Insertion", "Synonymous", "Non-synonymous"};
+ Vector<String> columnData = new Vector<String>();
+ for(String col: columnNames)
+ columnData.add(col);
+ Vector<Vector<Object>> rowData = new Vector<Vector<Object>>();
+
+ AbstractVCFReader vcfReaders[] = vcfView.getVcfReaders();
+ for(int vcfIndex=0; vcfIndex<vcfReaders.length; vcfIndex++)
+ {
+ AbstractVCFReader reader = vcfReaders[vcfIndex];
+ for (int j = 0; j < features.size(); j++)
+ {
+ int count[] = new int[6];
+ for(int c: count)
+ c = 0;
+
+ Feature f = features.elementAt(j);
+ FeatureSegmentVector segs = f.getSegments();
+
+ for(int k=0; k<segs.size(); k++)
+ {
+ FeatureSegment seg = segs.elementAt(k);
+ int sbeg = seg.getRawRange().getStart();
+ int send = seg.getRawRange().getEnd();
+
+ if(vcfView.isConcatenate())
+ {
+ String[] contigs = reader.getSeqNames();
+ for(int i=0; i<contigs.length; i++)
+ {
+ int offset = vcfView.getSequenceOffset(contigs[i]);
+ int nextOffset;
+ if(i<contigs.length-1)
+ nextOffset = vcfView.getSequenceOffset(contigs[i+1]);
+ else
+ nextOffset = vcfView.seqLength;
+
+ if( (offset >= sbeg && offset < send) ||
+ (offset < sbeg && sbeg < nextOffset) )
+ {
+ int thisStart = sbeg - offset;
+ if(thisStart < 1)
+ thisStart = 1;
+ int thisEnd = send - offset;
+
+ VCFRecord record;
+ while ((record = reader.getNextRecord(vcfView.getChr(), thisStart, thisEnd)) != null)
+ count(record, count, features, reader, vcfIndex, vcfView);
+ }
+ }
+ }
+ else
+ {
+ VCFRecord record;
+ while ((record = reader.getNextRecord(vcfView.getChr(), sbeg, send)) != null)
+ count(record, count, features, reader, vcfIndex, vcfView);
+ }
+ }
+
+ Object row[] = {
+ reader.getName(), f.getSystematicName(), count[0], count[1], count[2], count[3], count[4], count[5] };
+
+ Vector<Object> thisRow = new Vector<Object>();
+ for(Object obj: row)
+ thisRow.add(obj);
+ rowData.add(thisRow);
+ }
+ }
+
+ TableViewer tab = new TableViewer(rowData, columnData, "Variant Overview");
+ for(int i=2; i< columnData.size(); i++)
+ tab.setIntegerRowSorter(i);
+ }
+
+ private static void count(VCFRecord record, int count[], FeatureVector features, AbstractVCFReader reader, int vcfIndex, VCFview vcfView)
+ {
+ int basePosition = record.getPos() + vcfView.getSequenceOffset(record.getChrom());
+ if(!vcfView.showVariant(record, features, basePosition, reader, -1, vcfIndex) )
+ return;
+
+ if(record.getAlt().isNonVariant())
+ {
+ count[1]++;
+ return;
+ }
+ else
+ count[0]++;
+
+ if(record.getAlt().isDeletion(reader.isVcf_v4()))
+ count[2]++;
+ else if(record.getAlt().isInsertion(reader.isVcf_v4()))
+ count[3]++;
+
+ if(record.getAlt().length() == 1 && record.getRef().length() == 1)
+ {
+ short synFlag = record.getSynFlag(features, record.getPos());
+ switch(synFlag)
+ {
+ case 1: count[4]++; break; // synonymous
+ default: count[5]++; break; // non-synonymous
+ }
+ }
+ }
+
+ private static boolean isSNPorNonVariant(VCFRecord record)
+ {
+ return (record.getRef().length() == 1 && record.getAlt().length() == 1) || record.getAlt().isNonVariant();
+ }
+
+ protected static void wrapString(String bases, StringBuffer buff)
+ {
+ final int SEQUENCE_LINE_BASE_COUNT = 60;
+ for(int k=0; k<bases.length(); k+=SEQUENCE_LINE_BASE_COUNT)
+ {
+ int end = k + SEQUENCE_LINE_BASE_COUNT;
+ if(end > bases.length())
+ end = bases.length();
+ buff.append ( bases.substring(k,end) ).append("\n");
+ }
+ }
+
+ private static int writeSequence(Writer writer,
+ StringBuffer header,
+ String bases,
+ int startPos) throws IOException
+ {
+ if(header != null)
+ writer.write (">" + header.toString() + "\n");
+ int k = 0;
+ for(k=0; k<bases.length(); k+=SEQUENCE_LINE_BASE_COUNT)
+ {
+ int end = k + SEQUENCE_LINE_BASE_COUNT - startPos;
+ if(end > bases.length())
+ end = bases.length();
+ writer.write ( bases.substring(k,end) );
+
+ if(k < bases.length() -1)
+ writer.write("\n");
+
+ startPos = 0;
+ }
+
+ return k % SEQUENCE_LINE_BASE_COUNT;
+ }
+
+ /**
+ * Change the bases to reflect a variation record.
+ * @param vcfRecord
+ * @param bases
+ * @param sbeg
+ * @param isFwd
+ * @param vcf_v4
+ * @return
+ */
+ private static String getSeqsVariation(VCFRecord vcfRecord,
+ String bases, int sbeg, boolean isFwd, boolean vcf_v4)
+ {
+ int position = vcfRecord.getPos()-sbeg;
+ if(!isFwd)
+ position = bases.length()-position-1;
+
+ if(position > bases.length())
+ return bases;
+ else if(position < 0)
+ return bases;
+
+ if(position < bases.length()-1 && bases.charAt(position) == '-')
+ return bases;
+
+ StringBuffer buff = new StringBuffer();
+ buff.append(bases.substring(0,position));
+
+ if(vcfRecord.getAlt().isDeletion(vcf_v4))
+ {
+ int ndel = vcfRecord.getAlt().getNumberOfIndels(vcf_v4);
+ if(isFwd &&
+ !vcfRecord.getAlt().toString().equals(".") &&
+ !vcfRecord.getAlt().toString().startsWith("D"))
+ {
+ buff.append(getBase(vcfRecord.getAlt().toString(), isFwd));
+ position+=vcfRecord.getAlt().toString().length();
+ }
+
+ if(isFwd)
+ position+=ndel-1;
+ else
+ {
+ if(position-ndel+1 < 0)
+ buff.delete(0, position);
+ else
+ buff.delete(position-ndel+1, position);
+ }
+
+ for(int i=0; i<ndel; i++)
+ buff.append("-");
+ }
+ else if(vcfRecord.getAlt().isInsertion(vcf_v4))
+ {
+ if(!isFwd)
+ buff.delete(position-vcfRecord.getRef().length()+1, position);
+
+ String in = vcfRecord.getAlt().toString();
+ if(in.startsWith("I"))
+ in = in.substring(1);
+ buff.append(getBase(in, isFwd));
+
+ if(isFwd)
+ position+=(vcfRecord.getRef().toString().length()-1);
+ }
+ else if(vcfRecord.getAlt().isMultiAllele(-1))
+ {
+ String base = MultipleAlleleVariant.getIUBCode(vcfRecord);
+ if(base != null)
+ buff.append(base);
+ else
+ buff.append(bases.charAt(position));
+ }
+ else if(vcfRecord.getAlt().isNonVariant()) // non-variant
+ buff.append(getBase(vcfRecord.getRef(), isFwd).toUpperCase());
+ else
+ buff.append(getBase(vcfRecord.getAlt().toString().toLowerCase(), isFwd));
+
+ if(isFwd && position < bases.length())
+ buff.append(bases.substring(position+1));
+ else if(!isFwd && position < bases.length())
+ buff.append(bases.substring(position+1));
+
+ return buff.toString();
+ }
+
+ /**
+ * Get the actual bases by reverse complementing if on the
+ * reverse strand.
+ * @param baseStr
+ * @param isFwd
+ * @return
+ */
+ private static String getBase(String baseStr, boolean isFwd)
+ {
+ if(isFwd)
+ return baseStr;
+ return Bases.reverseComplement(baseStr);
+ }
+
+ /**
+ * Get all features in an entry group.
+ * @param predicate
+ * @param entryGroup
+ * @return
+ */
+ private static FeatureVector getFeatures(FeaturePredicate predicate, EntryGroup entryGroup)
+ {
+ final FeatureVector features = new FeatureVector ();
+ final FeatureEnumeration feature_enum = entryGroup.features ();
+ while (feature_enum.hasMoreFeatures ())
+ {
+ final Feature current_feature = feature_enum.nextFeature ();
+ if (predicate.testPredicate (current_feature))
+ features.add (current_feature);
+ }
+ return features;
+ }
+
+ /**
+ * Create features for each variant that has not been filtered out.
+ * @param vcfView
+ * @param entryGroup
+ */
+ protected static void createFeatures(final VCFview vcfView,
+ final EntryGroup entryGroup)
+ {
+ final Entry newEntry = entryGroup.createEntry("VCF");
+
+ int sbeg = 1;
+ int send = entryGroup.getSequenceLength();
+ int MAX_BASE_CHUNK = 1000 * SEQUENCE_LINE_BASE_COUNT;
+
+ Bases bases = entryGroup.getSequenceEntry().getBases();
+
+ for (int i = 0; i < send; i += MAX_BASE_CHUNK)
+ {
+ int sbegc = sbeg + i;
+ int sendc = sbeg + i + MAX_BASE_CHUNK - 1;
+ if (i + MAX_BASE_CHUNK - 1 > send)
+ sendc = send;
+
+ try
+ {
+ Range range = new Range(sbegc, sendc);
+ FeatureVector features = entryGroup.getFeaturesInRange(range);
+ String chr = vcfView.getChr();
+ AbstractVCFReader vcfReaders[] = vcfView.getVcfReaders();
+ for(int vcfIndex=0; vcfIndex<vcfReaders.length; vcfIndex++)
+ {
+ AbstractVCFReader reader = vcfReaders[vcfIndex];
+ if(vcfView.isConcatenate())
+ {
+ for(String contig: reader.getSeqNames())
+ makeFeatures(reader, vcfIndex, contig, sbegc, sendc, features, vcfView, bases, newEntry);
+ }
+ else
+ makeFeatures(reader, vcfIndex, chr, sbegc, sendc, features, vcfView, bases, newEntry);
+ }
+ }
+ catch (IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static void makeFeatures(
+ final AbstractVCFReader reader,
+ final int vcfIndex,
+ final String chr,
+ final int sbegc,
+ final int sendc,
+ final FeatureVector features,
+ final VCFview vcfView,
+ final Bases bases,
+ final Entry entry) throws IOException, OutOfRangeException
+ {
+ Key variantKey = new Key("misc_difference");
+ try
+ {
+ VCFRecord record;
+ while( (record = reader.getNextRecord(chr, sbegc, sendc)) != null)
+ {
+ makeFeature(record, reader.getName(), vcfView, features, bases, entry, variantKey, reader, vcfIndex);
+ }
+ }
+ catch (NullPointerException e)
+ {
+ System.err.println(chr + ":" + sbegc + "-" + sendc + "\n"
+ + e.getMessage());
+ }
+ }
+
+ private static void makeFeature(
+ final VCFRecord record,
+ final String vcfFileName,
+ final VCFview vcfView,
+ final FeatureVector features,
+ final Bases bases,
+ final Entry entry,
+ final Key variantKey,
+ final AbstractVCFReader vcfReader,
+ final int vcfIndex) throws OutOfRangeException, ReadOnlyException
+ {
+ int basePosition = record.getPos() + vcfView.getSequenceOffset(record.getChrom());
+ if (vcfView.showVariant(record, features, basePosition, vcfReader, -1, vcfIndex))
+ {
+ MarkerRange marker = new MarkerRange(bases.getForwardStrand(),
+ basePosition, basePosition);
+ Location location = marker.createLocation();
+ QualifierVector qualifiers = new QualifierVector();
+ String qualifierStr = record.getRef()+"->"+record.getAlt().toString()+
+ "; "+vcfFileName+"; score="+record.getQuality();
+ if(record.getAlt().isMultiAllele(-1))
+ qualifierStr += "; MULTI-ALLELE";
+ else if(record.getAlt().isDeletion(vcfReader.isVcf_v4()))
+ qualifierStr += "; DELETION";
+ else if(record.getAlt().isInsertion(vcfReader.isVcf_v4()))
+ qualifierStr += "; INSERTION";
+ else if(record.getAlt().isNonVariant())
+ return;
+
+ try
+ {
+ FeatureVector fs = entry.getFeaturesInRange(marker.getRange());
+ if(fs.size() > 0)
+ {
+ for(int i=0; i<fs.size(); i++)
+ {
+ Feature f = fs.elementAt(i);
+ if(f.getKey().compareTo(variantKey) == 0)
+ {
+ f.getQualifiers().addQualifierValues(
+ new Qualifier("note", qualifierStr));
+ return;
+ }
+ }
+ }
+
+ qualifiers.addQualifierValues(new Qualifier("note", qualifierStr));
+ entry.createFeature(variantKey, location, qualifiers);
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Test if this is a BCF file.
+ * @param fileName
+ * @return
+ * @throws IOException
+ */
+ protected static boolean isBCF(String fileName) throws IOException
+ {
+ InputStream ins;
+ if(fileName.startsWith("http:") || fileName.startsWith("ftp:"))
+ {
+ final URL urlBamIndexFile = new URL(fileName);
+ ins = urlBamIndexFile.openStream();
+ }
+ else
+ ins = new FileInputStream(fileName);
+ BlockCompressedInputStream is = new BlockCompressedInputStream(ins);
+ byte[] magic = new byte[4];
+ is.read(magic);
+ ins.close();
+ is.close();
+ String line = new String(magic);
+ if(line.equals("BCF\4"))
+ return true;
+ return false;
+ }
+
+ /**
+ * Return the dirtectory that the given entry was read from.
+ **/
+ private static File getBaseDirectoryFromEntry(final Entry entry)
+ {
+ final uk.ac.sanger.artemis.io.Entry embl_entry = entry.getEMBLEntry();
+
+ if(embl_entry instanceof DocumentEntry)
+ {
+ final DocumentEntry document_entry =(DocumentEntry) embl_entry;
+
+ if(document_entry.getDocument() instanceof FileDocument)
+ {
+ final FileDocument file_document =
+ (FileDocument) document_entry.getDocument();
+
+ if(file_document.getFile().getParent() != null)
+ return new File(file_document.getFile().getParent());
+ }
+ }
+ if(((DocumentEntry)entry.getEMBLEntry()).getDocument()
+ instanceof RemoteFileDocument ||
+ ((DocumentEntry)entry.getEMBLEntry()).getDocument()
+ instanceof DatabaseDocument)
+ return new File(System.getProperty("user.dir"));
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/MultipleAlleleVariant.java b/uk/ac/sanger/artemis/components/variant/MultipleAlleleVariant.java
new file mode 100644
index 0000000..3ade2ea
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/MultipleAlleleVariant.java
@@ -0,0 +1,116 @@
+/* MultipleAlleleVariant
+ *
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.variant;
+
+public class MultipleAlleleVariant
+{
+ private static String IUB_2[][] =
+ {
+ {"m", "a", "c"},
+ {"r", "a", "g"},
+ {"w", "a", "t"},
+ {"w", "a", "u"},
+ {"s", "c", "g"},
+ {"y", "c", "t"},
+ {"y", "c", "u"},
+ {"k", "g", "t"},
+ {"k", "g", "u"}
+ };
+
+ private static String IUB_3[][] =
+ {
+ {"b", "c", "g", "t"},
+ {"b", "c", "g", "u"},
+ {"d", "a", "g", "t"},
+ {"d", "a", "g", "u"},
+ {"h", "a", "c", "t"},
+ {"h", "a", "c", "u"},
+ {"v", "a", "c", "g"}
+ };
+
+ /**
+ * M (A or C) B (C, G or T/U)
+ * R (A or G) D (A, G or T/U)
+ * W (A or T/U) H (A, C or T/U)
+ * S (C or G) V (A, C or G)
+ * Y (C or T/U)
+ * K (G or T/U)
+ * @param base
+ * @return
+ */
+ protected static String getIUBCode(VCFRecord record)
+ {
+ String alt = record.getAlt().toString();
+ String alleles[] = alt.toLowerCase().split(",");
+ String pl;
+ if ((pl = record.getFormatValueForSample("PL", 0)) != null && pl.split(",").length == 3 &&
+ pl.split(",")[1].equals("0"))
+ {
+ // include ref
+ String[] temp = new String[alleles.length+1];
+ System.arraycopy(alleles, 0, temp, 0, alleles.length);
+ alleles = temp;
+ alleles[alleles.length-1] = record.getRef().toLowerCase();
+ }
+
+ boolean isSNP = true;
+ for(int i=0; i<alleles.length; i++)
+ {
+ alleles[i] = alleles[i];
+ if(alleles[i].length() > 1)
+ isSNP = false;
+ }
+
+ if(isSNP && alleles.length == 2)
+ {
+ for(int i=0; i<IUB_2.length; i++)
+ {
+ if( (IUB_2[i][1].equals(alleles[0]) && IUB_2[i][2].equals(alleles[1])) ||
+ (IUB_2[i][1].equals(alleles[1]) && IUB_2[i][2].equals(alleles[0])) )
+ return IUB_2[i][0];
+ }
+ }
+
+ if(isSNP && alleles.length == 3)
+ {
+ //System.out.println(record.toString());
+ for(int i=0; i<IUB_3.length; i++)
+ {
+ if( (IUB_3[i][1].equals(alleles[0]) && IUB_3[i][2].equals(alleles[1]) && IUB_3[i][3].equals(alleles[2])) ||
+ (IUB_3[i][1].equals(alleles[1]) && IUB_3[i][2].equals(alleles[0]) && IUB_3[i][3].equals(alleles[2])) ||
+ (IUB_3[i][1].equals(alleles[2]) && IUB_3[i][2].equals(alleles[0]) && IUB_3[i][3].equals(alleles[1])) ||
+ (IUB_3[i][1].equals(alleles[2]) && IUB_3[i][2].equals(alleles[1]) && IUB_3[i][3].equals(alleles[0])) ||
+ (IUB_3[i][1].equals(alleles[0]) && IUB_3[i][2].equals(alleles[2]) && IUB_3[i][3].equals(alleles[1])) ||
+ (IUB_3[i][1].equals(alleles[1]) && IUB_3[i][2].equals(alleles[2]) && IUB_3[i][3].equals(alleles[0])))
+ return IUB_3[i][0];
+ }
+ }
+
+ if(isSNP)
+ return "n";
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/PrintVCFview.java b/uk/ac/sanger/artemis/components/variant/PrintVCFview.java
new file mode 100644
index 0000000..73c1def
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/PrintVCFview.java
@@ -0,0 +1,299 @@
+/* PrintVCFview
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.variant;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.*;
+
+import uk.ac.sanger.artemis.editor.ScrollPanel;
+
+/**
+* Use to print images from VCFview
+*/
+public class PrintVCFview extends ScrollPanel implements Printable
+{
+ private static final long serialVersionUID = 1L;
+ private VCFview vcfView;
+
+ public PrintVCFview(VCFview vcfView)
+ {
+ super();
+ setBackground(Color.white);
+ this.vcfView = vcfView;
+ }
+
+ /**
+ *
+ * Override paintComponent to draw entry
+ *
+ */
+ public void paintComponent(Graphics g)
+ {
+// let UI delegate paint first (incl. background filling)
+ super.paintComponent(g);
+ vcfView.paintComponent(g);
+ }
+
+
+ /**
+ *
+ * Set the size of the image
+ *
+ */
+ private void setImageSize()
+ {
+ setPreferredSize(vcfView.getPreferredSize());
+ }
+
+ /**
+ *
+ * Display a print preview page
+ *
+ */
+ protected void printPreview()
+ {
+ final JFrame f = new JFrame("Print Preview");
+ JPanel jpane = (JPanel)f.getContentPane();
+
+ JScrollPane scrollPane = new JScrollPane(this);
+
+ jpane.setLayout(new BorderLayout());
+ jpane.add(scrollPane,BorderLayout.CENTER);
+
+ final Dimension dScreen = f.getToolkit().getScreenSize();
+ Dimension d = new Dimension((int)(3*dScreen.getWidth()/4),
+ (int)(dScreen.getHeight()/2));
+ f.setSize(d);
+ setImageSize();
+
+ JMenuBar menuBar = new JMenuBar();
+ JMenu filemenu = new JMenu("File");
+ menuBar.add(filemenu);
+
+// print png/jpeg
+ JMenuItem printImage = new JMenuItem("Save As Image Files (png/jpeg)...");
+ printImage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ print();
+ }
+ });
+ filemenu.add(printImage);
+
+// print PostScript
+ JMenuItem printPS = new JMenuItem("Print...");
+ printPS.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ doPrintActions();
+ }
+ });
+ filemenu.add(printPS);
+
+// close
+ filemenu.add(new JSeparator());
+ JMenuItem menuClose = new JMenuItem("Close");
+ menuClose.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+
+ filemenu.add(menuClose);
+ menuClose.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+
+ f.setJMenuBar(menuBar);
+ f.setVisible(true);
+ }
+
+ /**
+ *
+ * Print to a jpeg or png file
+ *
+ */
+ public void print()
+ {
+ // file chooser
+ String cwd = System.getProperty("user.dir");
+ JFileChooser fc = new JFileChooser(cwd);
+ File fselect = new File(cwd+
+ System.getProperty("file.separator")+
+ "vcfView.png");
+ fc.setSelectedFile(fselect);
+
+ // file name prefix
+ Box YBox = Box.createVerticalBox();
+ JLabel labFormat = new JLabel("Select Format:");
+ Font font = labFormat.getFont();
+ labFormat.setFont(font.deriveFont(Font.BOLD));
+ YBox.add(labFormat);
+
+ Box bacross = Box.createHorizontalBox();
+ JComboBox formatSelect =
+ new JComboBox(javax.imageio.ImageIO.getWriterFormatNames());
+ formatSelect.setSelectedItem("png");
+
+ Dimension d = formatSelect.getPreferredSize();
+ formatSelect.setMaximumSize(d);
+ bacross.add(Box.createHorizontalGlue());
+ bacross.add(formatSelect);
+ YBox.add(bacross);
+
+
+ // file prefix & format options
+ fc.setAccessory(YBox);
+ int n = fc.showSaveDialog(null);
+ if(n == JFileChooser.CANCEL_OPTION)
+ return;
+
+ // remove file extension
+ String fsave = fc.getSelectedFile().getAbsolutePath().toLowerCase();
+ if(fsave.endsWith(".png") ||
+ fsave.endsWith(".jpg") ||
+ fsave.endsWith(".jpeg") )
+ {
+ int ind = fsave.lastIndexOf(".");
+ fsave = fc.getSelectedFile().getAbsolutePath();
+ fsave = fsave.substring(0,ind);
+ }
+ else
+ fsave = fc.getSelectedFile().getAbsolutePath();
+
+ // image type
+ String ftype = (String)formatSelect.getSelectedItem();
+ try
+ {
+ RenderedImage rendImage = createImage();
+ writeImageToFile(rendImage, new File(fsave+"."+ftype),
+ ftype);
+ }
+ catch(NoClassDefFoundError ex)
+ {
+ JOptionPane.showMessageDialog(this,
+ "This option requires Java 1.4 or higher.");
+ }
+ }
+
+ protected void doPrintActions()
+ {
+ final PrinterJob pj=PrinterJob.getPrinterJob();
+ pj.setPrintable(PrintVCFview.this);
+ pj.printDialog();
+ try
+ {
+ pj.print();
+ }
+ catch (Exception PrintException) {}
+ }
+
+ /**
+ * Returns a generated image
+ * @param pageIndex page number
+ * @return image
+ */
+ private RenderedImage createImage()
+ {
+ setImageSize();
+ // Create a buffered image in which to draw
+ BufferedImage bufferedImage = new BufferedImage(
+ vcfView.getWidth(), vcfView.getHeight(),
+ BufferedImage.TYPE_INT_RGB);
+ // Create a graphics contents on the buffered image
+ Graphics2D g2d = bufferedImage.createGraphics();
+ paintComponent(g2d);
+
+ return bufferedImage;
+ }
+
+
+ /**
+ * Write out the image
+ * @param image image
+ * @param file file to write image to
+ * @param type type of image
+ */
+ private void writeImageToFile(RenderedImage image,
+ File file, String type)
+ {
+ try
+ {
+ javax.imageio.ImageIO.write(image,type,file);
+ }
+ catch ( IOException e )
+ {
+ System.out.println("Java 1.4+ is required");
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ *
+ * The method @print@ must be implemented for @Printable@ interface.
+ * Parameters are supplied by system.
+ *
+ */
+ public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException
+ {
+ setImageSize();
+ Graphics2D g2 = (Graphics2D) g;
+
+// RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);
+ Dimension d = this.getSize(); //get size of document
+ double panelWidth = d.width; //width in pixels
+ double panelHeight = d.height; //height in pixels
+
+ if(panelWidth == 0)
+ {
+ d = this.getPreferredSize();
+ panelWidth = d.width;
+ panelHeight = d.height;
+ }
+ double pageHeight = pf.getImageableHeight(); //height of printer page
+ double pageWidth = pf.getImageableWidth(); //width of printer page
+ double scale = pageWidth/panelWidth;
+ int totalNumPages = (int)Math.ceil(scale * panelHeight / pageHeight);
+ // Make sure not print empty pages
+ if(pageIndex >= totalNumPages)
+ return Printable.NO_SUCH_PAGE;
+
+ // Shift Graphic to line up with beginning of print-imageable region
+ g2.translate(pf.getImageableX(), pf.getImageableY());
+ // Shift Graphic to line up with beginning of next page to print
+ g2.translate(0f, -pageIndex*pageHeight);
+ // Scale the page so the width fits...
+ g2.scale(scale, scale);
+ paintComponent(g2);
+ return Printable.PAGE_EXISTS;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/variant/RecordFilter.java b/uk/ac/sanger/artemis/components/variant/RecordFilter.java
new file mode 100644
index 0000000..9433c5d
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/RecordFilter.java
@@ -0,0 +1,187 @@
+/*
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.variant;
+
+ public class RecordFilter
+ {
+ /** associated header line */
+ private HeaderLine hLine;
+ /** number of possible values */
+ private int NUMBER;
+ /** min/max filter values (for integer) */
+ protected int minIVal[];
+ protected int maxIVal[];
+
+ /** min/max filter values (for float) */
+ protected float minFVal[];
+ protected float maxFVal[];
+
+ RecordFilter(HeaderLine hLine, int NUMBER)
+ {
+ this.hLine = hLine;
+ this.NUMBER = NUMBER;
+
+ if (hLine.getType().equals("Integer"))
+ {
+ minIVal = new int[NUMBER];
+ maxIVal = new int[NUMBER];
+
+ for(int i=0; i<NUMBER; i++)
+ {
+ minIVal[i] = Integer.MIN_VALUE;
+ maxIVal[i] = Integer.MAX_VALUE;
+ }
+ }
+ else if(hLine.getType().equals("Float"))
+ {
+ minFVal = new float[NUMBER];
+ maxFVal = new float[NUMBER];
+
+ for(int i=0; i<NUMBER; i++)
+ {
+ minFVal[i] = Float.MIN_VALUE;
+ maxFVal[i] = Float.MAX_VALUE;
+ }
+ }
+ }
+
+ protected boolean pass(final VCFRecord record, final String valStr[], final AbstractVCFReader vcfReader)
+ {
+ String numStr = hLine.getNumberString();
+ if(numStr == null)
+ return pass(valStr);
+
+ // numStr - if this is not a number it can be:
+ // '.' - number of possible values varies, is unknown, or is unbounded
+ // 'A' - one value per alternate allele
+ // 'G' - one value for each possible genotype
+
+/* int nvals = 0;
+ if (numStr.equals("A"))
+ nvals = record.getAlt().getNumAlleles();
+ else if(numStr.equals("G"))
+ nvals = vcfReader.getNumberOfSamples();
+ else
+ nvals = valStr.length;*/
+
+ for (int i = 0; i < valStr.length; i++)
+ {
+ try
+ {
+ if (hLine.getType().equals("Integer"))
+ {
+ int val = Integer.parseInt(valStr[i]);
+ if (val < minIVal[0] || val > maxIVal[0])
+ return false;
+ }
+ else if (hLine.getType().equals("Float"))
+ {
+ float val = Float.parseFloat(valStr[i]);
+ if (val < minFVal[0] || val > maxFVal[0])
+ return false;
+ }
+ }
+ catch(NumberFormatException nfe)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * For a fixed number of values check the min and max
+ * values.
+ * @param valStr
+ * @return
+ */
+ private boolean pass(final String valStr[])
+ {
+ for (int i = 0; i < NUMBER; i++)
+ {
+ if (hLine.getType().equals("Integer"))
+ {
+ int val = Integer.parseInt(valStr[i]);
+ if (val < minIVal[i] || val > maxIVal[i])
+ return false;
+ }
+ else if (hLine.getType().equals("Float"))
+ {
+ float val = Float.parseFloat(valStr[i]);
+ if (val < minFVal[i] || val > maxFVal[i])
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected HeaderLine getHeaderLine()
+ {
+ return hLine;
+ }
+
+ public String toString()
+ {
+ if(hLine.isFlag())
+ return hLine.getDescription();
+
+ StringBuffer buff = new StringBuffer(hLine.getHeaderTypeStr());
+ buff.append(": ");
+ buff.append(hLine.getDescription());
+ buff.append(", ");
+
+ final String id = (hLine.getHeaderType() == HeaderLine.FORMAT_LINE ? "sample" : "") +
+ hLine.getID();
+
+ for(int i=0; i<NUMBER; i++)
+ {
+ if(i > 0)
+ buff.append(" : ");
+ if (hLine.getType().equals("Integer"))
+ {
+ if(minIVal[i] > Integer.MIN_VALUE)
+ {
+ buff.append(id+" < "+minIVal[i]);
+ if(maxIVal[i] < Integer.MAX_VALUE)
+ buff.append(" ");
+ }
+ if(maxIVal[i] < Integer.MAX_VALUE)
+ buff.append(id+" > "+maxIVal[i]);
+ }
+ else if (hLine.getType().equals("Float"))
+ {
+ if(minFVal[i] > Float.MIN_VALUE)
+ {
+ buff.append(id+" < "+minFVal[i]);
+ if(maxFVal[i] < Float.MAX_VALUE)
+ buff.append(" ");
+ }
+ if(maxFVal[i] < Float.MAX_VALUE)
+ buff.append(id+" > "+maxFVal[i]);;
+ }
+ }
+ return buff.toString();
+ }
+ }
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/TabixReader.java b/uk/ac/sanger/artemis/components/variant/TabixReader.java
new file mode 100644
index 0000000..02a8fd4
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/TabixReader.java
@@ -0,0 +1,440 @@
+package uk.ac.sanger.artemis.components.variant;
+
+/* The MIT License
+
+ Copyright (c) 2010 Broad Institute.
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+/* Contact: Heng Li <hengli at broadinstitute.org> */
+
+import net.sf.samtools.util.BlockCompressedInputStream;
+import net.sf.samtools.seekablestream.SeekableStream;
+
+import java.io.*;
+import java.net.URL;
+import java.nio.*;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.lang.StringBuffer;
+
+public class TabixReader extends AbstractVCFReader
+{
+ private String mFn;
+ private BlockCompressedInputStream mFp;
+
+ private int mPreset;
+ private int mSc;
+ private int mBc;
+ private int mEc;
+ private int mMeta;
+ private int mSkip;
+ private String[] mSeq;
+
+ private HashMap<String, Integer> mChr2tid;
+
+ private static int MAX_BIN = 37450;
+ private static int TAD_MIN_CHUNK_GAP = 32768;
+ private static int TAD_LIDX_SHIFT = 14;
+
+ private class TPair64 implements Comparable<TPair64> {
+ long u, v;
+ public TPair64(final long _u, final long _v) {
+ u = _u; v = _v;
+ }
+ public TPair64(final TPair64 p) {
+ u = p.u; v = p.v;
+ }
+ public int compareTo(final TPair64 p) {
+ return u == p.u? 0 : ((u < p.u) ^ (u < 0) ^ (p.u < 0))? -1 : 1; // unsigned 64-bit comparison
+ }
+ };
+
+ private class TIndex {
+ HashMap<Integer, TPair64[]> b; // binning index
+ long[] l; // linear index
+ };
+ private TIndex[] mIndex;
+
+ private class TIntv {
+ int tid, beg, end;
+ };
+
+ private static boolean less64(final long u, final long v) { // unsigned 64-bit comparison
+ return (u < v) ^ (u < 0) ^ (v < 0);
+ }
+
+ /**
+ * The constructor
+ *
+ * @param fn File name of the data file
+ */
+ public TabixReader(final String fn) throws IOException {
+ mFn = fn;
+ mFp = new BlockCompressedInputStream(new File(fn));
+ readIndex();
+ }
+
+ public TabixReader(final String fn, final SeekableStream ins) throws IOException {
+ mFn = fn;
+ mFp = new BlockCompressedInputStream(ins);
+ readIndex();
+ }
+
+ public TabixReader(final String fn, final URL url) throws IOException {
+ mFn = fn;
+ mFp = new BlockCompressedInputStream(url);
+ readIndex();
+ }
+
+ private static int reg2bins(final int beg, final int _end, final int[] list) {
+ int i = 0, k, end = _end;
+ if (beg >= end) return 0;
+ if (end >= 1<<29) end = 1<<29;
+ --end;
+ list[i++] = 0;
+ for (k = 1 + (beg>>26); k <= 1 + (end>>26); ++k) list[i++] = k;
+ for (k = 9 + (beg>>23); k <= 9 + (end>>23); ++k) list[i++] = k;
+ for (k = 73 + (beg>>20); k <= 73 + (end>>20); ++k) list[i++] = k;
+ for (k = 585 + (beg>>17); k <= 585 + (end>>17); ++k) list[i++] = k;
+ for (k = 4681 + (beg>>14); k <= 4681 + (end>>14); ++k) list[i++] = k;
+ return i;
+ }
+
+ public static int readInt(final InputStream is) throws IOException {
+ byte[] buf = new byte[4];
+ is.read(buf);
+ return ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN).getInt();
+ }
+
+ public static long readLong(final InputStream is) throws IOException {
+ byte[] buf = new byte[8];
+ is.read(buf);
+ return ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN).getLong();
+ }
+
+ public static String readLine(final InputStream is) throws IOException {
+ StringBuffer buf = new StringBuffer();
+ int c;
+ while ((c = is.read()) >= 0 && c != '\n')
+ buf.append((char)c);
+ if (c < 0) return null;
+ return buf.toString();
+ }
+
+ /**
+ * Read the Tabix index from a file
+ *
+ * @param fp File pointer
+ */
+ public void readIndex(final File fp) throws IOException {
+ if (fp == null) return;
+ BlockCompressedInputStream is = new BlockCompressedInputStream(fp);
+ byte[] buf = new byte[4];
+
+ is.read(buf, 0, 4); // read "TBI\1"
+ mSeq = new String[readInt(is)]; // # sequences
+ mChr2tid = new HashMap<String, Integer>();
+ mPreset = readInt(is);
+ mSc = readInt(is);
+ mBc = readInt(is);
+ mEc = readInt(is);
+ mMeta = readInt(is);
+ mSkip = readInt(is);
+ // read sequence dictionary
+ int i, j, k, l = readInt(is);
+ buf = new byte[l];
+ is.read(buf);
+ for (i = j = k = 0; i < buf.length; ++i) {
+ if (buf[i] == 0) {
+ byte[] b = new byte[i - j];
+ System.arraycopy(buf, j, b, 0, b.length);
+ String s = new String(b);
+ mChr2tid.put(s, k);
+ mSeq[k++] = s;
+ j = i + 1;
+ }
+ }
+ // read the index
+ mIndex = new TIndex[mSeq.length];
+ for (i = 0; i < mSeq.length; ++i) {
+ // the binning index
+ int n_bin = readInt(is);
+ mIndex[i] = new TIndex();
+ mIndex[i].b = new HashMap<Integer, TPair64[]>();
+ for (j = 0; j < n_bin; ++j) {
+ int bin = readInt(is);
+ TPair64[] chunks = new TPair64[readInt(is)];
+ for (k = 0; k < chunks.length; ++k) {
+ long u = readLong(is);
+ long v = readLong(is);
+ chunks[k] = new TPair64(u, v); // in C, this is inefficient
+ }
+ mIndex[i].b.put(bin, chunks);
+ }
+ // the linear index
+ mIndex[i].l = new long[readInt(is)];
+ for (k = 0; k < mIndex[i].l.length; ++k)
+ mIndex[i].l[k] = readLong(is);
+ }
+ // close
+ is.close();
+ }
+
+ /**
+ * Read the Tabix index from the default file.
+ */
+ public void readIndex() throws IOException {
+ readIndex(new File(mFn + ".tbi"));
+ }
+
+ /**
+ * Read one line from the data file.
+ */
+ public String readLine() throws IOException {
+ return readLine(mFp);
+ }
+
+ private int chr2tid(final String chr) {
+ if (mChr2tid.containsKey(chr)) return mChr2tid.get(chr);
+ else return -1;
+ }
+
+ /**
+ * Parse a region in the format of "chr1", "chr1:100" or "chr1:100-1000"
+ *
+ * @param reg Region string
+ * @return An array where the three elements are sequence_id,
+ * region_begin and region_end. On failure, sequence_id==-1.
+ */
+ public int[] parseReg(final String reg) { // FIXME: NOT working when the sequence name contains : or -.
+ String chr;
+ int colon, hyphen;
+ int[] ret = new int[3];
+ colon = reg.indexOf(':'); hyphen = reg.lastIndexOf('-');
+ chr = colon >= 0? reg.substring(0, colon) : reg;
+ ret[1] = colon >= 0? Integer.parseInt(reg.substring(colon+1, hyphen)) - 1 : 0;
+ ret[2] = hyphen >= 0? Integer.parseInt(reg.substring(hyphen+1)) : 0x7fffffff;
+ ret[0] = chr2tid(chr);
+ return ret;
+ }
+
+ private TIntv getIntv(final String s) {
+ TIntv intv = new TIntv();
+ int col = 0, end = 0, beg = 0;
+ while ((end = s.indexOf('\t', beg)) >= 0 || end == -1) {
+ ++col;
+ if (col == mSc) {
+ intv.tid = chr2tid(s.substring(beg, end));
+ } else if (col == mBc) {
+ intv.beg = intv.end = Integer.parseInt(s.substring(beg, end));
+ if ((mPreset&0x10000) != 0) ++intv.end;
+ else --intv.beg;
+ if (intv.beg < 0) intv.beg = 0;
+ if (intv.end < 1) intv.end = 1;
+ } else { // FIXME: SAM supports are not tested yet
+ if ((mPreset&0xffff) == 0) { // generic
+ if (col == mEc)
+ intv.end = Integer.parseInt(s.substring(beg, end));
+ } else if ((mPreset&0xffff) == 1) { // SAM
+ if (col == 6) { // CIGAR
+ int l = 0, i, j;
+ String cigar = s.substring(beg, end);
+ for (i = j = 0; i < cigar.length(); ++i) {
+ if (cigar.charAt(i) > '9') {
+ int op = cigar.charAt(i);
+ if (op == 'M' || op == 'D' || op == 'N')
+ l += Integer.parseInt(cigar.substring(j, i));
+ }
+ }
+ intv.end = intv.beg + l;
+ }
+ } else if ((mPreset&0xffff) == 2) { // VCF
+ String alt;
+ alt = end >= 0? s.substring(beg, end) : s.substring(beg);
+ if (col == 4) { // REF
+ if (alt.length() > 0) intv.end = intv.beg + alt.length();
+ } else if (col == 8) { // INFO
+ int e_off = -1, i = alt.indexOf("END=");
+ if (i == 0) e_off = 4;
+ else if (i > 0) {
+ i = alt.indexOf(";END=");
+ if (i >= 0) e_off = i + 5;
+ }
+ if (e_off > 0) {
+ i = alt.indexOf(";", e_off);
+ intv.end = Integer.parseInt(i > e_off? alt.substring(e_off, i) : alt.substring(e_off));
+ }
+ }
+ }
+ }
+ if (end == -1) break;
+ beg = end + 1;
+ }
+ return intv;
+ }
+
+ public class Iterator {
+ private int i, n_seeks;
+ private int tid, beg, end;
+ private TPair64[] off;
+ private long curr_off;
+ private boolean iseof;
+
+ public Iterator(final int _tid, final int _beg, final int _end, final TPair64[] _off) {
+ i = -1; n_seeks = 0; curr_off = 0; iseof = false;
+ off = _off; tid = _tid; beg = _beg; end = _end;
+ }
+
+ public String next() throws IOException {
+ if (iseof) return null;
+ for (;;) {
+ if (curr_off == 0 || !less64(curr_off, off[i].v)) { // then jump to the next chunk
+ if (i == off.length - 1) break; // no more chunks
+ if (i >= 0) assert(curr_off == off[i].v); // otherwise bug
+ if (i < 0 || off[i].v != off[i+1].u) { // not adjacent chunks; then seek
+ mFp.seek(off[i+1].u);
+ curr_off = mFp.getFilePointer();
+ ++n_seeks;
+ }
+ ++i;
+ }
+ String s;
+ if ((s = readLine(mFp)) != null) {
+ TIntv intv;
+ char[] str = s.toCharArray();
+ curr_off = mFp.getFilePointer();
+ if (str.length == 0 || str[0] == mMeta) continue;
+ intv = getIntv(s);
+ if (intv.tid != tid || intv.beg >= end) break; // no need to proceed
+ else if (intv.end > beg && intv.beg < end) return s; // overlap; return
+ } else break; // end of file
+ }
+ iseof = true;
+ return null;
+ }
+ };
+
+ public Iterator query(final int tid, final int beg, final int end) {
+ TPair64[] off, chunks;
+ long min_off;
+ TIndex idx = mIndex[tid];
+ int[] bins = new int[MAX_BIN];
+ int i, l, n_off, n_bins = reg2bins(beg, end, bins);
+ if (idx.l.length > 0)
+ min_off = (beg>>TAD_LIDX_SHIFT >= idx.l.length)? idx.l[idx.l.length-1] : idx.l[beg>>TAD_LIDX_SHIFT];
+ else min_off = 0;
+ for (i = n_off = 0; i < n_bins; ++i) {
+ if ((chunks = idx.b.get(bins[i])) != null)
+ n_off += chunks.length;
+ }
+ if (n_off == 0) return null;
+ off = new TPair64[n_off];
+ for (i = n_off = 0; i < n_bins; ++i)
+ if ((chunks = idx.b.get(bins[i])) != null)
+ for (int j = 0; j < chunks.length; ++j)
+ if (less64(min_off, chunks[j].v))
+ off[n_off++] = new TPair64(chunks[j]);
+ Arrays.sort(off, 0, n_off);
+ // resolve completely contained adjacent blocks
+ for (i = 1, l = 0; i < n_off; ++i) {
+ if (less64(off[l].v, off[i].v)) {
+ ++l;
+ off[l].u = off[i].u; off[l].v = off[i].v;
+ }
+ }
+ n_off = l + 1;
+ // resolve overlaps between adjacent blocks; this may happen due to the merge in indexing
+ for (i = 1; i < n_off; ++i)
+ if (!less64(off[i-1].v, off[i].u)) off[i-1].v = off[i].u;
+ // merge adjacent blocks
+ for (i = 1, l = 0; i < n_off; ++i) {
+ if (off[l].v>>16 == off[i].u>>16) off[l].v = off[i].v;
+ else {
+ ++l;
+ off[l].u = off[i].u;
+ off[l].v = off[i].v;
+ }
+ }
+ n_off = l + 1;
+ // return
+ TPair64[] ret = new TPair64[n_off];
+ for (i = 0; i < n_off; ++i) ret[i] = new TPair64(off[i].u, off[i].v); // in C, this is inefficient
+ return new TabixReader.Iterator(tid, beg, end, ret);
+ }
+
+ public Iterator query(final String reg) {
+ int[] x = parseReg(reg);
+ return query(x[0], x[1], x[2]);
+ }
+
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.out.println("Usage: java -cp .:sam.jar TabixReader <in.gz> [region]");
+ System.exit(1);
+ }
+ try {
+ TabixReader tr = new TabixReader(args[0]);
+ String s;
+ if (args.length == 1) { // no region is specified; print the whole file
+ while ((s = tr.readLine()) != null)
+ System.out.println(s);
+ } else { // a region is specified; random access
+ TabixReader.Iterator iter = tr.query(args[1]); // get the iterator
+ while ((s = iter.next()) != null)
+ System.out.println(s);
+ }
+ } catch (IOException e) {
+ }
+ }
+
+ public String[] getSeqNames()
+ {
+ return mSeq;
+ }
+
+ public int getStartColumn()
+ {
+ return mBc;
+ }
+
+ public int getEndColumn()
+ {
+ return mEc;
+ }
+
+ public int getSeqColumn()
+ {
+ return mSc;
+ }
+
+ public String getFileName()
+ {
+ return mFn;
+ }
+
+ public char getCommentChar()
+ {
+ return (char)mMeta;
+ }
+}
diff --git a/uk/ac/sanger/artemis/components/variant/TableViewer.java b/uk/ac/sanger/artemis/components/variant/TableViewer.java
new file mode 100644
index 0000000..12a6b59
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/TableViewer.java
@@ -0,0 +1,153 @@
+/* TableViewer
+ *
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.variant;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.table.TableModel;
+import javax.swing.table.TableRowSorter;
+
+import uk.ac.sanger.artemis.components.StickyFileChooser;
+
+
+class TableViewer extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+ private Comparator<Integer> comparator = new Comparator<Integer>()
+ {
+ public int compare(Integer i1, Integer i2)
+ {
+ return i1.compareTo(i2);
+ }
+ };
+
+ private TableRowSorter<TableModel> sorter;
+
+ public TableViewer(final Vector<Vector<Object>> rowData, final Vector<String> columnData, String title)
+ {
+ super(title);
+ final JTable variantData = new JTable(rowData, columnData);
+ sorter = new TableRowSorter<TableModel>(variantData.getModel());
+ variantData.setRowSorter(sorter);
+
+ final JFrame f = new JFrame("Variant Overview");
+ final JScrollPane jsp = new JScrollPane(variantData);
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ jsp.setPreferredSize(new Dimension(screen.width/3, screen.height/3));
+ final JPanel overviewPane = new JPanel(new BorderLayout());
+ overviewPane.add(jsp, BorderLayout.CENTER);
+
+ final Box xBox = Box.createHorizontalBox();
+ xBox.add(Box.createHorizontalGlue());
+
+ final JButton save = new JButton("SAVE");
+ save.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ save(variantData, columnData);
+ }
+ });
+ xBox.add(save);
+
+ final JButton close = new JButton("CLOSE");
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.dispose();
+ }
+ });
+ xBox.add(close);
+ overviewPane.add(xBox, BorderLayout.SOUTH);
+ f.getContentPane().add(overviewPane);
+ f.pack();
+ f.setVisible(true);
+ }
+
+ public void setIntegerRowSorter(int colIndex)
+ {
+ sorter.setComparator(colIndex, comparator);
+ }
+
+ private void save(final JTable table, final Vector<String> columnData)
+ {
+ StickyFileChooser fc = new StickyFileChooser();
+ int status = fc.showSaveDialog(TableViewer.this);
+ if(status != StickyFileChooser.APPROVE_OPTION)
+ return;
+ try
+ {
+ File f = fc.getSelectedFile();
+ if(f.exists())
+ {
+ status = JOptionPane.showConfirmDialog(TableViewer.this,
+ f.getName()+" exists overwrite?", "Overwrite", JOptionPane.YES_NO_OPTION);
+ if(status != JOptionPane.YES_OPTION)
+ return;
+ }
+
+ FileWriter writer = new FileWriter(fc.getSelectedFile());
+ for(String col: columnData)
+ writer.write(col+"\t");
+ writer.write("\n");
+
+ int nrows = table.getRowCount();
+ int ncols = table.getColumnCount();
+ for(int i=0; i<nrows; i++)
+ {
+ for(int j=0; j<ncols; j++)
+ {
+ // use the sorted row index
+ int rowIndex = table.convertRowIndexToModel(i);
+ writer.write(table.getModel().getValueAt(rowIndex, j)+"\t");
+ }
+ writer.write("\n");
+ }
+
+ writer.close();
+ }
+ catch (IOException e1)
+ {
+ JOptionPane.showMessageDialog(TableViewer.this, e1.getMessage(),
+ "Problem Writing", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/VCFFilter.java b/uk/ac/sanger/artemis/components/variant/VCFFilter.java
new file mode 100644
index 0000000..329122b
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/VCFFilter.java
@@ -0,0 +1,1042 @@
+/*
+ * created: 2011
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2011 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.components.variant;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.circular.TextFieldFloat;
+import uk.ac.sanger.artemis.circular.TextFieldInt;
+import uk.ac.sanger.artemis.components.Utilities;
+
+public class VCFFilter extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+
+ //private static boolean useHeader = true;
+/* private static int MIN_DP = 0;
+ private static float MIN_MQ = 0;
+ private static float MIN_AF1 = 0;
+ private static float MAX_CI95 = 10;
+ private static Pattern COMMA_PATTERN = Pattern.compile(",");*/
+ private static Pattern SEMICOLON_PATTERN = Pattern.compile(";");
+
+ private static Pattern HOMOZYGOUS_PATTERN = Pattern.compile("^0(/0)*+|(\\|0)*+$");
+ private FilteredPanel filterPanel;
+ protected static boolean manualFilter = false; // show manual filtering
+
+ /**
+ * Filter VCF records by the variant type and/or by different values in
+ * the record, QUAL, DP, MQ and AF1.
+ * @param vcfView
+ */
+ public VCFFilter(final VCFview vcfView)
+ {
+ super("Variant Filter");
+
+ final JPanel mainPanel = (JPanel)getContentPane();
+ final JPanel btmPanel = new JPanel(new GridBagLayout());
+ final JTabbedPane tabPane = new JTabbedPane();
+
+ final List<HeaderLine> filterHdrLines = vcfView.getVcfReaders()[0].getFILTER();
+
+ filterPanel = new FilteredPanel(filterHdrLines);
+ JScrollPane ftrScroll = new JScrollPane(filterPanel);
+ ftrScroll.setPreferredSize( new Dimension(ftrScroll.getPreferredSize().width, 150) );
+
+ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ tabPane.setPreferredSize(new Dimension(screen.width*9/22, screen.height*2/5));
+ mainPanel.add(tabPane, BorderLayout.NORTH);
+ mainPanel.add(ftrScroll, BorderLayout.CENTER);
+ mainPanel.add(btmPanel, BorderLayout.SOUTH);
+
+ ////
+ ////
+ //// Filter by type
+ final Box typeBox = Box.createVerticalBox();
+ tabPane.addTab("Type", typeBox);
+ JLabel typeLabel = new JLabel("VARIANT TYPE");
+ typeLabel.setFont(typeLabel.getFont().deriveFont(Font.BOLD));
+ typeBox.add(typeLabel);
+
+ final JCheckBox showSyn = new JCheckBox("Synonymous", vcfView.showSynonymous);
+ typeBox.add(showSyn);
+ showSyn.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ setFlagFilter(HeaderLine.FILTER_SYN, "SYN", showSyn.getText(), showSyn.isSelected());
+ vcfView.showSynonymous = showSyn.isSelected();
+ vcfView.repaint();
+ }
+ });
+
+ final JCheckBox showNonSyn = new JCheckBox("Non-synonymous", vcfView.showNonSynonymous);
+ typeBox.add(showNonSyn);
+ showNonSyn.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ setFlagFilter(HeaderLine.FILTER_NONSYN, "NONSYN", showNonSyn.getText(), showNonSyn.isSelected());
+ vcfView.showNonSynonymous = showNonSyn.isSelected();
+ vcfView.repaint();
+ }
+ });
+
+ final JCheckBox showDeletionsMenu = new JCheckBox("Deletions", vcfView.showDeletions);
+ typeBox.add(showDeletionsMenu);
+ showDeletionsMenu.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ setFlagFilter(HeaderLine.FILTER_DEL, "DEL", showDeletionsMenu.getText(), showDeletionsMenu.isSelected());
+ vcfView.showDeletions = showDeletionsMenu.isSelected();
+ vcfView.repaint();
+ }
+ });
+
+ final JCheckBox showInsertionsMenu = new JCheckBox("Insertions", vcfView.showInsertions);
+ typeBox.add(showInsertionsMenu);
+ showInsertionsMenu.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ setFlagFilter(HeaderLine.FILTER_INS, "INS", showInsertionsMenu.getText(), showInsertionsMenu.isSelected());
+ vcfView.showInsertions = showInsertionsMenu.isSelected();
+ vcfView.repaint();
+ }
+ });
+
+ final JCheckBox showMultiAllelesMenu = new JCheckBox("Multiple alleles", vcfView.showMultiAlleles);
+ typeBox.add(showMultiAllelesMenu);
+ showMultiAllelesMenu.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ setFlagFilter(HeaderLine.FILTER_MULTALL_FLAG, "MULTI_ALLELLES", "Multiple alleles", showMultiAllelesMenu.isSelected());
+ vcfView.showMultiAlleles = showMultiAllelesMenu.isSelected();
+ vcfView.repaint();
+ }
+ });
+
+ final JCheckBox showNonOverlappingsMenu = new JCheckBox("Variants not overlapping CDS", vcfView.showNonOverlappings);
+ typeBox.add(showNonOverlappingsMenu);
+ showNonOverlappingsMenu.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ setFlagFilter(HeaderLine.FILTER_OVERLAP_FLAG, "NO-OVERLAP", showNonOverlappingsMenu.getText(), showNonOverlappingsMenu.isSelected());
+ vcfView.showNonOverlappings = showNonOverlappingsMenu.isSelected();
+ vcfView.repaint();
+ }
+ });
+
+ final JCheckBox showNonVariantMenu = new JCheckBox("Non-Variants", vcfView.showNonVariants);
+ typeBox.add(showNonVariantMenu);
+ showNonVariantMenu.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ setFlagFilter(HeaderLine.FILTER_NV_FLAG, "NV", showNonVariantMenu.getText(), showNonVariantMenu.isSelected());
+ vcfView.showNonVariants = showNonVariantMenu.isSelected();
+ vcfView.repaint();
+ }
+ });
+ setFlagFilter(HeaderLine.FILTER_NV_FLAG, "NV", showNonVariantMenu.getText(), showNonVariantMenu.isSelected());
+
+ typeBox.add(Box.createVerticalStrut(20));
+ final JCheckBox manualMenu = new JCheckBox("Manual Annotation", manualFilter);
+ typeBox.add(manualMenu);
+ manualMenu.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ manualFilter = manualMenu.isSelected();
+ setFlagFilter(HeaderLine.FILTER_MANUAL, "MANUAL", manualMenu.getText(), manualMenu.isSelected());
+ vcfView.repaint();
+ }
+ });
+ setFlagFilter(HeaderLine.FILTER_MANUAL, "MANUAL", manualMenu.getText(), manualMenu.isSelected());
+
+ typeBox.add(Box.createVerticalGlue());
+
+ if(vcfView.getEntryGroup() == null || vcfView.getEntryGroup().getAllFeaturesCount() == 0)
+ {
+ showSyn.setEnabled(false);
+ showNonSyn.setEnabled(false);
+ showNonOverlappingsMenu.setEnabled(false);
+ }
+
+ ////
+ ////
+ //// Filter by property
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.NORTHWEST;
+
+ final List<HeaderLine> info = vcfView.getVcfReaders()[0].getINFO();
+
+ final JPanel propPanel = new JPanel(new GridBagLayout());
+ final JScrollPane jspProp = new JScrollPane(propPanel);
+ propPanel.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+ tabPane.addTab("Info", jspProp);
+ c.gridy += 1;
+ propPanel.add(new JLabel(" "), c);
+ c.gridy += 1;
+ JLabel propLabel = new JLabel("VARIANT PROPERTY:");
+ propLabel.setFont(propLabel.getFont().deriveFont(Font.BOLD));
+ propPanel.add(propLabel, c);
+
+ // min quality
+ c.gridy += 1;
+ c.gridx = 1;
+ propPanel.add(new JLabel("MIN"), c);
+ c.gridx += 1;
+ propPanel.add(new JLabel("MAX"), c);;
+
+ c.gridy += 1;
+ c.gridx = 0;
+ propPanel.add(new JLabel("Quality score (QUAL):"), c);
+ final TextFieldFloat minQuality = new TextFieldFloat();
+ minQuality.setColumns(8);
+ c.gridx = 1;
+ propPanel.add(minQuality, c);
+ final TextFieldFloat maxQuality = new TextFieldFloat();
+ maxQuality.setColumns(8);
+ c.gridx += 1;
+ propPanel.add(maxQuality, c);
+
+ String hdrLine = "##FILTER=<ID=QUAL,Type=Float,Number=1,Description=\"QUAL "
+ + (minQuality.getText().equals("") ? "" : " < " + minQuality.getValue() + " ")
+ + (maxQuality.getText().equals("") ? "" : " > " + maxQuality.getValue() + " ") + "\">";
+ HeaderLine hLine = new HeaderLine(hdrLine, "FILTER_QUAL",
+ AbstractVCFReader.getLineHash("FILTER", hdrLine));
+
+ maxQuality.addKeyListener(new FilterListener(hLine, false, 0, 1));
+ minQuality.addKeyListener(new FilterListener(hLine, true, 0, 1));
+
+
+ ////
+ ////
+/* final JTextField minDP = new JTextField(Integer.toString(MIN_DP), 8);
+ final JTextField minMQ = new JTextField(Float.toString(MIN_MQ),8);
+ final JTextField minAF1 = new JTextField(Float.toString(MIN_AF1),8);
+ final JTextField maxCI95 = new JTextField(Float.toString(MAX_CI95),8);*/
+
+ if(info.size() == 0)
+ {
+ //useHeader = false;
+/* // min DP
+ c.gridy = c.gridy+1;
+ c.gridx = 0;
+ propPanel.add(new JLabel("Minimum combined depth across samples (DP):"), c);
+
+ c.gridx = 1;
+ propPanel.add(minDP, c);
+
+ // min MQ
+ c.gridy = c.gridy+1;
+ c.gridx = 0;
+ propPanel.add(new JLabel("Minimum RMS mapping quality (MQ):"), c);
+
+ c.gridx = 1;
+ propPanel.add(minMQ, c);
+
+ // min AF1
+ c.gridy = c.gridy+1;
+ c.gridx = 0;
+ propPanel.add(new JLabel("Minimum site frequency of strongest non-reference allele (AF1):"), c);
+
+ c.gridx = 1;
+ propPanel.add(minAF1, c);
+
+ // max CI95
+ c.gridy = c.gridy+1;
+ c.gridx = 0;
+ propPanel.add(new JLabel("Maximum 95% confidence interval variation from AF (CI95):"), c);
+
+ c.gridx = 1;
+ propPanel.add(maxCI95, c);*/
+ }
+ else
+ {
+ //
+ createPanel(vcfView, propPanel, c, info, "INFO FIELDS:");
+ }
+
+ ////
+ ////
+ //// FORMAT - Genotype Filter
+ final List<HeaderLine> format = vcfView.getVcfReaders()[0].getFORMAT();
+
+ final JPanel formatPanel = new JPanel(new GridBagLayout());
+ formatPanel.setBorder(BorderFactory.createLineBorder(Color.gray));
+
+ tabPane.addTab("Genotype", formatPanel);
+ createPanel(vcfView, formatPanel, c, format, "GENOTYPE FIELDS:");
+
+ //
+ c.gridy = c.gridy+1;
+ c.gridx = 0;
+ c.anchor = GridBagConstraints.EAST;
+ JButton apply = new JButton("APPLY");
+ apply.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ try
+ {
+/* if(info.size() == 0)
+ {
+ MIN_DP = Integer.parseInt(minDP.getText());
+ MIN_MQ = Float.parseFloat(minMQ.getText());
+ MIN_AF1 = Float.parseFloat(minAF1.getText());
+ MAX_CI95 = Float.parseFloat(maxCI95.getText());
+ }*/
+ vcfView.repaint();
+ }
+ catch(NumberFormatException ex)
+ {
+ JOptionPane.showMessageDialog(null,
+ ex.getMessage(),
+ "Format Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ });
+ btmPanel.add(apply, c);
+
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.WEST;
+ JButton close = new JButton("OK");
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ vcfView.repaint();
+ setVisible(false);
+ }
+ });
+ btmPanel.add(close, c);
+
+ pack();
+ Utilities.centreFrame(this);
+ }
+
+ private void setFlagFilter(final int hdrIndex, final String id, final String descr, final boolean isSelected)
+ {
+ final String ID = id+"_FLAG";
+ if(!isSelected)
+ {
+ String hdrLine = "##FILTER=<ID="+id+",Type=Flag,Number=0,Description=\""+descr+"\">";
+ HeaderLine hLine = new HeaderLine(hdrLine, HeaderLine.filterFlagStr[hdrIndex],
+ AbstractVCFReader.getLineHash("FILTER", hdrLine));
+ filterPanel.addFilter(ID, hLine, 0);
+ }
+ else
+ filterPanel.removeFilter(ID);
+ filterPanel.updateFilters();
+ }
+
+ private void createPanel(final VCFview vcfView,
+ final JPanel panel,
+ final GridBagConstraints c,
+ final List<HeaderLine> headerLineList,
+ final String name)
+ {
+ c.gridx = 0;
+ c.gridy += 1;
+ c.weightx = 1.d;
+ c.weighty = 1.d;
+
+ panel.add(new JLabel(" "), c);
+ c.gridy += 1;
+ JLabel nameLabel = new JLabel(name);
+ nameLabel.setFont(panel.getFont().deriveFont(Font.BOLD));
+ panel.add(nameLabel, c);
+
+ c.gridy += 1;
+ panel.add(new JLabel(" "), c);
+ c.gridy += 1;
+
+ for (int i = 0; i < headerLineList.size(); i++)
+ {
+ final HeaderLine hLine = headerLineList.get(i);
+ final String type = hLine.getType();
+
+ if (type != null && type.equals("String"))
+ continue;
+
+ int num = hLine.getNumber();
+
+ c.gridy += 1;
+ c.gridx = 0;
+ for (int j = 0; j < num; j++)
+ {
+ c.gridx += 1;
+ panel.add(new JLabel("MIN"), c);
+ c.gridx += 1;
+ panel.add(new JLabel("MAX"), c);
+ c.gridx += 1;
+ }
+
+ c.gridy += 1;
+ c.gridx = 0;
+
+ JLabel lab = new JLabel(hLine.getID() + " (" + type + ")");
+ lab.setToolTipText(hLine.getDescription());
+ panel.add(lab, c);
+
+ if (num == 0 && type.equals("Flag"))
+ {
+ final JCheckBox flag = new JCheckBox();
+ flag.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ final String ID = hLine.getHeaderTypeStr()+":"+hLine.getID();
+ if(flag.isSelected())
+ filterPanel.addFilter(ID, hLine, 0);
+ else
+ filterPanel.removeFilter(ID);
+ filterPanel.updateFilters();
+ }
+ });
+ c.gridx += 1;
+ panel.add(flag, c);
+ }
+
+ for (int j = 0; j < num; j++)
+ {
+ if (type != null && type.equals("Integer"))
+ {
+ final TextFieldInt min = new TextFieldInt();
+ min.setColumns(8);
+ final TextFieldInt max = new TextFieldInt();
+ max.setColumns(8);
+ c.gridx += 1;
+ panel.add(min, c);
+ c.gridx += 1;
+ panel.add(max, c);
+
+ max.addKeyListener(new FilterListener(hLine, false, j, num));
+ min.addKeyListener(new FilterListener(hLine, true, j, num));
+ }
+ else if (type != null && type.equals("Float"))
+ {
+ TextFieldFloat min = new TextFieldFloat();
+ min.setColumns(8);
+ TextFieldFloat max = new TextFieldFloat();
+ max.setColumns(8);
+ c.gridx += 1;
+ panel.add(min, c);
+ c.gridx += 1;
+ panel.add(max, c);
+
+ max.addKeyListener(new FilterListener(hLine, false, j, num));
+ min.addKeyListener(new FilterListener(hLine, true, j, num));
+ }
+
+ if(j<num-1)
+ {
+ c.gridx += 1;
+ panel.add(new JLabel(":"), c);
+ }
+ }
+ }
+
+ // Filter out homozygous reference samples, GT - 0/0
+ final JCheckBox showHomozygousMenu = new JCheckBox("Homozygous sample (GT)", vcfView.showHomozygous);
+ c.gridx = 0;
+ c.gridy += 1;
+ c.gridwidth = 2;
+ panel.add(showHomozygousMenu, c);
+ showHomozygousMenu.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ setFlagFilter(HeaderLine.FILTER_HOMOZYG, "FILTER_HOMOZYG", "Homozygous", showHomozygousMenu.isSelected());
+ vcfView.showHomozygous = showHomozygousMenu.isSelected();
+ vcfView.repaint();
+ }
+ });
+ c.gridwidth = 1;
+
+ c.gridx = 100;
+ c.weightx = 200.d;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ panel.add(new JLabel(""), c);
+ c.gridy += 1;
+ c.weighty = 200.d;
+ c.fill = GridBagConstraints.VERTICAL;
+ panel.add(new JLabel(""), c);
+ c.fill = GridBagConstraints.NONE;
+ }
+
+ /**
+ * Check a VCF record against the hash of manually annotated variants
+ * @param manualHash - hash containing manually annotated variants
+ * @param record - a VCF record
+ * @param vcfIndex - the file index for the VCF record
+ * @return 0 - if no manual annotation
+ * 1 - if manually passed
+ * 2 - if manually failed
+ */
+ protected static int checkManualHash(final Map<String, Boolean> manualHash,
+ final VCFRecord record,
+ final int vcfIndex,
+ final boolean applyManualFilter)
+ {
+ if(applyManualFilter || manualHash.size() == 0)
+ return 0;
+
+ final StringBuilder keyName = new StringBuilder();
+ keyName.append(record.getPos());
+ keyName.append(":");
+ keyName.append(record.getChrom());
+ keyName.append(":");
+ keyName.append(vcfIndex);
+ final String keyStr = keyName.toString();
+
+ if(manualHash.containsKey(keyStr))
+ {
+ if(manualHash.get(keyStr))
+ return 1;
+ else
+ return 2;
+ }
+ return 0;
+ }
+
+ protected static int checkManualHash(final Map<String, Boolean> manualHash, final VCFRecord record, final int vcfIndex)
+ {
+ return checkManualHash(manualHash, record, vcfIndex, manualFilter);
+ }
+
+ /**
+ * Test for a given VCF record to see if it passes the filters.
+ * @param manualHash
+ * @param record
+ * @param vcfReader
+ * @param features
+ * @param basePosition
+ * @param sampleIndex
+ * @return
+ */
+ protected static boolean passFilter(final Map<String, Boolean> manualHash,
+ final VCFRecord record,
+ final AbstractVCFReader vcfReader,
+ final FeatureVector features,
+ final int basePosition,
+ final int sampleIndex,
+ final int vcfIndex)
+ {
+ final int manual = checkManualHash(manualHash, record, vcfIndex);
+ if(manual == 1)
+ return true;
+ else if(manual == 2)
+ return false;
+
+ try
+ {
+ if(sampleIndex > -1) // look at a specific sample and ignore
+ { // those without values
+ final String sample = record.getFormatValueForSample(sampleIndex);
+ if(sample == null || sample.equals("."))
+ return false;
+ }
+
+ if(record.getFilter().equals(".") || record.getFilter().equals("PASS"))
+ {
+ }
+ else
+ {
+ // check if filter still being applied
+ final String filterStr[] = SEMICOLON_PATTERN.split(record.getFilter());
+ for(String s: filterStr)
+ {
+ if(FilteredPanel.getHeaderLineFiltersIDs().contains(s))
+ return false;
+ }
+ }
+
+ final Hashtable<String, RecordFilter> filters = FilteredPanel.getFilters();
+ final Enumeration<String> enumFilter = filters.keys();
+ while(enumFilter.hasMoreElements())
+ {
+ final String recid = enumFilter.nextElement();
+ final RecordFilter recFilter = filters.get(recid);
+ final String id = recFilter.getHeaderLine().getID();
+
+ switch (recFilter.getHeaderLine().getHeaderType())
+ {
+ case HeaderLine.FILTER_NV_FLAG: // FILTER non-variant
+ if( record.getAlt().isNonVariant() )
+ return false;
+ break;
+ case HeaderLine.FILTER_INS:
+ if( record.getAlt().isInsertion(vcfReader.isVcf_v4()) )
+ return false;
+ break;
+ case HeaderLine.FILTER_DEL:
+ if( record.getAlt().isDeletion(vcfReader.isVcf_v4()) )
+ return false;
+ break;
+ case HeaderLine.FILTER_OVERLAP_FLAG: // FILTER no overlap
+ if( !VCFview.isOverlappingFeature(features, basePosition) )
+ return false;
+ break;
+ case HeaderLine.INFO_LINE: // INFO line
+ if (recFilter.getHeaderLine().isFlag())
+ {
+ if(record.containsInfoFlag(id))
+ return false;
+ continue;
+ }
+ else
+ {
+ String inf = record.getInfoValue(id);
+ if(inf == null || !recFilter.pass(record, inf.split(","), vcfReader))
+ return false;
+ }
+ break;
+ case HeaderLine.FORMAT_LINE: // FORMAT Genotype line
+ final String samples[] = record.getFormatValues(id);
+ if(samples == null)
+ return false;
+ if (recFilter.getHeaderLine().isFlag())
+ return true;
+
+ if(sampleIndex > -1) // look at a specific sample
+ {
+ if(samples[sampleIndex] == null || !recFilter.pass(record, samples[sampleIndex].split(","), vcfReader))
+ return false;
+ }
+ else // look at all samples
+ {
+ for(int i=0; i<samples.length; i++)
+ {
+ if( samples[i] == null || !recFilter.pass(record, samples[i].split(","), vcfReader))
+ return false;
+ }
+ }
+ break;
+ case HeaderLine.FILTER_LINE: // FILTER
+ break;
+ case HeaderLine.FILTER_QUAL: // FILTER by quality score
+ if( !recFilter.pass(record, new String[] { Float.toString(record.getQuality()) }, vcfReader))
+ return false;
+ break;
+
+ case HeaderLine.FILTER_MULTALL_FLAG:
+ if( record.getAlt().isMultiAllele(sampleIndex) )
+ return false;
+ break;
+ case HeaderLine.FILTER_HOMOZYG:
+ final String smpls[] = record.getFormatValues("GT");
+ // look at a specific sample
+ if(smpls != null &&
+ sampleIndex > -1 && smpls[sampleIndex] != null &&
+ HOMOZYGOUS_PATTERN.matcher(smpls[sampleIndex]).matches())
+ return false;
+ break;
+ case HeaderLine.FILTER_NONSYN:
+ if( !record.getAlt().isDeletion(vcfReader.isVcf_v4()) &&
+ !record.getAlt().isInsertion(vcfReader.isVcf_v4()) &&
+ record.getAlt().length() == 1 &&
+ record.getRef().length() == 1)
+ {
+ short isSyn = record.getSynFlag(features, basePosition);
+ if( (isSyn == 0 || isSyn == 2) )
+ return false;
+ }
+ break;
+ case HeaderLine.FILTER_SYN:
+ if( !record.getAlt().isDeletion(vcfReader.isVcf_v4()) &&
+ !record.getAlt().isInsertion(vcfReader.isVcf_v4()) &&
+ record.getAlt().length() == 1 &&
+ record.getRef().length() == 1)
+ {
+ short isSyn = record.getSynFlag(features, basePosition);
+ if(isSyn == 1)
+ return false;
+ }
+ default:
+ break;
+ }
+ }
+ return true;
+
+/* try
+ {
+ if(VCFFilter.MIN_DP > 0 && Integer.parseInt(record.getInfoValue("DP")) < VCFFilter.MIN_DP)
+ return false;
+ }
+ catch(NullPointerException npe){}
+
+ try
+ {
+ if(VCFFilter.MIN_MQ > 0 && Float.parseFloat(record.getInfoValue("MQ")) < VCFFilter.MIN_MQ)
+ return false;
+ }
+ catch(NullPointerException npe){}
+
+ try
+ {
+ // AF1 filtered - except for non-variant sites
+ if((!record.getAlt().isNonVariant()) && VCFFilter.MIN_AF1 > 0 && Float.parseFloat(record.getInfoValue("AF1")) < VCFFilter.MIN_AF1)
+ return false;
+ }
+ catch(NullPointerException npe){}
+
+ try
+ {
+ String vals[] = COMMA_PATTERN.split(record.getInfoValue("CI95"));
+ for(int i=0; i<vals.length; i++)
+ {
+ if(VCFFilter.MAX_CI95 < 10 && Float.parseFloat(vals[i]) > VCFFilter.MAX_CI95)
+ return false;
+ }
+ }
+ catch(NullPointerException npe){}*/
+ }
+ catch(NumberFormatException e)
+ {
+ System.err.println(e.getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Test for a given VCF record to see if it passes the filters.
+ * @param manualHash
+ * @param record
+ * @param vcfView
+ * @param basePosition
+ * @param features
+ * @param vcfReader
+ */
+ protected static void setFilterString(final Map<String, Boolean> manualHash,
+ final VCFRecord record,
+ final VCFview vcfView,
+ final int basePosition,
+ final FeatureVector features,
+ final AbstractVCFReader vcfReader,
+ final int vcfIndex)
+ {
+
+
+ final int manual = checkManualHash(manualHash, record, vcfIndex);
+ if(manual == 1)
+ {
+ record.setFilter("PASS");
+ return;
+ }
+
+ if(record.getFilter().equals(".") || record.getFilter().equals("PASS"))
+ {
+ }
+ else
+ {
+ record.setFilter("");
+ // check if filter still being applied
+ final List<String> ids = FilteredPanel.getHeaderLineFiltersIDs();
+ for(int i=0; i<ids.size(); i++)
+ {
+ record.appendFilter(ids.get(i));
+ if(i<ids.size()-1)
+ record.appendFilter(";");
+ }
+ }
+
+ try
+ {
+ // INFO, FORMAT
+ if(FilteredPanel.getFilters().size() > 0)
+ {
+ Hashtable<String, RecordFilter> filters = FilteredPanel.getFilters();
+ Enumeration<String> enumFilter = filters.keys();
+ while(enumFilter.hasMoreElements())
+ {
+ String recid = enumFilter.nextElement();
+ RecordFilter recFilter = filters.get(recid);
+ String id = recFilter.getHeaderLine().getID();
+
+ switch (recFilter.getHeaderLine().getHeaderType())
+ {
+ case HeaderLine.FILTER_NV_FLAG: // FILTER non-variant
+ if( record.getAlt().isNonVariant() )
+ record.appendFilter(id);
+ break;
+ case HeaderLine.FILTER_INS:
+ if( record.getAlt().isInsertion(vcfReader.isVcf_v4()) )
+ record.appendFilter(id);
+ break;
+ case HeaderLine.FILTER_DEL:
+ if( record.getAlt().isDeletion(vcfReader.isVcf_v4()) )
+ record.appendFilter(id);
+ break;
+ case HeaderLine.FILTER_OVERLAP_FLAG: // FILTER no overlap
+ if( !VCFview.isOverlappingFeature(features, basePosition) )
+ record.appendFilter(id);
+ break;
+ case HeaderLine.INFO_LINE: // INFO line
+ if (recFilter.getHeaderLine().isFlag())
+ {
+ if (recFilter.getHeaderLine().isFlag())
+ {
+ if (record.containsInfoFlag(id))
+ record.appendFilter(id);
+ }
+ }
+ else if ( record.getInfoValue(id) == null ||
+ !recFilter.pass(record, record.getInfoValue(id).split(","), vcfReader))
+ record.appendFilter(id);
+ break;
+
+ case HeaderLine.FORMAT_LINE: // FORMAT Genotype line
+ final String samples[] = record.getFormatValues(id);
+
+ id = "sample" + recFilter.getHeaderLine().getID();
+ if(samples == null)
+ {
+ record.appendFilter(id);
+ break;
+ }
+
+ //if (recFilter.getHeaderLine().isFlag())
+ //{
+ // return true;
+ //}
+
+ for(int i=0; i<samples.length; i++)
+ {
+ if( samples[i] == null || !recFilter.pass(record, samples[i].split(","), vcfReader))
+ {
+ record.appendFilter(id);
+ break;
+ }
+ }
+
+ break;
+ case HeaderLine.FILTER_LINE: // FILTER
+ break;
+ case HeaderLine.FILTER_QUAL: // FILTER by quality score
+ if( !recFilter.pass(record, new String[] { Float.toString(record.getQuality()) }, vcfReader))
+ record.appendFilter(id);
+ break;
+ case HeaderLine.FILTER_MULTALL_FLAG: // FILTER by quality score
+ if( record.getAlt().isMultiAllele(-1) )
+ record.appendFilter(id);
+ break;
+ case HeaderLine.FILTER_NONSYN:
+ if( !record.getAlt().isDeletion(vcfReader.isVcf_v4()) &&
+ !record.getAlt().isInsertion(vcfReader.isVcf_v4()) &&
+ record.getAlt().length() == 1 &&
+ record.getRef().length() == 1)
+ {
+ short isSyn = record.getSynFlag(features, basePosition);
+ if( (isSyn == 0 || isSyn == 2) )
+ record.appendFilter(id);
+ }
+ break;
+ case HeaderLine.FILTER_SYN:
+ if( !record.getAlt().isDeletion(vcfReader.isVcf_v4()) &&
+ !record.getAlt().isInsertion(vcfReader.isVcf_v4()) &&
+ record.getAlt().length() == 1 &&
+ record.getRef().length() == 1)
+ {
+ short isSyn = record.getSynFlag(features, basePosition);
+ if(isSyn == 1)
+ record.appendFilter(id);
+ }
+ case HeaderLine.FILTER_MANUAL:
+ if(manual == 2)
+ record.appendFilter(id);
+ default:
+ break;
+ }
+ }
+
+ if( record.getFilter().length() == 0 ||
+ (record.getFilter().length() == 1 && record.getFilter().equals(".")) )
+ record.setFilter("PASS");
+ return;
+ }
+
+ // OTHERS
+/* try
+ {
+ if(VCFFilter.MIN_DP > 0 && Integer.parseInt(record.getInfoValue("DP")) < VCFFilter.MIN_DP)
+ record.appendFilter("DP");
+ }
+ catch(NullPointerException npe){}
+
+ try
+ {
+ if(VCFFilter.MIN_MQ > 0 && Float.parseFloat(record.getInfoValue("MQ")) < VCFFilter.MIN_MQ)
+ record.appendFilter("MQ");
+ }
+ catch(NullPointerException npe){}
+
+ try
+ {
+ // AF1 filtered - except for non-variant sites
+ if((!record.getAlt().isNonVariant()) && VCFFilter.MIN_AF1 > 0 && Float.parseFloat(record.getInfoValue("AF1")) < VCFFilter.MIN_AF1)
+ record.appendFilter("AF1");
+ }
+ catch(NullPointerException npe){}
+
+ try
+ {
+ String vals[] = COMMA_PATTERN.split(record.getInfoValue("CI95"));
+ for(int i=0; i<vals.length; i++)
+ {
+ if(VCFFilter.MAX_CI95 < 10 && Float.parseFloat(vals[i]) > VCFFilter.MAX_CI95)
+ record.appendFilter("CI95");
+ }
+ }
+ catch(NullPointerException npe){}*/
+ }
+ catch(NumberFormatException e)
+ {
+ System.err.println(e.getMessage());
+ }
+
+ if(record.getFilter().length() == 0)
+ record.setFilter("PASS");
+ }
+
+
+
+
+ /**
+ * Filter listener for integers and floats
+ */
+ class FilterListener extends KeyAdapter
+ {
+ private HeaderLine hLine;
+ private boolean isMin;
+ private int index;
+ private int NUMBER;
+
+ public FilterListener(HeaderLine hLine, boolean isMin, int index, int NUMBER)
+ {
+ this.hLine = hLine;
+ this.isMin = isMin;
+ this.index = index;
+ this.NUMBER = NUMBER;
+ }
+
+ public void keyReleased(KeyEvent e)
+ {
+ String type = hLine.getType();
+ if (type.equals("Integer"))
+ setFilterForInt(e);
+ else if (type.equals("Float"))
+ setFilterForFloat(e);
+ filterPanel.updateFilters();
+ }
+
+ private void setFilterForInt(KeyEvent e)
+ {
+ String ID = hLine.getHeaderTypeStr()+":"+hLine.getID();
+ TextFieldInt field = (TextFieldInt)e.getComponent();
+ RecordFilter filter;
+
+ Hashtable<String, RecordFilter> filters = FilteredPanel.getFilters();
+ if(filters.containsKey(ID))
+ filter = filters.get(ID);
+ else
+ filter = new RecordFilter(hLine, NUMBER);
+
+ if(field.getText().trim().equals(""))
+ {
+ if(isMin)
+ filter.minIVal[index] = Integer.MIN_VALUE;
+ else
+ filter.maxIVal[index] = Integer.MAX_VALUE;
+ }
+ else if(isMin)
+ filter.minIVal[index] = field.getValue();
+ else
+ filter.maxIVal[index] = field.getValue();
+
+ if( filter.minIVal[index] == Integer.MIN_VALUE &&
+ filter.maxIVal[index] == Integer.MAX_VALUE)
+ filters.remove(ID);
+ else
+ filters.put(ID, filter);
+ }
+
+ private void setFilterForFloat(KeyEvent e)
+ {
+ String ID = hLine.getHeaderTypeStr()+":"+hLine.getID();
+ TextFieldFloat field = (TextFieldFloat)e.getComponent();
+ RecordFilter filter;
+ Hashtable<String, RecordFilter> filters = FilteredPanel.getFilters();
+ if(filters.containsKey(ID))
+ filter = filters.get(ID);
+ else
+ filter = new RecordFilter(hLine, NUMBER);
+
+ if(field.getText().trim().equals(""))
+ {
+ if(isMin)
+ filter.minFVal[index] = Float.MIN_VALUE;
+ else
+ filter.maxFVal[index] = Float.MAX_VALUE;
+ }
+ else if(isMin)
+ filter.minFVal[index] = (float) field.getValue();
+ else
+ filter.maxFVal[index] = (float) field.getValue();
+
+ if( filter.minFVal[index] == Float.MIN_VALUE &&
+ filter.maxFVal[index] == Float.MAX_VALUE)
+ filters.remove(ID);
+ else
+ filters.put(ID, filter);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/VCFRecord.java b/uk/ac/sanger/artemis/components/variant/VCFRecord.java
new file mode 100644
index 0000000..ab5c6f6
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/VCFRecord.java
@@ -0,0 +1,606 @@
+/*
+ * created: 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.variant;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
+import uk.ac.sanger.artemis.sequence.Bases;
+
+public class VCFRecord
+{
+ //private static Logger logger = Logger.getLogger(VCFRecord.class);
+ private String chrom;
+ private int pos;
+ private String ID;
+ private String ref;
+ private VariantBase var;
+ private float quality;
+ private String filter;
+ private String info;
+ private String infos[];
+ private String format;
+ private String genotypeData[][];
+ private short synFlag = -1;
+ private boolean markAsNewStop = false;
+
+ protected static Pattern MULTI_ALLELE_PATTERN = Pattern.compile(
+ "^[AGCTNMRWSYKBDHVagctnmrwsykbdhv]+,[AGCTNMRWSYKBDHVagctnmrwsykbdhv,]+$");
+ protected static Pattern COLON_PATTERN = Pattern.compile(":");
+ protected static Pattern SEMICOLON_PATTERN = Pattern.compile(";");
+ protected static Pattern TAB_PATTERN = Pattern.compile("\\t");
+
+ /**
+ * Return the string representation of the VCF record as a
+ * tab-delimited string.
+ */
+ public String toString()
+ {
+ return chrom+"\t"+pos+"\t"+ID+"\t"+ref+"\t"+var.toString()+"\t"+quality+
+ "\t"+filter+"\t"+info+"\t"+format+"\t"+getSampleDataString();
+ }
+
+
+ /**
+ * Parse a VCF line and return a VCFRecord
+ * @param line
+ * @return
+ */
+ protected static VCFRecord parse(final String line, int nsamples)
+ {
+ final VCFRecord rec = new VCFRecord();
+ final String parts[] = split(line, "\t", 9+nsamples);
+ //final String parts[] = TAB_PATTERN.split(line);
+
+ rec.chrom = parts[0];
+ rec.pos = Integer.parseInt(parts[1]);
+ rec.ID = parts[2];
+ rec.ref = parts[3];
+ rec.var = new VariantBase(rec, parts[4]);
+
+ try
+ {
+ rec.quality = Float.parseFloat(parts[5]);
+ }
+ catch(NumberFormatException e)
+ {
+ rec.quality = 0.f;
+ }
+
+ rec.filter = parts[6];
+ rec.info = parts[7];
+
+ if(parts.length > 9)
+ {
+ rec.format = (parts[8]).trim();
+ final int nfmt = countOccurrences(rec.format, ':')+1; //rec.format.split(":").length;
+ nsamples = parts.length-9;
+
+ rec.genotypeData = new String[nsamples][nfmt];
+ for(int i=0; i<nsamples; i++)
+ {
+ //rec.genotypeData[i] = COLON_PATTERN.split(parts[9+i]);
+ rec.genotypeData[i] = split(parts[9+i], ":", nfmt);
+ }
+ }
+ return rec;
+ }
+
+ protected static int countOccurrences(final String str, final char search)
+ {
+ int count = 0;
+ for(int i=0; i < str.length(); i++)
+ {
+ if(str.charAt(i) == search)
+ count++;
+ }
+ return count;
+ }
+
+ /**
+ * Split a string into an array
+ * @param arg
+ * @param splitChar
+ * @param nsize
+ * @return
+ */
+ protected static String[] split(final String argStr, final String splitChar, final int nsize)
+ {
+ final String str[] = new String[nsize];
+ String value;
+
+ int ind1 = 0;
+ int ind2;
+ int count = 0;
+ int argLen = argStr.length();
+
+ while(ind1 < argLen)
+ {
+ ind2 = argStr.indexOf(splitChar,ind1);
+ if(ind2 == ind1)
+ {
+ ind1++;
+ continue;
+ }
+
+ if(ind2 < 0)
+ ind2 = argLen;
+
+ value = argStr.substring(ind1,ind2);
+ ind1 = ind2+1;
+
+ str[count] = value;
+ count++;
+ }
+
+ // shrink array if there are fewer elements
+ if(count < nsize)
+ {
+ String tmp[] = new String[count];
+ System.arraycopy( str, 0, tmp, 0, count );
+ return tmp;
+ }
+
+ return str;
+ }
+
+ /**
+ * For example DP or MQ
+ * @param key
+ * @return
+ */
+ protected String getInfoValue(String key)
+ {
+ if(infos == null)
+ infos = SEMICOLON_PATTERN.split(info);
+ for(int i=0; i<infos.length; i++)
+ if(infos[i].startsWith(key+"="))
+ return infos[i].substring(key.length()+1);
+ return null;
+ }
+
+ /**
+ * Test if a INFO flag key is present
+ * @param key
+ * @return
+ */
+ protected boolean containsInfoFlag(String key)
+ {
+ if(infos == null)
+ infos = SEMICOLON_PATTERN.split(info);
+ for(int i=0; i<infos.length; i++)
+ if(infos[i].equals(key))
+ return true;
+ return false;
+ }
+
+ /**
+ * Get genotype values for a given sample.
+ * @param sampleIndex
+ * @return
+ */
+ protected String getFormatValueForSample(int sampleIndex)
+ {
+ if(getFormat() == null)
+ return null;
+ final StringBuffer buff = new StringBuffer();
+ for(int i=0; i<genotypeData[sampleIndex].length; i++) // loop over values
+ {
+ buff.append(genotypeData[sampleIndex][i]);
+ if(i<genotypeData[sampleIndex].length-1)
+ buff.append(":");
+ }
+ return buff.toString();
+ }
+
+ /**
+ * Get genotype values for a given key within a given sample.
+ * @param key
+ * @param sampleIndex
+ * @return
+ */
+ protected String getFormatValueForSample(String key, int sampleIndex)
+ {
+ final String fmtStr[] = getFormatValues(key);
+ if(fmtStr == null)
+ return null;
+ return fmtStr[sampleIndex];
+ }
+
+ /**
+ * Get genotype values for a given key
+ * @param key
+ * @return
+ */
+ protected String[] getFormatValues(final String key)
+ {
+ if(getFormat() == null)
+ return null;
+ final String fmts[] = COLON_PATTERN.split(getFormat());
+
+ for(int i=0; i<fmts.length; i++)
+ {
+ if(fmts[i].equals(key))
+ {
+ final String keyData[] = new String[genotypeData.length];
+ for(int j=0; j<genotypeData.length; j++)
+ {
+ if(genotypeData[j].length == fmts.length)
+ keyData[j] = genotypeData[j][i];
+ }
+ return keyData;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the sample data as a tab-delimited string
+ * @return
+ */
+ protected String getSampleDataString()
+ {
+ if(genotypeData == null)
+ return "";
+ StringBuffer buff = new StringBuffer();
+ for(int i=0; i<genotypeData.length; i++) // loop over samples
+ {
+ for(int j=0; j<genotypeData[i].length; j++) // loop over values
+ {
+ buff.append(genotypeData[i][j]);
+ if(j<genotypeData[i].length-1)
+ buff.append(":");
+ }
+ if(i<genotypeData.length-1)
+ buff.append("\t");
+ }
+ return buff.toString();
+ }
+
+ /**
+ * @return the chrom
+ */
+ protected String getChrom()
+ {
+ return chrom;
+ }
+
+ /**
+ * @param chrom the chrom to set
+ */
+ protected void setChrom(String chrom)
+ {
+ this.chrom = chrom;
+ }
+
+ /**
+ * @return the pos
+ */
+ protected int getPos()
+ {
+ return pos;
+ }
+
+ /**
+ * @param pos the pos to set
+ */
+ protected void setPos(int pos)
+ {
+ this.pos = pos;
+ }
+
+ /**
+ * @return the iD
+ */
+ protected String getID()
+ {
+ return ID;
+ }
+
+ /**
+ * @param iD the iD to set
+ */
+ protected void setID(String iD)
+ {
+ ID = iD;
+ }
+
+ /**
+ * @return the ref
+ */
+ protected String getRef()
+ {
+ return ref;
+ }
+
+ /**
+ * @param ref the ref to set
+ */
+ protected void setRef(String ref)
+ {
+ this.ref = ref;
+ }
+
+ /**
+ * @return the alt
+ */
+ protected VariantBase getAlt()
+ {
+ return var;
+ }
+
+ /**
+ * @param alt the alt to set
+ */
+ protected void setAlt(String alt)
+ {
+ this.var = new VariantBase(this, alt);
+ }
+
+ /**
+ * @return the quality
+ */
+ protected float getQuality()
+ {
+ return quality;
+ }
+
+ /**
+ * @param quality the quality to set
+ */
+ protected void setQuality(float quality)
+ {
+ this.quality = quality;
+ }
+
+ /**
+ * @return the filter
+ */
+ protected String getFilter()
+ {
+ return filter;
+ }
+
+ /**
+ * @param filter the filter to set
+ */
+ protected void setFilter(String filter)
+ {
+ if(filter.equals(":"))
+ this.filter = ".";
+ else
+ this.filter = filter;
+ }
+
+ protected void appendFilter(String filter)
+ {
+ if( getFilter().length() == 0 ||
+ (getFilter().length() == 1 && getFilter().equals(".")) ||
+ (getFilter().length() == 3 && getFilter().equals("PASS")))
+ this.filter = filter;
+ else
+ this.filter += ";" + filter;
+ }
+
+ /**
+ * @return the info
+ */
+ protected String getInfo()
+ {
+ return info;
+ }
+
+ /**
+ * @param info the info to set
+ */
+ protected void setInfo(String info)
+ {
+ this.info = info;
+ }
+
+ /**
+ * @return the format
+ */
+ protected String getFormat()
+ {
+ return format;
+ }
+
+ /**
+ * @param format the format to set
+ */
+ protected void setFormat(String format)
+ {
+ this.format = format;
+ }
+
+ /**
+ * @return the data
+ */
+ protected String[][] getGenoTypeData()
+ {
+ return genotypeData;
+ }
+
+ /**
+ * @param data the data to set
+ */
+ protected void setGenoTypeData(String[][] data)
+ {
+ this.genotypeData = data;
+ }
+
+
+
+ /**
+ * @param features
+ * @param basePosition
+ * 0 if non-synonymous;
+ * 1 if synonymous;
+ * 2 if non-synonymous and creates a stop codon
+ */
+ protected short getSynFlag(FeatureVector features, int basePosition)
+ {
+ if(synFlag == -1)
+ this.synFlag = isSynonymous(features, basePosition);
+ return synFlag;
+ }
+
+ protected short getSynFlag(List<CDSFeature> features, int basePosition)
+ {
+ //logger.info("getSynFlag(List<CDSFeature>) current syn : " + synFlag + " size? " + features.size());
+ if(synFlag == -1)
+ this.synFlag = isSynonymous(features, basePosition);
+ return synFlag;
+ }
+
+ /**
+ * @param features
+ * @param basePosition
+ * @return
+ * 0 if non-synonymous;
+ * 1 if synonymous;
+ * 2 if non-synonymous and creates a stop codon
+ * 3 not within a gene
+ */
+ private short isSynonymous(FeatureVector features, int basePosition)
+ {
+
+ char variant = getAlt().toString().toLowerCase().charAt(0);
+ for(int i = 0; i<features.size(); i++)
+ {
+ Feature feature = features.elementAt(i);
+ short isSyn = checkSyn(new CDSFeature(feature), basePosition, variant);
+ if(isSyn > - 1)
+ return isSyn;
+ }
+
+ return 3;
+ }
+
+ private short isSynonymous(List<CDSFeature> features, int basePosition)
+ {
+ char variant = getAlt().toString().toLowerCase().charAt(0);
+ for(CDSFeature feature : features)
+ {
+ short isSyn = checkSyn(feature, basePosition, variant);
+ if(isSyn > - 1)
+ return isSyn;
+ }
+
+ return 3;
+ }
+
+ protected static short checkSyn(CDSFeature gfeat, int basePosition, char variant)
+ {
+ //logger.info("CDSFEATURE\t"+gfeat);
+ //logger.info("BASEANDVARIANT\t"+basePosition + "\t" + variant);
+ if(gfeat.firstBase < basePosition && gfeat.lastBase > basePosition)
+ {
+ RangeVector ranges = gfeat.ranges;
+ for(int j=0; j< ranges.size(); j++)
+ {
+ Range range = (Range) ranges.get(j);
+ if(j > 0)
+ {
+ if(gfeat.isFwd)
+ gfeat.intronlength+=range.getStart()-gfeat.lastRange.getEnd()-1;
+ else
+ gfeat.intronlength+=gfeat.lastRange.getStart()-range.getEnd()-1;
+
+ if(gfeat.intronlength < 0)
+ gfeat.intronlength = 0;
+ }
+
+ if(range.getStart() < basePosition && range.getEnd() > basePosition)
+ {
+ int mod;
+ int codonStart;
+
+ if(gfeat.isFwd)
+ {
+ mod = (basePosition-gfeat.firstBase-gfeat.intronlength)%3;
+ codonStart = basePosition-gfeat.firstBase-gfeat.intronlength-mod;
+ }
+ else
+ {
+ mod = (gfeat.lastBase-basePosition-gfeat.intronlength)%3;
+ codonStart = gfeat.lastBase-basePosition-gfeat.intronlength-mod;
+ }
+
+ try
+ {
+ if(codonStart+3 > gfeat.bases.length())
+ return 0;
+ char codon[] = gfeat.bases.substring(codonStart,
+ codonStart + 3).toLowerCase().toCharArray();
+
+ char aaRef = AminoAcidSequence.getCodonTranslation(codon[0],
+ codon[1], codon[2]);
+ //logger.info(String.format("%d %d %s%s%s", mod, codonStart, codon[0],codon[1],codon[2]));
+ if(!gfeat.isFwd)
+ variant = Bases.complement(variant);
+ codon[mod] = variant;
+ //logger.info(String.format("%d %d %s%s%s", mod, codonStart, codon[0],codon[1],codon[2]));
+ char aaNew = AminoAcidSequence.getCodonTranslation(codon[0],
+ codon[1], codon[2]);
+
+ if (aaNew == aaRef)
+ return 1;
+ else if(AminoAcidSequence.isStopCodon(aaNew))
+ return 2;
+ else
+ return 0;
+ }
+ catch(Exception e)
+ {
+ for(int k=0; k<ranges.size(); k++)
+ System.out.println(k+" "+ ((Range)ranges.get(k)).getStart() );
+
+ System.out.println(gfeat.feature.getIDString()+" "+codonStart+" "+gfeat.intronlength+" basePosition="+basePosition+" segment="+range.getStart()+".."+range.getEnd()+" mod="+mod);
+ throw new RuntimeException(e);
+ }
+ }
+
+ gfeat.lastRange = range;
+ }
+ }
+ return -1;
+ }
+
+ protected boolean isMarkAsNewStop()
+ {
+ return markAsNewStop;
+ }
+
+ protected void setMarkAsNewStop(boolean markAsNewStop)
+ {
+ this.markAsNewStop = markAsNewStop;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/VCFview.java b/uk/ac/sanger/artemis/components/variant/VCFview.java
new file mode 100644
index 0000000..31a60ed
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/VCFview.java
@@ -0,0 +1,2210 @@
+/* VCFview
+ *
+ * created: July 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.components.variant;
+import java.awt.AlphaComposite;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Composite;
+import java.awt.Container;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.JScrollBar;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+
+import net.sf.samtools.util.BlockCompressedInputStream;
+
+import org.apache.log4j.Level;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureKeyPredicate;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.SelectionChangeEvent;
+import uk.ac.sanger.artemis.SelectionChangeListener;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
+import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
+import uk.ac.sanger.artemis.components.EntryEdit;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.components.FeatureDisplay;
+import uk.ac.sanger.artemis.components.FileViewer;
+import uk.ac.sanger.artemis.components.IndexReferenceEvent;
+import uk.ac.sanger.artemis.components.MessageDialog;
+import uk.ac.sanger.artemis.components.MultiComparator;
+import uk.ac.sanger.artemis.components.SequenceComboBox;
+import uk.ac.sanger.artemis.components.Utilities;
+import uk.ac.sanger.artemis.components.alignment.FileSelectionDialog;
+import uk.ac.sanger.artemis.components.alignment.LineAttributes;
+import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.Key;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.RangeVector;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.MarkerRange;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.FTPSeekableStream;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+
+public class VCFview extends JPanel
+ implements DisplayAdjustmentListener, SelectionChangeListener
+{
+
+ private static final long serialVersionUID = 1L;
+
+ private JScrollPane jspView;
+
+ private JScrollBar scrollBar;
+ private JPanel vcfPanel;
+ private AbstractVCFReader vcfReaders[];
+ private List<String> vcfFiles;
+ private List<Integer> hideVcfList = new Vector<Integer>();
+
+ private FeatureDisplay feature_display;
+ private Selection selection;
+ private int nbasesInView;
+ protected int seqLength;
+ private EntryGroup entryGroup;
+ private String chr;
+ private Point lastMousePoint;
+ private VCFRecord mouseVCF;
+ private int mouseOverIndex = -1;
+ private int mouseOverSampleIndex = -1;
+
+ private GraphPanel graphPanel;
+
+//record of where a mouse drag starts
+ private int dragStart = -1;
+ private JPopupMenu popup;
+ private JMenu vcfFilesMenu = new JMenu("VCF files");
+ private int LINE_HEIGHT = 14;
+
+ protected boolean showSynonymous = true;
+ protected boolean showNonSynonymous = true;
+ protected boolean showDeletions = true;
+ protected boolean showInsertions = true;
+ protected boolean showMultiAlleles = true;
+ protected boolean showHomozygous = true;
+ // show variants that do not overlap CDS
+ protected boolean showNonOverlappings = true;
+ protected boolean showNonVariants = false;
+
+ private Map<String, Boolean> manualHash = new HashMap<String, Boolean>();
+
+ //private boolean markAsNewStop = false;
+
+ private boolean showLabels = false;
+
+ private JCheckBoxMenuItem markNewStops =
+ new JCheckBoxMenuItem("Mark new stops within CDS features", true);
+
+ private static int VARIANT_COLOUR_SCHEME = 0;
+ private static int SYN_COLOUR_SCHEME = 1;
+ private static int QUAL_COLOUR_SCHEME = 2;
+
+ private int colourScheme = 0;
+ private Color colMap[] = makeColours(Color.RED, 255);
+ private Color lighterGrey = new Color(220,220,220);
+
+ private VCFFilter filter;
+ Hashtable<String, Integer> offsetLengths = null;
+ private boolean concatSequences = false;
+ private boolean splitSamples = true;
+
+ protected static Pattern tabPattern = Pattern.compile("\t");
+
+ public static String VCFFILE_SUFFIX = ".*\\.[bv]{1}cf(\\.gz)*$";
+ private static String FILE_SUFFIX = "\\.[bv]{1}cf(\\.gz)*$";
+
+ private List<Integer> cacheVariantLines;
+ private SequenceComboBox combo;
+
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(VCFview.class);
+
+ public VCFview(final JFrame frame,
+ final JPanel vcfPanel,
+ final List<String> vcfFiles,
+ final int nbasesInView,
+ final int seqLength,
+ final String chr,
+ final String reference,
+ final EntryEdit entry_edit,
+ final FeatureDisplay feature_display)
+ {
+ super();
+
+ this.nbasesInView = nbasesInView;
+ this.seqLength = seqLength;
+ this.chr = chr;
+
+ this.feature_display = feature_display;
+ this.vcfPanel = vcfPanel;
+ this.vcfFiles = vcfFiles;
+
+ jspView = new JScrollPane(this,
+ JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+ JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+
+ setBackground(Color.white);
+ MultiLineToolTipUI.initialize();
+ setToolTipText("");
+ vcfPanel.setPreferredSize(new Dimension(900,
+ (vcfFiles.size()+1)*(LINE_HEIGHT+5)));
+
+ if(feature_display != null)
+ this.entryGroup = feature_display.getEntryGroup();
+ else if(reference != null)
+ this.entryGroup = getReference(reference);
+ if(entryGroup != null)
+ this.seqLength = entryGroup.getSequenceEntry().getBases().getLength();
+
+ try
+ {
+ vcfReaders = new AbstractVCFReader[vcfFiles.size()];
+
+ for(int i=0; i<vcfFiles.size(); i++)
+ {
+ readHeader(vcfFiles.get(i), i);
+ }
+ }
+ catch(java.lang.UnsupportedClassVersionError err)
+ {
+ JOptionPane.showMessageDialog(null,
+ "This requires Java 1.6 or higher.",
+ "Check Java Version", JOptionPane.WARNING_MESSAGE);
+ }
+
+ vcfPanel.setLayout(new BorderLayout());
+ vcfPanel.add(jspView, BorderLayout.CENTER);
+
+ JPanel bottomPanel = new JPanel(new BorderLayout());
+ graphPanel = new GraphPanel(this);
+ graphPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.gray));
+ graphPanel.setVisible(false);
+
+ bottomPanel.add(graphPanel, BorderLayout.CENTER);
+ vcfPanel.add(bottomPanel, BorderLayout.SOUTH);
+
+ if(this.nbasesInView > this.seqLength)
+ this.nbasesInView = this.seqLength/2;
+
+ scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 1, this.nbasesInView, 1, this.seqLength);
+ scrollBar.setUnitIncrement(nbasesInView/20);
+ scrollBar.addAdjustmentListener(new AdjustmentListener()
+ {
+ public void adjustmentValueChanged(AdjustmentEvent e)
+ {
+ repaint();
+ }
+ });
+
+ //
+ //
+ addMouseListener(new PopupListener());
+
+ //
+ createTopPanel(frame, entry_edit);
+ createMenus();
+ setDisplay();
+
+ if(feature_display == null)
+ {
+ bottomPanel.add(scrollBar, BorderLayout.SOUTH);
+ selection = new Selection(null);
+ }
+ else
+ {
+ Border empty = new EmptyBorder(0,0,0,0);
+ jspView.setBorder(empty);
+ selection = feature_display.getSelection();
+ }
+ }
+
+ private void createMenus()
+ {
+ // popup menu
+ popup = new JPopupMenu();
+
+ JMenuItem addVCFMenu = new JMenuItem("Add VCF ...");
+ addVCFMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ FileSelectionDialog fileSelection = new FileSelectionDialog(
+ null, true, "VCFview", "VCF");
+ List<String> vcfFileList = fileSelection.getFiles(VCFFILE_SUFFIX);
+ vcfFiles.addAll(vcfFileList);
+
+ int count = vcfFileList.size();
+ int oldSize = vcfReaders.length;
+
+ AbstractVCFReader[] trTmp = new AbstractVCFReader[count + vcfReaders.length];
+ System.arraycopy(vcfReaders, 0, trTmp, 0, vcfReaders.length);
+ vcfReaders = trTmp;
+
+ for (int i = 0; i < vcfFileList.size(); i++)
+ readHeader(vcfFileList.get(i), i+oldSize);
+
+ for(int i=0; i<vcfFileList.size(); i++)
+ addToViewMenu(i+oldSize);
+
+ setDisplay();
+ repaint();
+ jspView.revalidate();
+ }
+ });
+ popup.add(addVCFMenu);
+ popup.add(vcfFilesMenu);
+
+ for(int i=0; i<vcfFiles.size(); i++)
+ addToViewMenu(i);
+
+ final JMenu lineHgt = new JMenu("Row Height");
+ popup.add(lineHgt);
+ final ButtonGroup groupLnHgt = new ButtonGroup();
+ for(int i=8; i<37; i+=2)
+ {
+ final int ii = i;
+ final JCheckBoxMenuItem hgtMenu = new JCheckBoxMenuItem(
+ Integer.toString(i), (i == LINE_HEIGHT));
+ groupLnHgt.add(hgtMenu);
+ hgtMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(hgtMenu.isSelected())
+ {
+ LINE_HEIGHT = ii;
+ setDisplay();
+ revalidate();
+ }
+ }
+ });
+ lineHgt.add(hgtMenu);
+ }
+
+ popup.addSeparator();
+
+ markNewStops.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ repaint();
+ }
+ });
+ popup.add(markNewStops);
+
+
+ final JCheckBoxMenuItem split = new JCheckBoxMenuItem("Separate out into samples", splitSamples);
+ split.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ splitSamples = split.isSelected();
+ setDisplay();
+ revalidate();
+ }
+ });
+ popup.add(split);
+
+
+ final JMenuItem byQuality = new JMenuItem("Filter ...");
+ if(!Options.getOptions().getPropertyTruthValue("java.awt.headless"))
+ filter = new VCFFilter(VCFview.this);
+
+ byQuality.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ filter.setVisible(true);
+ }
+ });
+ popup.add(byQuality);
+
+ final JMenu colourBy = new JMenu("Colour By");
+ popup.add(colourBy);
+ ButtonGroup group = new ButtonGroup();
+ final JRadioButtonMenuItem colByAlt = new JRadioButtonMenuItem("Variant");
+ colByAlt.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ if(colByAlt.isSelected())
+ colourScheme = 0;
+ repaint();
+ }
+ });
+ colourBy.add(colByAlt);
+
+ final JRadioButtonMenuItem colBySyn = new JRadioButtonMenuItem("Synonymous/Non-synonymous");
+ colBySyn.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ if(colBySyn.isSelected())
+ colourScheme = 1;
+ repaint();
+ }
+ });
+ colourBy.add(colBySyn);
+
+ final JRadioButtonMenuItem colByScore = new JRadioButtonMenuItem("Score");
+ colByScore.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ if(colByScore.isSelected())
+ colourScheme = 2;
+ repaint();
+ }
+ });
+ colourBy.add(colByScore);
+
+ group.add(colByAlt);
+ group.add(colBySyn);
+ group.add(colByScore);
+ colByAlt.setSelected(true);
+
+ popup.addSeparator();
+
+ if (feature_display != null)
+ {
+ final JMenu create = new JMenu("Create");
+ final JMenuItem createTab = new JMenuItem(
+ "Features from variants");
+ popup.add(create);
+ create.add(createTab);
+ createTab.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Container f = getVcfContainer();
+ try
+ {
+ f.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ IOUtils.createFeatures(VCFview.this, entryGroup);
+ }
+ finally
+ {
+ f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+ }
+
+ final JMenu export = new JMenu("Write");
+ popup.add(export);
+
+ final JMenuItem exportVCF = new JMenuItem("Filtered VCF");
+ exportVCF.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ Container f = getVcfContainer();
+ try
+ {
+ f.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ IOUtils.export(manualHash, vcfFiles, VCFview.this);
+ }
+ finally
+ {
+ f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+ export.add(exportVCF);
+
+ export.add(new JSeparator());
+
+ final JMenuItem exportFastaSelected = new JMenuItem("FASTA of selected feature(s) ...");
+ exportFastaSelected.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ Container f = getVcfContainer();
+ try
+ {
+ f.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ IOUtils.exportFasta(VCFview.this, selection.getAllFeatures(), false, null);
+ }
+ finally
+ {
+ f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+ export.add(exportFastaSelected);
+
+ final JMenuItem exportFasta = new JMenuItem("FASTA of selected base range ...");
+ exportFasta.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ Container f = getVcfContainer();
+ try
+ {
+ f.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ IOUtils.exportFastaByRange(VCFview.this, selection, false, null);
+ }
+ finally
+ {
+ f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+ export.add(exportFasta);
+
+ final JMenuItem viewMinimalFasta = new JMenuItem("FASTA of variant sites only ...");
+ viewMinimalFasta.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ Container f = getVcfContainer();
+ try
+ {
+ f.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ IOUtils.exportVariantFasta(VCFview.this);
+ }
+ finally
+ {
+ f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+ export.addSeparator();
+ export.add(viewMinimalFasta);
+
+
+ final JMenu view = new JMenu("View");
+ popup.add(view);
+ final JMenuItem viewFastaSelected = new JMenuItem("FASTA of selected feature(s) ...");
+ viewFastaSelected.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ Container f = getVcfContainer();
+ try
+ {
+ f.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ IOUtils.exportFasta(VCFview.this, selection.getAllFeatures(), true, null);
+ }
+ finally
+ {
+ f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+ view.add(viewFastaSelected);
+
+ final JMenuItem viewFasta = new JMenuItem("FASTA of selected base range ...");
+ viewFasta.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ Container f = getVcfContainer();
+ try
+ {
+ f.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ IOUtils.exportFastaByRange(VCFview.this, selection, true, null);
+ }
+ finally
+ {
+ f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+ view.add(viewFasta);
+
+
+ JMenu graph = new JMenu("Graph");
+ popup.add(graph);
+
+ final JCheckBoxMenuItem graphSNP = new JCheckBoxMenuItem("SNP");
+ final JCheckBoxMenuItem graphDP = new JCheckBoxMenuItem("Depth (DP)");
+ final JCheckBoxMenuItem graphSim = new JCheckBoxMenuItem("Base Similarity (%)");
+ graph.add(graphSNP);
+ graphSNP.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ graphPanel.setVisible(graphSNP.isSelected());
+ graphDP.setSelected(false);
+ graphSim.setSelected(false);
+ graphPanel.setType(0);
+ setGraphSize();
+ }
+ });
+
+ graph.add(graphDP);
+ graphDP.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ graphPanel.setVisible(graphDP.isSelected());
+ graphSNP.setSelected(false);
+ graphSim.setSelected(false);
+ graphPanel.setType(1);
+ setGraphSize();
+ }
+ });
+
+ graph.add(graphSim);
+ graphSim.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ graphPanel.setVisible(graphSim.isSelected());
+ graphSNP.setSelected(false);
+ graphDP.setSelected(false);
+ graphPanel.setType(2);
+ setGraphSize();
+ }
+ });
+
+ final JMenuItem snpOverview = new JMenuItem("Overview for selected features");
+ popup.add(snpOverview);
+ snpOverview.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Container f = getVcfContainer();
+ try
+ {
+ f.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+ IOUtils.countVariants(VCFview.this, selection.getAllFeatures());
+ }
+ catch (IOException e1)
+ {
+ JOptionPane.showMessageDialog(null, e1.getMessage());
+ }
+ finally
+ {
+ f.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ });
+
+ final JCheckBoxMenuItem labels = new JCheckBoxMenuItem("Show Labels", showLabels);
+ labels.addActionListener(new ActionListener(){
+ public void actionPerformed(ActionEvent e)
+ {
+ showLabels = labels.isSelected();
+ repaint();
+ }
+ });
+ popup.add(new JSeparator());
+ popup.add(labels);
+ }
+
+ private void setGraphSize()
+ {
+ repaint();
+ if(graphPanel.isVisible())
+ graphPanel.setPreferredSize(new Dimension(900, 70));
+ else
+ graphPanel.setPreferredSize(new Dimension(900, 1));
+
+ vcfPanel.setPreferredSize(new Dimension(900,
+ graphPanel.getPreferredSize().height+getPreferredSize().height));
+ vcfPanel.revalidate();
+ }
+
+ private void createTopPanel(final JFrame frame, final EntryEdit entry_edit)
+ {
+ final JComponent topPanel;
+ if(feature_display != null)
+ topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
+ else
+ {
+ markNewStops.setSelected(false);
+ markNewStops.setEnabled(false);
+ topPanel = new JMenuBar();
+ if(frame != null)
+ frame.setJMenuBar((JMenuBar)topPanel);
+
+ JMenu fileMenu = new JMenu("File");
+ topPanel.add(fileMenu);
+
+ JMenuItem printImage = new JMenuItem("Save As Image Files (png/jpeg)...");
+ printImage.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintVCFview part = new PrintVCFview(VCFview.this);
+ part.print();
+ }
+ });
+ fileMenu.add(printImage);
+
+ JMenuItem printPS = new JMenuItem("Print...");
+ printPS.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ PrintVCFview part = new PrintVCFview(VCFview.this);
+ part.validate();
+ part.doPrintActions();
+ }
+ });
+ fileMenu.add(printPS);
+
+
+ JMenuItem close = new JMenuItem("Close");
+ fileMenu.add(close);
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ VCFview.this.setVisible(false);
+ Component comp = VCFview.this;
+
+ while( !(comp instanceof JFrame) )
+ comp = comp.getParent();
+ ((JFrame)comp).dispose();
+ }
+ });
+
+ JButton zoomIn = new JButton("-");
+ Insets ins = new Insets(1,1,1,1);
+ zoomIn.setMargin(ins);
+ zoomIn.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setZoomLevel((int) (VCFview.this.nbasesInView * 1.1));
+ }
+ });
+ topPanel.add(zoomIn);
+
+ JButton zoomOut = new JButton("+");
+ zoomOut.setMargin(ins);
+ zoomOut.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setZoomLevel((int) (VCFview.this.nbasesInView * .9));
+ }
+ });
+ topPanel.add(zoomOut);
+ }
+
+ combo = new SequenceComboBox(vcfReaders[0].getSeqNames()){
+ private static final long serialVersionUID = 1L;
+ public void update(IndexReferenceEvent event)
+ {
+ if(combo.getSelectedItem().equals("Combine References"))
+ concatSequences = true;
+ else
+ {
+ VCFview.this.chr = (String) combo.getSelectedItem();
+ concatSequences = false;
+ }
+ repaint();
+ }
+ };
+
+ if(vcfReaders[0].getSeqNames().length > 1)
+ combo.addItem("Combine References");
+
+ if(chr == null)
+ this.chr = vcfReaders[0].getSeqNames()[0];
+ combo.setSelectedItem(this.chr);
+
+ topPanel.add(combo);
+ if(topPanel instanceof JPanel)
+ vcfPanel.add(topPanel, BorderLayout.NORTH);
+
+ // auto hide top panel
+ final JCheckBox buttonAutoHide = new JCheckBox("Hide", true);
+ buttonAutoHide.setToolTipText("Auto-Hide");
+ topPanel.add(buttonAutoHide);
+ final MouseMotionListener mouseMotionListener = new MouseMotionListener()
+ {
+ public void mouseDragged(MouseEvent event)
+ {
+ handleCanvasMouseDrag(event);
+ }
+
+ public void mouseMoved(MouseEvent e)
+ {
+ lastMousePoint = e.getPoint();
+
+ int thisHgt = HEIGHT;
+ if (thisHgt < 5)
+ thisHgt = 15;
+
+ int y = (int) (e.getY() - jspView.getViewport().getViewRect().getY());
+ if (y < thisHgt)
+ topPanel.setVisible(true);
+ else
+ {
+ if (buttonAutoHide.isSelected())
+ topPanel.setVisible(false);
+ }
+
+ }
+ };
+ addMouseMotionListener(mouseMotionListener);
+
+
+ if(feature_display != null)
+ {
+ JButton close = new JButton("Close");
+ topPanel.add(close);
+ close.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setVisible(false);
+ if(entry_edit != null)
+ entry_edit.setNGDivider();
+ else
+ vcfPanel.setVisible(false);
+ }
+ });
+ }
+ }
+
+ /**
+ * Create an icon of a box using the given colour.
+ * @param c
+ * @return
+ */
+ protected ImageIcon getImageIcon(Color c)
+ {
+ BufferedImage image = (BufferedImage)this.createImage(10, 10);
+ if(image == null)
+ return null;
+ Graphics2D g2 = image.createGraphics();
+ g2.setColor(c);
+ g2.fillRect(0, 0, 10, 10);
+ return new ImageIcon(image);
+ }
+
+ private void addToViewMenu(final int thisBamIndex)
+ {
+ LineAttributes ln[] = GraphPanel.getLineAttributes(vcfReaders.length);
+ final JCheckBoxMenuItem cbBam = new JCheckBoxMenuItem(
+ getLabel(thisBamIndex),
+ getImageIcon(ln[thisBamIndex].getLineColour()),
+ true);
+
+ vcfFilesMenu.add(cbBam);
+ cbBam.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(cbBam.isSelected())
+ hideVcfList.remove(new Integer(thisBamIndex));
+ else
+ hideVcfList.add(new Integer(thisBamIndex));
+ repaint();
+ }
+ });
+ }
+
+ /**
+ * Test and download if on a http server
+ * @param fileName
+ * @return
+ */
+ private String testForURL(String fileName, boolean isBCF)
+ {
+ if(!fileName.startsWith("http:") && !fileName.startsWith("ftp:"))
+ return fileName;
+
+ return download(fileName+".tbi", ".tbi");
+ }
+
+ protected String download(String f, String suffix)
+ {
+ try
+ {
+ final URL urlFile = new URL(f);
+ InputStream is = urlFile.openStream();
+
+ // Create temp file.
+ String name = urlFile.getFile();
+ int ind = name.lastIndexOf('/');
+ if(ind > -1)
+ name = name.substring(ind+1);
+ File bcfFile = File.createTempFile(name.replaceAll("[\\/\\s]", "_"), suffix);
+
+ bcfFile.deleteOnExit();
+
+ FileOutputStream out = new FileOutputStream(bcfFile);
+ int c;
+ while ((c = is.read()) != -1)
+ out.write(c);
+ out.flush();
+ out.close();
+ is.close();
+ return bcfFile.getAbsolutePath();
+ }
+ catch(IOException ioe)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Problem downloading\n"+f,
+ "Problem", JOptionPane.WARNING_MESSAGE);
+ }
+ return null;
+ }
+
+ private static EntryGroup getReference(String reference)
+ {
+ EntryGroup entryGroup = new SimpleEntryGroup();
+ final Document entry_document = DocumentFactory.makeDocument(reference);
+ final EntryInformation artemis_entry_information =
+ Options.getArtemisEntryInformation();
+
+ final uk.ac.sanger.artemis.io.Entry new_embl_entry =
+ EntryFileDialog.getEntryFromFile(null, entry_document,
+ artemis_entry_information,
+ false);
+ if(new_embl_entry != null) // the read failed
+ {
+ Entry entry = null;
+ Bases bases = null;
+ try
+ {
+ if (entryGroup.getSequenceEntry() != null)
+ bases = entryGroup.getSequenceEntry().getBases();
+ if (bases == null)
+ {
+ entry = new Entry(new_embl_entry);
+ bases = entry.getBases();
+ }
+ else
+ entry = new Entry(bases, new_embl_entry);
+ entryGroup.add(entry);
+ }
+ catch (OutOfRangeException e)
+ {
+ new MessageDialog(null, "read failed: one of the features in "
+ + reference + " has an out of range " + "location: "
+ + e.getMessage());
+ }
+ catch (NoSequenceException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ return entryGroup;
+ }
+
+ /**
+ * Read the vcf header
+ * @param fileName
+ * @return
+ */
+ private void readHeader(String fileName, int index)
+ {
+ StringBuffer buff = new StringBuffer();
+ //buff.append(fileName+"\n");
+ try
+ {
+ if(IOUtils.isBCF(fileName))
+ {
+ vcfReaders[index] = new BCFReader(fileName);
+ String hdr = ((BCFReader)vcfReaders[index]).headerToString();
+ if(hdr.indexOf("VCFv4") > -1)
+ vcfReaders[index].setVcf_v4(true);
+
+ vcfReaders[index].setHeader(hdr);
+ return;
+ }
+
+ String indexfileName = testForURL(fileName, false);
+ BlockCompressedInputStream is;
+ if(fileName.startsWith("http")|| fileName.startsWith("ftp"))
+ {
+ URL url = new URL(fileName);
+ if(fileName.startsWith("ftp"))
+ vcfReaders[index] = new TabixReader(indexfileName.substring(0, indexfileName.length()-4), new FTPSeekableStream(url));
+ else
+ vcfReaders[index] = new TabixReader(indexfileName.substring(0, indexfileName.length()-4), url);
+ is = new BlockCompressedInputStream(url);
+ }
+ else
+ {
+ vcfReaders[index] = new TabixReader(fileName);
+ is = new BlockCompressedInputStream(new FileInputStream(fileName));
+ }
+
+ String line;
+ while( (line = TabixReader.readLine(is) ) != null )
+ {
+ if(!line.startsWith("#"))
+ break;
+
+ if(line.indexOf("VCFv4") > -1)
+ vcfReaders[index].setVcf_v4(true);
+ buff.append(line+"\n");
+ }
+ is.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ vcfReaders[index].setHeader(buff.toString());
+ return;
+ }
+
+ /**
+ * Set the number of bases being displayed
+ * @param nbasesInView
+ */
+ private void setZoomLevel(final int nbasesInView)
+ {
+ int startValue = scrollBar.getValue();
+ this.nbasesInView = nbasesInView;
+ float pixPerBase = getPixPerBaseByWidth();
+ this.nbasesInView = (int)(getWidth()/pixPerBase);
+
+ if(scrollBar != null)
+ {
+ scrollBar.setValues(startValue, nbasesInView, 1, seqLength);
+ scrollBar.setUnitIncrement(nbasesInView/20);
+ scrollBar.setBlockIncrement(nbasesInView);
+ }
+ }
+
+ public String getToolTipText()
+ {
+ if(vcfReaders == null)
+ return null;
+
+ mouseVCF = null;
+ findVariantAtPoint(lastMousePoint);
+ if(mouseVCF == null)
+ return null;
+
+ String msg =
+ "Seq: "+mouseVCF.getChrom()+"\n";
+ msg += "Pos: "+mouseVCF.getPos()+"\n";
+ msg += "ID: "+mouseVCF.getID()+"\n";
+ msg += "Variant: "+mouseVCF.getRef()+" -> "+mouseVCF.getAlt().toString()+"\n";
+ msg += "Qual: "+mouseVCF.getQuality()+"\n";
+ String dp;
+
+ if(splitSamples && mouseOverSampleIndex >= 0)
+ {
+ msg += "Genotype ";
+ msg += mouseVCF.getFormat();
+ msg += "\n";
+ msg += mouseVCF.getFormatValueForSample(mouseOverSampleIndex);
+ }
+ else if((dp = mouseVCF.getInfoValue("DP")) != null)
+ {
+ msg += "DP:"+dp;
+ }
+ return msg;
+ }
+
+
+ /**
+ * For VCF files with multiple references sequences, calculate
+ * the offset from the start of the concatenated sequence for
+ * a given reference.
+ * @param refName
+ * @return
+ */
+ protected int getSequenceOffset(String refName)
+ {
+ if(!concatSequences)
+ return 0;
+
+ if(offsetLengths == null)
+ {
+ String[] contigs = vcfReaders[0].getSeqNames();
+ FeatureVector features = entryGroup.getAllFeatures();
+ offsetLengths = new Hashtable<String, Integer>(contigs.length);
+ for(int i=0; i<contigs.length; i++)
+ {
+ FeatureContigPredicate predicate = new FeatureContigPredicate(contigs[i]);
+ for(int j=0; j<features.size(); j++)
+ {
+ if(predicate.testPredicate(features.elementAt(j)))
+ {
+ offsetLengths.put(contigs[i], features.elementAt(j).getFirstBase()-1);
+ break;
+ }
+ }
+ }
+
+ if(offsetLengths.size() != contigs.length)
+ {
+ System.err.println("Number of contigs found : "+offsetLengths.size() +
+ "\nNumber of contigs in VCF: "+contigs.length);
+ JOptionPane.showMessageDialog(this,
+ "There is a problem matching the reference sequences\n"+
+ "to the names in the VCF file. This may mean the labels\n"+
+ "on the reference features do not match those in the in\n"+
+ "the VCF file.",
+ "Problem Found", JOptionPane.WARNING_MESSAGE);
+ concatSequences = false;
+ return 0;
+ }
+ }
+ return offsetLengths.get(refName);
+ }
+
+ public void repaint()
+ {
+ super.repaint();
+ if(graphPanel != null && graphPanel.isVisible())
+ graphPanel.repaint();
+ }
+
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+
+ Graphics2D g2d = (Graphics2D)g;
+ mouseVCF = null;
+
+ float pixPerBase = getPixPerBaseByWidth();
+ int start = getBaseAtStartOfView();
+ int end = start+nbasesInView;
+
+ drawSelectionRange((Graphics2D)g, pixPerBase, start, end);
+
+ int sumSamples = 0;
+ FeatureVector features = getCDSFeaturesInRange(start, end);
+
+ for (int i = 0; i < vcfReaders.length; i++)
+ {
+ if(hideVcfList.contains(i))
+ continue;
+
+ if(concatSequences)
+ {
+ String[] contigs = vcfReaders[0].getSeqNames();
+ for(int j=0; j<contigs.length; j++)
+ {
+ int offset = getSequenceOffset(contigs[j]);
+ int nextOffset;
+ if(j<contigs.length-1)
+ nextOffset = getSequenceOffset(contigs[j+1]);
+ else
+ nextOffset = seqLength;
+
+ if( (offset >= start && offset < end) ||
+ (offset < start && start < nextOffset) )
+ {
+ int thisStart = start - offset;
+ if(thisStart < 1)
+ thisStart = 1;
+ int thisEnd = end - offset;
+
+ drawRegion(g2d, contigs[j], thisStart, thisEnd, i, sumSamples, start, pixPerBase, features);
+
+ }
+ }
+
+ }
+ else
+ {
+ int thisStart = start;
+ if(thisStart < 1)
+ thisStart = 1;
+ drawRegion(g2d, chr, thisStart, end, i, sumSamples, start, pixPerBase, features);
+ }
+ sumSamples += vcfReaders[i].getNumberOfSamples();
+ }
+
+ if(feature_display == null)
+ drawScale(g2d, start, end, pixPerBase, getHeight());
+
+ // show labels for each VCF
+ if(showLabels)
+ showLabels(g2d);
+ }
+
+ private void showLabels(Graphics2D g2d)
+ {
+ int max = 0;
+ final FontMetrics fm = getFontMetrics(getFont());
+
+ for (int i = 0; i < vcfReaders.length; i++)
+ {
+ if(hideVcfList.contains(i))
+ continue;
+
+ if(splitSamples)
+ {
+ for(int sampleIdx = 0; sampleIdx < vcfReaders[i].getNumberOfSamples(); sampleIdx++)
+ {
+ if(vcfReaders[i].sampleNames == null)
+ {
+ int width = fm.stringWidth(getLabel(i));
+ if (max < width)
+ max = width;
+ break;
+ }
+
+ String labStr = vcfReaders[i].sampleNames[sampleIdx];
+ int width = fm.stringWidth(labStr);
+ if (max < width)
+ max = width;
+ }
+ }
+ else
+ {
+ String labStr = getLabel(i);
+ int width = fm.stringWidth(labStr);
+ if (max < width)
+ max = width;
+ }
+ }
+
+ Rectangle square = new Rectangle(0, 0, max, getHeight());
+ Composite originalComposite = g2d.getComposite();
+ g2d.setPaint(Color.lightGray);
+ g2d.setComposite(makeComposite(0.75f));
+ g2d.fill(square);
+ g2d.setComposite(originalComposite);
+
+ g2d.setColor(Color.black);
+ g2d.drawLine(max+1, 0, max+1, getHeight());
+
+ int sumSample = 0;
+ for (int i = 0; i < vcfReaders.length; i++)
+ {
+ if(hideVcfList.contains(i))
+ continue;
+
+ if(splitSamples)
+ {
+ for(int sampleIdx=0; sampleIdx < vcfReaders[i].getNumberOfSamples(); sampleIdx++)
+ {
+ if(vcfReaders[i].sampleNames == null)
+ g2d.drawString(getLabel(i), 1, getYPostion(sumSample+sampleIdx));
+ else
+ g2d.drawString(vcfReaders[i].sampleNames[sampleIdx], 1, getYPostion(sumSample+sampleIdx));
+ }
+ sumSample += vcfReaders[i].getNumberOfSamples();
+ }
+ else
+ g2d.drawString(getLabel(i), 1, getYPostion(i));
+ }
+ }
+
+ private String getLabel(int index)
+ {
+ return vcfReaders[index].getName().replaceAll(FILE_SUFFIX, "");
+ }
+
+ private AlphaComposite makeComposite(float alpha)
+ {
+ return(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
+ }
+
+
+ private void drawRegion(final Graphics2D g,
+ final String chr,
+ final int sbeg,
+ final int send,
+ final int vcfFileIndex,
+ final int sumSamples,
+ final int start,
+ final float pixPerBase,
+ final FeatureVector features)
+ {
+ cacheVariantLines = new Vector<Integer>(5);
+ try
+ {
+ VCFRecord record;
+
+ // viewport position and height
+ int viewIndex = getHeight()/(LINE_HEIGHT+5) - jspView.getViewport().getViewPosition().y/(LINE_HEIGHT+5);
+ int viewHgt = jspView.getViewport().getExtentSize().height/(LINE_HEIGHT+5);
+
+ while((record = vcfReaders[vcfFileIndex].getNextRecord(chr, sbeg, send)) != null)
+ {
+ int basePosition = record.getPos() + getSequenceOffset(record.getChrom());
+ if(!splitSamples)
+ {
+ drawVariantCall(g, record, start, vcfFileIndex, -1, -1, pixPerBase, features,
+ vcfReaders[vcfFileIndex], basePosition);
+ continue;
+ }
+
+ for(int sampleIndex = 0; sampleIndex < vcfReaders[vcfFileIndex].getNumberOfSamples(); sampleIndex++)
+ {
+ if(sampleIndex+sumSamples <= viewIndex+2 && sampleIndex+sumSamples >= viewIndex-viewHgt-2)
+ {
+ drawVariantCall(g, record, start, vcfFileIndex, sampleIndex, sumSamples, pixPerBase, features,
+ vcfReaders[vcfFileIndex], basePosition);
+ }
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ logger4j.warn(chr+":"+sbeg+"-"+send+"\n"+e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ protected FeatureVector getCDSFeaturesInRange(int start, int end)
+ {
+ if(entryGroup == null)
+ return null;
+ try
+ {
+ Range range = new Range(start, end);
+ FeatureVector features = entryGroup.getFeaturesInRange(range);
+
+ FeatureKeyPredicate predicate = new FeatureKeyPredicate(Key.CDS);
+ final FeatureVector cdsFeatures = new FeatureVector();
+
+ for(int i=0; i<features.size(); i++)
+ {
+ final Feature this_feature = features.elementAt(i);
+ if(predicate.testPredicate(this_feature))
+ cdsFeatures.add(this_feature);
+ }
+ return cdsFeatures;
+ }
+ catch (OutOfRangeException e1)
+ {
+ e1.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Highlight a selected range
+ * @param g2
+ * @param pixPerBase
+ * @param start
+ * @param end
+ */
+ private void drawSelectionRange(Graphics2D g2, float pixPerBase, int start, int end)
+ {
+ if(getSelection() != null)
+ {
+ Range selectedRange = getSelection().getSelectionRange();
+
+ if(selectedRange != null)
+ {
+ int rangeStart = selectedRange.getStart();
+ int rangeEnd = selectedRange.getEnd();
+
+ if(end < rangeStart || start > rangeEnd)
+ return;
+
+ int x = (int) (pixPerBase*(rangeStart-getBaseAtStartOfView()));
+ int width = (int) (pixPerBase*(rangeEnd-rangeStart+1));
+
+ g2.setColor(Color.pink);
+ g2.fillRect(x, 0, width, getHeight());
+ }
+ }
+ }
+
+ private Selection getSelection()
+ {
+ return selection;
+ }
+
+ protected int getBaseAtStartOfView()
+ {
+ if(feature_display != null)
+ return feature_display.getForwardBaseAtLeftEdge();
+ else
+ return scrollBar.getValue();
+ }
+
+ protected boolean showVariant(final VCFRecord record, final FeatureVector features, final int basePosition,
+ final AbstractVCFReader vcfReader, final int nsample, final int vcfIndex)
+ {
+ return VCFFilter.passFilter(manualHash, record, vcfReader, features, basePosition, nsample, vcfIndex);
+ }
+
+ private void setAsStop(VCFRecord record, FeatureVector features, int basePosition, AbstractVCFReader vcfReader)
+ {
+ if(!record.isMarkAsNewStop() &&
+ markNewStops.isSelected() &&
+ !record.getAlt().isDeletion(vcfReader.isVcf_v4()) &&
+ !record.getAlt().isInsertion(vcfReader.isVcf_v4()) &&
+ record.getAlt().length() == 1 &&
+ record.getRef().length() == 1)
+ {
+ short isSyn = record.getSynFlag(features, basePosition);
+ if(isSyn == 2)
+ record.setMarkAsNewStop(true);
+ }
+ }
+
+ protected static boolean isOverlappingFeature(FeatureVector features, int basePosition)
+ {
+ for(int i = 0; i<features.size(); i++)
+ {
+ Feature feature = features.elementAt(i);
+ if(feature.getRawFirstBase() < basePosition && feature.getRawLastBase() > basePosition)
+ {
+ RangeVector ranges = feature.getLocation().getRanges();
+ for(int j=0; j< ranges.size(); j++)
+ {
+ Range range = (Range) ranges.get(j);
+ if(range.getStart() < basePosition && range.getEnd() > basePosition)
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected static boolean isOverlappingFeature(List<CDSFeature> cdsFeatures, int basePosition) {
+ for (CDSFeature cdsFeature : cdsFeatures) {
+ if (cdsFeature.firstBase < basePosition && cdsFeature.lastBase > basePosition)
+ {
+ for(int i = 0 ; i < cdsFeature.ranges.size() ; ++i)
+ {
+ Range range = (Range)cdsFeature.ranges.elementAt(i);
+ if (range.getStart() < basePosition && range.getEnd() > basePosition)
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Draw the VCF record
+ * @param g
+ * @param record
+ * @param start
+ * @param vcfIndex
+ * @param sampleIndex
+ * @param sumSamples
+ * @param pixPerBase
+ * @param features
+ * @param vcfReader
+ * @param basePosition
+ */
+ private void drawVariantCall(final Graphics2D g,
+ final VCFRecord record,
+ final int start,
+ final int vcfIndex,
+ final int sampleIndex,
+ final int sumSamples,
+ final float pixPerBase,
+ final FeatureVector features,
+ final AbstractVCFReader vcfReader,
+ final int basePosition)
+ {
+ boolean show = showVariant(record, features, basePosition, vcfReader, sampleIndex, vcfIndex);
+ if( !show )
+ return;
+
+ setAsStop(record, features, basePosition, vcfReader);
+ final int pos[];
+ if(sampleIndex < 0)
+ pos = getScreenPosition(basePosition, pixPerBase, start, vcfIndex);
+ else
+ pos = getScreenPosition(basePosition, pixPerBase, start, sampleIndex+sumSamples);
+
+ if (colourScheme == QUAL_COLOUR_SCHEME)
+ g.setColor(getQualityColour(record));
+ else if (record.getAlt().isDeletion(vcfReader.isVcf_v4()))
+ g.setColor(Color.gray);
+ else if (record.getAlt().isInsertion(vcfReader.isVcf_v4()))
+ g.setColor(Color.magenta);
+ else if (record.getAlt().isMultiAllele(sampleIndex))
+ {
+ g.setColor(Color.orange);
+ g.fillArc(pos[0] - 3, pos[1] - LINE_HEIGHT - 3, 6, 6, 0, 360);
+ }
+ else if (record.getAlt().length() == 1 && record.getRef().length() == 1)
+ {
+ g.setColor(getColourForSNP(record, features, basePosition));
+ if (record.getAlt().isNonVariant())
+ {
+ // use the cache to avoid drawing over a variant with a non-variant
+ if (!cacheVariantLines.contains(pos[0]))
+ g.drawLine(pos[0], pos[1], pos[0], pos[1] - LINE_HEIGHT + 6);
+ return;
+ }
+ }
+ else
+ g.setColor(Color.pink);
+
+ if (record.isMarkAsNewStop())
+ g.fillArc(pos[0] - 3, pos[1] - (LINE_HEIGHT / 2) - 3, 6, 6, 0, 360);
+
+ if (cacheVariantLines.size() == 5)
+ cacheVariantLines.clear();
+ cacheVariantLines.add(pos[0]);
+
+ g.drawLine(pos[0], pos[1], pos[0], pos[1] - LINE_HEIGHT);
+ }
+
+ /**
+ * Determine the colour depending on the colour scheme in use.
+ * @param record
+ * @param features
+ * @param basePosition
+ * @return
+ */
+ private Color getColourForSNP(VCFRecord record, FeatureVector features, int basePosition)
+ {
+ if(colourScheme == VARIANT_COLOUR_SCHEME)
+ return getVariantColour(record.getAlt().toString());
+ else if(colourScheme == SYN_COLOUR_SCHEME) // synonymous / non-synonymous
+ {
+ if(!record.getAlt().isNonVariant())
+ {
+ short synFlag = record.getSynFlag(features, basePosition);
+ if(synFlag == 1)
+ return Color.red;
+ else if(synFlag == 0 || synFlag == 2)
+ return Color.blue;
+ }
+ return getVariantColour(record.getAlt().toString());
+ }
+ else // score
+ return getQualityColour(record);
+ }
+
+ private Color getQualityColour(VCFRecord record)
+ {
+ if(colMap == null)
+ colMap = makeColours(Color.RED, 255);
+ int idx = (int) record.getQuality()-1;
+ if(idx > colMap.length-1)
+ idx = colMap.length-1;
+ else if(idx < 0)
+ idx = 0;
+ return colMap[idx];
+ }
+
+ private Color getVariantColour(String variant)
+ {
+ if(variant.equals("C"))
+ return Color.red;
+ else if(variant.equals("A"))
+ return Color.green;
+ else if(variant.equals("G"))
+ return Color.blue;
+ else if(variant.equals("T"))
+ return Color.black;
+ else
+ return lighterGrey; // non-variant
+ }
+
+ /**
+ * Generate the colours for heat map plots.
+ * @param col
+ * @param NUMBER_OF_SHADES
+ * @return
+ */
+ private Color[] makeColours(Color col, int NUMBER_OF_SHADES)
+ {
+ Color definedColour[] = new Color[NUMBER_OF_SHADES];
+ for(int i = 0; i < NUMBER_OF_SHADES; ++i)
+ {
+ int R = col.getRed();
+ int G = col.getGreen();
+ int B = col.getBlue();
+
+ float scale = ((float)(NUMBER_OF_SHADES-i) * (float)(255 / NUMBER_OF_SHADES )) ;
+
+ if((R+scale) <= 255)
+ R += scale;
+ else
+ R = 254;
+ if((G+scale) <= 255)
+ G += scale;
+ else
+ G = 254;
+ if((B+scale) <= 255)
+ B += scale;
+ else
+ B = 254;
+
+ definedColour[i] = new Color(R,G,B);
+ }
+ return definedColour;
+ }
+
+ private int[] getScreenPosition(int base, float pixPerBase, int start, int vcfFileIndex)
+ {
+ int pos[] = new int[2];
+ pos[0] = Math.round((base - start)*pixPerBase);
+ pos[1] = getYPostion(vcfFileIndex);
+ return pos;
+ }
+
+ private int getYPostion(int vcfFileIndex)
+ {
+ int pos = 0;
+ if(hideVcfList.size() == 0 || splitSamples)
+ pos = vcfFileIndex;
+ else
+ {
+ for(int i=0; i<vcfFileIndex; i++)
+ if(!hideVcfList.contains(i))
+ pos++;
+ }
+
+ return getHeight() - 15 - (pos*(LINE_HEIGHT+5));
+ }
+
+ private void findVariantAtPoint(Point mousePoint)
+ {
+ float pixPerBase = getPixPerBaseByWidth();
+ int startBase = getBaseAtStartOfView();
+ int start = startBase + (int)(mousePoint.getX()/pixPerBase) - 20;
+ int end = start+20;
+ FeatureVector features = getCDSFeaturesInRange(start, end);
+ int sumSamples = 0;
+
+ for (int i = 0; i < vcfReaders.length; i++)
+ {
+
+ if(concatSequences)
+ {
+ String[] contigs = vcfReaders[0].getSeqNames();
+ for(int k=0; k<contigs.length; k++)
+ {
+ int offset = getSequenceOffset(contigs[k]);
+ int nextOffset;
+ if(k<contigs.length-1)
+ nextOffset = getSequenceOffset(contigs[k+1]);
+ else
+ nextOffset = seqLength;
+
+ if( (offset >= start && offset < end) ||
+ (offset < start && start < nextOffset) )
+ {
+ int thisStart = start - offset;
+ if(thisStart < 1)
+ thisStart = 1;
+ int thisEnd = end - offset;
+ searchRegion(contigs[k], thisStart, thisEnd, i, sumSamples, mousePoint, features, startBase, pixPerBase);
+ }
+ }
+ }
+ else
+ {
+ int thisStart = start;
+ if(thisStart < 1)
+ thisStart = 1;
+ searchRegion(chr, thisStart, end, i, sumSamples, mousePoint, features, startBase, pixPerBase);
+ }
+
+
+ sumSamples += vcfReaders[i].getNumberOfSamples();
+ }
+ }
+
+ private void searchRegion(final String chr,
+ final int sbeg, final int send,
+ final int fileIndex,
+ final int sumSamples,
+ final Point mousePoint, FeatureVector features,
+ int start, float pixPerBase)
+ {
+ try
+ {
+ VCFRecord bcfRecord;
+ while((bcfRecord = vcfReaders[fileIndex].getNextRecord(chr, sbeg, send)) != null)
+ {
+
+ if(splitSamples)
+ {
+ for(int sampleIndex=0; sampleIndex<vcfReaders[fileIndex].getNumberOfSamples(); sampleIndex++)
+ {
+ int ypos = getYPostion(sampleIndex+sumSamples);
+ if(mousePoint.getY() > ypos &&
+ mousePoint.getY() < ypos-LINE_HEIGHT)
+ continue;
+
+ isMouseOver(mousePoint, bcfRecord, features, fileIndex, sampleIndex, sumSamples, start, pixPerBase, vcfReaders[fileIndex]);
+ }
+ }
+ else
+ {
+ int ypos = getYPostion(fileIndex);
+ if(mousePoint.getY() > ypos &&
+ mousePoint.getY() < ypos-LINE_HEIGHT)
+ continue;
+
+ isMouseOver(mousePoint, bcfRecord, features, fileIndex, -1, sumSamples, start, pixPerBase, vcfReaders[fileIndex]);
+ }
+
+
+ }
+ }
+ catch (IOException e)
+ {
+ logger4j.warn(chr+":"+sbeg+"-"+send+"\n"+e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ private void isMouseOver(final Point mousePoint,
+ final VCFRecord record,
+ final FeatureVector features,
+ final int vcfFileIndex,
+ final int sampleIndex,
+ final int sumSamples,
+ final int start, final float pixPerBase,
+ final AbstractVCFReader vcfReader)
+ {
+ int basePosition = record.getPos() + getSequenceOffset(record.getChrom());
+ if( !showVariant(record, features, basePosition, vcfReader, sampleIndex, vcfFileIndex) )
+ return;
+
+ int pos[];
+ if(!splitSamples)
+ pos = getScreenPosition(basePosition, pixPerBase, start, vcfFileIndex);
+ else
+ pos = getScreenPosition(basePosition, pixPerBase, start, sampleIndex+sumSamples);
+
+ if(mousePoint != null &&
+ mousePoint.getY() < pos[1] &&
+ mousePoint.getY() > pos[1]-LINE_HEIGHT &&
+ mousePoint.getX() > pos[0]-3 &&
+ mousePoint.getX() < pos[0]+3)
+ {
+ mouseVCF = record;
+ mouseOverIndex = vcfFileIndex;
+ mouseOverSampleIndex = sampleIndex;
+ }
+ }
+
+ private void drawScale(Graphics2D g2, int start, int end, float pixPerBase, int ypos)
+ {
+ g2.setColor(Color.black);
+ g2.drawLine( 0, ypos-14,
+ (int)((end - start)*pixPerBase), ypos-14);
+ int interval = end-start;
+
+ if(interval > 20000000)
+ drawTicks(g2, start, end, pixPerBase, 10000000, ypos);
+ else if(interval > 4000000)
+ drawTicks(g2, start, end, pixPerBase, 2000000, ypos);
+ else if(interval > 800000)
+ drawTicks(g2, start, end, pixPerBase, 400000, ypos);
+ else if(interval > 160000)
+ drawTicks(g2, start, end, pixPerBase, 80000, ypos);
+ else if(interval > 50000)
+ drawTicks(g2, start, end, pixPerBase, 25000, ypos);
+ else if(interval > 16000)
+ drawTicks(g2, start, end, pixPerBase, 8000, ypos);
+ else if(interval > 4000)
+ drawTicks(g2, start, end, pixPerBase, 2000, ypos);
+ else if(interval > 1000)
+ drawTicks(g2, start, end, pixPerBase, 500, ypos);
+ else
+ drawTicks(g2, start, end, pixPerBase, 100, ypos);
+ }
+
+ /**
+ * Handle a mouse drag event on the drawing canvas.
+ **/
+ private void handleCanvasMouseDrag(final MouseEvent event)
+ {
+ if(event.getButton() == MouseEvent.BUTTON3)
+ return;
+
+ if(event.getClickCount() > 1)
+ {
+ getSelection().clear();
+ repaint();
+ return;
+ }
+
+ highlightRange(event,
+ MouseEvent.BUTTON1_DOWN_MASK & MouseEvent.BUTTON2_DOWN_MASK);
+ }
+
+ /**
+ *
+ * @param event
+ * @param onmask
+ */
+ private void highlightRange(MouseEvent event, int onmask)
+ {
+ if(entryGroup == null)
+ return;
+ float pixPerBase = getPixPerBaseByWidth();
+ int start = (int) ( ( (event.getPoint().getX())/pixPerBase) + getBaseAtStartOfView() );
+
+ if(start < 1)
+ start = 1;
+ if(start > seqLength)
+ start = seqLength;
+
+ if (dragStart < 0 && (event.getModifiersEx() & onmask) == onmask)
+ dragStart = start;
+ else if((event.getModifiersEx() & onmask) != onmask)
+ dragStart = -1;
+
+ MarkerRange drag_range;
+ try
+ {
+ if(dragStart < 0)
+ drag_range = new MarkerRange (entryGroup.getSequenceEntry().getBases().getForwardStrand(), start, start);
+ else
+ drag_range = new MarkerRange (entryGroup.getSequenceEntry().getBases().getForwardStrand(), dragStart, start);
+ getSelection().setMarkerRange(drag_range);
+ repaint();
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void drawTicks(Graphics2D g2, int start, int end,
+ float pixPerBase, int division, int ypos)
+ {
+ int markStart = (Math.round(start/division)*division);
+
+ if(markStart < 1)
+ markStart = 1;
+
+ int sm = markStart-(division/2);
+ float x;
+ if(sm > start)
+ {
+ x = (sm-start)*pixPerBase;
+ g2.drawLine((int)x, ypos-14,(int)x, ypos-12);
+ }
+
+ for(int m=markStart; m<end; m+=division)
+ {
+ x = (m-start)*pixPerBase;
+ g2.drawString(Integer.toString(m), x, ypos-1);
+ g2.drawLine((int)x, ypos-14,(int)x, ypos-11);
+
+ sm = m+(division/2);
+
+ if(sm < end)
+ {
+ x = (sm-start)*pixPerBase;
+ g2.drawLine((int)x, ypos-14,(int)x, ypos-12);
+ }
+
+ if(m == 1)
+ m = 0;
+ }
+ }
+
+ protected float getPixPerBaseByWidth()
+ {
+ return (float)vcfPanel.getWidth() / (float)nbasesInView;
+ }
+
+ protected int getBasesInView()
+ {
+ return nbasesInView;
+ }
+
+
+ protected EntryGroup getEntryGroup()
+ {
+ return entryGroup;
+ }
+
+ protected String getChr()
+ {
+ return chr;
+ }
+
+ protected boolean isConcatenate()
+ {
+ return concatSequences;
+ }
+
+ /**
+ * @return the combo
+ */
+ public SequenceComboBox getCombo()
+ {
+ return combo;
+ }
+
+ /**
+ * @return the vcfReaders
+ */
+ protected AbstractVCFReader[] getVcfReaders()
+ {
+ return vcfReaders;
+ }
+
+
+ private Container getVcfContainer()
+ {
+ try
+ {
+ Frame fs[] = JFrame.getFrames();
+ for(Frame f: fs)
+ {
+ if( f instanceof JFrame &&
+ ((JFrame)f) instanceof EntryEdit ||
+ ((JFrame)f) instanceof MultiComparator)
+ return ((JFrame)f).getContentPane();
+ }
+ }
+ catch(Exception e){}
+ return VCFview.this;
+ }
+
+ /**
+ * Popup menu listener
+ */
+ class PopupListener extends MouseAdapter
+ {
+ JMenuItem showDetails;
+ JMenuItem annotate;
+
+ public void mouseClicked(MouseEvent e)
+ {
+ if(e.isPopupTrigger() ||
+ e.getButton() == MouseEvent.BUTTON3)
+ return;
+
+ if(e.getClickCount() > 1)
+ {
+ getSelection().clear();
+ repaint();
+ }
+ }
+
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ dragStart = -1;
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ {
+ if(showDetails != null)
+ {
+ popup.remove(showDetails);
+ popup.remove(annotate);
+ }
+
+ mouseVCF = null;
+ findVariantAtPoint(e.getPoint());
+ final VCFRecord thisMouseVCF = mouseVCF;
+ final int thisMouseOverIndex = mouseOverIndex;
+ if( thisMouseVCF != null )
+ {
+ showDetails = new JMenuItem("Show details of : "+
+ thisMouseVCF.getChrom()+":"+thisMouseVCF.getPos()+" "+thisMouseVCF.getID());
+ showDetails.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ FileViewer viewDetail = new FileViewer(
+ thisMouseVCF.getChrom()+":"+thisMouseVCF.getPos()+" "+thisMouseVCF.getID(), true, false, true);
+
+ viewDetail.appendString(vcfReaders[mouseOverIndex].getHeader()+"\n", Level.INFO);
+
+ viewDetail.appendString("Seq : "+thisMouseVCF.getChrom()+"\n", Level.DEBUG);
+ viewDetail.appendString("Pos : "+thisMouseVCF.getPos()+"\n", Level.DEBUG);
+ viewDetail.appendString("ID : "+thisMouseVCF.getID()+"\n", Level.DEBUG);
+ viewDetail.appendString("Ref : "+thisMouseVCF.getRef()+"\n", Level.DEBUG);
+ viewDetail.appendString("Alt : "+thisMouseVCF.getAlt().toString()+"\n", Level.DEBUG);
+ viewDetail.appendString("Qual : "+thisMouseVCF.getQuality()+"\n", Level.DEBUG);
+ viewDetail.appendString("Filter: "+thisMouseVCF.getFilter()+"\n", Level.DEBUG);
+ viewDetail.appendString("Info : "+thisMouseVCF.getInfo()+"\n", Level.DEBUG);
+
+ if(thisMouseVCF.getFormat() != null)
+ {
+ viewDetail.appendString("\nGenotype information:\n", Level.INFO);
+ viewDetail.appendString("Format: "+thisMouseVCF.getFormat()+"\n", Level.DEBUG);
+ viewDetail.appendString(thisMouseVCF.getSampleDataString()+"\n", Level.DEBUG);
+ }
+
+ viewDetail.getTextPane().setCaretPosition(0);
+ }
+ });
+ popup.add(showDetails);
+
+ annotate = new JMenuItem("Manual PASS / FAIL");
+ annotate.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ new ManualAnnotation(thisMouseVCF, thisMouseOverIndex);
+ }
+ });
+ popup.add(annotate);
+ }
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ }
+ }
+
+ private void setDisplay()
+ {
+ Dimension d = new Dimension();
+
+ if(splitSamples)
+ {
+ int count = 0;
+ for(int i=0; i<vcfReaders.length; i++)
+ count += vcfReaders[i].getNumberOfSamples();
+ d.setSize(nbasesInView*getPixPerBaseByWidth(), (count+1)*(LINE_HEIGHT+5));
+ }
+ else
+ d.setSize(nbasesInView*getPixPerBaseByWidth(), (vcfReaders.length+1)*(LINE_HEIGHT+5));
+ setPreferredSize(d);
+ }
+
+ public void displayAdjustmentValueChanged(DisplayAdjustmentEvent event)
+ {
+ nbasesInView = feature_display.getMaxVisibleBases();
+ setDisplay();
+ repaint();
+ }
+
+ public void selectionChanged(SelectionChangeEvent event)
+ {
+ repaint();
+ }
+
+ /**
+ * Manual annotation of a variant record.
+ */
+ class ManualAnnotation extends JFrame
+ {
+ private static final long serialVersionUID = 1L;
+
+ ManualAnnotation(final VCFRecord thisMouseVCF, final int thisMouseOverIndex)
+ {
+ super("Manual PASS / FAIL");
+ final JPanel pane = (JPanel) getContentPane();
+ pane.setLayout(new BorderLayout());
+ final JPanel panel = new JPanel(new GridBagLayout());
+ final JScrollPane jsp = new JScrollPane(panel);
+ jsp.setPreferredSize(new Dimension(350, 180));
+ pane.add(jsp, BorderLayout.NORTH);
+
+ final GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 3;
+ c.anchor = GridBagConstraints.NORTHWEST;
+ panel.add(new JLabel("Seq : "+thisMouseVCF.getChrom()), c);
+ c.gridy++;
+ panel.add(new JLabel("Pos : "+thisMouseVCF.getPos()), c);
+ c.gridy++;
+ panel.add(new JLabel("ID : "+thisMouseVCF.getPos()), c);
+ c.gridy++;
+ panel.add(new JLabel("Ref : "+thisMouseVCF.getRef()), c);
+ c.gridy++;
+ panel.add(new JLabel("Alt : "+thisMouseVCF.getAlt().toString()), c);
+ c.gridy++;
+ panel.add(new JLabel("Qual : "+thisMouseVCF.getQuality()), c);
+ c.gridy++;
+ panel.add(new JLabel("Filter: "+thisMouseVCF.getFilter()), c);
+ c.gridy++;
+ panel.add(new JLabel("Info : "+thisMouseVCF.getInfo()), c);
+
+ final JRadioButton passB = new JRadioButton("PASS");
+ final JRadioButton failB = new JRadioButton("FAIL");
+ final JRadioButton noManualB = new JRadioButton("NO MANUAL FILTER", true);
+
+ final ButtonGroup group = new ButtonGroup();
+ group.add(passB);
+ group.add(failB);
+ group.add(noManualB);
+
+ int res = VCFFilter.checkManualHash(manualHash, thisMouseVCF, thisMouseOverIndex, false);
+ switch(res)
+ {
+ case 1: passB.setSelected(true);
+ break;
+ case 2: failB.setSelected(true);
+ break;
+ default: noManualB.setSelected(true);
+ break;
+ }
+
+ c.gridy++;
+ panel.add(Box.createVerticalStrut(10), c);
+
+ c.gridwidth = 1;
+ c.gridy++;
+ panel.add(passB, c);
+ c.gridx++;
+ panel.add(failB, c);
+ c.gridx++;
+ panel.add(noManualB, c);
+
+ final JButton apply = new JButton("Apply");
+ apply.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent arg0)
+ {
+ setVisible(false);
+ if(passB.isSelected())
+ manualHash.put(thisMouseVCF.getPos()+":"+thisMouseVCF.getChrom()+":"+thisMouseOverIndex, true);
+ else if(failB.isSelected())
+ manualHash.put(thisMouseVCF.getPos()+":"+thisMouseVCF.getChrom()+":"+thisMouseOverIndex, false);
+ else
+ manualHash.remove(thisMouseVCF.getPos()+":"+thisMouseVCF.getChrom()+":"+thisMouseOverIndex);
+ VCFview.this.repaint();
+ ManualAnnotation.this.dispose();
+ }
+ });
+ pane.add(apply, BorderLayout.SOUTH);
+ pack();
+ Utilities.centreFrame(this);
+ setVisible(true);
+ }
+ }
+
+ public static void main(String args[])
+ {
+ List<String> vcfFileList = new Vector<String>();
+ String reference = null;
+ if(args.length == 0)
+ {
+ System.setProperty("default_directory", System.getProperty("user.dir"));
+ FileSelectionDialog fileSelection = new FileSelectionDialog(
+ null, true, "VCFview", "VCF");
+ vcfFileList = fileSelection.getFiles(VCFFILE_SUFFIX);
+ reference = fileSelection.getReferenceFile();
+ if(reference.equals(""))
+ reference = null;
+
+ if(vcfFileList == null || vcfFileList.size() < 1)
+ System.exit(0);
+ }
+ else if(!args[0].startsWith("-"))
+ {
+ for(int i=0; i< args.length; i++)
+ vcfFileList.add(args[i]);
+ }
+
+ int nbasesInView = 5000000;
+
+ for(int i=0;i<args.length; i++)
+ {
+ if(args[i].equals("-f"))
+ {
+ while(i < args.length-1 && !args[++i].startsWith("-"))
+ {
+ String filename = args[i];
+ if(FileSelectionDialog.isListOfFiles(filename))
+ vcfFileList.addAll(FileSelectionDialog.getListOfFiles(filename));
+ else
+ vcfFileList.add(filename);
+ }
+ --i;
+ }
+ else if(args[i].equals("-r"))
+ reference = args[++i];
+ else if(args[i].equals("-v"))
+ nbasesInView = Integer.parseInt(args[++i]);
+ else if(args[i].startsWith("-h"))
+ {
+ System.out.println("-h\t show help");
+
+ System.out.println("-f\t VCF file to display");
+ System.out.println("-r\t reference file (optional)");
+ System.out.println("-v\t number of bases to display in the view (optional)");
+ //System.out.println("-t\t chr:start-end - this writes out the given region");
+
+ System.exit(0);
+ }
+ }
+
+ JFrame f = new JFrame();
+ new VCFview(f, (JPanel) f.getContentPane(), vcfFileList,
+ nbasesInView, 100000000, null, reference, null, null);
+
+ f.pack();
+ f.setVisible(true);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/components/variant/VariantBase.java b/uk/ac/sanger/artemis/components/variant/VariantBase.java
new file mode 100644
index 0000000..fae106f
--- /dev/null
+++ b/uk/ac/sanger/artemis/components/variant/VariantBase.java
@@ -0,0 +1,120 @@
+
+package uk.ac.sanger.artemis.components.variant;
+
+import java.util.regex.Pattern;
+
+public class VariantBase
+{
+ private VCFRecord record;
+ private String alt;
+ protected static Pattern COMMA_PATTERN = Pattern.compile(",");
+
+ public VariantBase(VCFRecord record, String alt)
+ {
+ this.record = record;
+ this.alt = alt;
+ }
+
+ public String toString()
+ {
+ return alt;
+ }
+
+ protected int length()
+ {
+ return alt.length();
+ }
+
+ /**
+ * Is this a deletion type.
+ * @param variant
+ * @return
+ */
+ protected boolean isDeletion(boolean vcf_v4)
+ {
+ if (vcf_v4)
+ {
+ if (alt.length() < record.getRef().length() && !(alt.indexOf(",") > -1))
+ return true;
+ }
+ else if (alt.indexOf("D") > -1)
+ return true;
+ return false;
+ }
+
+ /**
+ * Is this an insertion type.
+ *
+ * @param variant
+ * @return
+ */
+ protected boolean isInsertion(boolean vcf_v4)
+ {
+ if (vcf_v4)
+ {
+ if (alt.length() > record.getRef().length() && !(alt.indexOf(",") > -1))
+ return true;
+ }
+ else if (alt.indexOf("I") > -1)
+ return true;
+ return false;
+ }
+
+ protected boolean isMultiAllele(int sampleIndex)
+ {
+ if (VCFRecord.MULTI_ALLELE_PATTERN.matcher(alt).matches())
+ return true;
+
+ // look at probability of each genotype (PL) information as well
+ if(sampleIndex < 0)
+ sampleIndex = 0;
+ final String pl = record.getFormatValueForSample("PL", sampleIndex);
+ if(pl != null)
+ {
+ final String pls[] = COMMA_PATTERN.split(pl);
+ if(pls.length == 3 && pls[1].equals("0")) // middle value is zero, e.g.
+ return true;
+ }
+
+ return false;
+ }
+
+ protected int getNumAlleles()
+ {
+ return VCFRecord.countOccurrences(alt, ',')+2;
+ //return COMMA_PATTERN.split(alt).length + 1;
+ //return alt.split(",").length + 1;
+ }
+
+ protected int getNumberOfIndels(boolean vcf_v4)
+ {
+ if (vcf_v4)
+ {
+ if (alt.equals("."))
+ return record.getRef().length();
+ return Math.abs(record.getRef().length() - alt.length());
+ }
+
+ int index = alt.indexOf("D");
+ if(index < 0)
+ index = alt.indexOf("I");
+ int ndel = 0;
+ try
+ {
+ ndel = Integer.parseInt(alt.substring(index + 1));
+ }
+ catch (NumberFormatException e)
+ {
+ System.out.println(alt);
+ e.printStackTrace();
+ }
+ return ndel;
+ }
+
+ protected boolean isNonVariant()
+ {
+ if ((alt.equals(".") || alt.equals("X")) && record.getRef().length() == 1)
+ return true;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/editor/Annotation.java b/uk/ac/sanger/artemis/editor/Annotation.java
new file mode 100644
index 0000000..a9b625c
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/Annotation.java
@@ -0,0 +1,634 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import java.util.StringTokenizer;
+import java.util.Vector;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import javax.swing.text.html.*;
+import javax.swing.text.Document;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.io.IOException;
+import javax.swing.text.BadLocationException;
+import java.net.URL;
+
+public class Annotation extends JEditorPane
+ implements HyperlinkListener
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ private int startRange;
+ //private int endRange;
+ /** busy cursor */
+ private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
+ /** done cursor */
+ private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
+ /** desktop pane */
+ private JDesktopPane desktop = null;
+ /** back option */
+ private Vector back = new Vector();
+ /** popup menu */
+ private JPopupMenu popup;
+ /** known qualifiers */
+ private Vector qualifier = new Vector();
+
+ public Annotation(JDesktopPane desktop)
+ {
+ super();
+ this.desktop = desktop;
+
+ setEditable(false);
+ setContentType("text/html");
+ setFont(BigPane.font);
+ addHyperlinkListener(this);
+ }
+
+
+ public Annotation(URL url) throws IOException
+ {
+ super(url);
+
+ setEditable(false);
+ addHyperlinkListener(this);
+
+// popup
+ addMouseListener(new PopupListener());
+ popup = new JPopupMenu();
+ JMenuItem backMenu = new JMenuItem("Back");
+ popup.add(backMenu);
+ backMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ goBack();
+ }
+ });
+ back.add(url);
+ }
+
+ protected void setAnnotation(String text)
+ {
+// setText("<html><body>"+text+"</html></body>");
+// reportHTML();
+// startRange = getDocument().getLength();
+ String line = null;
+
+// record qualifiers used
+ try
+ {
+ BufferedReader buffRead = new BufferedReader(new StringReader(text));
+ while((line = buffRead.readLine()) != null)
+ {
+ int ind = line.indexOf("=");
+ if(ind > -1)
+ qualifier.add(line.substring(0,ind+1).toLowerCase());
+ }
+ }
+ catch(IOException ioe){}
+ qualifier.add("/similarity=");
+ qualifier.add("/gene=");
+ qualifier.add("/GO_component=");
+ qualifier.add("/product=");
+ qualifier.add("/EC_number=");
+ qualifier.add("/note=");
+
+ text = getDatabaseHTML(text,"SWALL:");
+ text = getDatabaseHTML(text,"UniProt:");
+ text = getDatabaseHTML(text,"EMBL:");
+ setText("<html><body><font size=3>"+text+"</font></html></body>");
+ startRange = getDocument().getLength();
+ }
+
+
+ protected void reportHTML()
+ {
+ try
+ {
+ String txt = ((HTMLDocument)getDocument()).getText(0,getDocument().getLength());
+ System.out.println("TXT:\n"+txt);
+ System.out.println("\nHTML:\n"+getText()+"\n\n");
+ }
+ catch(BadLocationException ble)
+ {
+ ble.printStackTrace();
+ }
+ }
+
+
+ protected String getFeatureText()
+ {
+ String txt = "";
+ try
+ {
+ txt = ((HTMLDocument)getDocument()).getText(0,getDocument().getLength()).trim();
+
+ StringBuffer buff = new StringBuffer();
+ StringTokenizer tok = new StringTokenizer(txt,"/");
+ int ntok = 0;
+
+ while(tok.hasMoreTokens())
+ {
+ String tokTxt = "/"+tok.nextToken().trim();
+
+ int ind = tokTxt.indexOf("=");
+
+ if(ntok != 0 && ind > -1 && qualifier.contains(tokTxt.substring(0,ind+1)))
+ buff.append("\n"+tokTxt);
+ else
+ buff.append(tokTxt);
+
+ ntok++;
+ }
+
+ txt = buff.toString();
+ }
+ catch(BadLocationException ble)
+ {
+ ble.printStackTrace();
+ }
+
+ return txt;
+ }
+
+
+ protected void insert(String s, boolean ortholog)
+ {
+ s = getDatabaseHTML(s,"SWALL:");
+ s = getDatabaseHTML(s,"UniProt:");
+ s = getDatabaseHTML(s,"EMBL:");
+ s = getGeneDBHTML(s);
+
+// int ind = s.indexOf("/gene");
+
+ Document doc = getDocument();
+ int offset = doc.getLength();
+ if(ortholog)
+ offset = startRange;
+
+ insert(s,offset);
+
+ setCaretPosition(doc.getLength());
+// reportHTML();
+ }
+
+
+ protected void insert(String s, int offset)
+ {
+ try
+ {
+ HTMLEditorKit edKit = (HTMLEditorKit)getEditorKit();
+// ((HTMLDocument)getDocument()).insertString(offset,"\n",null);
+ edKit.insertHTML((HTMLDocument)getDocument(),offset,"<BR>\n"+s,0,0,HTML.Tag.BR);
+// HTMLEditorKit.InsertHTMLTextAction("similarity",s,HTML.Tag.BODY,HTML.Tag.P);
+ }
+ catch(BadLocationException ble)
+ {
+ System.out.println("Offset "+offset);
+ ble.printStackTrace();
+ }
+ catch(Exception exp)
+ {
+ exp.printStackTrace();
+ }
+ }
+
+
+ private String getGeneDBHTML(String s)
+ {
+ int ind = s.indexOf("GeneDB");
+ if(ind>-1)
+ {
+ String startStr = s.substring(0,ind);
+ int ind2 = s.indexOf(";",ind);
+
+ String midStr = s.substring(ind,ind2);
+ String endStr = s.substring(ind2);
+
+ ind2 = midStr.indexOf(":")+1;
+
+ String db = midStr.substring(7,8)+".+"+
+ midStr.substring(8,ind2-1);
+ String genedb = "http://www.genedb.org/genedb/Search?name="+
+ midStr.substring(ind2)+
+ "&organism="+db;
+
+ s = startStr + "<a href=\""+genedb+"\">" +
+ midStr + "</a>" + endStr;
+ }
+ return s;
+ }
+
+
+ private String getDatabaseHTML(String s, String db)
+ {
+// int ind = s.indexOf(db);
+
+// if(ind == -1)
+// ind = s.indexOf(db.toLowerCase());
+
+// if(ind>-1)
+// {
+// s = setHyperLinks(s,ind);
+// String startStr = s.substring(0,ind);
+// int ind2 = s.indexOf(" ",ind);
+// int ind3 = s.indexOf(")",ind);
+// if(ind3>-1 && ind3<ind2)
+// ind2 = ind3;
+// ind3 = s.indexOf(";",ind);
+// if(ind3>-1 && ind3<ind2)
+// ind2 = ind3;
+
+// String midStr = s.substring(ind,ind2);
+// String endStr = s.substring(ind2);
+
+// String srscmd = "http://srs.sanger.ac.uk/srsbin/cgi-bin/wgetz?-e+" +
+// "["+midStr+"]";
+
+// s = startStr + "<a href=\""+srscmd+"\">" +
+// midStr + "</a>" + endStr;
+// }
+
+ int ind = 0;
+ while((ind = s.indexOf(db, ind)) > -1)
+ {
+ s = setHyperLinks(s,ind);
+ ind = s.lastIndexOf("</a>");
+ }
+
+ db = db.toLowerCase();
+ ind = 0;
+ while((ind = s.indexOf(db+":", ind)) > -1)
+ {
+ s = setHyperLinks(s,ind);
+ ind = s.lastIndexOf("</a>");
+ }
+
+ return s;
+ }
+
+ private String setHyperLinks(String s, int ind)
+ {
+ String startStr = s.substring(0,ind);
+ int ind2 = s.indexOf(" ",ind);
+ if(ind2 == -1)
+ ind2 = s.indexOf(";",ind);
+
+ int ind3 = s.indexOf(")",ind);
+ if(ind3>-1 && ind3<ind2)
+ ind2 = ind3;
+ ind3 = s.indexOf(";",ind);
+ if(ind3>-1 && ind3<ind2)
+ ind2 = ind3;
+ ind3 = s.indexOf("\"",ind);
+ if(ind3>-1 && ind3<ind2)
+ ind2 = ind3;
+
+ String midStr = s.substring(ind,ind2);
+ String endStr = s.substring(ind2);
+ String srscmd = DataCollectionPane.srs_url+"/wgetz?-e+["+midStr+"]";
+
+ // link to uniprot accession
+ if( (ind2 = srscmd.indexOf("UniProt:")) > -1)
+ srscmd = srscmd.substring(0,ind2+7)+"-acc:"+
+ srscmd.substring(ind2+8);
+
+ if(srscmd.indexOf("ebi.ac.uk") > -1)
+ srscmd = srscmd + "+-vn+2";
+
+ return startStr + "<a href=\""+srscmd+"\">" +
+ midStr + "</a>" + endStr;
+ }
+
+ /*private void replaceRange(String newStr,int start,int end)
+ {
+ HTMLDocument doc = (HTMLDocument)getDocument();
+
+ try
+ {
+ doc.remove(start,(end-start));
+ insert(newStr,start);
+ }
+ catch(BadLocationException ble)
+ {
+ ble.printStackTrace();
+ }
+ setDocument(doc);
+ }*/
+
+
+ /**
+ *
+ * Deletes the annotation line that contains an ID.
+ *
+ */
+ protected void deleteGo(String id, String go_id)
+ {
+ String txt = getText();
+ int ind1 = 0;
+ int ind2 = 0;
+ int indID = 0;
+
+ while((ind2 = txt.indexOf("<br>",ind1)) > -1 ||
+ (ind2 = txt.indexOf("</body>",ind1)) > -1)
+ {
+ String line = txt.substring(ind1,ind2);
+
+ if( ((indID = line.indexOf(id)) > -1) &&
+ (line.indexOf(go_id) > -1) )
+ break;
+ else
+ ind1 = ind2+1;
+ }
+
+// ind2 = txt.indexOf("<br>",indID);
+
+ if(ind2 == -1 || ind2 > txt.length())
+ ind2 = txt.length();
+
+ if(ind1 == 0)
+ return;
+
+ setText(txt.substring(0,ind1-1)+txt.substring(ind2));
+ }
+
+ /**
+ *
+ * Deletes the annotation similarity line that contains an ID.
+ *
+ */
+ protected void delete(String id, boolean ortholog)
+ {
+ String txt = getText();
+
+ int ind1 = 0;
+ int ind2 = 0;
+ int indID = 0;
+ String line = null;
+
+ while((ind2 = txt.indexOf("<br>",ind1)) > -1 ||
+ (ind2 = txt.indexOf("</body>",ind1)) > -1)
+ {
+ line = txt.substring(ind1,ind2);
+
+ if((line.indexOf("/similarity=") > -1) &&
+ ((indID = line.indexOf(id)) > -1) &&
+ (line.indexOf("GO:") == -1) )
+ break;
+ else
+ ind1 = ind2+1;
+ }
+
+ // if ortholog then delete gene and product lines as well
+ if(ortholog)
+ {
+ if(ind1 > -1)
+ ind2 = txt.indexOf("/product=",ind1);
+ else
+ ind2 = txt.indexOf("/product=");
+
+ if(ind2 > -1)
+ ind2 = txt.indexOf("<br>",ind2+4);
+ }
+
+ if(ind2 == -1 || ind2 > txt.length())
+ ind2 = txt.length();
+
+ if(ind1 == 0)
+ return;
+
+ setText(txt.substring(0,ind1-1)+txt.substring(ind2));
+ }
+
+
+
+ /**
+ *
+ * Deletes the annotation note line that contains an ID.
+ *
+ */
+ protected void deleteNote()
+ {
+ String txt = getText();
+
+ int ind1 = 0;
+ int ind2 = 0;
+ int indID = 0;
+ String line = null;
+
+ while((ind2 = txt.indexOf("<br>",ind1)) > -1 ||
+ (ind2 = txt.indexOf("</body>",ind1)) > -1)
+ {
+ line = txt.substring(ind1,ind2);
+
+ if(line.indexOf("/note="Similar to ") > -1)
+ break;
+ else
+ ind1 = ind2+1;
+ }
+
+ if(ind2 == -1 || ind2 > txt.length())
+ ind2 = txt.length();
+
+ if(ind1 == 0)
+ return;
+
+ setText(txt.substring(0,ind1-1)+txt.substring(ind2));
+ }
+
+
+ protected void setUpSRSFrame(URL url, String search)
+ throws IOException
+ {
+ if(BigPane.srsFrame == null)
+ {
+ BigPane.setUpSRSFrame((2*desktop.getHeight())/3,desktop);
+ Border loweredbevel = BorderFactory.createLoweredBevelBorder();
+ Border raisedbevel = BorderFactory.createRaisedBevelBorder();
+ Border compound = BorderFactory.createCompoundBorder(raisedbevel,loweredbevel);
+
+ JTextField statusField = new JTextField();
+ statusField.setBorder(compound);
+ statusField.setEditable(false);
+ BigPane.srsFrame.getContentPane().add(statusField, BorderLayout.SOUTH);
+ }
+
+ Annotation edPane = new Annotation(url);
+ JScrollPane jsp = new JScrollPane(edPane);
+ JTabbedPane jtab = (JTabbedPane)BigPane.srsFrame.getContentPane().getComponent(0);
+ jtab.insertTab(search, null,jsp,null,0);
+ BigPane.srsFrame.setVisible(true);
+ }
+
+ protected void goBack()
+ {
+ if(back.size() < 2)
+ return;
+
+ try
+ {
+ URL url = (URL)back.get(back.size()-2);
+ back.remove(back.size()-1);
+ setPage(url);
+ }
+ catch(IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ }
+
+ /**
+ *
+ * Method to handle hyper link events.
+ * @param event hyper link event
+ *
+ */
+ public void hyperlinkUpdate(HyperlinkEvent event)
+ {
+ if(event.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
+ {
+ setCursor(cbusy);
+ try
+ {
+ URL url = event.getURL();
+
+ int ind1 = event.getDescription().indexOf("[");
+ int ind2 = event.getDescription().lastIndexOf("]");
+
+ String search = "";
+ if(ind1 > -1 && ind2 > -1)
+ search = event.getDescription().substring(ind1+1,ind2);
+ else
+ {
+ ind1 = event.getDescription().indexOf("=")+1; // genedb
+ if(ind1 > -1)
+ search = event.getDescription().substring(ind1);
+ }
+
+ if(desktop != null)
+ {
+ if(BigPane.srsTabPane.isSelected())
+ setUpSRSFrame(url,search);
+
+ if(BigPane.srsWin.isSelected())
+ {
+ int hgt = (2*desktop.getHeight())/3;
+ Annotation edPane = new Annotation(url);
+ JScrollPane jsp = new JScrollPane(edPane);
+ JInternalFrame jif = new JInternalFrame("SRS "+search,
+ true, //resizable
+ true, //closable
+ true, //maximizable
+ true);//iconifiable);
+ JMenuBar menuBar = new JMenuBar();
+ menuBar.add(new CommonMenu(jif));
+ jif.setJMenuBar(menuBar);
+ jif.getContentPane().add(jsp);
+ jif.setLocation(0,0);
+ jif.setSize(800,hgt);
+ jif.setVisible(true);
+ desktop.add(jif);
+ }
+
+ if(BigPane.srsBrowser.isSelected())
+ BrowserControl.displayURL(event.getDescription());
+ }
+ else
+ {
+ setPage(url);
+ back.add(url);
+ }
+ }
+ catch(IOException ioe)
+ {
+ String msg = event.getDescription();
+ if(msg.length() > 50)
+ msg = msg.substring(0,50)+"....";
+
+ JOptionPane.showMessageDialog(this,
+ "Cannot reach URL:\n"+msg,
+ "Cannot Connect",
+ JOptionPane.INFORMATION_MESSAGE);
+// ioe.printStackTrace();
+// ("Can't follow link to " +
+// event.getURL().toExternalForm() );
+ }
+
+ setCursor(cdone);
+ }
+ else if(event.getEventType() == HyperlinkEvent.EventType.ENTERED)
+ {
+ try
+ {
+ JTextField statusField = (JTextField)BigPane.srsFrame.getContentPane().getComponent(1);
+ statusField.setText(event.getDescription());
+ }
+ catch(Exception exp){}
+
+ }
+ else if(event.getEventType() == HyperlinkEvent.EventType.EXITED)
+ {
+ try
+ {
+ JTextField statusField = (JTextField)BigPane.srsFrame.getContentPane().getComponent(1);
+ statusField.setText("");
+ }
+ catch(Exception exp){}
+
+ }
+
+ }
+
+ /**
+ *
+ * Popup menu listener
+ *
+ */
+ class PopupListener extends MouseAdapter
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger() && back.size() > 1)
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/editor/BigPane.java b/uk/ac/sanger/artemis/editor/BigPane.java
new file mode 100644
index 0000000..ab267c5
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/BigPane.java
@@ -0,0 +1,692 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.util.Hashtable;
+
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JDesktopPane;
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTabbedPane;
+import javax.swing.JToolBar;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.components.QualifierTextArea;
+import uk.ac.sanger.artemis.components.genebuilder.cv.CVPanel;
+import uk.ac.sanger.artemis.components.genebuilder.ortholog.MatchPanel;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.EntryInformation;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.Qualifier;
+import uk.ac.sanger.artemis.io.QualifierInfo;
+import uk.ac.sanger.artemis.io.QualifierVector;
+import uk.ac.sanger.artemis.io.StreamQualifier;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+public class BigPane extends JFrame
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ protected static Font font = new Font("Monospaced",Font.PLAIN,11);
+ protected static Font font_sm = new Font("Monospaced",Font.PLAIN,10);
+ protected static JCheckBoxMenuItem srsBrowser;
+ protected static JCheckBoxMenuItem srsTabPane;
+ protected static JCheckBoxMenuItem srsWin;
+ protected static JInternalFrame srsFrame;
+ protected static JCheckBox addNote = new JCheckBox("Add Note");
+
+ public static int CACHE_SIZE = 300;
+ public static int MAX_CACHE_SIZE = 1000;
+ private QualifierTextArea qualifierTextArea;
+ private DataViewInternalFrame dataView;
+ //private FeatureVector overlapFeature;
+ private Feature edit_feature;
+ private JDesktopPane desktop = null;
+ private MatchPanel matchForm;
+ private CVPanel cvForm;
+ private EntryInformation entryInformation;
+
+ public BigPane(EntryInformation entryInformation)
+ {
+ super("Object Editor");
+ addWindowListener(new winExit());
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ MultiLineToolTipUI.initialize();
+ this.entryInformation = entryInformation;
+ }
+
+ public void set(Hashtable dataFile, QualifierTextArea qualifierTextArea,
+ FeatureVector overlapFeature,
+ final Feature edit_feature,
+ final MatchPanel matchForm,
+ final CVPanel cvForm)
+ {
+ this.matchForm = matchForm;
+ this.cvForm = cvForm;
+ set(dataFile,qualifierTextArea.getText(),overlapFeature,edit_feature);
+
+ this.qualifierTextArea = qualifierTextArea;
+ }
+
+ public void set(Hashtable dataFile, String qualifier_txt,
+ FeatureVector overlapFeature,
+ final Feature edit_feature)
+ {
+ if(matchForm != null || cvForm != null)
+ {
+ QualifierVector matchQualifiers = matchForm.getMatchQualifiers();
+
+ if(matchQualifiers != null)
+ qualifier_txt = getQualifierString(matchQualifiers);
+ Qualifier productQualifier = cvForm.getCvQualifiers().getQualifierByName("product");
+
+ if(productQualifier != null)
+ {
+ QualifierVector qv = new QualifierVector();
+ qv.addQualifierValues(productQualifier);
+ qualifier_txt = qualifier_txt + getQualifierString(qv) + "\n";
+ }
+ }
+
+
+ //this.overlapFeature = overlapFeature;
+ this.edit_feature = edit_feature;
+ addNote.setSelected(false);
+
+ setFont(font);
+
+ if(desktop == null)
+ {
+ desktop = new JDesktopPane();
+ desktop.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
+ getContentPane().add(desktop);
+ }
+
+ //Make the big window be indented 80 pixels from each edge
+ //of the screen.
+ final int inset = 80;
+ final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+
+ if(screenSize.width > 1280)
+ screenSize.width = 1280;
+
+ setBounds(inset, inset,
+ screenSize.width - inset*2,
+ screenSize.height - inset*2);
+
+ final JScrollPane scrollEvidence = new JScrollPane();
+ // data set
+ final int hgt = getHeight()-85;
+ final int wid = getWidth()/2-10;
+
+ dataView = new DataViewInternalFrame(dataFile,desktop, scrollEvidence,
+ wid,hgt,qualifier_txt,edit_feature);
+ dataView.setLocation(5,0);
+ dataView.setSize(wid,hgt);
+ dataView.setVisible(true);
+ desktop.add(dataView);
+
+ // evidence
+ final JInternalFrame evidence = new JInternalFrame("Evidence", true,
+ true, true, true);
+
+ Box evidenceBox = dataView.getEvidenceBox();
+ if(overlapFeature != null)
+ evidenceBox.add(getOverlapFeatures(overlapFeature,desktop),0);
+ evidenceBox.add(Box.createVerticalGlue());
+
+ ScrollPanel scroller = new ScrollPanel();
+ scroller.add(evidenceBox);
+ scrollEvidence.setViewportView(scroller);
+ evidence.getContentPane().add(scrollEvidence);
+ evidence.setLocation(wid+10,0);
+ evidence.setSize(wid,hgt);
+ evidence.setVisible(true);
+ desktop.add(evidence);
+
+ final JMenuBar menuBar = createMenuBar(desktop);
+ setJMenuBar(menuBar);
+
+ // toolbar
+ final JToolBar toolBar = createToolbar();
+ getContentPane().add(toolBar,BorderLayout.NORTH);
+
+ setVisible(true);
+ }
+
+ /**
+ * Return a string containing one qualifier per line. These are the
+ * original qualifiers, not the qualifiers from the qualifier_text_area.
+ **/
+ private String getQualifierString(QualifierVector qualifiers)
+ {
+ final StringBuffer buffer = new StringBuffer();
+
+ for(int qualifier_index = 0; qualifier_index < qualifiers.size();
+ ++qualifier_index)
+ {
+ final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(qualifier_index);
+
+
+ final QualifierInfo qualifier_info =
+ entryInformation.getQualifierInfo(this_qualifier.getName());
+
+ final StringVector qualifier_strings =
+ StreamQualifier.toStringVector(qualifier_info, this_qualifier);
+
+ for(int value_index = 0; value_index < qualifier_strings.size();
+ ++value_index)
+ {
+ final String qualifier_string = (String)qualifier_strings.elementAt(value_index);
+ buffer.append(qualifier_string + "\n");
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ *
+ * Display for overlapping Pfam features.
+ *
+ */
+ private Box getOverlapFeatures(FeatureVector overlapFeature,
+ JDesktopPane desktop)
+ {
+ Box bdown = Box.createVerticalBox();
+ bdown.add(new EvidenceViewer(edit_feature,overlapFeature,desktop));
+ return bdown;
+ }
+
+ /**
+ *
+ * Create a toolbar
+ * @return toolbar.
+ *
+ */
+ private JToolBar createToolbar()
+ {
+ JToolBar toolBar = new JToolBar();
+
+ JButton applyButt = new JButton("APPLY");
+ applyButt.setToolTipText("Apply annotation changed to feature editor");
+ applyButt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ transferAnnotation(false);
+ }
+ });
+ applyButt.setBackground(new Color(0,0,81));
+ applyButt.setForeground(Color.red);
+ applyButt.setBorderPainted(false);
+ applyButt.setMargin(new Insets(0,0,0,0));
+ applyButt.setFont(font);
+ toolBar.add(applyButt);
+
+ JButton closeButt = new JButton("CLOSE");
+ closeButt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ onClose();
+ }
+ });
+ closeButt.setBackground(new Color(0,0,81));
+ closeButt.setBorderPainted(false);
+ closeButt.setMargin(new Insets(0,0,0,0));
+ closeButt.setFont(font);
+ toolBar.add(closeButt);
+
+ addNote.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ if(addNote.isSelected())
+ dataView.updateNote();
+ else
+ dataView.deleteNote();
+ }
+ });
+ toolBar.add(addNote);
+
+ return toolBar;
+ }
+
+ /**
+ * Transfer annotation data back to the feature editor
+ */
+ private void transferAnnotation(final boolean askToUpdate)
+ {
+ final String dataViewStringOriginal = dataView.getFeatureText().trim();
+
+ ////////
+ //////// standard text area feature editor
+
+ if(matchForm == null && cvForm == null)
+ {
+ final String oldTxt = qualifierTextArea.getText().trim();
+
+ // changes have been made to feature annotation
+ if(!oldTxt.equals(dataViewStringOriginal))
+ {
+ if(askToUpdate)
+ {
+ if(askToUpdate())
+ qualifierTextArea.setText(dataViewStringOriginal);
+ }
+ else
+ qualifierTextArea.setText(dataViewStringOriginal);
+ }
+ return;
+ }
+
+ ////////
+ //////// tabbed feature editor
+
+ if(askToUpdate && !askToUpdate())
+ return;
+
+ QualifierVector matchQualifiers= new QualifierVector();
+ Qualifier productQualifier = null;
+ String otherString = null;
+
+ ChadoCanonicalGene chadoGene =
+ ((GFFStreamFeature)edit_feature.getEmblFeature()).getChadoGene();
+
+
+ final StringVector v = StringVector.getStrings(dataViewStringOriginal, "\n");
+ for(int i=0; i<v.size(); i++)
+ {
+ String value = (String)v.get(i);
+ if(MatchPanel.isMatchTag(value) ||
+ value.startsWith("/product"))
+ {
+ int index = value.indexOf('=');
+ String key;
+ if(index > -1)
+ {
+ key = value.substring(1, index);
+ value = value.substring(index+1);
+ }
+ else
+ {
+ key = value;
+ value = null;
+ }
+
+ if(value.startsWith("\""))
+ value = value.substring(1);
+ if(value.endsWith("\""))
+ value = value.substring(0, value.length()-1);
+
+ if(MatchPanel.isMatchTag(key))
+ {
+ Qualifier qualifier = matchQualifiers.getQualifierByName(key);
+ if(qualifier == null)
+ qualifier = new Qualifier(key);
+ qualifier.addValue(value);
+ matchQualifiers.setQualifier(qualifier);
+ }
+ else
+ {
+ if(productQualifier == null)
+ productQualifier = new Qualifier("product");
+
+ if( productQualifier.getValues() == null ||
+ !containsProductValue(productQualifier.getValues(),value))
+ {
+ if(value.startsWith("term="))
+ productQualifier.addValue(value);
+ else
+ {
+ org.gmod.schema.cv.CvTerm cvTerm =
+ DatabaseDocument.getCvTermByCvPartAndCvTerm(value, ChadoTransactionManager.PRODUCT_CV);
+
+ if(cvTerm == null)
+ {
+ int val = JOptionPane.showConfirmDialog(this,
+ "This term is missing from the database:\n"+
+ value+"\nAdd this to the database?",
+ "Add Product",JOptionPane.OK_CANCEL_OPTION);
+ if(val == JOptionPane.CANCEL_OPTION)
+ return;
+ }
+ productQualifier.addValue("term="+value+";");
+ }
+ }
+ }
+ }
+ else if(value.startsWith("/gene=") && chadoGene != null)
+ {
+ // Added as the Name qualifier on the gene feature, also
+ // added to the peptide with first letter made uppercase
+ String nameGene = value.substring(6);
+ if(nameGene.startsWith("\""))
+ nameGene = nameGene.substring(1);
+ if(nameGene.endsWith("\""))
+ nameGene = nameGene.substring(0, nameGene.length()-1);
+
+ String namePep =
+ Character.toUpperCase(nameGene.charAt(0)) + nameGene.substring(1);
+
+ Qualifier qualifierGene = new Qualifier("Name", nameGene);
+ Qualifier qualifierPep = new Qualifier("Name", namePep);
+ try
+ {
+ ((Feature)chadoGene.getGene().getUserData()).setQualifier(qualifierGene);
+ edit_feature.setQualifier(qualifierPep);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ else
+ {
+ if(otherString == null)
+ otherString = new String();
+ otherString = otherString+value+"\n";
+ }
+ }
+ matchForm.updateFromQualifiers(matchQualifiers,edit_feature);
+
+ if(productQualifier != null)
+ {
+ QualifierVector cvQualifiers = cvForm.getCvQualifiers().copy();
+ int productIndex = cvQualifiers.indexOfQualifierWithName("product");
+ if(productIndex > -1)
+ cvQualifiers.remove(productIndex);
+ else
+ productIndex = 0;
+
+ cvQualifiers.add(productIndex, productQualifier);
+ cvForm.updateFromQualifiers(cvQualifiers);
+ }
+
+ if(otherString != null)
+ {
+ otherString = qualifierTextArea.getText() +
+ (qualifierTextArea.getText().endsWith("\n") ? "" : "\n") +
+ otherString;
+ qualifierTextArea.setText(otherString);
+ }
+ }
+
+ /**
+ * Check the values of a StringVector to see if it already contains
+ * a new value.
+ * @param values
+ * @param newValue
+ * @return
+ */
+ private boolean containsProductValue(StringVector values, String newValue)
+ {
+ for(int i=0; i<values.size(); i++)
+ {
+ String thisValue = (String)values.get(i);
+ if(thisValue.startsWith("term="))
+ {
+ thisValue = thisValue.substring(5);
+ if(thisValue.endsWith(";"))
+ thisValue = thisValue.substring(0, thisValue.length()-1);
+ }
+ if(thisValue.equals(newValue))
+ return true;
+ }
+ return false;
+ }
+
+ private boolean askToUpdate()
+ {
+ int ok = JOptionPane.showConfirmDialog(BigPane.this,
+ "Apply changes now?",
+ "Apply Changes",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+ if(ok == JOptionPane.OK_OPTION)
+ return true;
+ return false;
+ }
+
+ /**
+ *
+ * Create a menu bar.
+ * @param desktop pane.
+ * @return menu bar.
+ *
+ */
+ private JMenuBar createMenuBar(final JDesktopPane desktop)
+ {
+ JMenuBar menuBar = new JMenuBar();
+ JMenu fileMenu = new JMenu("File");
+ menuBar.add(fileMenu);
+
+ final JMenuItem reReadMenu = new JMenuItem("Re-read selected results");
+ reReadMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ dataView.reReadSelectedResults();
+ }
+ });
+ fileMenu.add(reReadMenu);
+ fileMenu.add(new JSeparator());
+
+ JMenuItem applyMenu = new JMenuItem("Apply to Feature Editor");
+ applyMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ transferAnnotation(false);
+ }
+ });
+ fileMenu.add(applyMenu);
+ fileMenu.add(new JSeparator());
+//
+ final JMenuItem exitMenu = new JMenuItem("Close");
+ exitMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ onClose();
+ }
+ });
+
+ fileMenu.add(exitMenu);
+
+ //srs menu items
+ final JMenu optionMenu = new JMenu("Options");
+ menuBar.add(optionMenu);
+
+ final JMenu srsMenu = new JMenu("Show SRS in");
+ optionMenu.add(srsMenu);
+
+ srsBrowser = new JCheckBoxMenuItem("Browser",false);
+ srsTabPane = new JCheckBoxMenuItem("Tab Pane",true);
+ srsWin = new JCheckBoxMenuItem("New Window",false);
+
+ srsMenu.add(srsBrowser);
+ srsMenu.add(srsTabPane);
+ srsMenu.add(srsWin);
+
+ //drag mode
+ final JMenu dragMenu = new JMenu("Drag Mode");
+ optionMenu.add(dragMenu);
+
+ JRadioButtonMenuItem liveDrag = new JRadioButtonMenuItem("Live",true);
+ liveDrag.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ desktop.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
+ }
+ });
+
+ dragMenu.add(liveDrag);
+
+ final JRadioButtonMenuItem outlineDrag = new JRadioButtonMenuItem("Outline",false);
+ outlineDrag.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
+ }
+ });
+ dragMenu.add(outlineDrag);
+ ButtonGroup buttGroup = new ButtonGroup();
+ buttGroup.add(liveDrag);
+ buttGroup.add(outlineDrag);
+
+ // cache size
+ /*final JMenuItem cacheMenu = new JMenuItem("Cache Size");
+ cacheMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ JTextField cacheSize = new JTextField(Integer.toString(CACHE_SIZE));
+ int select = JOptionPane.showConfirmDialog(BigPane.this,
+ cacheSize, "Cache Size",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(select == JOptionPane.CANCEL_OPTION)
+ return;
+
+ CACHE_SIZE = Integer.parseInt(cacheSize.getText());
+
+ HitInfo[] cacheHits = FastaTextPane.cacheHits;
+
+ FastaTextPane.cacheHits = new HitInfo[CACHE_SIZE];
+ for(int i=0; i<cacheHits.length; i++)
+ {
+ if(i >= FastaTextPane.cacheHits.length)
+ break;
+ FastaTextPane.cacheHits[i] = cacheHits[i];
+ }
+ }
+ });
+ optionMenu.add(cacheMenu);*/
+ return menuBar;
+ }
+
+ /**
+ *
+ * Routine to call when the editor is closed.
+ *
+ */
+ private void onClose()
+ {
+ // remember the splitpane divider locations
+ dataView.setDataDividerLocation();
+ dataView.setAnnotationDividerLocation();
+
+ transferAnnotation(true);
+ // stop getz processes
+ setVisible(false);
+ dataView.stopGetz();
+ dataView.dispose();
+ BigPane.srsFrame = null;
+ dispose();
+ }
+
+ /**
+ *
+ * Set up the tabbed SRS frame
+ *
+ */
+ protected static void setUpSRSFrame(int hgt, JDesktopPane desktop)
+ {
+ BigPane.srsFrame = new JInternalFrame("SRS",
+ true, //resizable
+ true, //closable
+ true, //maximizable
+ true);//iconifiable
+ BigPane.srsFrame.setDefaultCloseOperation(JInternalFrame.HIDE_ON_CLOSE);
+
+ BigPane.srsFrame.setLocation(0,0);
+ BigPane.srsFrame.setSize(500,hgt);
+ final JTabbedPane jtab = new JTabbedPane();
+ BigPane.srsFrame.getContentPane().add(jtab);
+
+ final JMenuBar menuBar = new JMenuBar();
+ final CommonMenu cmen = new CommonMenu(BigPane.srsFrame);
+ menuBar.add(cmen);
+ final JMenuItem closeTabMenu = new JMenuItem("Close tab");
+ cmen.add(closeTabMenu);
+ closeTabMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ int select = jtab.getSelectedIndex();
+ if(select > -1)
+ jtab.removeTabAt(select);
+ }
+ });
+
+ BigPane.srsFrame.setJMenuBar(menuBar);
+ desktop.add(BigPane.srsFrame);
+ }
+
+ /**
+ *
+ * Extends WindowAdapter to close window.
+ *
+ */
+ class winExit extends WindowAdapter
+ {
+ public void windowClosing(WindowEvent we)
+ {
+ onClose();
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/editor/BrowserControl.java b/uk/ac/sanger/artemis/editor/BrowserControl.java
new file mode 100644
index 0000000..54ccde9
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/BrowserControl.java
@@ -0,0 +1,168 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ *
+ * A simple, static class to display a URL in the system browser.
+ *
+ * Under Unix, the system browser is hard-coded to be 'netscape'.
+ * Netscape must be in your PATH for this to work.
+ *
+ * Under Windows, this will bring up the default browser under windows,
+ * usually either Netscape or Microsoft IE. The default browser is
+ * determined by the OS.
+ *
+ * Taken from: http://www.javaworld.com/javaworld/javatips/jw-javatip66_p.html
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import java.io.IOException;
+
+public class BrowserControl
+{
+ // The default system browser under windows.
+ private static final String WIN_PATH = "rundll32";
+ // The flag to display a url.
+ private static final String WIN_FLAG = "url.dll,FileProtocolHandler";
+ private static final String UNIX_FLAG = "-remote openURL";
+ private static final String MAC_PATH = "/usr/bin/open";
+
+ /**
+ * Display a file in the system browser. If you want to display a
+ * file, you must include the absolute path name.
+ * @param url the file's url (the url must start with either "http://"
+ * or "file://").
+ */
+ public static void displayURL(String url)
+ {
+ String cmd = null;
+ try
+ {
+ if(isWindowsPlatform())
+ {
+ // cmd = 'rundll32 url.dll,FileProtocolHandler http://...'
+ cmd = WIN_PATH + " " + WIN_FLAG + " " + url;
+ Runtime.getRuntime().exec(cmd);
+ }
+ else if(isMac())
+ {
+ cmd = MAC_PATH + " " + url;
+ Runtime.getRuntime().exec(cmd);
+ }
+ else
+ {
+ String[] browsers =
+ {
+ "x-www-browser", "mozilla", "firefox", "opera", "konqueror",
+ "epiphany", "netscape"
+ };
+ String browser = null;
+ for(int count = 0; count < browsers.length && browser == null; count++)
+ {
+ ExternalApplication exApp = new ExternalApplication(
+ new String[] {"which", browsers[count]}, null, null);
+ String stdout = exApp.getProcessStdout();
+ if(stdout != null && stdout.startsWith("/"))
+ browser = browsers[count];
+ }
+
+ if(browser == null)
+ {
+ try
+ {
+ java.awt.Desktop.getDesktop().browse(java.net.URI.create(url));
+ }
+ catch(Exception e)
+ {
+ System.err.println("Could not find web browser");
+ }
+ }
+ else
+ {
+ if(browser.equals("netscape") || browser.equals("mozilla"))
+ handleNetscapeAndMozilla(url, browser);
+ else
+ Runtime.getRuntime().exec(new String[] {browser, url});
+ }
+ }
+ }
+ catch(IOException x)
+ {
+ // couldn't exec browser
+ System.err.println("Could not invoke browser, command=" + cmd);
+ System.err.println("Caught: " + x);
+ }
+ }
+
+ private static void handleNetscapeAndMozilla(final String url, final String browser)
+ throws IOException
+ {
+ String cmd = browser + " " + UNIX_FLAG + "(" + url + ")";
+ Process p = Runtime.getRuntime().exec(cmd);
+ try
+ {
+ // wait for exit code -- if it's 0, command worked,
+ // otherwise we need to start the browser up.
+ int exitCode = p.waitFor();
+ if (exitCode != 0)
+ {
+ // Command failed, start up the browser
+ cmd = browser + " " + url;
+ p = Runtime.getRuntime().exec(cmd);
+ }
+ }
+ catch (InterruptedException x)
+ {
+ System.err.println("Error bringing up browser, cmd='" + cmd + "'");
+ System.err.println("Caught: " + x);
+ }
+ }
+
+ /**
+ * Try to determine whether this application is running under Windows
+ * or some other platform by examing the "os.name" property.
+ * @return true if this application is running under a Windows OS
+ */
+ public static boolean isWindowsPlatform()
+ {
+ String os = System.getProperty("os.name");
+ if ( os != null && os.startsWith("Windows"))
+ return true;
+ else
+ return false;
+ }
+
+ private static boolean isMac()
+ {
+ return System.getProperty("mrj.version") != null ||
+ System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0;
+ }
+
+ /**
+ * Simple example.
+ */
+ public static void main(String[] args)
+ {
+ displayURL("http://www.google.co.uk");
+ }
+}
diff --git a/uk/ac/sanger/artemis/editor/CommonMenu.java b/uk/ac/sanger/artemis/editor/CommonMenu.java
new file mode 100644
index 0000000..feb9567
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/CommonMenu.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import javax.swing.*;
+import java.awt.event.*;
+
+public class CommonMenu extends JMenu
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public CommonMenu(final JInternalFrame f)
+ {
+ super("File");
+
+ JMenuItem closeMenu = new JMenuItem("Close");
+ closeMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ f.setVisible(false);
+ try
+ {
+ JTabbedPane jtab = (JTabbedPane)f.getContentPane().getComponent(0);
+ jtab.removeAll();
+ }
+ catch(ClassCastException cce)
+ {
+ f.dispose();
+ }
+ }
+ });
+ add(closeMenu);
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/editor/DBViewer.java b/uk/ac/sanger/artemis/editor/DBViewer.java
new file mode 100644
index 0000000..98ea782
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/DBViewer.java
@@ -0,0 +1,412 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Enumeration;
+
+public class DBViewer extends ScrollPanel
+ implements FastaListener
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ /** collection of hits */
+ private Vector hitInfoCollection = null;
+ /** query length */
+ private int qlen;
+ /** viewer boundary */
+ private int bound = 10;
+ /** y displacement for each hit */
+ private int ydisp = 5;
+ /** maximum hit score */
+ private float max_score = 0;
+ /** colour the hits by score or by evalue */
+ private boolean colourByScore = false;
+ /** popup menu */
+ private JPopupMenu popup;
+ /** scroll bar this panel sits in */
+ private JScrollPane jsp;
+ /** initial scale */
+ private float scale = 1.f;
+ /** blast/fasta results text view */
+ private FastaTextPane fastaTextPane;
+ /** ruler */
+ private JPanel ruler;
+
+ /**
+ *
+ * Database results graphical viewer.
+ * @param fastaTextPane fasta/blast parser
+ * @param jsp scroll pane to add this object to
+ *
+ */
+ public DBViewer(FastaTextPane fastaTextPane, final JScrollPane jsp)
+ {
+ super();
+
+ this.jsp = jsp;
+ this.fastaTextPane = fastaTextPane;
+
+ hitInfoCollection = fastaTextPane.getHitCollection();
+ qlen = fastaTextPane.getQueryLength();
+
+ Dimension dsize = new Dimension(500, (hitInfoCollection.size()*ydisp)+(6*bound));
+ setPreferredSize(dsize);
+ setToolTipText(""); //enable tooltip display
+
+ Enumeration enumHits = hitInfoCollection.elements();
+ while(enumHits.hasMoreElements())
+ {
+ HitInfo hit = (HitInfo)enumHits.nextElement();
+ float score = Float.parseFloat(hit.getScore());
+ if(score > max_score)
+ max_score = score;
+ }
+
+ // Popup menu
+ addMouseListener(new PopupListener());
+ popup = new JPopupMenu();
+ getOptionsMenu(popup);
+ }
+
+ public void update()
+ {
+ hitInfoCollection = fastaTextPane.getHitCollection();
+ qlen = fastaTextPane.getQueryLength();
+ Dimension d = new Dimension(500,
+ (hitInfoCollection.size()*ydisp)+(6*bound));
+ setPreferredSize(d);
+
+ Enumeration enumHits = hitInfoCollection.elements();
+ while(enumHits.hasMoreElements())
+ {
+ HitInfo hit = (HitInfo)enumHits.nextElement();
+ float score = Float.parseFloat(hit.getScore());
+ if(score > max_score)
+ max_score = score;
+ }
+ jsp.setViewportView(this);
+ }
+
+ protected JMenu getFileMenu(final JInternalFrame jif)
+ {
+ JMenu fileMenu = new JMenu("File");
+
+ JMenuItem exitMenu = new JMenuItem("Close");
+ exitMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ jif.dispose();
+ }
+ });
+ fileMenu.add(exitMenu);
+ return fileMenu;
+ }
+
+ protected void getOptionsMenu(JComponent menuOptions)
+ {
+ JMenuItem zoomInMenu = new JMenuItem("Zoom In");
+ zoomInMenu.setAccelerator(KeyStroke.getKeyStroke('I',
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+ zoomInMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setZoom(1.1f);
+ }
+ });
+ menuOptions.add(zoomInMenu);
+
+ JMenuItem zoomOutMenu = new JMenuItem("Zoom Out");
+ zoomOutMenu.setAccelerator(KeyStroke.getKeyStroke('O',
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+ zoomOutMenu.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setZoom(0.9f);
+ }
+ });
+ menuOptions.add(zoomOutMenu);
+
+ menuOptions.add(new JSeparator());
+
+ JRadioButtonMenuItem colourScore = new JRadioButtonMenuItem("Colour by Score");
+ colourScore.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setColourByScore(true);
+ }
+ });
+ menuOptions.add(colourScore);
+
+ JRadioButtonMenuItem colourEval = new JRadioButtonMenuItem("Colour by E-value");
+ colourEval.setSelected(true);
+ colourEval.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ setColourByScore(false);
+ }
+ });
+ menuOptions.add(colourEval);
+
+ ButtonGroup butt = new ButtonGroup();
+ butt.add(colourScore);
+ butt.add(colourEval);
+ }
+
+
+ /**
+ *
+ * Set the scale to zoom in/out by.
+ * @param scale scale to zoom in/out by.
+ *
+ */
+ protected void setZoom(float new_scale)
+ {
+ scale = scale*new_scale;
+ Dimension d = getPreferredSize();
+ double width = d.getWidth()*new_scale;
+ d = new Dimension((int)width,(int)d.getHeight());
+ setPreferredSize(d);
+ ruler.setPreferredSize(new Dimension((int)width,25));
+ ruler.repaint();
+ jsp.setViewportView(this);
+ }
+
+
+ /**
+ *
+ * Get the ruler
+ *
+ */
+ protected JPanel getRuler()
+ {
+ ruler = new JPanel()
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Font font = new Font("monospaced",Font.PLAIN,10);
+ g.setFont(font);
+ FontMetrics metrics = g.getFontMetrics();
+ int hgtNumber = metrics.getAscent();
+
+ Graphics2D g2 = (Graphics2D)g;
+ int resultwidth = (int)getPreferredSize().getWidth()-(2*bound);
+ g2.setColor(Color.black);
+ g2.setStroke(new BasicStroke(3.f));
+ g2.drawLine(bound,bound+hgtNumber,bound+resultwidth,bound+hgtNumber);
+
+// draw ruler
+ g2.setStroke(new BasicStroke(2.f));
+ g2.drawLine(bound,bound+hgtNumber,bound,bound+hgtNumber-6);
+ g2.drawString("0",bound,hgtNumber+3);
+
+ //Point viewPos = jsp.getViewport().getViewPosition();
+ //Dimension extent = jsp.getViewport().getExtentSize();
+
+ int npoints = (int)(scale*10);
+ int unit = (int)(resultwidth/npoints);
+ int blastUnit = qlen/npoints;
+
+ for(int i=1; i<npoints; i++)
+ {
+ String strPos = Integer.toString(i*blastUnit);
+ int strwid = metrics.stringWidth(strPos);
+
+ g2.drawLine(bound+(unit*i), bound+hgtNumber,
+ bound+(unit*i), bound+hgtNumber-6);
+ g2.drawString(strPos,bound+(unit*i)-strwid,hgtNumber+3);
+ }
+
+ String strQlen = Integer.toString(qlen);
+ int strwid = metrics.stringWidth(strQlen);
+
+ g2.drawLine(bound+resultwidth,bound+hgtNumber,
+ bound+resultwidth,bound+hgtNumber-6);
+ g2.drawString(strQlen,bound+resultwidth-strwid,hgtNumber+3);
+ }
+ };
+ ruler.setPreferredSize(new Dimension(getPreferredSize().width,25));
+ return ruler;
+ }
+
+
+ /**
+ *
+ * Override paintComponent
+ * @param g graphics
+ *
+ */
+ public void paintComponent(Graphics g)
+ {
+// let UI delegate paint first (incl. background filling)
+ super.paintComponent(g);
+
+ Font font = new Font("monospaced",Font.PLAIN,10);
+ g.setFont(font);
+ //FontMetrics metrics = g.getFontMetrics();
+
+ Graphics2D g2 = (Graphics2D)g;
+ int resultwidth = (int)getPreferredSize().getWidth()-(2*bound);
+ g2.setColor(Color.black);
+ g2.setStroke(new BasicStroke(1.f));
+
+// draw hits
+ int ypos = 0;
+ float hit_unit = (float)resultwidth/(float)qlen;
+ Enumeration enumHits = hitInfoCollection.elements();
+ while(enumHits.hasMoreElements())
+ {
+ HitInfo hit = (HitInfo)enumHits.nextElement();
+
+ if(colourByScore)
+ {
+ float score = Float.parseFloat(hit.getScore());
+ if(score > max_score/2)
+ g2.setColor(Color.red);
+ else if(score > max_score/4)
+ g2.setColor(Color.blue);
+ else
+ g2.setColor(Color.cyan);
+ }
+ else
+ {
+ Double evalue = new Double(hit.getEValue());
+
+ if(evalue.compareTo(new Double("0.005")) < 0)
+ g2.setColor(Color.red);
+ else if(evalue.compareTo(new Double("0.0")) < 0)
+ g2.setColor(Color.blue);
+ else
+ g2.setColor(Color.cyan);
+ }
+
+// g2.setStroke(new BasicStroke(1.f));
+// float hit_unit = (float)resultwidth/(float)qlen;
+
+ Vector queryPosition = hit.getQueryPosition();
+
+ if(queryPosition.size() == 0)
+ continue;
+
+ for(int i=0; i<queryPosition.size(); i+=2)
+ {
+ int start = (int)(bound+(hit_unit*((Integer)queryPosition.elementAt(i)).intValue()));
+ int end = (int)(bound+(hit_unit*((Integer)queryPosition.elementAt(i+1)).intValue()));
+ g2.drawLine(start,bound+ypos,end,bound+ypos);
+ }
+
+ ypos += ydisp;
+// System.out.println(hit.getID()+" "+hit.getQueryStart()+" -> "+hit.getQueryEnd()+
+// " start "+start+" end "+end+" resultwidth "+resultwidth);
+// ", E = "+hit.getEValue()+" Length = "+hit.getQueryLength());
+ }
+
+ }
+
+
+ protected void setColourByScore(boolean colourByScore)
+ {
+ this.colourByScore = colourByScore;
+ repaint();
+ }
+
+
+ /**
+ *
+ * Popup menu listener
+ *
+ */
+ class PopupListener extends MouseAdapter
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ maybeShowPopup(e);
+ else if(e.getClickCount() == 2)
+ {
+// int seqPos = (int)((e.getY()-bound-ydisp-bound-ydisp-hgtNumber)/ydisp);
+ int seqPos = (int)((e.getY()-bound)/ydisp);
+ HitInfo hit = (HitInfo)hitInfoCollection.get(seqPos);
+ fastaTextPane.show(hit);
+ }
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ maybeShowPopup(e);
+ }
+
+ private void maybeShowPopup(MouseEvent e)
+ {
+ if(e.isPopupTrigger())
+ popup.show(e.getComponent(),
+ e.getX(), e.getY());
+ }
+ }
+
+
+ /**
+ *
+ * Determine the tool tip to display
+ * @param e mouse event
+ * @return tool tip
+ *
+ */
+ public String getToolTipText(MouseEvent e)
+ {
+ Point loc = e.getPoint();
+// int seqPos = (int)((loc.y-bound-ydisp-bound-ydisp-hgtNumber)/ydisp);
+ int seqPos = (int)((loc.y-bound)/ydisp);
+
+ if(seqPos >= 0 && seqPos<hitInfoCollection.size())
+ {
+ HitInfo hit = (HitInfo)hitInfoCollection.get(seqPos);
+
+ StringBuffer tip = new StringBuffer();
+ tip.append(hit.getID()+"\n");
+ if(hit.getOrganism() != null)
+ tip.append(hit.getOrganism()+"\n");
+ tip.append("E-value:"+hit.getEValue()+" Score:"+hit.getScore());
+
+ return tip.toString();
+ }
+ return null;
+ }
+
+
+}
+
diff --git a/uk/ac/sanger/artemis/editor/DataCollectionPane.java b/uk/ac/sanger/artemis/editor/DataCollectionPane.java
new file mode 100644
index 0000000..4f0f798
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/DataCollectionPane.java
@@ -0,0 +1,946 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.util.StringVector;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import java.util.StringTokenizer;
+
+import java.io.StringReader;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import java.awt.*;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+public class DataCollectionPane extends JScrollPane
+ implements FastaListener
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+ private FastaTextPane fastaTextPane;
+ private Annotation ann;
+ private JDesktopPane desktop;
+ private DataViewInternalFrame dataView;
+ protected static String srs_url = getSrsSite();
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(FastaTextPane.class);
+
+ /**
+ *
+ * @param fastaTextPane fasta/blast display.
+ * @param ann annotation display.
+ * @param desktop desktop pane.
+ *
+ */
+ public DataCollectionPane(FastaTextPane fastaTextPane,
+ Annotation ann, JDesktopPane desktop,
+ DataViewInternalFrame dataView)
+ {
+ super();
+ this.fastaTextPane = fastaTextPane;
+ this.ann = ann;
+ this.desktop = desktop;
+ this.dataView = dataView;
+
+// getSrsSite();
+ Box bdown = Box.createVerticalBox();
+ ScrollPanel scrollPanel = new ScrollPanel();
+ scrollPanel.add(bdown);
+
+ Vector hitInfoCollection = fastaTextPane.getHitCollection();
+ Hashtable goHash = getIDHash(hitInfoCollection);
+ getGoHash("/nfs/disk222/yeastpub/analysis/pathogen/GO/go.flat",goHash);
+
+ setResultLines(bdown,hitInfoCollection,goHash);
+ setViewportView(scrollPanel);
+ setPreferredSize(new Dimension(500,300));
+ }
+
+ public static String getSrsSite()
+ {
+ StringVector srs = Options.getOptions().getOptionValues("srs_url");
+ if(srs != null)
+ srs_url = (String)srs.elementAt(0);
+ else
+ srs_url = "http://srs.ebi.ac.uk/srsbin/cgi-bin/";
+ return srs_url;
+ }
+
+ /**
+ *
+ * fasta/blast results changed.
+ *
+ */
+ public void update()
+ {
+ Box bdown = Box.createVerticalBox();
+ ScrollPanel scrollPanel = new ScrollPanel();
+ scrollPanel.add(bdown);
+
+ Vector hitInfoCollection = fastaTextPane.getHitCollection();
+ Hashtable goHash = getIDHash(hitInfoCollection);
+ getGoHash("/nfs/disk222/yeastpub/analysis/pathogen/GO/go.flat",goHash);
+
+ setResultLines(bdown,hitInfoCollection,goHash);
+ setViewportView(scrollPanel);
+ }
+
+
+ /**
+ *
+ * @param bdown vertical Box containing all one line results.
+ * @param hitInfoCollection Collection of HitInfo from results.
+ * @param goHash Hash of GO ID's with description.
+ *
+ */
+ private void setResultLines(Box bdown, Vector hitInfoCollection,
+ Hashtable goHash)
+ {
+ final Vector orthoCheckBox = new Vector();
+ Enumeration hitEnum = hitInfoCollection.elements();
+
+ while(hitEnum.hasMoreElements())
+ {
+ Box bacross = Box.createHorizontalBox();
+ final HitInfo hit = (HitInfo)hitEnum.nextElement();
+
+// ortholog / paralog
+ final JCheckBox orthoBox = new JCheckBox("ORTH");
+ orthoBox.setFont(BigPane.font_sm);
+ final JCheckBox paraBox = new JCheckBox("PARA");
+ paraBox.setFont(BigPane.font_sm);
+
+ orthoBox.setMargin(new Insets(1,1,1,1));
+ paraBox.setMargin(new Insets(1,1,1,1));
+
+ orthoBox.setActionCommand(hit.getAcc());
+ orthoCheckBox.add(orthoBox);
+
+ bacross.add(orthoBox);
+ orthoBox.setFont(BigPane.font);
+ orthoBox.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ Enumeration checkEnum = orthoCheckBox.elements();
+ while(checkEnum.hasMoreElements())
+ {
+ JCheckBox cb = (JCheckBox)checkEnum.nextElement();
+ if( cb.isSelected() &&
+ !cb.getActionCommand().equals(orthoBox.getActionCommand()))
+ {
+ cb.setSelected(false);
+ ann.delete(cb.getActionCommand(),true);
+ }
+ }
+
+ if(orthoBox.isSelected())
+ {
+ if(paraBox.isSelected())
+ {
+ paraBox.setSelected(false);
+ ann.delete(hit.getAcc(),false);
+ }
+
+ try
+ {
+ setAnnotation(hit,ann,fastaTextPane.getFormat(),true);
+ }
+ catch(NullPointerException npe)
+ {
+ JOptionPane.showMessageDialog(DataCollectionPane.this,
+ "There may be a probelem retrieving "+hit.getAcc()+
+ "\nfrom SRS",
+ "Connection Error to SRS?",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ else
+ ann.delete(hit.getAcc(),true);
+
+ ann.deleteNote();
+ if(BigPane.addNote.isSelected())
+ dataView.updateNote();
+ }
+ });
+
+ bacross.add(paraBox);
+ paraBox.setFont(BigPane.font);
+ paraBox.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(paraBox.isSelected())
+ {
+ if(orthoBox.isSelected())
+ {
+ orthoBox.setSelected(false);
+ ann.delete(hit.getAcc(),true);
+ }
+
+ try
+ {
+ setAnnotation(hit,ann,fastaTextPane.getFormat(),false);
+ }
+ catch(NullPointerException npe)
+ {
+ JOptionPane.showMessageDialog(DataCollectionPane.this,
+ "There may be a probelem retrieving "+hit.getAcc()+
+ "\nfrom SRS",
+ "Connection Error to SRS?",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ else
+ ann.delete(hit.getAcc(),false);
+
+ ann.deleteNote();
+ if(BigPane.addNote.isSelected())
+ dataView.updateNote();
+ }
+ });
+
+
+// go to alignment
+ MouseOverButton butt = new MouseOverButton();
+ butt.setPreferredSize(new Dimension(12,12));
+ butt.setBorderPainted(false);
+ butt.setToolTipText("Go to alignment");
+ butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ fastaTextPane.show(hit);
+ }
+ });
+ bacross.add(butt);
+ bacross.add(Box.createHorizontalStrut(2));
+
+// SRS entry
+ MouseOverButton srsRetrieve = new MouseOverButton(hit);
+ srsRetrieve.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ getSRSEntry(hit,desktop);
+ }
+ });
+ srsRetrieve.setForeground(Color.blue);
+ srsRetrieve.setToolTipText("");
+ srsRetrieve.setFont(BigPane.font);
+ srsRetrieve.setMargin(new Insets(1,1,1,1));
+ srsRetrieve.setBorderPainted(false);
+ bacross.add(srsRetrieve);
+
+//
+// String org = hit.getOrganism();
+// if(org != null)
+// {
+// JLabel orgLabel = new JLabel(org);
+// orgLabel.setFont(BigPane.font);
+// orgLabel.setForeground(Color.green);
+// bacross.add(orgLabel);
+// }
+
+// heading
+ String head = hit.getHeader();
+ if(head.startsWith(hit.getID()))
+ head = head.substring(hit.getID().length());
+
+ JLabel hiLabel = new JLabel(head);
+ hiLabel.setFont(BigPane.font);
+ hiLabel.setForeground(Color.black);
+// hiLabel.setForeground(Color.red);
+
+// align button
+// final JButton selectButt = new JButton("ALIGN");
+// selectButt.setMargin(new Insets(1,1,1,1));
+// selectButt.addActionListener(new ActionListener()
+// {
+// public void actionPerformed(ActionEvent e)
+// {
+// fastaTextPane.show(hit);
+// }
+// });
+// selectButt.setFont(BigPane.font);
+
+// retrieve srs entry
+// JButton srsButt = new JButton("->SRS");
+// srsButt.setMargin(new Insets(1,1,1,1));
+
+// srsButt.addActionListener(new ActionListener()
+// {
+// public void actionPerformed(ActionEvent e)
+// {
+// getSRSEntry(hit,desktop);
+// }
+// });
+// srsButt.setFont(BigPane.font);
+
+ bacross.add(hiLabel);
+// bacross.add(selectButt);
+// bacross.add(srsButt);
+
+ bacross.add(Box.createHorizontalGlue());
+ bdown.add(bacross);
+
+// go entry
+ Vector gov = hit.getGO();
+ if(gov != null)
+ {
+ Enumeration gov_enum = gov.elements();
+ while(gov_enum.hasMoreElements())
+ {
+ final String go_id = ((String)gov_enum.nextElement()).trim();
+ bacross = Box.createHorizontalBox();
+
+ final String goLine = (String)goHash.get(go_id);
+ final JCheckBox goBox = new JCheckBox();
+ goBox.setSelected(false);
+ goBox.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ if(goBox.isSelected())
+ {
+ String go_term = hit.getGoAssociation(go_id);
+ if(go_term == null)
+ {
+ go_term = setGoAnnotation(ann,hit,go_id,goLine);
+ // hit.setGoAssociation(go_id,go_term);
+ }
+ else
+ ann.insert(go_term,false);
+ }
+ else
+ {
+ // possibly 2 line to delete
+ ann.deleteGo(hit.getAcc(),go_id);
+ ann.deleteGo(hit.getAcc(),go_id);
+ }
+ }
+ });
+ goBox.setMargin(new Insets(0,1,0,1));
+
+ MouseOverButton goButton = new MouseOverButton("GO:"+go_id);
+ goButton.setFont(BigPane.font);
+ goButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ String go_cmd = "http://www.godatabase.org/cgi-bin/amigo/go.cgi?query=GO%3A"+go_id;
+ BrowserControl.displayURL(go_cmd);
+
+ if(BigPane.srsTabPane.isSelected())
+ {
+ try
+ {
+ setUpSRSFrame(new URL(go_cmd),go_id,desktop);
+ }
+ catch(java.net.ConnectException connect)
+ {
+ JOptionPane.showMessageDialog(DataCollectionPane.this,
+ "Cannot retrieve "+go_id+
+ "\nConnection failed to:\n"+go_cmd,
+ "Connection Error",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ catch(Exception exp)
+ {
+ exp.printStackTrace();
+ }
+ }
+
+ }
+ });
+ goButton.setForeground(Color.blue);
+ goButton.setMargin(new Insets(1,1,1,1));
+ goButton.setBorderPainted(false);
+
+ bacross.add(Box.createHorizontalStrut(10));
+ bacross.add(goBox);
+ bacross.add(goButton);
+ goButton.setMargin(new Insets(0,1,0,1));
+
+ JLabel goLabel = new JLabel(goLine);
+ goLabel.setFont(BigPane.font);
+ bacross.add(goLabel);
+ bdown.add(bacross);
+ bacross.add(Box.createHorizontalGlue());
+ }
+ }
+ }
+ }
+
+
+ /**
+ *
+ * @param hit HitInfo for a single hit.
+ * @param desktop desktop pane.
+ *
+ */
+ private void getSRSEntry(HitInfo hit, JDesktopPane desktop)
+ {
+ String srscmd = "/wgetz?-e+";
+
+ if(hit.getID() != null)
+ {
+ String search = hit.getID();
+ srscmd = srscmd.concat("[{uniprot}-ID:"+search+"*]");
+ if(hit.getAcc() != null)
+ srscmd = srscmd.concat("|[{uniprot}-AccNumber:"+hit.getAcc()+"*]");
+
+ if(srs_url.indexOf("ebi.ac.uk") > -1)
+ srscmd = srscmd + "+-vn+2";
+
+ if(BigPane.srsBrowser.isSelected())
+ BrowserControl.displayURL(srs_url+srscmd);
+
+ try
+ {
+ URL url = new URL(srs_url+srscmd);
+
+ if(BigPane.srsTabPane.isSelected())
+ setUpSRSFrame(url,search,desktop);
+
+ if(BigPane.srsWin.isSelected())
+ {
+ int hgt = (2*desktop.getHeight())/3;
+ Annotation edPane = new Annotation(url);
+ JScrollPane jsp = new JScrollPane(edPane);
+ JInternalFrame jif = new JInternalFrame("SRS "+search,
+ true, //resizable
+ true, //closable
+ true, //maximizable
+ true);//iconifiable);
+ JMenuBar menuBar = new JMenuBar();
+ menuBar.add(new CommonMenu(jif));
+ jif.setJMenuBar(menuBar);
+ jif.getContentPane().add(jsp);
+ jif.setLocation(0,0);
+ jif.setSize(800,hgt);
+ jif.setVisible(true);
+ desktop.add(jif);
+ }
+ }
+ catch(java.net.ConnectException connect)
+ {
+ JOptionPane.showMessageDialog(DataCollectionPane.this,
+ "Cannot retrieve "+search+
+ "\nConnection failed to:\n"+srs_url+srscmd,
+ "Connection Error",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ catch(Exception exp)
+ {
+ exp.printStackTrace();
+ }
+ }
+ else
+ JOptionPane.showMessageDialog(DataCollectionPane.this,
+ "No ID to retrieve SRS entry!", "Missing ID",
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+
+
+ /**
+ *
+ * @param url URL to be displayed.
+ * @param name URL name.
+ * @param desktop desktop pane.
+ *
+ */
+ protected static void setUpSRSFrame(URL url, String name, JDesktopPane desktop)
+ throws IOException
+ {
+ if(BigPane.srsFrame == null)
+ {
+ BigPane.setUpSRSFrame((2*desktop.getHeight())/3,desktop);
+ Border loweredbevel = BorderFactory.createLoweredBevelBorder();
+ Border raisedbevel = BorderFactory.createRaisedBevelBorder();
+ Border compound = BorderFactory.createCompoundBorder(raisedbevel,loweredbevel);
+
+ JTextField statusField = new JTextField();
+ statusField.setBorder(compound);
+ statusField.setEditable(false);
+ BigPane.srsFrame.getContentPane().add(statusField, BorderLayout.SOUTH);
+ }
+
+ Annotation edPane = new Annotation(url);
+ JScrollPane jsp = new JScrollPane(edPane);
+ JTabbedPane jtab = (JTabbedPane)BigPane.srsFrame.getContentPane().getComponent(0);
+ jtab.insertTab(name, null,jsp,null,0);
+ BigPane.srsFrame.setVisible(true);
+ }
+
+
+ /*private HitInfo findHitInfo(String acc)
+ {
+ Enumeration hitEnum = fastaTextPane.getHitCollection().elements();
+ while(hitEnum.hasMoreElements())
+ {
+ HitInfo hit = (HitInfo)hitEnum.nextElement();
+ if(acc.equals(hit.getID()))
+ return hit;
+ }
+
+ return null;
+ }*/
+
+
+ /**
+ *
+ * @param hit HitInfo for a single hit.
+ * @param ortholog true if ortholog is selected.
+ *
+ */
+ private static void getzCall(HitInfo hit, boolean ortholog)
+ {
+ final String env[] = { "PATH=/usr/local/pubseq/bin/" };
+
+ if(hit.getOrganism() == null ||
+ hit.getDescription() == null)
+ {
+ String res = null;
+
+ if(FastaTextPane.isRemoteMfetch())
+ {
+ String cmd =
+ "mfetch -f \"org des gen\" -d uniprot -i \"acc:"+hit.getID()+"\"" ;
+ uk.ac.sanger.artemis.j2ssh.SshPSUClient ssh =
+ new uk.ac.sanger.artemis.j2ssh.SshPSUClient(cmd);
+ ssh.run();
+ res = ssh.getStdOut();
+ }
+ else if(!FastaTextPane.getMfetchExecutable().exists())
+ {
+ try
+ {
+ final String queryURL = DataCollectionPane.srs_url+
+ "/wgetz?-f+acc%20org%20description%20gen+"+
+ "[uniprot:"+hit.getAcc()+"]|[uniprot:"+hit.getID()+"]";
+ logger4j.debug(queryURL);
+ URL wgetz = new URL(queryURL);
+
+ InputStream in = wgetz.openStream();
+
+ BufferedReader strbuff = new BufferedReader(new InputStreamReader(in));
+ StringBuffer resBuff = new StringBuffer();
+ String line;
+ while((line = strbuff.readLine()) != null)
+ resBuff.append(line);
+ strbuff.close();
+ in.close();
+
+ res = resBuff.toString();
+ res = FastaTextPane.insertNewline(res, "OS ");
+ res = FastaTextPane.insertNewline(res, "DE ");
+ res = FastaTextPane.insertNewline(res, "GN ");
+ res = FastaTextPane.insertNewline(res, "AC ");
+ }
+ catch(MalformedURLException e) {System.err.println(e);}
+ catch(IOException e) {System.err.println(e);}
+ }
+ else
+ {
+ String cmd[] = { "mfetch", "-f", "org des gen",
+ "-d", "uniprot", "-i", "acc:"+hit.getID() };
+
+ ExternalApplication app = new ExternalApplication(cmd,
+ env,null);
+ res = app.getProcessStdout();
+ }
+
+ StringTokenizer tok = new StringTokenizer(res,"\n");
+ while(tok.hasMoreTokens())
+ {
+ String token = tok.nextToken();
+ String tokenline = token.substring(3).trim();
+ if(tokenline.equals(""))
+ continue;
+
+ if(token.startsWith("OS "))
+ hit.setOrganism(tokenline);
+ else if(token.startsWith("DE "))
+ hit.appendDescription(tokenline);
+ else if(token.startsWith("GN "))
+ {
+ StringTokenizer tokGN = new StringTokenizer(tokenline,";");
+ while(tokGN.hasMoreTokens())
+ {
+ token = tokGN.nextToken();
+ if(token.startsWith("Name="))
+ hit.setGeneName(tokenline.substring(5));
+// else
+// hit.appendDescription(token);
+ }
+ }
+ }
+ }
+
+ if(hit.getEMBL() == null)
+ {
+ File fgetz = new File("/usr/local/pubseq/bin/getz");
+ String res = FastaTextPane.getUniprotLinkToDatabase(fgetz,
+ FastaTextPane.getMfetchExecutable().exists(), hit, env, "embl");
+
+ int ind1 = res.indexOf("ID ");
+ if(ind1 > -1)
+ {
+ StringTokenizer tok = new StringTokenizer(res);
+ tok.nextToken();
+ hit.setEMBL(tok.nextToken());
+ }
+ }
+
+ }
+
+ /**
+ *
+ * Sets the GO annotation for a given ID, first by querying
+ * Amigo and if not successful querying SRS.
+ * @param ann annotation display
+ * @param id database id
+ * @param go_id GO id
+ * @return annotation
+ *
+ */
+ private String setGoAnnotation(Annotation ann, HitInfo hit,
+ String go_id, String goLine)
+ {
+ String go_ann = new String("/GO=\"GOid=GO:"+go_id+
+ "; with="+hit.getDB()+":"+hit.getAcc()+
+ "; "+goLine+"\"");
+
+ String prog = DataCollectionPane.class.getResource("/etc/go_associations.pl").getPath();
+ String cmd[] = { prog, "-assoc", hit.getAcc() };
+ ExternalApplication app = new ExternalApplication(cmd,
+ null,null);
+ String res = app.getProcessStdout();
+ boolean found = false;
+
+ try
+ {
+ String line;
+ BufferedReader buffRead = new BufferedReader(new StringReader(res));
+ while((line = buffRead.readLine()) != null)
+ {
+
+ int ind1 = line.indexOf("GO:");
+ int ind2 = line.indexOf(" ",ind1);
+ if(ind1 > -1 && ind2 > -1)
+ {
+ String this_go_id = line.substring(ind1+3,ind2).trim();
+
+// see http://intweb.sanger.ac.uk/help/wiki/html/Intweb/PSUEukaryoticQualifiers.html
+ int ref = line.indexOf("db_xref=");
+ String db_xref = null;
+ if(ref > -1)
+ {
+ db_xref = line.substring(ref);
+ line = line.substring(0,ref);
+ }
+
+ // build GO line
+ StringBuffer goBuff = new StringBuffer();
+ if(line.startsWith("/GO_component"))
+ goBuff.append("/GO=\"aspect=component; ");
+ else if(line.startsWith("/GO_process"))
+ goBuff.append("/GO=\"aspect=process; ");
+ else
+ goBuff.append("/GO=\"aspect=function; ");
+ goBuff.append("GOid=GO:"+this_go_id+"; ");
+
+ ind1 = line.indexOf("(");
+ ind2 = line.indexOf(")")+1;
+ if(ind1 > -1 && ind2 > -1)
+ goBuff.append("term="+line.substring(ind1,ind2)+"; ");
+
+ ind1 = ind2+1;
+ ind2 = line.indexOf(";",ind1);
+ if(ind1 > -1 && ind2 > -1)
+ goBuff.append("evidence="+line.substring(ind1,ind2)+"; ");
+
+ if(db_xref != null)
+ goBuff.append(db_xref+" ");
+
+ goBuff.append("with="+hit.getDB()+":"+hit.getAcc()+"; ");
+
+ if(go_id.equals(this_go_id))
+ go_ann = line + "\"<br>" + goBuff.toString() +"\"";
+
+ hit.setGoAssociation(this_go_id,line + "\"<br>" + goBuff.toString() +"\"");
+ found = true;
+ }
+ }
+ }
+ catch(IOException ioe) { ioe.printStackTrace(); }
+
+ if(!found) // try SRS
+ {
+ String env[] = { "PATH=/usr/local/pubseq/bin/" };
+ String cmd2[] = { "getz", "-f", "dbxref", "[uniprot:"+hit.getAcc()+"]" };
+ app = new ExternalApplication(cmd2,env,null);
+ res = app.getProcessStdout();
+
+ try
+ {
+ String line;
+ BufferedReader buffRead = new BufferedReader(new StringReader(res));
+ while((line = buffRead.readLine()) != null)
+ {
+
+ int ind1 = line.indexOf("GO:");
+ int ind2 = line.indexOf(";",ind1);
+ if(ind1 > -1 && ind2 > -1)
+ {
+ String this_go_id = line.substring(ind1+3,ind2).trim();
+
+ line = line.substring(3).trim();
+ String aspect = null;
+ String term = null;
+ String evidence = null;
+ ind1 = -1;
+ ind2 = -1;
+
+ if((ind1 = line.indexOf(" F:"))> -1)
+ {
+ aspect = "function";
+ ind2 = line.indexOf(";",ind1+4);
+ }
+ else if((ind1 = line.indexOf(" P:"))> -1)
+ {
+ aspect = "process";
+ ind2 = line.indexOf(";",ind1+4);
+ }
+ else if((ind1 = line.indexOf(" C:"))> -1)
+ {
+ aspect = "component";
+ ind2 = line.indexOf(";",ind1+4);
+ }
+
+ if(ind1 > -1 && ind2 > -1)
+ term = line.substring(ind1+3,ind2);
+
+ if(ind2 > -1)
+ evidence = line.substring(ind2+1).trim();
+
+ StringBuffer goBuff = new StringBuffer();
+
+ goBuff.append("/GO_"+aspect);
+ goBuff.append("=\"GO:"+this_go_id+"; ");
+ goBuff.append(evidence+"; ");
+ goBuff.append(hit.getDB()+":"+hit.getAcc()+";\"<br>");
+
+ goBuff.append("/GO=\"aspect="+aspect+"; ");
+ goBuff.append("GOid=GO:"+this_go_id+"; ");
+ goBuff.append("term="+term+"; ");
+ goBuff.append("evidence="+evidence+"; ");
+ goBuff.append("db_xref= ;");
+ goBuff.append("with="+hit.getDB()+":"+hit.getAcc()+";\"");
+
+ if(go_id.equals(this_go_id))
+ go_ann = goBuff.toString();
+
+ hit.setGoAssociation(this_go_id,goBuff.toString());
+ found = true;
+// break;
+ }
+ }
+ }
+ catch(IOException ioe) { ioe.printStackTrace(); }
+ }
+ ann.insert(go_ann,false);
+
+ return go_ann;
+ }
+
+
+ /**
+ *
+ * @param hit HitInfo for a single hit.
+ * @param ann annotation display.
+ * @param resultFormat format of the results (blastp/fasta).
+ * @param ortholog true if ortholog selected.
+ *
+ */
+ private void setAnnotation(HitInfo hit, Annotation ann,
+ String resultFormat, boolean ortholog)
+ {
+ getzCall(hit,ortholog);
+
+// gene name for orthologs
+ StringBuffer orthoText = new StringBuffer();
+ if(ortholog)
+ {
+ //String geneName = hit.getGeneName();
+
+ if(hit.getGeneName() != null &&
+ !hit.getGeneName().equals(""))
+ orthoText.append("<br>\n/gene=\""+hit.getGeneName()+"\"");
+
+ if(hit.getEC_number() != null)
+ orthoText.append("<br>\n/EC_number=\""+hit.getEC_number()+"\"");
+
+ String product = hit.getDescription();
+
+ if(product != null && !product.equals(""))
+ {
+ if(product.endsWith(".") ||
+ product.endsWith(";"))
+ product = product.substring(0,product.length()-1);
+
+ int ind;
+ if((product.startsWith("RecName:") ||
+ product.startsWith("SubName:") ||
+ product.startsWith("AltName:")) &&
+ (ind = product.indexOf("="))>0)
+ product = product.substring(ind+1);
+
+ orthoText.append("\n<br>\n/product=\""+product.toLowerCase()+"\"");
+ }
+ }
+
+// System.out.println("ID "+hit.getID()+"\nOS "+hit.getOrganism()+
+// "\nDE "+hit.getDescription()+"\nGN "+hit.getGeneName());
+
+ StringBuffer buff = new StringBuffer();
+
+ if(hit.getDB() != null)
+ buff.append(" with="+hit.getDB()+":"+hit.getAcc());
+ else
+ buff.append(" with=UniProt:"+hit.getAcc());
+
+ if(hit.getEMBL() != null &&
+ !hit.getEMBL().equals(""))
+ buff.append(" (EMBL:"+hit.getEMBL()+")");
+ buff.append(";");
+
+ if(hit.getOrganism() != null &&
+ !hit.getOrganism().equals(""))
+ buff.append(" "+hit.getOrganism()+";");
+ if(hit.getGeneName() != null &&
+ !hit.getGeneName().equals(""))
+ buff.append(" "+hit.getGeneName()+";");
+ if(hit.getDescription() != null)
+ buff.append(" "+hit.getDescription()+";");
+ if(hit.getLength() != null)
+ buff.append(" length=" + hit.getLength()+";");
+ if(hit.getIdentity() != null)
+ buff.append(" id " + hit.getIdentity()+";");
+ if(hit.getUngapped() != null)
+ buff.append(" ungapped id " + hit.getUngapped()+";");
+ if(hit.getEValue() != null)
+ buff.append(" E()=" + hit.getEValue()+";");
+ if(hit.getOverlap() != null)
+ buff.append(" "+hit.getOverlap()+";");
+ if(hit.getQueryRange() != null)
+ buff.append(" query " + hit.getQueryRange()+";");
+ if(hit.getSubjectRange() != null)
+ buff.append(" subject " + hit.getSubjectRange());
+ buff.append("\"");
+
+ ann.insert("\n/similarity=\""+resultFormat+";"+
+ buff.toString()+orthoText.toString(), ortholog);
+ }
+
+
+ /**
+ *
+ * @param hitInfoCollection Collection of HitInfo.
+ *
+ */
+ private Hashtable getIDHash(Vector hitInfoCollection)
+ {
+ Hashtable goHash = new Hashtable();
+
+ Enumeration hitEnum = hitInfoCollection.elements();
+ while(hitEnum.hasMoreElements())
+ {
+ HitInfo hit = (HitInfo)hitEnum.nextElement();
+ Vector gov = hit.getGO();
+ if(gov != null)
+ {
+ Enumeration gov_enum = gov.elements();
+ while(gov_enum.hasMoreElements())
+ {
+ String id = ((String)gov_enum.nextElement()).trim();
+ goHash.put(id,"");
+ }
+ }
+ }
+
+ return goHash;
+ }
+
+ /**
+ *
+ * @param filename name of the file containing the GO id and
+ * description.
+ * @param goHash Hashtable of GO id's and description.
+ *
+ */
+ protected void getGoHash(String filename, Hashtable goHash)
+ {
+ try
+ {
+ String line = null;
+ BufferedReader buffRead = new BufferedReader(new FileReader(filename));
+ while((line = buffRead.readLine()) != null)
+ {
+ StringTokenizer tok = new StringTokenizer(line,"\t");
+ String ID = tok.nextToken().substring(3);
+ String desc = tok.nextToken();
+
+ if(tok.hasMoreTokens())
+ desc = desc.concat("; "+tok.nextToken());
+
+ if(goHash.containsKey(ID))
+ goHash.put(ID,desc);
+ }
+ }
+ catch(FileNotFoundException fnf) { }
+ catch(IOException ioe) { ioe.printStackTrace(); }
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/editor/DataViewInternalFrame.java b/uk/ac/sanger/artemis/editor/DataViewInternalFrame.java
new file mode 100644
index 0000000..7950e5a
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/DataViewInternalFrame.java
@@ -0,0 +1,464 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import javax.swing.*;
+import java.awt.*;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.components.ViewMenu;
+import uk.ac.sanger.artemis.util.Document;
+
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.StringTokenizer;
+import java.util.Enumeration;
+import java.io.File;
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.io.IOException;
+
+public class DataViewInternalFrame extends JInternalFrame
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ protected static int dataDividerLocation = 250;
+ protected static int annotationDividerLocation = 150;
+
+ private JTabbedPane tabPane = new JTabbedPane();
+ private Annotation ann;
+ private Box evidenceBox;
+ private Vector fastaCollection = new Vector();
+ private JSplitPane split;
+
+ public DataViewInternalFrame(Hashtable dataFile, JDesktopPane desktop,
+ final JScrollPane scrollEvidence,
+ int wid, int hgt, String qualifier_txt,
+ final Feature edit_feature)
+ {
+ super("Document " ,
+ true, //resizable
+ true, //closable
+ true, //maximizable
+ true);//iconifiable
+
+// graphical evidence display
+ JInternalFrame evidence = new JInternalFrame("Evidence", true,
+ true, true, true);
+ //JPanel evidencePanel = (JPanel)evidence.getContentPane();
+ evidenceBox = Box.createVerticalBox();
+
+//
+ ann = new Annotation(desktop);
+
+ StringBuffer annFormat = new StringBuffer();
+ annFormat.append(htmlBreaks(qualifier_txt.trim()));
+
+ int icount = 0;
+ Enumeration enumPrograms = dataFile.keys();
+ while(enumPrograms.hasMoreElements())
+ {
+ String programName = (String) enumPrograms.nextElement();
+ Vector files = (Vector) dataFile.get(programName);
+
+ for(int i = 0; i < files.size(); i++)
+ {
+ String fileName = (String) files.get(i);
+ Document document = null;
+
+ try
+ {
+ String thisFileName = fileName;
+ int ind = fileName.indexOf(programName + File.separatorChar);
+ if(ind > -1)
+ thisFileName = fileName.substring (ind + programName.length () + 1);
+
+ document = ViewMenu.getSearchDocument(edit_feature, programName, thisFileName);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ if(document == null)
+ {
+ JOptionPane.showMessageDialog(desktop, "Results file: \n"
+ + fileName + "\ndoes not exist!", "File Not Found",
+ JOptionPane.WARNING_MESSAGE);
+ continue;
+ }
+
+ String tabName = (String) fileName;
+ int ind = tabName.lastIndexOf("/");
+ if(ind > -1)
+ {
+ String go = "";
+
+ if(tabName.indexOf("blastp+go") > -1)
+ go = ":: GO :: ";
+ tabName = go + tabName.substring(ind + 1);
+ }
+
+ // add fasta results internal frame
+ FastaTextPane fastaPane = new FastaTextPane(document);
+ if(fastaPane.getFormat() != null)
+ fastaCollection.add(fastaPane);
+ else
+ continue;
+
+ /*
+ if(qualifier_txt.indexOf("/" + fastaPane.getFormat() + "_file=\"") == -1)
+ {
+ if(icount > 0)
+ annFormat.append("\n<br>");
+ annFormat.append("/" + fastaPane.getFormat() + "_file=\"" + fileName
+ + "\"");
+ }
+ */
+
+ // graphical view
+ final JScrollPane dbviewScroll = new JScrollPane();
+ final DBViewer dbview = new DBViewer(fastaPane, dbviewScroll);
+ dbviewScroll.setViewportView(dbview);
+
+ final Dimension d = new Dimension((int) dbviewScroll.getPreferredSize()
+ .getWidth(), hgt / 3);
+
+ final Box yBox = Box.createVerticalBox();
+ final Box xBox = Box.createHorizontalBox();
+ final MouseOverButton hide = new MouseOverButton("X");
+ hide.setForeground(Color.blue);
+ hide.setBackground(Color.white);
+ hide.setFont(BigPane.font);
+ hide.setMargin(new Insets(0, 0, 0, 0));
+ hide.setBorderPainted(false);
+ hide.setActionCommand("HIDE");
+
+ final Box bacross = Box.createHorizontalBox();
+ bacross.add(dbviewScroll);
+
+ hide.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(hide.getActionCommand().equals("HIDE"))
+ {
+ bacross.remove(dbviewScroll);
+ bacross.add(yBox);
+ hide.setText("+");
+ scrollEvidence.setViewportView(evidenceBox);
+ hide.setActionCommand("SHOW");
+ }
+ else
+ {
+ bacross.remove(yBox);
+ dbviewScroll.setColumnHeaderView(yBox);
+ bacross.add(dbviewScroll);
+ hide.setText("X");
+ scrollEvidence.setViewportView(evidenceBox);
+ hide.setActionCommand("HIDE");
+ }
+ }
+ });
+
+ xBox.add(hide);
+ JLabel tabLabel = new JLabel(fastaPane.getFormat() + " " + tabName);
+ tabLabel.setFont(BigPane.font);
+
+ tabLabel.setOpaque(true);
+ xBox.add(tabLabel);
+ xBox.add(Box.createHorizontalGlue());
+
+ yBox.add(xBox);
+ yBox.add(dbview.getRuler());
+
+ dbviewScroll.setPreferredSize(d);
+ dbviewScroll.setColumnHeaderView(yBox);
+ fastaPane.addFastaListener(dbview);
+
+ evidenceBox.add(bacross);
+
+ // add data pane
+ DataCollectionPane dataPane = new DataCollectionPane(fastaPane, ann,
+ desktop, this);
+ fastaPane.addFastaListener(dataPane);
+
+ ActiveJSplitPane split = new ActiveJSplitPane(
+ JSplitPane.VERTICAL_SPLIT, fastaPane, dataPane);
+ split.setLabel(tabLabel);
+ split.setDividerLocation(DataViewInternalFrame.dataDividerLocation);
+ split.setOneTouchExpandable(true);
+ if(icount == 0)
+ split.setActive(true);
+
+ tabPane.add(fastaPane.getFormat() + " " + tabName, split);
+ icount++;
+ }
+ }
+
+// evidenceBox.add(Box.createVerticalGlue());
+
+ // add tab pane listener
+ tabPane.addChangeListener(new TabChangeListener());
+
+ // add setActive annotator text pane
+ ann.setAnnotation(annFormat.toString().trim());
+
+ JScrollPane annotationScroll = new JScrollPane(ann);
+ annotationScroll.setPreferredSize(new Dimension(500,150));
+
+ split = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
+ annotationScroll,tabPane);
+
+ split.setDividerLocation(annotationDividerLocation);
+ getContentPane().add(split);
+
+ setVisible(true);
+ evidence.setVisible(true);
+ desktop.add(evidence);
+ }
+
+
+ protected Box getEvidenceBox()
+ {
+ return evidenceBox;
+ }
+
+ protected String getFeatureText()
+ {
+ return ann.getFeatureText();
+ }
+
+ protected void reReadSelectedResults()
+ {
+ ActiveJSplitPane split = (ActiveJSplitPane)tabPane.getSelectedComponent();
+ Component comps[] = split.getComponents();
+ for(int i=0; i<comps.length;i++)
+ {
+ if(comps[i] instanceof FastaTextPane)
+ {
+ ((FastaTextPane)comps[i]).reRead();
+ return;
+ }
+ }
+ }
+
+
+ protected void stopGetz()
+ {
+ Enumeration fastaEnum = fastaCollection.elements();
+
+ while(fastaEnum.hasMoreElements())
+ ((FastaTextPane)fastaEnum.nextElement()).stopGetz();
+ }
+
+
+ /**
+ *
+ * Delete note field in for all similar hits.
+ *
+ */
+ protected void deleteNote()
+ {
+ ann.deleteNote();
+ }
+
+
+ /**
+ *
+ * Add a note field in for all similar hits.
+ *
+ */
+ protected void updateNote()
+ {
+ StringReader in = new StringReader(getFeatureText());
+ BufferedReader buff = new BufferedReader(in);
+ String line = null;
+ StringBuffer note = null;
+
+ try
+ {
+ while((line = buff.readLine()) != null)
+ {
+ if(line.startsWith("/similarity="))
+ {
+ if(note == null)
+ note = new StringBuffer("\n/note=\"Similar to ");
+ else
+ note.append(", and to ");
+
+ StringTokenizer tok = new StringTokenizer(line,";");
+ String type = tok.nextToken();
+ int ind1 = type.indexOf("\"");
+ type = type.substring(ind1+1);
+
+ String id = tok.nextToken();
+ ind1 = id.indexOf("with=")+4;
+ if(ind1 == 3)
+ ind1 = 0;
+
+// ind1 = id.indexOf(":");
+ id = id.substring(ind1+1).trim();
+
+ String next = tok.nextToken().trim();
+ if(next.endsWith("."))
+ next = next.substring(0,next.length()-1);
+
+ note.append(next);
+ note.append(tok.nextToken().toLowerCase());
+
+ String length = tok.nextToken().trim();
+ if(!length.startsWith("length"))
+ note.append(" "+length.toLowerCase());
+ note.append(" "+id);
+
+ while(!length.startsWith("length"))
+ length = tok.nextToken().trim();
+
+ ind1 = length.indexOf("=");
+ if(ind1 == -1)
+ ind1 = length.indexOf(" ");
+
+ length = length.substring(ind1+1);
+
+ if(!length.endsWith(" aa"))
+ length = length + " aa";
+
+ note.append(" ("+length+") ");
+ note.append(type+" scores: ");
+
+ String pid = tok.nextToken().trim(); // percent id
+
+ if(pid.endsWith("%")) // fasta
+ {
+ ind1 = pid.indexOf(" ");
+ pid = pid.substring(ind1+1);
+
+ tok.nextToken(); // ungapped id
+ String eval = tok.nextToken().trim();
+ String len = tok.nextToken().trim();
+
+ while(!len.endsWith("aa overlap"))
+ len = tok.nextToken().trim();
+
+ len = len.substring(0,len.length()-8);
+
+ note.append(eval);
+ note.append(", "+pid+" id in ");
+ note.append(len);
+ }
+ else // blastp
+ note.append(pid);
+ }
+ }
+ }
+ catch(IOException ioe){}
+
+ if(note != null)
+ note.append("\"");
+ else
+ return;
+
+ ann.insert(note.toString(),true);
+// System.out.println(note.toString());
+// Enumeration enumHits = hits.elements();
+// while(enumHits.hasMoreElements())
+// {
+// String id = (String)enumHits.nextElement();
+// System.out.println(id);
+// findHitInfo(String
+// }
+ return;
+ }
+
+ private String htmlBreaks(String t)
+ {
+ int ind = 0;
+ while((ind = t.indexOf("\n",ind+5)) > -1)
+ t = t.substring(0,ind) + "<br>" +
+ t.substring(ind);
+
+ return t;
+ }
+
+ public class TabChangeListener implements ChangeListener
+ {
+ ActiveJSplitPane lastSelected = (ActiveJSplitPane)tabPane.getSelectedComponent();
+ public void stateChanged(ChangeEvent e)
+ {
+ ActiveJSplitPane split = (ActiveJSplitPane)tabPane.getSelectedComponent();
+ lastSelected.setActive(false);
+ split.setActive(true);
+ lastSelected = split;
+ }
+ }
+
+ protected void setDataDividerLocation()
+ {
+ ActiveJSplitPane lastSelected = (ActiveJSplitPane)tabPane.getSelectedComponent();
+ if(lastSelected != null)
+ dataDividerLocation = lastSelected.getDividerLocation();
+ }
+
+ protected void setAnnotationDividerLocation()
+ {
+ annotationDividerLocation = split.getDividerLocation();
+ }
+
+
+ public class ActiveJSplitPane extends JSplitPane
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+ private JLabel tabLabel;
+ private Color bg;
+
+ public ActiveJSplitPane(int newOrientation,
+ Component newLeftComponent,
+ Component newRightComponent)
+ {
+ super(newOrientation,newLeftComponent,newRightComponent);
+ }
+
+ public void setLabel(JLabel tabLabel)
+ {
+ this.tabLabel = tabLabel;
+ this.bg = tabLabel.getBackground();
+ }
+
+ public void setActive(boolean active)
+ {
+ if(active)
+ tabLabel.setBackground(Color.yellow);
+ else
+ tabLabel.setBackground(bg);
+ }
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/editor/EvidenceViewer.java b/uk/ac/sanger/artemis/editor/EvidenceViewer.java
new file mode 100644
index 0000000..637a3c1
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/EvidenceViewer.java
@@ -0,0 +1,301 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import javax.swing.JPanel;
+import javax.swing.JOptionPane;
+import javax.swing.JDesktopPane;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.geom.AffineTransform;
+import java.util.StringTokenizer;
+
+import uk.ac.sanger.artemis.io.Location;
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.Marker;
+
+public class EvidenceViewer extends JPanel
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+ //private String evidenceType;
+ //private int evidenceStart;
+ //private int evidenceEnd;
+ private int featStart;
+ private int featEnd;
+ /** viewer boundary */
+ private int bound = 35;
+ private Feature edit_feature;
+ private FeatureVector overlapFeature;
+ private float unitLength;
+ private int YDISPLACEMENT = 20;
+ private JDesktopPane desktop;
+
+
+ public EvidenceViewer(Feature edit_feature,
+ FeatureVector overlapFeature, JDesktopPane desktop)
+ {
+ this.edit_feature = edit_feature;
+ this.overlapFeature = overlapFeature;
+ final Location this_loc = edit_feature.getLocation();
+ featStart = this_loc.getFirstBase();
+ featEnd = this_loc.getLastBase();
+
+ this.desktop = desktop;
+ //this.evidenceStart = evidenceStart;
+ //this.evidenceEnd = evidenceEnd;
+
+ final int hgt = 100+(20*overlapFeature.size());
+ final Dimension dim = new Dimension(500,hgt);
+ setPreferredSize(dim);
+ setMaximumSize(dim);
+
+ addMouseListener(new MouseClickListener());
+ }
+
+ /**
+ *
+ * Override paintComponent
+ * @param g graphics
+ *
+ */
+ public void paintComponent(Graphics g)
+ {
+// let UI delegate paint first (incl. background filling)
+ super.paintComponent(g);
+
+ Font font = new Font("monospaced",Font.PLAIN,10);
+ g.setFont(font);
+ FontMetrics metrics = g.getFontMetrics();
+ int hgtNumber = metrics.getAscent();
+
+ Graphics2D g2 = (Graphics2D)g;
+ int resultwidth = (int)getPreferredSize().getWidth()-(2*bound);
+ int scale = 1;
+ int npoints = (int)(scale*10);
+ int unit = (int)(resultwidth/npoints);
+ int featUnit = (featEnd-featStart)/npoints;
+
+ unitLength = (float)resultwidth/(float)(featEnd-featStart);
+
+// feature segments
+ final FeatureSegmentVector this_feature_segments = edit_feature.getSegments();
+ // draw each segment/exon
+ for(int i = 0 ; i < this_feature_segments.size() ; ++i)
+ {
+ final FeatureSegment current_segment =
+ this_feature_segments.elementAt(i);
+
+ drawSegment(g2, current_segment, unitLength, featStart, hgtNumber);
+
+// if(i + 1 < this_feature_segments.size())
+// {
+// // draw a line between the segments
+
+// final FeatureSegment next_segment =
+// this_feature_segments.elementAt(i + 1);
+
+// drawSegmentConnection(g, current_segment, next_segment);
+// }
+ }
+
+//
+ g2.setColor(Color.black);
+ g2.setStroke(new BasicStroke(3.f));
+ g2.drawLine(bound,bound+hgtNumber,bound+resultwidth,bound+hgtNumber);
+
+// draw ruler
+ g2.setStroke(new BasicStroke(2.f));
+
+ for(int i=0; i<=npoints; i++)
+ g2.drawLine(bound+(unit*i),bound+hgtNumber,
+ bound+(unit*i),bound+hgtNumber-6);
+
+ AffineTransform origin = g2.getTransform();
+ AffineTransform newOrig = (AffineTransform)(origin.clone());
+ newOrig.rotate(Math.toRadians(90.),0,0);
+ g2.setTransform(newOrig);
+
+ String strPos = Integer.toString(featStart);
+ int strwid = metrics.stringWidth(strPos);
+ int hgtNumber2 = hgtNumber/2;
+ g2.drawString(strPos,bound-strwid,-bound+hgtNumber2);
+
+ for(int i=1; i<=npoints; i++)
+ {
+ strPos = Integer.toString((i*featUnit) + featStart);
+ strwid = metrics.stringWidth(strPos);
+ g2.drawString(strPos,bound-strwid,-bound-(unit*i)+hgtNumber2);
+ }
+
+ g2.setTransform(origin);
+
+//
+ int bound2 = bound*2;
+ int ydisp = 0;
+ for(int i = 0; i<overlapFeature.size(); ++i)
+ {
+ Feature this_feature = overlapFeature.elementAt(i);
+ String note = this_feature.getNote();
+
+ int indPF = note.indexOf("PF"); // Pfam ID
+ if(indPF == -1)
+ continue;
+
+ StringTokenizer tok = new StringTokenizer(note.substring(indPF)," ,;");
+ String pfamID = tok.nextToken();
+ Location this_loc = this_feature.getLocation();
+
+ int start = this_loc.getFirstBase();
+ int end = this_loc.getLastBase();
+
+ start = (int)((start-featStart)*unitLength);
+ end = (int)((end-featStart)*unitLength);
+
+ g2.setColor(Color.red);
+ g2.setStroke(new BasicStroke(15.f));
+ g2.drawLine(bound+start, bound2+ydisp,
+ bound+end, bound2+ydisp);
+
+ strwid = metrics.stringWidth(pfamID);
+ g2.setColor(Color.black);
+ g2.setStroke(new BasicStroke(3.f));
+ g2.drawString(pfamID, bound+start+(end-start-strwid)/2, bound2+ydisp+5);
+
+ ydisp += YDISPLACEMENT;
+ }
+ }
+
+
+ private void drawSegment(Graphics2D g2, FeatureSegment segment,
+ float unitLength, int featStart, int hgtNumber)
+ {
+ final Feature segment_feature = segment.getFeature();
+
+ final Marker segment_start_marker = segment.getStart();
+ int startPosition = segment_start_marker.getRawPosition();
+
+ final Marker segment_end_marker = segment.getEnd();
+ int endPosition = segment_end_marker.getRawPosition();
+
+ final Color feature_colour = segment_feature.getColour();
+ g2.setColor(feature_colour);
+
+ g2.setStroke(new BasicStroke(10.f));
+ int start = (int)((startPosition-featStart)*unitLength);
+ int stop = (int)((endPosition-featStart)*unitLength);
+
+ g2.drawLine(bound+start, bound+hgtNumber+6,
+ bound+stop, bound+hgtNumber+6);
+ }
+
+ public class MouseClickListener implements MouseListener
+ {
+ public void mousePressed(MouseEvent e)
+ {
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ }
+
+ public void mouseEntered(MouseEvent e)
+ {
+ }
+
+ public void mouseExited(MouseEvent e)
+ {
+ }
+
+ public void mouseClicked(MouseEvent e)
+ {
+ if(e.getClickCount() < 2)
+ return;
+
+ setCursor(new Cursor(Cursor.WAIT_CURSOR));
+
+ int ydisp = 0;
+ int bound2 = bound*2;
+ int ydisp2 = YDISPLACEMENT/2;
+ Point loc = e.getPoint();
+ for(int i = 0; i<overlapFeature.size(); ++i)
+ {
+ Feature this_feature = overlapFeature.elementAt(i);
+ Location this_loc = this_feature.getLocation();
+ int start = this_loc.getFirstBase();
+ int end = this_loc.getLastBase();
+
+ start = (int)((start-featStart)*unitLength)+bound;
+ end = (int)((end-featStart)*unitLength)+bound;
+
+ if(loc.x < end &&
+ loc.x > start &&
+ loc.y < ydisp+ydisp2+bound2 &&
+ loc.y > ydisp-ydisp2+bound2)
+ {
+ String note = this_feature.getNote();
+
+ int indPF = note.indexOf("PF"); // Pfam ID
+ if(indPF == -1)
+ continue;
+
+ StringTokenizer tok = new StringTokenizer(note.substring(indPF)," ,;");
+ String pfamID = tok.nextToken();
+
+
+ String pfam_cmd = "http://www.sanger.ac.uk/cgi-bin/Pfam/getacc?"+pfamID;
+ BrowserControl.displayURL(pfam_cmd);
+
+ if(BigPane.srsTabPane.isSelected())
+ {
+ try
+ {
+ DataCollectionPane.setUpSRSFrame(new java.net.URL(pfam_cmd),
+ pfamID,desktop);
+ }
+ catch(java.net.ConnectException connect)
+ {
+ JOptionPane.showMessageDialog(EvidenceViewer.this,
+ "Cannot retrieve "+pfamID+
+ "\nConnection failed to:\n"+pfamID,
+ "Connection Error",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ catch(Exception exp)
+ {
+ exp.printStackTrace();
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ }
+ ydisp += YDISPLACEMENT;
+ }
+ setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/editor/ExternalApplication.java b/uk/ac/sanger/artemis/editor/ExternalApplication.java
new file mode 100644
index 0000000..fa80d3b
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/ExternalApplication.java
@@ -0,0 +1,313 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import java.io.*;
+
+/**
+*
+* Used to run an external command and get the stdout
+* and stderr.
+*
+*/
+public class ExternalApplication
+{
+
+ /** running process */
+ private Process p;
+ /** standard out */
+ private StringBuffer stdout = new StringBuffer();
+ /** standard error */
+ private StringBuffer stderr = new StringBuffer();
+ /** running directory */
+ //private File project;
+ /** process status */
+ private String status;
+ private StdoutHandler stdouth;
+ private StderrHandler stderrh;
+
+ /**
+ *
+ * @param cmd command to run
+ * @param envp environment
+ * @param project running directory
+ *
+ */
+ public ExternalApplication(String[] cmd,
+ String[] envp, File project)
+ {
+ //this.project = project;
+ status = "0";
+
+ Runtime cmdRun = Runtime.getRuntime();
+ try
+ {
+ p = cmdRun.exec(cmd,envp,project);
+
+ // 2 threads to read in stdout & stderr buffers
+ // to prevent blocking
+ stdouth = new StdoutHandler(this);
+ stderrh = new StderrHandler(this);
+ stdouth.start();
+ stderrh.start();
+ }
+ catch(IOException ioe)
+ {
+ ioe.printStackTrace();
+ System.out.println("ExternalApplication Error executing:");
+ for(int i=0;i<cmd.length;i++)
+ System.out.println(cmd[i]);
+ status = "1";
+ }
+ }
+
+
+ /**
+ *
+ * Read in the process stderr.
+ *
+ */
+ private void readProcessStderr()
+ {
+
+ BufferedInputStream stderrStream = null;
+ BufferedReader stderrRead = null;
+ try
+ {
+ stderrStream =
+ new BufferedInputStream(p.getErrorStream());
+ stderrRead =
+ new BufferedReader(new InputStreamReader(stderrStream));
+ char c[] = new char[100];
+ int nc = 0;
+
+ while((nc = stderrRead.read(c,0,100)) != -1)
+ stderr = stderr.append(new String(c,0,nc));
+ }
+ catch (IOException io)
+ {
+ System.err.println("ExternalApplication: Error in "+
+ "collecting standard out");
+ }
+ finally
+ {
+ try
+ {
+ if(stderrStream!=null)
+ stderrStream.close();
+ }
+ catch(IOException ioe)
+ {
+ System.err.println("ExternalApplication: Error closing stream");
+ }
+ try
+ {
+ if(stderrRead!=null)
+ stderrRead.close();
+ }
+ catch(IOException ioe)
+ {
+ System.err.println("ExternalApplication: Error closing reader");
+ }
+ }
+
+ return;
+ }
+
+ /**
+ *
+ * Read in the process stdout.
+ *
+ */
+ private void readProcessStdout()
+ {
+
+ BufferedInputStream stdoutStream = null;
+ BufferedReader stdoutRead = null;
+ try
+ {
+ String line;
+ stdoutStream =
+ new BufferedInputStream(p.getInputStream());
+ stdoutRead =
+ new BufferedReader(new InputStreamReader(stdoutStream));
+
+
+ char c[] = new char[100];
+ int nc = 0;
+ String chunk;
+
+ while((nc = stdoutRead.read(c,0,100)) != -1)
+ {
+ chunk = new String(c,0,nc);
+ stdout = stdout.append(chunk);
+ }
+
+ }
+ catch (IOException io)
+ {
+ System.err.println("ExternalApplication: Error in "+
+ "collecting standard out");
+ }
+ finally
+ {
+ try
+ {
+ if(stdoutStream!=null)
+ stdoutStream.close();
+ }
+ catch(IOException ioe)
+ {
+ System.err.println("ExternalApplication: Error closing stream");
+ }
+ try
+ {
+ if(stdoutRead!=null)
+ stdoutRead.close();
+ }
+ catch(IOException ioe)
+ {
+ System.err.println("ExternalApplication: Error closing reader");
+ }
+ }
+
+ return;
+ }
+
+ /**
+ *
+ * Get the stdout for the process.
+ * @return standard out.
+ *
+ */
+ public String getProcessStdout()
+ {
+ try
+ {
+ // make sure we hang around for stdout
+ while(stdouth.isAlive())
+ Thread.currentThread().sleep(10);
+ }
+ catch(InterruptedException ie)
+ {
+ ie.printStackTrace();
+ }
+
+ return new String(stdout.toString().trim());
+ }
+
+
+ /**
+ *
+ * Get the stderr for the process.
+ * @return standard error.
+ *
+ */
+ public String getProcessStderr()
+ {
+ try
+ {
+ // make sure we hang around for stderr
+ while(stderrh.isAlive())
+ Thread.currentThread().sleep(10);
+ }
+ catch(InterruptedException ie)
+ {
+ ie.printStackTrace();
+ }
+
+ return new String(stderr.toString().trim());
+ }
+
+ /**
+ *
+ * Wait for the process to end
+ *
+ */
+ public int waitFor()
+ {
+ try
+ {
+ return p.waitFor();
+ }
+ catch(InterruptedException ie)
+ {
+ ie.printStackTrace();
+ }
+ return -1;
+ }
+
+ /**
+ *
+ * @return process
+ *
+ */
+ public Process getProcess()
+ {
+ return p;
+ }
+
+ /**
+ *
+ * @return status
+ *
+ */
+ public String getStatus()
+ {
+ return status;
+ }
+
+ class StdoutHandler extends Thread
+ {
+ ExternalApplication rea;
+
+ protected StdoutHandler(ExternalApplication rea)
+ {
+ this.rea = rea;
+ }
+
+ public void run()
+ {
+ rea.readProcessStdout();
+ }
+ }
+
+ class StderrHandler extends Thread
+ {
+ ExternalApplication rea;
+
+ protected StderrHandler(ExternalApplication rea)
+ {
+ this.rea = rea;
+ }
+
+ public void run()
+ {
+ rea.readProcessStderr();
+ }
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/editor/FastaListener.java b/uk/ac/sanger/artemis/editor/FastaListener.java
new file mode 100644
index 0000000..5db3dc8
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/FastaListener.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+public interface FastaListener
+{
+ void update();
+}
+
diff --git a/uk/ac/sanger/artemis/editor/FastaTextPane.java b/uk/ac/sanger/artemis/editor/FastaTextPane.java
new file mode 100644
index 0000000..848b53d
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/FastaTextPane.java
@@ -0,0 +1,1288 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import java.awt.Point;
+import javax.swing.JTextArea;
+import javax.swing.JScrollPane;
+
+import com.sshtools.j2ssh.sftp.FileAttributes;
+
+import java.awt.Font;
+import java.awt.Dimension;
+import java.io.InputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import uk.ac.sanger.artemis.components.filetree.FileList;
+import uk.ac.sanger.artemis.util.Document;
+
+public class FastaTextPane extends JScrollPane
+{
+ /***/
+ private static final long serialVersionUID = 1L;
+ private JTextArea textArea;
+ private Vector<HitInfo> hitInfoCollection = null;
+ private String format = null;
+ private Document document;
+ private int qlen;
+ private Vector<FastaListener> listeners = new Vector<FastaListener>();
+ private Vector<GetzThread> threads = new Vector<GetzThread>();
+ protected static int MAX_HITS = 70;
+ private static boolean remoteMfetch = false;
+ private static boolean forceUrl = false;
+ private boolean blastPlus = false;
+
+ static
+ {
+ if(System.getProperty("useSRS") != null)
+ forceUrl = true;
+ }
+
+ public static HitInfo[] cacheHits = new HitInfo[BigPane.CACHE_SIZE];
+ public static int nCacheHits = 0;
+ private static String mfetchExecutablePath = "/nfs/disk100/pubseq/bin/mfetch";
+ private static File mfetchExecutable = new File(mfetchExecutablePath);
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(FastaTextPane.class);
+
+ public FastaTextPane(Document document)
+ {
+ super();
+
+ this.document = document;
+ format = getResultsFormat();
+
+ if(format != null)
+ {
+ StringBuffer contents = null;
+ if(format.equals("fasta"))
+ contents = readFASTAFile(format);
+ else if(format.equals("blastp"))
+ contents = readBLASTPFile(format);
+ textArea = new JTextArea(contents.toString());
+ setTextAreaFont(BigPane.font);
+ textArea.setEditable(false);
+
+ setViewportView(textArea);
+ setPreferredSize(new Dimension(500,300));
+ }
+ }
+
+ protected void addFastaListener(FastaListener obj)
+ {
+ listeners.add(obj);
+ }
+
+ protected void reRead()
+ {
+ StringBuffer contents = null;
+
+ format = getResultsFormat();
+ if(format.equals("fasta"))
+ contents = readFASTAFile(format);
+ else if(format.equals("blastp"))
+ contents = readBLASTPFile(format);
+
+ textArea.setText(contents.toString());
+ setViewportView(textArea);
+
+ Enumeration<FastaListener> enumListeners = listeners.elements();
+ while(enumListeners.hasMoreElements())
+ enumListeners.nextElement().update();
+ }
+
+ /**
+ *
+ * Get the format of the results (e.g. FASTA or BLASTP).
+ * @return format
+ *
+ */
+ protected String getFormat()
+ {
+ return format;
+ }
+
+ private void setTextAreaFont(Font f)
+ {
+ textArea.setFont(f);
+ }
+
+ /**
+ *
+ * Get the format of the results in a file. FASTA and
+ * BLASTP are supported.
+ *
+ */
+ private String getResultsFormat()
+ {
+ InputStreamReader streamReader = null;
+ BufferedReader buffReader = null;
+ String line = null;
+ String format = null;
+
+ try
+ {
+ streamReader = new InputStreamReader(document.getInputStream());
+ buffReader = new BufferedReader(streamReader);
+ while( (line = buffReader.readLine()) != null)
+ {
+ if(line.startsWith("BLASTP"))
+ {
+ format = "blastp";
+ if(line.indexOf("+") > -1)
+ blastPlus = true;
+ break;
+ }
+ else if(line.indexOf("FASTA") > -1)
+ {
+ format = "fasta";
+ break;
+ }
+ }
+ streamReader.close();
+ buffReader.close();
+ }
+ catch(IOException ioe)
+ {
+ System.out.println("Cannot read file: " +
+ document.getName() +"\n"+ioe.getMessage());
+ }
+
+ return format;
+ }
+
+
+ private StringBuffer readBLASTPFile(final String format)
+ {
+ //File fn = dataFile;
+ StringBuffer sbuff = new StringBuffer();
+
+ InputStreamReader streamReader = null;
+ BufferedReader buffReader = null;
+
+ hitInfoCollection = new Vector<HitInfo>();
+ try
+ {
+ streamReader = new InputStreamReader(document.getInputStream());
+ buffReader = new BufferedReader(streamReader);
+
+ String line = null;
+ int textPosition = 0;
+ int len = 0;
+ HitInfo hit = null;
+ int ind1 = 0;
+ //int endQuery = 0;
+
+ while( (line = buffReader.readLine()) != null)
+ {
+ len = line.length()+1;
+ sbuff.append(line+"\n");
+ if(line.startsWith("Sequences producing significant alignments:"))
+ {
+ buffReader.readLine();
+ while( !(line = buffReader.readLine()).equals("") )
+ {
+ textPosition += line.length()+1;
+ sbuff.append(line+"\n");
+
+ hit = new HitInfo(line,format);
+
+ hitInfoCollection.add(hit);
+ }
+ }
+ else if(line.indexOf(" ----") > -1 ||
+ line.indexOf(" --- ") > -1 ||
+ line.indexOf(" -- ") > -1 )
+ {
+ }
+ else if(line.startsWith(">")) // start of alignment
+ {
+ String currentID = line;
+
+ int ind = line.indexOf(" ");
+ if(ind > -1)
+ {
+ currentID = line.substring(1,ind);
+ int indDot;
+ if( (indDot = currentID.indexOf(".")) > 5)
+ {
+ String version = currentID.substring(indDot+1);
+ try
+ {
+ int version_no = Integer.parseInt(version);
+ // version number looks like uniprot so strip this
+ if(version_no < 50)
+ currentID= currentID.substring(0,indDot);
+ }
+ catch(NumberFormatException nfe){}
+ }
+ }
+
+ int ind2 = currentID.indexOf(":");
+ if(ind2 > -1)
+ {
+ currentID = currentID.substring(ind2+1);
+ }
+
+ if(hit != null)
+ hit.setEndPosition(textPosition);
+
+ hit = getHitInfo(currentID,hitInfoCollection);
+ hit.setStartPosition(textPosition);
+
+ String going = "";
+ ind = line.indexOf("GO:");
+ if(ind > -1)
+ going = line.substring(ind+3);
+
+ String nextLine = null;
+// buffReader.mark(210);
+
+// get GO numbers
+ while((nextLine = buffReader.readLine()).indexOf("Length") == -1)
+ {
+ len += nextLine.length()+1;
+ sbuff.append(nextLine+"\n");
+ if(going.equals("") && ((ind = nextLine.indexOf("GO:")) > -1))
+ going = nextLine.substring(ind+3);
+ else if(!going.equals(""))
+ going = going.concat(nextLine);
+ }
+
+ if(!going.equals(""))
+ hit.setGO(going);
+
+ if(nextLine != null)
+ {
+ len += nextLine.length()+1;
+ sbuff.append(nextLine+"\n");
+ if( (ind1 = nextLine.indexOf(" Length = ")) > -1)
+ hit.setLength(nextLine.substring(ind1+11));
+ }
+
+// get query start
+ int start = 999999;
+ int end = 0;
+ boolean seen = false;
+
+ while((nextLine = buffReader.readLine()) != null &&
+ !nextLine.startsWith(">"))
+ {
+ len += nextLine.length()+1;
+ sbuff.append(nextLine+"\n");
+
+ if(nextLine.startsWith(" Score ="))
+ {
+// System.out.println("start:: "+start+" end:: "+end);
+ if(end != 0)
+ hit.setQueryPosition(start,end);
+ start = 999999;
+ end = 0;
+ seen = true;
+ }
+ else if(nextLine.startsWith("Query:"))
+ {
+ ind1 = nextLine.indexOf(" ",8);
+ int nstart = Integer.parseInt(nextLine.substring(7,ind1).trim());
+ if(nstart < start)
+ start = nstart;
+ end = Integer.parseInt(nextLine.substring(nextLine.lastIndexOf(" ")).trim());
+ seen = false;
+ }
+ buffReader.mark(100);
+ }
+ if(!seen && end != 0)
+ hit.setQueryPosition(start,end);
+
+ buffReader.reset();
+ }
+ else if( (ind1 = line.indexOf("Identities = ")) > -1)
+ {
+ ind1 = line.indexOf("(",ind1)+1;
+ if(ind1 > -1)
+ hit.setIdentity(line.substring(ind1,line.indexOf(")",ind1)).trim());
+ }
+ else if( (ind1 = line.indexOf(" Length = ")) > -1)
+ hit.setLength(line.substring(ind1+11));
+// else if(line.startsWith("Query: "))
+// {
+// hit.setQueryEnd(Integer.parseInt(line.substring(line.lastIndexOf(" ")).trim()));
+// }
+ else if(line.startsWith("Query="))
+ {
+ int ind2 = 0;
+ if(blastPlus)
+ {
+ ind2 = line.indexOf("Length=");
+ if(ind2 == -1)
+ {
+ while((line = buffReader.readLine()).indexOf("Length=") < 0)
+ {
+ len += line.length()+1;
+ sbuff.append(line+"\n");
+ }
+ ind2 = line.indexOf("Length=");
+ }
+ ind1 = line.length();
+ qlen = Integer.parseInt(line.substring(ind2+7,ind1).trim());
+ }
+ else
+ {
+ ind1 = line.indexOf(" letters)");
+ if(ind1 == -1)
+ {
+ String nextLine = null;
+ while((nextLine = buffReader.readLine()).indexOf(" letters)") < 0)
+ {
+ len += nextLine.length()+1;
+ sbuff.append(nextLine+"\n");
+ }
+ line = nextLine;
+ ind1 = nextLine.indexOf(" letters)");
+ ind2 = nextLine.indexOf("(");
+ }
+ else
+ ind2 = line.indexOf("(");
+
+ qlen = Integer.parseInt(line.substring(ind2+1,ind1).trim());
+ }
+ }
+ textPosition += len;
+ }
+
+ if(hit != null)
+ hit.setEndPosition(textPosition);
+
+ streamReader.close();
+ buffReader.close();
+
+ GetzThread getz = new GetzThread(hitInfoCollection);
+ getz.start();
+ threads.add(getz);
+ }
+ catch (IOException ioe)
+ {
+ System.out.println("Cannot read file: " + document.getName());
+ }
+ return sbuff;
+ }
+
+
+ private StringBuffer readFASTAFile(final String format)
+ {
+ //File fn = dataFile;
+ StringBuffer sbuff = new StringBuffer();
+
+ InputStreamReader streamReader = null;
+ BufferedReader buffReader = null;
+
+ hitInfoCollection = new Vector<HitInfo>();
+ try
+ {
+ streamReader = new InputStreamReader(document.getInputStream());
+ buffReader = new BufferedReader(streamReader);
+
+ String line = null;
+ int textPosition = 0;
+ int len = 0;
+ HitInfo hi = null;
+
+ while( (line = buffReader.readLine()) != null)
+ {
+ len = line.length()+1;
+ sbuff.append(line+"\n");
+
+ int ind1;
+
+ if(line.endsWith(" aa"))
+ {
+ String tmp = line.substring(0,line.length()-3).trim();
+ int in1 = tmp.lastIndexOf(" ");
+ if(in1 > -1)
+ qlen = Integer.parseInt(tmp.substring(in1).trim());
+ }
+ else if(line.startsWith("The best scores are:"))
+ {
+ while( !(line = buffReader.readLine()).equals("") )
+ {
+ textPosition += line.length()+1;
+ sbuff.append(line+"\n");
+ hitInfoCollection.add(new HitInfo(line,format));
+ }
+ }
+ else if(line.startsWith(">>")) // start of alignment
+ {
+ int ind = line.indexOf(" ");
+ String currentID = line.substring(2,ind);
+
+ int indDot;
+ if( (indDot = currentID.indexOf(".")) > 5)
+ {
+ String version = currentID.substring(indDot+1);
+ try
+ {
+ int version_no = Integer.parseInt(version);
+ // version number looks like uniprot so strip this
+ if(version_no < 50)
+ currentID= currentID.substring(0,indDot);
+ }
+ catch(NumberFormatException nfe){}
+
+ // HERE currentID= currentID.substring(0,indDot);
+ }
+
+ if(hi != null)
+ hi.setEndPosition(textPosition);
+
+ hi = getHitInfo(currentID,hitInfoCollection);
+ hi.setStartPosition(textPosition);
+ }
+ else if(line.startsWith("Smith-Waterman")) // Smith-Waterman
+ {
+ ind1 = line.indexOf("score:");
+ int ind2;
+ if(ind1 > -1)
+ {
+ ind2 = line.indexOf(";",ind1);
+
+ hi.setScore(line.substring(ind1+6,ind2));
+
+ ind1 = ind2+1;
+ ind2 = line.indexOf("identity");
+ if(ind2 > -1)
+ hi.setIdentity(line.substring(ind1,ind2).trim());
+
+ ind1 = line.indexOf("(",ind2);
+ if(ind1 > -1)
+ {
+ ind2 = line.indexOf("ungapped)",ind1);
+
+ // fasta34 changed to similar
+ if(ind2 == -1)
+ ind2 = line.indexOf("similar)",ind1);
+
+ if(ind2 > -1)
+ hi.setUngapped(line.substring(ind1+1,ind2).trim());
+ }
+
+ ind1 = line.indexOf(" in ",ind2);
+ ind2 = line.indexOf("(",ind1);
+ if(ind1 > -1 && ind2 > -1)
+ hi.setOverlap(line.substring(ind1+4,ind2).trim());
+
+ ind1 = ind2+1;
+ ind2 = line.indexOf(":",ind1);
+ if(ind2 > -1)
+ {
+ String range = line.substring(ind1,ind2);
+ hi.setQueryRange(range);
+ int split = range.indexOf("-");
+ if(split > -1)
+ {
+// hi.setQueryStart(Integer.parseInt(range.substring(0,split)));
+// hi.setQueryEnd(Integer.parseInt(range.substring(split+1)));
+ hi.setQueryPosition(Integer.parseInt(range.substring(0,split)),
+ Integer.parseInt(range.substring(split+1)));
+ }
+ }
+
+ ind1 = ind2+1;
+ ind2 = line.indexOf(")",ind1);
+ if(ind2 > -1)
+ hi.setSubjectRange(line.substring(ind1,ind2));
+ }
+ }
+ else if( (ind1 = line.indexOf(" E():")) > -1)
+ {
+ StringTokenizer tok = new StringTokenizer(line.substring(ind1+5));
+ hi.setEValue(tok.nextToken().trim());
+ }
+
+ textPosition += len;
+ }
+
+ if(hi != null)
+ hi.setEndPosition(textPosition);
+
+ streamReader.close();
+ buffReader.close();
+
+ GetzThread getz = new GetzThread(hitInfoCollection);
+ getz.start();
+ threads.add(getz);
+ }
+ catch (IOException ioe)
+ {
+ System.out.println("Cannot read file: " + document.getName());
+ }
+ return sbuff;
+ }
+
+ /**
+ *
+ * Get the query sequence length.
+ * @return query sequence length.
+ *
+ */
+ protected int getQueryLength()
+ {
+ return qlen;
+ }
+
+ protected Vector<HitInfo> getHitCollection()
+ {
+ return hitInfoCollection;
+ }
+
+
+ private HitInfo getHitInfo(String acc, final Vector<HitInfo> hits)
+ {
+ int ind = 0;
+ acc = acc.trim();
+
+ if((ind = acc.indexOf(";")) > -1)
+ acc = acc.substring(0,ind);
+
+ HitInfo hit = null;
+ for(int i=0;i<hits.size(); i++)
+ {
+ hit = (HitInfo)hits.get(i);
+ if(hit.getAcc().equals(acc) ||
+ hit.getID().equals(acc))
+ {
+ return hit;
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ *
+ * Stop all getz processes
+ *
+ */
+ protected void stopGetz()
+ {
+ Enumeration<GetzThread> threadEnum = threads.elements();
+
+ while(threadEnum.hasMoreElements())
+ {
+ GetzThread gthread = threadEnum.nextElement();
+ if(gthread.isAlive())
+ gthread.stopMe();
+ }
+ }
+
+ class GetzThread extends Thread
+ {
+ private Vector<HitInfo> hitInfoCollection;
+ private boolean keepRunning = true;
+
+ protected GetzThread(Vector<HitInfo> hitInfoCollection)
+ {
+ this.hitInfoCollection = hitInfoCollection;
+ }
+
+ public void run()
+ {
+ getzCall(hitInfoCollection,hitInfoCollection.size());
+ }
+
+ protected void stopMe()
+ {
+ keepRunning = false;
+ }
+
+ /**
+ *
+ * Creates and executes an SRS query for all the hits in the
+ * collection.
+ * @param hit HitInfo for a single hit.
+ * @param ortholog true if ortholog is selected.
+ *
+ */
+ private void getzCall(final Vector<HitInfo> hits, final int nretrieve)
+ {
+ final String env[] = { "PATH=/usr/local/pubseq/bin/:/nfs/disk100/pubseq/bin/" };
+
+ // split mfetch query up - max 70 hits per query
+ int nhits = hits.size()/MAX_HITS + 1;
+ StringBuffer querySRS = new StringBuffer();
+ StringBuffer queryMfetch[] = new StringBuffer[nhits];
+
+ int n = 0;
+ int this_nhit = 0;
+ nhits = 0;
+
+ if(!getMfetchExecutable().exists() && !isRemoteMfetch() && !isForceUrl() &&
+ System.getProperty("j2ssh") != null &&
+ FileList.isConnected())
+ {
+ FileList fileList = new FileList();
+ FileAttributes attr = fileList.stat(mfetchExecutablePath);
+ if(attr != null)
+ remoteMfetch = attr.isFile();
+ }
+
+ Enumeration<HitInfo> ehits = hits.elements();
+ while(ehits.hasMoreElements() && keepRunning)
+ {
+ if(n>nretrieve)
+ break;
+ HitInfo hit = ehits.nextElement();
+
+ HitInfo cacheHit = checkCache(hit);
+ if(cacheHit != null)
+ {
+ logger4j.debug("Retrieved early from cache "+cacheHit.getID()+
+ " cache size="+cacheHits.length);
+ hit.setOrganism(cacheHit.getOrganism());
+ hit.setDescription(cacheHit.getDescription());
+ hit.setGeneName(cacheHit.getGeneName());
+ if(cacheHit.getEMBL() != null)
+ hit.setEMBL(cacheHit.getEMBL());
+ continue;
+ }
+
+ if(n > 0)
+ {
+ querySRS.append("|");
+
+ if(nhits > 0)
+ queryMfetch[this_nhit].append("|acc:");
+ }
+
+ querySRS.append(hit.getAcc());
+
+ if(queryMfetch[this_nhit] == null)
+ queryMfetch[this_nhit] = new StringBuffer();
+
+ queryMfetch[this_nhit].append(hit.getAcc());
+ n++;
+ nhits++;
+ if(nhits > 70)
+ {
+ nhits = 0;
+ this_nhit++;
+ }
+ }
+
+ if(!keepRunning)
+ return;
+
+ boolean isLocalMfetchExists = getMfetchExecutable().exists();
+ if(isForceUrl())
+ isLocalMfetchExists = false;
+
+ BufferedReader strbuff = null;
+ final File fgetz = new File("/usr/local/pubseq/bin/getz");
+
+ if(isLocalMfetchExists || remoteMfetch)
+ {
+ StringBuffer buff = new StringBuffer();
+ for(int i=0; i<queryMfetch.length; i++)
+ {
+ if(queryMfetch[i] == null ||
+ queryMfetch[i].toString().length() == 0 )
+ continue;
+
+ String mfetch = queryMfetch[i].toString();
+
+ if(isLocalMfetchExists) // local
+ {
+ String cmd[] = { "mfetch", "-f", "acc org des gen",
+ "-d", "uniprot", "-i", "acc:"+mfetch };
+
+ ExternalApplication app = new ExternalApplication(cmd,
+ env,null);
+ buff.append(app.getProcessStdout());
+ }
+ else // remote
+ {
+ String cmd =
+ "mfetch -f \"acc org des gen\" -d uniprot -i \"acc:"+mfetch+"\"" ;
+ uk.ac.sanger.artemis.j2ssh.SshPSUClient ssh =
+ new uk.ac.sanger.artemis.j2ssh.SshPSUClient(cmd);
+ ssh.run();
+ buff.append(ssh.getStdOutBuffer());
+ }
+ }
+
+ final StringReader strread = new StringReader(buff.toString());
+ strbuff = new BufferedReader(strread);
+ }
+ else if(!fgetz.exists())
+ {
+ try
+ {
+ String queryURL = DataCollectionPane.srs_url+
+ "/wgetz?-noSession+-ascii+-f+acc%20org%20description%20gen+[uniprot-acc:"+
+ querySRS.toString()+"]+-lv+500";
+ URL wgetz = new URL(queryURL);
+ logger4j.debug(queryURL);
+
+ InputStream in = wgetz.openStream();
+
+ strbuff = new BufferedReader(new InputStreamReader(in));
+ StringBuffer resBuff = new StringBuffer();
+ String line;
+ while((line = strbuff.readLine()) != null)
+ resBuff.append(line);
+
+ strbuff.close();
+ in.close();
+
+ String res = resBuff.toString();
+ res= insertNewline(res, "OS ");
+ res= insertNewline(res, "DE ");
+ res= insertNewline(res, "GN ");
+ res= insertNewline(res, "AC ");
+
+ StringReader strread = new StringReader(res);
+ strbuff = new BufferedReader(strread);
+
+// System.out.println(DataCollectionPane.srs_url+
+// "/wgetz?-f+acc%20org%20description%20gen+[uniprot-acc:"+
+// query.toString()+"]+-lv+500");
+// System.out.println("HERE\n"+res);
+ }
+ catch(MalformedURLException e) {System.err.println(e);}
+ catch(IOException e) {System.err.println(e);}
+ }
+ else
+ {
+ String cmd[] = { "getz", "-f", "acc org description gen",
+ "[uniprot-acc:"+querySRS.toString()+"]" };
+
+ ExternalApplication app = new ExternalApplication(cmd,
+ env,null);
+ String res = app.getProcessStdout();
+ StringReader strread = new StringReader(res);
+ strbuff = new BufferedReader(strread);
+ }
+
+ HitInfo hit[] = new HitInfo[2];
+ HitInfo this_hit = null;
+ String line = null;
+ String lineStrip = null;
+
+ try
+ {
+ while((line = strbuff.readLine()) != null)
+ {
+ line = line.trim();
+ if(line.equals(""))
+ continue;
+
+ if(line.length() < 3) // empty description line
+ continue;
+
+ lineStrip = line.substring(3).trim();
+ if(line.startsWith("AC "))
+ {
+ String acc1;
+ String acc2 = null;
+
+ if(lineStrip.endsWith(";"))
+ lineStrip = lineStrip.substring(0, lineStrip.length()-1);
+
+ int ind = lineStrip.indexOf(";");
+
+ if(ind > -1)
+ {
+ acc1 = lineStrip.substring(0,ind);
+ acc2 = lineStrip.substring(ind+1);
+ }
+ else
+ acc1 = lineStrip;
+
+ hit[0] = getHitInfo(acc1,hits);
+ if(acc2 != null)
+ hit[1] = getHitInfo(acc2,hits);
+ else
+ hit[1] = null;
+
+ if(hit[0] == null)
+ {
+ logger4j.warn("HIT NOT FOUND "+lineStrip);
+ continue;
+ }
+
+ hit[0].setGeneName("");
+
+ if(hit[1] != null)
+ hit[1].setGeneName("");
+ }
+
+ for(int i = 0; i < hit.length; i++)
+ {
+ this_hit = hit[i];
+ if(this_hit == null)
+ continue;
+
+ if(line.startsWith("OS "))
+ this_hit.setOrganism(lineStrip);
+ else if(line.startsWith("DE "))
+ this_hit.appendDescription(lineStrip);
+ else if(line.startsWith("GN "))
+ {
+ StringTokenizer tokGN = new StringTokenizer(lineStrip, ";");
+ while(tokGN.hasMoreTokens())
+ {
+ line = tokGN.nextToken();
+ if(line.startsWith("Name="))
+ this_hit.setGeneName(line.substring(5));
+ // else
+ // hit.appendDescription(line);
+ }
+ }
+ }
+ }
+
+ strbuff.close();
+ }
+ catch(IOException ioe){}
+
+
+ //
+ //
+ // mfetch
+ if(isLocalMfetchExists || remoteMfetch)
+ {
+ getDbXRefWithMfetch(isLocalMfetchExists, queryMfetch, env, hits);
+ return;
+ }
+
+ //
+ //
+ // SRS methods
+ String res;
+ ehits = hits.elements();
+ while(ehits.hasMoreElements() && keepRunning)
+ {
+ this_hit = (HitInfo)ehits.nextElement();
+
+ HitInfo cacheHit = checkCache(this_hit);
+
+ if(cacheHit != null && cacheHit.getEMBL() != null)
+ {
+ logger4j.debug("Retrieved from cache "+cacheHit.getID()+
+ " cache size="+cacheHits.length);
+ this_hit.setEMBL(cacheHit.getEMBL());
+ this_hit.setEC_number(cacheHit.getEC_number());
+ this_hit.setGeneName(cacheHit.getGeneName());
+ continue;
+ }
+
+ res = getUniprotLinkToDatabase(fgetz, isLocalMfetchExists, this_hit, env, "embl");
+
+ int ind1 = res.indexOf("ID ");
+ if(ind1 > -1)
+ {
+ StringTokenizer tok = new StringTokenizer(res);
+ tok.nextToken();
+ this_hit.setEMBL(tok.nextToken());
+ }
+ else
+ this_hit.setEMBL("");
+
+ // EC_number
+ if(this_hit.getEC_number() != null)
+ continue;
+
+ res = getUniprotLinkToDatabase(fgetz, isLocalMfetchExists, this_hit, env, "enzyme");
+
+ ind1 = res.indexOf("ID ");
+ if(ind1 > -1)
+ {
+ StringTokenizer tok = new StringTokenizer(res);
+ tok.nextToken();
+ this_hit.setEC_number(tok.nextToken());
+ }
+
+ addHitToCache(this_hit);
+ }
+ }
+ }
+
+ /**
+ * Add a hit to the cache
+ * @param thisHit
+ */
+ private void addHitToCache(final HitInfo thisHit)
+ {
+ if(nCacheHits >= cacheHits.length)
+ nCacheHits = 0;
+ cacheHits[nCacheHits] = thisHit;
+ nCacheHits++;
+ }
+
+ /**
+ *
+ * @param isLocalMfetchExists
+ * @param queryMfetch
+ * @param env
+ * @param hits
+ */
+ private void getDbXRefWithMfetch(final boolean isLocalMfetchExists,
+ final StringBuffer queryMfetch[],
+ final String env[],
+ final Vector<HitInfo> hits)
+ {
+ String res = null;
+ String line = null;
+
+ for(int i = 0; i < queryMfetch.length; i++)
+ {
+ if(queryMfetch[i] == null || queryMfetch[i].toString().length() == 0)
+ continue;
+
+ final String mfetch = queryMfetch[i].toString();
+
+ //
+ // link to EMBL
+ res = getUniprotLinkToDatabaseByMFetch(isLocalMfetchExists, mfetch, env,
+ "embl");
+
+ StringReader strread = new StringReader(res);
+ BufferedReader strbuff = new BufferedReader(strread);
+
+ try
+ {
+ while((line = strbuff.readLine()) != null)
+ {
+ String acc;
+ if(line.startsWith("linked from:"))
+ {
+ acc = line.substring(12).trim();
+ int ind = acc.indexOf('.');
+ if(ind > -1)
+ acc = acc.substring(0, ind);
+
+ HitInfo thisHit = getHitInfo(acc, hits);
+ if(thisHit == null)
+ {
+ logger4j.warn(acc+" NOT FOUND");
+ continue;
+ }
+
+ line = strbuff.readLine();
+
+ int ind1 = line.indexOf("ID ");
+ if(ind1 > -1)
+ {
+ int ind2 = line.indexOf(';');
+ thisHit.setEMBL(line.substring(3, ind2).trim());
+ }
+ else
+ thisHit.setEMBL("");
+ addHitToCache(thisHit);
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ final String accessions[] = mfetch.split("\\|");
+
+ for(int j=0;j<accessions.length; j++)
+ {
+ String acc;
+ if(accessions[j].startsWith("acc:"))
+ acc = accessions[j].substring(4);
+ else
+ acc = accessions[j];
+ HitInfo thisHit = getHitInfo(acc, hits);
+ if(thisHit != null && thisHit.getEMBL() == null)
+ thisHit.setEMBL("");
+ }
+
+ //
+ // link to enzyme
+ res = getUniprotLinkToDatabaseByMFetch(isLocalMfetchExists, mfetch, env, "enzyme");
+
+ strread = new StringReader(res);
+ strbuff = new BufferedReader(strread);
+
+ try
+ {
+ while((line = strbuff.readLine()) != null)
+ {
+ String acc;
+ if(line.startsWith("linked from:"))
+ {
+ acc = line.substring(12).trim();
+ int ind = acc.indexOf('.');
+ if(ind > -1)
+ acc = acc.substring(0, ind);
+
+ HitInfo thisHit = getHitInfo(acc, hits);
+ if(thisHit == null)
+ {
+ logger4j.warn(acc + " NOT FOUND");
+ continue;
+ }
+
+ line = strbuff.readLine();
+
+ int ind1 = line.indexOf("ID ");
+ if(ind1 > -1)
+ {
+ int ind2 = line.indexOf(';');
+ if(ind2 < 0)
+ ind2 = line.length();
+ thisHit.setEC_number(line.substring(3, ind2).trim());
+ }
+ }
+ }
+
+ for(int j=0;j<accessions.length; j++)
+ {
+ String acc;
+ if(accessions[j].startsWith("acc:"))
+ acc = accessions[j].substring(4);
+ else
+ acc = accessions[j];
+ HitInfo thisHit = getHitInfo(acc, hits);
+ if(thisHit != null && thisHit.getEMBL() == null)
+ thisHit.setEC_number("");
+ }
+ }
+ catch(IOException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ return;
+ }
+
+ /**
+ *
+ * @param hit
+ * @return
+ */
+ protected static HitInfo checkCache(final HitInfo hit)
+ {
+ for(int i=0; i<cacheHits.length; i++)
+ {
+ if(cacheHits[i] != null &&
+ cacheHits[i].getID().equals(hit.getID()))
+ return cacheHits[i];
+ }
+ return null;
+ }
+
+ protected static String insertNewline(String s1, final String s2)
+ {
+ int index = 0;
+ while((index = s1.indexOf(s2, index)) > -1)
+ {
+ if(index > 0)
+ s1 = s1.substring(0,index-1)+"\n"+s1.substring(index);
+ index++;
+ }
+ return s1;
+ }
+
+ /**
+ *
+ * Link Uniprot to the another database (e.g. EMBL or ENZYME)
+ *
+ */
+ protected static String getUniprotLinkToDatabase(final File fgetz,
+ final boolean isLocalMfetchExists,
+ final HitInfo hit,
+ final String env[], final String DB)
+ {
+ String res = null;
+
+ if(isLocalMfetchExists)
+ {
+ final String cmd[] = { "mfetch", "-f", "id",
+ "-d", "uniprot", "-i", "acc:"+hit.getID(),
+ "-l", DB };
+
+ ExternalApplication app = new ExternalApplication(cmd,
+ env,null);
+ res = app.getProcessStdout();
+
+ }
+ else if(remoteMfetch)
+ {
+ final String cmd =
+ "mfetch -f id -d uniprot -i \"acc:"+hit.getID()+"\" -l "+DB ;
+ uk.ac.sanger.artemis.j2ssh.SshPSUClient ssh =
+ new uk.ac.sanger.artemis.j2ssh.SshPSUClient(cmd);
+ ssh.run();
+ res = ssh.getStdOut();
+ }
+ else if(!fgetz.exists())
+ {
+ try
+ {
+ final String queryURL = DataCollectionPane.srs_url+
+ "/wgetz?-noSession+-ascii+-f+id+[uniprot-acc:"+hit.getAcc()+"]%3E"+DB;
+ URL wgetz = new URL(queryURL);
+ logger4j.debug(queryURL);
+
+ InputStream in = wgetz.openStream();
+ BufferedReader strbuff = new BufferedReader(new InputStreamReader(in));
+ StringBuffer resBuff = new StringBuffer();
+ String line;
+ while((line = strbuff.readLine()) != null)
+ resBuff.append(line);
+
+ strbuff.close();
+ in.close();
+
+ res = resBuff.toString();
+
+ if(res.indexOf("SRS error") > -1)
+ return "";
+
+// System.out.println(DataCollectionPane.srs_url+
+// "/wgetz?-f+id+[uniprot-acc:"+hit.getAcc()+"]%3E"+DB);
+ }
+ catch(MalformedURLException e) {System.err.println(e);}
+ catch(IOException e) {System.err.println(e);}
+
+ }
+ else
+ {
+ String cmd3[] = { "getz", "-f", "id",
+ "[libs={uniprot}-acc:"+hit.getID()+"]>"+DB };
+ ExternalApplication app = new ExternalApplication(cmd3,env,null);
+ res = app.getProcessStdout();
+ }
+
+ return res;
+ }
+
+
+
+
+
+
+ /**
+ *
+ * Link Uniprot to the another database (e.g. EMBL or ENZYME)
+ *
+ */
+ protected static String getUniprotLinkToDatabaseByMFetch(
+ final boolean isLocalMfetchExists,
+ final String mfetchList,
+ final String env[], final String DB)
+ {
+ String res = null;
+
+ if(isLocalMfetchExists)
+ {
+ final String cmd[] = { "mfetch", "-f", "id",
+ "-d", "uniprot", "-i", "acc:"+mfetchList,
+ "-L", DB };
+
+ ExternalApplication app = new ExternalApplication(cmd,
+ env,null);
+ res = app.getProcessStdout();
+
+ }
+ else if(remoteMfetch)
+ {
+ final String cmd =
+ "mfetch -f id -d uniprot -i \"acc:"+mfetchList+"\" -L "+DB ;
+ uk.ac.sanger.artemis.j2ssh.SshPSUClient ssh =
+ new uk.ac.sanger.artemis.j2ssh.SshPSUClient(cmd);
+ ssh.run();
+ res = ssh.getStdOut();
+ }
+
+
+ return res;
+ }
+
+
+ protected void show(Object obj)
+ {
+ if(obj instanceof HitInfo)
+ {
+ HitInfo hit = (HitInfo)obj;
+
+ int start = hit.getStartPosition();
+// int end = hit.getEndPosition();
+// textArea.moveCaretPosition(end);
+ textArea.moveCaretPosition(start);
+
+ Point pos = getViewport().getViewPosition();
+ Dimension rect = getViewport().getViewSize();
+ double hgt = rect.getHeight()+pos.getY();
+ pos.setLocation(pos.getX(),hgt);
+ getViewport().setViewPosition(pos);
+ }
+ }
+
+ public static boolean isRemoteMfetch()
+ {
+ return remoteMfetch;
+ }
+
+ public static void setRemoteMfetch(boolean remoteMfetch)
+ {
+ FastaTextPane.remoteMfetch = remoteMfetch;
+ }
+
+ public static boolean isForceUrl()
+ {
+ return forceUrl;
+ }
+
+ public static void setForceUrl(boolean forceUrl)
+ {
+ FastaTextPane.forceUrl = forceUrl;
+ }
+
+ public static File getMfetchExecutable()
+ {
+ return mfetchExecutable;
+ }
+
+
+}
+
diff --git a/uk/ac/sanger/artemis/editor/HitInfo.java b/uk/ac/sanger/artemis/editor/HitInfo.java
new file mode 100644
index 0000000..8924c15
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/HitInfo.java
@@ -0,0 +1,739 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import java.util.StringTokenizer;
+import java.util.NoSuchElementException;
+import java.util.Vector;
+import java.util.Hashtable;
+
+/**
+*
+* Hit information is contained in this object.
+*
+*/
+public class HitInfo
+{
+ /** Hit in database */
+ private String db = null;
+ /** Hit ID */
+ private String id = null;
+ /** Hit accession number */
+ private String acc = null;
+ /** Organism source */
+ private String org = null;
+ /** Gene name */
+ private String geneName = null;
+ /** Description */
+ private String desc = null;
+ /** Sequence length */
+ private String length = null;
+
+ /** */
+ //private String opt = null;
+ //private String zscore = null;
+ private String evalue = null;
+ private String header = null;
+ /** EMBL ID from linking to EMBL in SRS */
+ private String emblID = null;
+ private String score = null;
+ /** percentage identity */
+ private String identity = null;
+ /** ungapped identity */
+ private String ungapped = null;
+ /** amino acid overlap */
+ private String aaOverlap = null;
+ /** query range in the alignment */
+ private String queryRange = null;
+ /** subject range in the alignment */
+ private String subjectRange = null;
+ /** start position of this hit in the results output */
+ private int startPosition = 0;
+ /** end position of this hit in the results output */
+ private int endPosition = 0;
+ /** collection of GO terms */
+ private Vector go;
+ /** EC_number */
+ private String ec_number = null;
+
+ /** query hit range */
+ private Vector queryPosition = new Vector();
+
+ /** hastable for go terms */
+ private Hashtable go_hash;
+
+ public HitInfo(String header, String format)
+ {
+ header = header.trim();
+ this.header = header;
+ if(format.equals("fasta"))
+ setFastaHitInfo(header);
+ else if(format.equals("blastp"))
+ setBLASTPInfo(header);
+ }
+
+
+ /**
+ *
+ * Extract id, accession numbers etc. from the
+ * header of BLASTP results.
+ * @param header results header line from BLASTP.
+ *
+ */
+ protected void setBLASTPInfo(String header)
+ {
+ int ind1 = header.indexOf(" ");
+ if(ind1 > -1)
+ {
+ id = header.substring(0,ind1);
+
+ int indDot;
+
+ if( (indDot = id.indexOf(".")) > 5)
+ {
+ //
+ // uniprot fasta headers
+ // >pac.sv id desc
+ //
+ String version = id.substring(indDot+1);
+ try
+ {
+ int version_no = Integer.parseInt(version);
+ // version number looks like uniprot so strip this
+ if(version_no < 50)
+ id = id.substring(0,indDot);
+ }
+ catch(NumberFormatException nfe){}
+
+ acc = id;
+ }
+ }
+ else
+ return;
+
+ int ind1a = id.indexOf(":");
+ if(ind1a > -1)
+ {
+ db = id.substring(0,ind1a);
+
+ if(db.equals("UNIPROT"))
+ db = "UniProt";
+
+ id = id.substring(ind1a+1);
+ }
+
+ int ind2 = header.indexOf(" ",ind1+1);
+ if(ind2 > -1)
+ {
+ if(acc == null)
+ acc = header.substring(ind1+1,ind2);
+// else
+// id = header.substring(ind1+1,ind2);
+ }
+ else
+ return;
+
+ if(acc.startsWith("|")) // genedb
+ acc = id;
+
+ ind1 = header.lastIndexOf(" ");
+ setEValue(header.substring(ind1).trim());
+
+ ind2 = header.substring(0,ind1).trim().lastIndexOf(" ");
+ if(ind2>-1)
+ score = header.substring(ind2,ind1).trim();
+ }
+
+ /**
+ *
+ * Extract id, accession numbers etc. from the
+ * header of FASTA results.
+ * @param header results header line from FASTA.
+ *
+ */
+ protected void setFastaHitInfo(String header)
+ {
+ int ind1 = header.indexOf(" ");
+ if(ind1 > -1)
+ {
+ id = header.substring(0,ind1);
+ int indDot;
+ if( (indDot = id.indexOf(".")) > 5)
+ {
+ //
+ // uniprot fasta headers
+ // >pac.sv id desc
+ //
+ String version = id.substring(indDot+1);
+ try
+ {
+ int version_no = Integer.parseInt(version);
+ // version number looks like uniprot so strip this
+ if(version_no < 50)
+ id = id.substring(0,indDot);
+ }
+ catch(NumberFormatException nfe){}
+ acc = id;
+ }
+ }
+ else
+ return;
+
+ int ind2 = header.indexOf(" ",ind1+1);
+ if(ind2 > -1)
+ {
+ if(acc == null)
+ acc = header.substring(ind1+1,ind2);
+// else
+// id = header.substring(ind1+1,ind2);
+ }
+ else
+ return;
+
+ ind1 = ind2;
+ ind2 = header.lastIndexOf("(");
+
+ if(ind2 > -1)
+ desc = "";
+// desc = header.substring(ind1,ind2).trim();
+ else
+ return;
+
+ ind1 = ind2+1;
+ ind2 = header.indexOf(")",ind1);
+ if(ind2 > -1)
+ length = header.substring(ind1,ind2).trim();
+ else
+ return;
+
+
+ StringTokenizer tok = new StringTokenizer(header.substring(ind2+1));
+ try
+ {
+ tok.nextToken(); // opt
+ tok.nextToken(); // zscore
+ setEValue(tok.nextToken());
+ }
+ catch(NoSuchElementException exp){}
+
+ }
+
+
+ /**
+ *
+ * Set EC_number
+ *
+ */
+ protected void setEC_number(String ec_number)
+ {
+ this.ec_number = ec_number;
+ }
+
+
+ /**
+ *
+ * Get EC_number
+ *
+ */
+ protected String getEC_number()
+ {
+ return ec_number;
+ }
+
+
+ /**
+ *
+ * Set the start position for the query sequence in the alignment.
+ * @param startQuery startQuery
+ *
+ */
+ protected void setQueryPosition(int startQuery, int endQuery)
+ {
+ queryPosition.add(new Integer(startQuery));
+ queryPosition.add(new Integer(endQuery));
+ }
+
+
+ /**
+ *
+ * Get the start position for the query sequence in the alignment.
+ * @param startQuery startQuery
+ *
+ */
+ protected Vector getQueryPosition()
+ {
+ return queryPosition;
+ }
+
+ /**
+ *
+ * Set the start position of the alignment in the
+ * results.
+ * @param startPosition start position.
+ *
+ */
+ protected void setStartPosition(int startPosition)
+ {
+ this.startPosition = startPosition;
+ }
+
+ /**
+ *
+ * Set the end position of the alignment in the
+ * results.
+ * @param endPosition end position.
+ *
+ */
+ protected void setEndPosition(int endPosition)
+ {
+ this.endPosition = endPosition;
+ }
+
+
+ /**
+ *
+ * Set the sequence length.
+ * @param length sequence length.
+ *
+ */
+ protected void setLength(String length)
+ {
+ this.length = length;
+ }
+
+ /**
+ *
+ * Get the sequence length.
+ * @return sequence length.
+ *
+ */
+ protected String getLength()
+ {
+ return length;
+ }
+
+ /**
+ *
+ * Get the results header.
+ * @return results header.
+ *
+ */
+ protected String getHeader()
+ {
+ return header;
+ }
+
+ /**
+ *
+ * Get the alignment start position.
+ * @return start position.
+ *
+ */
+ protected int getStartPosition()
+ {
+ return startPosition;
+ }
+
+ /**
+ *
+ * Get the alignment end position.
+ * @return end position.
+ *
+ */
+ protected int getEndPosition()
+ {
+ return endPosition;
+ }
+
+ /**
+ *
+ * Get the database.
+ * @return database.
+ *
+ */
+ protected String getDB()
+ {
+ return db;
+ }
+
+
+ /**
+ *
+ * Get the sequence ID.
+ * @return sequence ID.
+ *
+ */
+ protected String getID()
+ {
+ return id;
+ }
+
+ /**
+ *
+ * Get the sequence accession number.
+ * @return sequence accession number.
+ *
+ */
+ protected String getAcc()
+ {
+ return acc;
+ }
+
+ /**
+ *
+ * Get the organism source (OS).
+ * @return organism source.
+ *
+ */
+ protected String getOrganism()
+ {
+ return org;
+ }
+
+ /**
+ *
+ * Set the organism source (OS).
+ * @param org organism source.
+ *
+ */
+ protected void setOrganism(final String orgStr)
+ {
+ if(org == null || org.equals(""))
+ this.org = orgStr;
+ else
+ this.org = this.org + " " + orgStr;
+ }
+
+ /**
+ *
+ * Set the description (DE).
+ * @param desc sequence description.
+ *
+ */
+ protected void setDescription(String desc)
+ {
+ this.desc = desc;
+ }
+
+
+ /**
+ *
+ * Append to the description.
+ * @param description to append.
+ *
+ */
+ protected void appendDescription(String s)
+ {
+ if(desc == null || desc.equals(""))
+ desc = new String(s);
+ else
+ desc = desc + " " + s;
+
+ desc = desc.trim();
+
+ // check for EC number
+ int ind1 = s.indexOf("(EC ");
+ if(ind1 > -1)
+ {
+ int ind2 = s.indexOf(")",ind1);
+ if(ind2 > -1)
+ setEC_number(s.substring(ind1+4,ind2).trim());
+ }
+ }
+
+
+ /**
+ *
+ * Get the description (DE).
+ * @return sequence description.
+ *
+ */
+ protected String getDescription()
+ {
+ return desc;
+ }
+
+ /**
+ *
+ * Set the EMBL linked entry (obtained from database
+ * linking in SRS).
+ * @param emblID EMBL ID.
+ *
+ */
+ protected void setEMBL(String emblID)
+ {
+ if(emblID.endsWith(";"))
+ emblID = emblID.substring(0, emblID.length()-1);
+ this.emblID = emblID;
+ }
+
+ /**
+ *
+ * Get the EMBL linked entry (obtained from database
+ * linking in SRS).
+ * @return EMBL ID.
+ *
+ */
+ protected String getEMBL()
+ {
+ return emblID;
+ }
+
+ /**
+ *
+ * Set the sequence alignment score.
+ * @param score alignment score.
+ *
+ */
+ protected void setScore(String score)
+ {
+ this.score = score;
+ }
+
+ /**
+ *
+ * Get the sequence alignment score.
+ * @return alignment score.
+ *
+ */
+ protected String getScore()
+ {
+ return score;
+ }
+
+ /**
+ *
+ * Set the percentage identity found in the alignment.
+ * @param identity percentage identity.
+ *
+ */
+ protected void setIdentity(String identity)
+ {
+ this.identity = identity;
+ }
+
+ /**
+ *
+ * Get the percentage identity found in the alignment.
+ * @return percentage identity.
+ *
+ */
+ protected String getIdentity()
+ {
+ return identity;
+ }
+
+ /**
+ *
+ * Set the percentage ungapped identity found in the alignment.
+ * @param ungapped percentage ungapped identity.
+ *
+ */
+ protected void setUngapped(String ungapped)
+ {
+ this.ungapped = ungapped;
+ }
+
+ /**
+ *
+ * Get the percentage ungapped identity found in the alignment.
+ * @return percentage ungapped identity.
+ *
+ */
+ protected String getUngapped()
+ {
+ return ungapped;
+ }
+
+ /**
+ *
+ * Set the e-value for this hit.
+ * @param evalue e-value.
+ *
+ */
+ protected void setEValue(String evalue)
+ {
+ if(evalue.startsWith("e"))
+ evalue = "0"+evalue;
+ this.evalue = evalue;
+ }
+
+ /**
+ *
+ * Get the e-value for this hit.
+ * @return e-value.
+ *
+ */
+ protected String getEValue()
+ {
+ return evalue;
+ }
+
+ /**
+ *
+ * Set the number of amino acids that overlap in this
+ * alignment.
+ * @param aaOverlap number of aa-overlaps.
+ *
+ */
+ protected void setOverlap(String aaOverlap)
+ {
+ this.aaOverlap = aaOverlap;
+ }
+
+ /**
+ *
+ * Get the number of amino acids that overlap in this
+ * alignment.
+ * @return number of aa-overlaps.
+ *
+ */
+ protected String getOverlap()
+ {
+ return aaOverlap;
+ }
+
+ /**
+ *
+ * Set the query range in this alignment.
+ * @param queryRange query range.
+ *
+ */
+ protected void setQueryRange(String queryRange)
+ {
+ this.queryRange = queryRange;
+ }
+
+ /**
+ *
+ * Get the query range in this alignment.
+ * @return query range.
+ *
+ */
+ protected String getQueryRange()
+ {
+ return queryRange;
+ }
+
+ /**
+ *
+ * Set the subject range in this alignment.
+ * @param subjectRange subject range.
+ *
+ */
+ protected void setSubjectRange(String subjectRange)
+ {
+ this.subjectRange = subjectRange;
+ }
+
+ /**
+ *
+ * Get the subject range in this alignment.
+ * @return subject range.
+ *
+ */
+ protected String getSubjectRange()
+ {
+ return subjectRange;
+ }
+
+ /**
+ *
+ * Set the gene name (for ortholog).
+ * @param geneName gene name.
+ *
+ */
+ protected void setGeneName(String geneName)
+ {
+ this.geneName = geneName;
+ }
+
+ /**
+ *
+ * Get the gene name (for ortholog).
+ * @return gene name.
+ *
+ */
+ protected String getGeneName()
+ {
+ return geneName;
+ }
+
+
+ /**
+ *
+ * Set the GO term
+ *
+ */
+ protected void setGO(String go_terms)
+ {
+ go_terms = go_terms.trim();
+ StringTokenizer tok = new StringTokenizer(go_terms);
+ while(tok.hasMoreElements())
+ {
+ if(go == null)
+ go = new Vector();
+ go.add(tok.nextToken());
+ }
+ }
+
+
+ /**
+ *
+ * Get GO collection.
+ *
+ */
+ protected Vector getGO()
+ {
+ return go;
+ }
+
+
+ /**
+ *
+ * Set a GO association.
+ *
+ */
+ protected void setGoAssociation(String go_id, String assoc)
+ {
+ if(go_hash == null)
+ go_hash = new Hashtable();
+ go_hash.put(go_id,assoc);
+ }
+
+ /**
+ *
+ * Get a GO association.
+ *
+ */
+ protected String getGoAssociation(String go_id)
+ {
+ if(go_hash != null && go_hash.containsKey(go_id))
+ return (String)go_hash.get(go_id);
+ return null;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/editor/MouseOverButton.java b/uk/ac/sanger/artemis/editor/MouseOverButton.java
new file mode 100644
index 0000000..21de974
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/MouseOverButton.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * created: Wed Sep 7 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+*
+* Extend JButton to show mouse over colour change.
+*
+*/
+public class MouseOverButton extends JButton
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+ private boolean over = false;
+ private HitInfo hit;
+
+ public MouseOverButton()
+ {
+ super();
+ }
+
+ public MouseOverButton(String s)
+ {
+ super(s);
+ }
+
+ public MouseOverButton(HitInfo hit)
+ {
+ super(hit.getID());
+ this.hit = hit;
+ }
+
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+
+ if(!getText().equals(""))
+ return;
+
+ Graphics2D g2 = (Graphics2D)g;
+
+ if(over)
+ g2.setColor(new Color(100,100,200));
+ else
+ g2.setColor(Color.blue);
+ g2.setStroke(new BasicStroke(1.5f));
+ g2.drawLine(1,3,11,3);
+ g2.drawLine(1,7,11,7);
+
+ setSize(12,12);
+ }
+
+ public String getToolTipText()
+ {
+ if(hit == null)
+ return null;
+
+ return hit.getOrganism();
+ }
+
+ protected void processMouseEvent(MouseEvent evt)
+ {
+ super.processMouseEvent(evt);
+ switch (evt.getID())
+ {
+ case MouseEvent.MOUSE_ENTERED:
+ over = true;
+ setForeground(new Color(100,100,200));
+ repaint();
+ break;
+ case MouseEvent.MOUSE_EXITED:
+ over = false;
+ setForeground(Color.blue);
+ repaint();
+ break;
+ }
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/editor/MultiLineToolTipUI.java b/uk/ac/sanger/artemis/editor/MultiLineToolTipUI.java
new file mode 100644
index 0000000..718c9ac
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/MultiLineToolTipUI.java
@@ -0,0 +1,212 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+import javax.swing.plaf.ToolTipUI;
+import javax.swing.plaf.ComponentUI;
+
+/**
+*
+* UI for multiple line tooltips
+*
+*/
+public class MultiLineToolTipUI extends ToolTipUI
+{
+ static MultiLineToolTipUI SINGLETON = new MultiLineToolTipUI();
+ static boolean DISPLAY_ACCELERATOR=true;
+ static boolean displayAccelerator;
+ int accelerator_offset = 15;
+ int inset = 3;
+ Graphics g;
+
+ private MultiLineToolTipUI() {}
+
+ public static void initialize()
+ {
+ // don't hardcode class name, this way we can obfuscate.
+ String key = "ToolTipUI";
+ Class cls = SINGLETON.getClass();
+ String name = cls.getName();
+ UIManager.put(key,name);
+ UIManager.put(name,cls);
+ }
+
+ public static ComponentUI createUI(JComponent c)
+ {
+ return SINGLETON;
+ }
+
+ public void installUI(JComponent c)
+ {
+ LookAndFeel.installColorsAndFont(c, "ToolTip.background",
+ "ToolTip.foreground", "ToolTip.font");
+ LookAndFeel.installBorder(c, "ToolTip.border");
+ }
+
+ public void uninstallUI(JComponent c)
+ {
+ LookAndFeel.uninstallBorder(c);
+ }
+
+ public static void setDisplayAcceleratorKey(boolean val)
+ {
+ displayAccelerator=val;
+ }
+
+ public Dimension getPreferredSize(JComponent c)
+ {
+ Font font = c.getFont();
+ String tipText = ((JToolTip)c).getTipText();
+ MyToolTip mtt = new MyToolTip();
+ FontMetrics fontMetrics = mtt.toolTipFontMetrics(font);
+ int fontHeight = fontMetrics.getHeight();
+
+ if (tipText == null)
+ tipText = "";
+ String lines[] = PlafMacros.breakupLines(tipText);
+ int num_lines = lines.length;
+
+ int width, height, onewidth;
+ height = num_lines * fontHeight;
+ width = 0;
+ for (int i=0; i<num_lines; i++)
+ {
+ onewidth = fontMetrics.stringWidth(lines[i]);
+ if (displayAccelerator && i == num_lines - 1)
+ {
+ String keyText = getAcceleratorString((JToolTip)c);
+ if (!keyText.equals(""))
+ onewidth += fontMetrics.stringWidth(keyText)
+ + accelerator_offset;
+ }
+ width = Math.max(width,onewidth);
+ }
+ return new Dimension(width+inset*2,height+inset*2);
+ }
+
+ public Dimension getMinimumSize(JComponent c)
+ {
+ return getPreferredSize(c);
+ }
+
+ public Dimension getMaximumSize(JComponent c)
+ {
+ return getPreferredSize(c);
+ }
+
+ public void paint(Graphics g, JComponent c)
+ {
+ Font font = c.getFont();
+ MyToolTip mtt = new MyToolTip();
+ FontMetrics fontMetrics = mtt.toolTipFontMetrics(font);
+ Dimension dimension = c.getSize();
+ int fontHeight = fontMetrics.getHeight();
+ int fontAscent = fontMetrics.getAscent();
+ String tipText = ((JToolTip)c).getTipText();
+ if(tipText == null)
+ return;
+ String lines[] = PlafMacros.breakupLines(tipText);
+ int num_lines = lines.length;
+ int height;
+ int i;
+
+ g.setColor(c.getBackground());
+ g.fillRect(0, 0, dimension.width, dimension.height);
+ g.setColor(c.getForeground());
+ for (i=0, height=2+fontAscent;
+ i<num_lines; i++, height+=fontHeight)
+ {
+ g.drawString(lines[i], inset, height);
+ if (displayAccelerator && i == num_lines - 1)
+ {
+ String keyText = getAcceleratorString((JToolTip)c);
+ if (!keyText.equals(""))
+ {
+ Font smallFont = new Font(font.getName(),
+ font.getStyle(), font.getSize()-2);
+ g.setFont(smallFont);
+ g.drawString(keyText, fontMetrics.stringWidth(lines[i])
+ + accelerator_offset, height);
+ }
+ }
+ }
+ }
+
+ public String getAcceleratorString(JToolTip tip)
+ {
+ JComponent comp = tip.getComponent();
+ if (comp == null)
+ return "";
+ KeyStroke[] keys =comp.getRegisteredKeyStrokes();
+ String controlKeyStr = "";
+ KeyStroke postTip=KeyStroke.getKeyStroke(
+ KeyEvent.VK_F1,Event.CTRL_MASK);
+
+ for (int i = 0; i < keys.length; i++)
+ {
+ // Ignore ToolTipManager postTip action,
+ // in swing1.1beta3 and onward
+ if (postTip.equals(keys[i]))
+ continue;
+ //char c = (char)keys[i].getKeyCode();
+ int mod = keys[i].getModifiers();
+ if ( mod == InputEvent.CTRL_MASK )
+ {
+ controlKeyStr = "Ctrl+"+(char)keys[i].getKeyCode();
+ break;
+ }
+ else if (mod == InputEvent.ALT_MASK)
+ {
+ controlKeyStr = "Alt+"+(char)keys[i].getKeyCode();
+ break;
+ }
+ }
+ return controlKeyStr;
+ }
+
+ /**
+ *
+ * Use this to getFontMetrics
+ *
+ */
+ private class MyToolTip extends JToolTip
+ {
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ protected FontMetrics toolTipFontMetrics(Font currentFont)
+ {
+ return getFontMetrics(currentFont);
+ }
+ }
+
+}
+
+
diff --git a/uk/ac/sanger/artemis/editor/PlafMacros.java b/uk/ac/sanger/artemis/editor/PlafMacros.java
new file mode 100644
index 0000000..550acf3
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/PlafMacros.java
@@ -0,0 +1,96 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import javax.swing.*;
+import java.util.*;
+
+/**
+*
+* Macros class
+*
+*/
+public class PlafMacros implements SwingConstants
+{
+ // don't make these final, since the value is
+ // different on each platform
+ private static String LINE_SEPARATOR =
+ System.getProperty("line.separator");
+ private static int LINE_SEPARATOR_LEN =
+ LINE_SEPARATOR.length();
+
+ /**
+ *
+ * Break text into separate lines
+ * @param text text to break up
+ * @return multiple lines in string array
+ *
+ */
+ public static String[] breakupLines(String text)
+ {
+ int len = text.length();
+ if (len == 0)
+ return new String[] {""};
+ else
+ {
+ Vector data = new Vector(10);
+ int start=0;
+ int i=0;
+ while (i<len) {
+ if (text.startsWith(LINE_SEPARATOR,i))
+ {
+ data.addElement(text.substring(start,i));
+ start=i+LINE_SEPARATOR_LEN;
+ i=start;
+ }
+ else if (text.charAt(i)=='\n')
+ {
+ data.addElement(text.substring(start,i));
+ start=i+1;
+ i=start;
+ }
+ else { i++; }
+ }
+ if (start != len)
+ data.addElement(text.substring(start));
+ int numlines = data.size();
+ String lines[] = new String[numlines];
+ data.copyInto(lines);
+ return lines;
+ }
+ }
+
+ /**
+ *
+ * Get the line separator string
+ * @return line separator
+ *
+ */
+ public static String getLineSeparator()
+ {
+ return LINE_SEPARATOR;
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/editor/ScrollPanel.java b/uk/ac/sanger/artemis/editor/ScrollPanel.java
new file mode 100644
index 0000000..1f10129
--- /dev/null
+++ b/uk/ac/sanger/artemis/editor/ScrollPanel.java
@@ -0,0 +1,83 @@
+/*
+ *
+ * created: Wed Aug 3 2004
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.editor;
+
+import javax.swing.*;
+import java.awt.LayoutManager;
+import java.awt.Rectangle;
+import java.awt.Dimension;
+
+/**
+*
+* Extends JPanel to implement Scrollable to speed scroll pane
+* scrolling
+*
+*/
+public class ScrollPanel extends JPanel implements Scrollable
+{
+
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ public ScrollPanel()
+ {
+ super();
+ }
+
+ public ScrollPanel(LayoutManager l)
+ {
+ super(l);
+ }
+
+ public Dimension getPreferredScrollableViewportSize()
+ {
+ return getPreferredSize();
+ }
+
+ public boolean getScrollableTracksViewportHeight()
+ {
+ return false;
+ }
+
+ public boolean getScrollableTracksViewportWidth()
+ {
+ return false;
+ }
+
+ public int getScrollableBlockIncrement(Rectangle r,
+ int orientation, int direction)
+ {
+ return 60;
+ }
+
+ public int getScrollableUnitIncrement(Rectangle r,
+ int orientation, int direction)
+ {
+ return 10;
+ }
+
+
+}
+
diff --git a/uk/ac/sanger/artemis/io/BetweenRange.java b/uk/ac/sanger/artemis/io/BetweenRange.java
new file mode 100644
index 0000000..29bfea4
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/BetweenRange.java
@@ -0,0 +1,85 @@
+/* BetweenRange.java
+ *
+ * created: Thu Feb 11 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/BetweenRange.java,v 1.1 2004-06-09 09:48:50 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+/**
+ * This is a Range that represents a position between two bases.
+ *
+ * @author Kim Rutherford
+ * @version $Id: BetweenRange.java,v 1.1 2004-06-09 09:48:50 tjc Exp $
+ **/
+
+public class BetweenRange extends Range {
+ /**
+ * Create a new BetweenRange object.
+ * @param start the start of the range
+ * @param end the end of the range
+ **/
+ public BetweenRange (int start, int end)
+ throws OutOfRangeException {
+ super (start, end);
+ }
+
+ /**
+ * Return a string representation of this object in the form "start^end",
+ * where start and end are the integers that were passed to the
+ * constructor.
+ **/
+ public String toString () {
+ return getStart () + "^" + getEnd ();
+ }
+
+ /**
+ * Return true if and only if the argument equals this Range.
+ **/
+ public boolean equals (final Range test_range) {
+ if (test_range instanceof BetweenRange) {
+ if (test_range.getStart () == getStart () &&
+ test_range.getEnd () == getEnd ()) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return a copy of this object.
+ **/
+ public Range copy () {
+ try {
+ return new BetweenRange (getStart (), getEnd ());
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/BioJavaEntry.java b/uk/ac/sanger/artemis/io/BioJavaEntry.java
new file mode 100644
index 0000000..2be172e
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/BioJavaEntry.java
@@ -0,0 +1,441 @@
+
+package uk.ac.sanger.artemis.io;
+
+import java.io.*;
+import java.util.Iterator;
+
+import org.biojava.bio.BioException;
+import org.biojava.bio.symbol.Alphabet;
+import org.biojava.bio.symbol.SymbolList;
+import org.biojava.bio.seq.SequenceIterator;
+import org.biojava.bio.seq.DNATools;
+import org.biojava.bio.seq.FeatureHolder;
+import org.biojava.bio.seq.io.SymbolTokenization;
+import org.biojava.bio.seq.io.EmblProcessor;
+import org.biojava.bio.seq.io.SequenceFormat;
+import org.biojava.bio.seq.io.EmblLikeFormat;
+// import org.biojava.bio.seq.io.GAMEFormat;
+// import org.biojava.bio.seq.io.BSMLFormat;
+import org.biojava.bio.seq.io.SequenceBuilderFactory;
+import org.biojava.bio.seq.io.SmartSequenceBuilder;
+import org.biojava.bio.seq.io.StreamReader;
+
+import uk.ac.sanger.artemis.util.*;
+
+public class BioJavaEntry implements Entry
+{
+
+ // tjc uk.ac.sanger.artemis.components/BioJavaEntrySource.java barfed
+ // so I added this constructor
+ public BioJavaEntry(final Document document,
+ final SequenceFormat sequenceFormat)
+ throws IOException
+ {
+ this(null,document,sequenceFormat);
+ }
+
+ public BioJavaEntry(final EntryInformation entryInformation,
+ final Document document,
+ final SequenceFormat sequenceFormat)
+ throws IOException
+ {
+ this.entryInformation = entryInformation;
+ this.sequenceFormat = sequenceFormat;
+ this.document = document;
+ featTable = new StreamFeatureTable ();
+
+ BufferedReader reader =
+ new BufferedReader (document.getLinePushBackReader ());
+
+ try {
+ SequenceBuilderFactory sFact =
+ new EmblProcessor.Factory(SmartSequenceBuilder.FACTORY);
+ Alphabet alpha = DNATools.getDNA();
+ SymbolTokenization rParser = alpha.getTokenization("token");
+ SequenceFormat eFormat = new EmblLikeFormat();
+ SequenceIterator seqIterator =
+ new StreamReader(reader, eFormat, rParser, sFact);
+
+ bioJavaSequence = seqIterator.nextSequence();
+ } catch (BioException be) {
+ be.printStackTrace ();
+ throw new IOException("Error reading BioJava sequence: " + be);
+ }
+
+ setArtemisFeatures(bioJavaSequence);
+ setArtemisSequence(bioJavaSequence);
+ }
+
+ public BioJavaEntry (final org.biojava.bio.seq.Sequence sequence) {
+ setArtemisFeatures(bioJavaSequence);
+ setArtemisSequence(bioJavaSequence);
+ }
+
+ public BioJavaEntry (final Entry old_entry) {
+ final Sequence old_sequence = old_entry.getSequence ();
+ final String old_sequence_str =
+ old_sequence.getSubSequence (1, old_sequence.length ());
+
+ try {
+ bioJavaSequence =
+ org.biojava.bio.seq.DNATools.createDNASequence (old_sequence_str, "dna");
+
+ setArtemisSequence (bioJavaSequence);
+ } catch (org.biojava.bio.symbol.IllegalSymbolException e) {
+ // XXX
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ final FeatureEnumeration feature_enum = old_entry.features ();
+
+ while (feature_enum.hasMoreFeatures ()) {
+ final Feature old_feature = feature_enum.nextFeature ();
+
+ try {
+ createFeature (old_feature.getKey (),
+ old_feature.getLocation (),
+ old_feature.getQualifiers ());
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+
+
+ public String getHeaderText()
+ {
+ return null;
+ }
+
+ public EntryInformation getEntryInformation()
+ {
+ return entryInformation;
+ }
+
+ /**
+ * Return the File reference that was passed to the constructor or null if
+ * none was passed.
+ **/
+ public Document getDocument () {
+ return document;
+ }
+
+ /**
+ * Set the document to use when saving this DocumentEntry.
+ **/
+ public void setDocument (final Document document) {
+ this.document = document;
+ }
+
+ public void save () throws IOException {
+ save (getDocument ());
+ }
+
+ public void save (Document document) throws IOException {
+ final PrintStream printStream =
+ new PrintStream (document.getOutputStream ());
+
+ sequenceFormat.writeSequence (bioJavaSequence, printStream);
+ }
+
+ public boolean hasUnsavedChanges () {
+ return lastChangeTime != null;
+ }
+
+ public boolean isReadOnly () {
+ return false;
+ }
+
+ /**
+ * Set the header of this Entry to be the given text.
+ * @return true if and only if the header was successfully set. Not all
+ * Entry objects can change their header, so it is up to the calling
+ * function to check the return value.
+ * @exception IOException thrown if there is a problem reading the header
+ * from the String - most likely ReadFormatException.
+ **/
+ public boolean setHeaderText (final String newHeader)
+ throws IOException {
+ return false;
+ }
+
+ /**
+ * Set the name of this Entry - if possible (the return value will let the
+ * caller know).
+ * @return true if and only if the name was successfully set. Not all
+ * Entry objects can change their name, so it is up to the calling
+ * function to check the return value.
+ **/
+ public boolean setName (final String name) {
+ return false;
+ }
+
+ /**
+ * Create a new Feature object of an appropriate type in this Entry.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature (can be null if
+ * there are no qualifiers).
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ * @exception EntryInformationException Thrown if a Feature in this Entry
+ * cannot contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public Feature createFeature (Key key,
+ Location location,
+ QualifierVector qualifiers)
+ throws ReadOnlyException, OutOfRangeException {
+
+ final org.biojava.bio.seq.StrandedFeature.Template template =
+ new org.biojava.bio.seq.StrandedFeature.Template ();
+
+ if (location.isComplement ()) {
+ template.strand = org.biojava.bio.seq.StrandedFeature.NEGATIVE;
+ } else {
+ template.strand = org.biojava.bio.seq.StrandedFeature.POSITIVE;
+ }
+
+ template.annotation = new org.biojava.bio.SimpleAnnotation ();
+
+ template.type = key.toString ();
+
+ template.location = BioJavaFeature.makeBioJavaLocation (location);
+
+ try {
+ final org.biojava.bio.seq.Feature bioJavaFeature =
+ bioJavaSequence.createFeature (template);
+
+ final BioJavaFeature newFeature =
+ new BioJavaFeature (bioJavaFeature, this);
+
+ featTable.add (newFeature);
+
+ setDirtyFlag ();
+
+ return newFeature;
+ } catch (org.biojava.utils.ChangeVetoException e) {
+ throw new ReadOnlyException ("feature cannot be created");
+ } catch (org.biojava.bio.BioException e) {
+ // XXX - createFeature () should throw BioException
+ throw new ReadOnlyException ("BioJava error: " + e);
+ }
+ }
+
+ /**
+ * Add the given Feature to this Entry. If the Feature is already in an
+ * Entry then Entry.remove () should be called on that Entry before calling
+ * Entry.add (). An Error will be thrown otherwise.
+ * @exception ReadOnlyException If this entry is read only.
+ * @exception EntryInformationException Thrown if this Entry
+ * cannot contain the Key, Qualifier or Key/Qualifier combination of the
+ * given Feature.
+ * @return A reference that was passed to add (), if that Feature can be
+ * stored directly in this Entry, otherwise returns a reference to a new
+ * Feature, that is a copy of the argument. The argument reference
+ * should not be used after the call to add (), unless the return
+ * reference happens to be the same as the argument.
+ **/
+ public Feature add (Feature feature)
+ throws EntryInformationException, ReadOnlyException {
+ return forcedAdd (feature);
+ }
+
+ /**
+ * Add the given Feature to this Entry. If the Feature is already in an
+ * Entry then Entry.remove () should be called on that Entry before calling
+ * Entry.add (). An Error will be thrown otherwise. Invalid qualifiers
+ * will be quietly thrown away. Features with invalid keys will not be
+ * added (and null will be returned). "Invalid" means that the
+ * key/qualifier is non allowed to occur in an Entry of this type (probably
+ * determined by the EntryInformation object of this Entry).
+ * @exception ReadOnlyException If this entry is read only.
+ * @exception EntryInformationException Thrown if this Entry
+ * cannot contain the Key, Qualifier or Key/Qualifier combination of the
+ * given Feature
+ * @return A reference that was passed to add (), if that Feature can be
+ * stored directly in this Entry, otherwise returns a reference to a new
+ * Feature, that is a copy of the argument. The argument reference
+ * should not be used after the call to add (), unless the return
+ * reference happens to be the same as the argument. Returns null if and
+ * only if the new Feature has a key that is invalid for this Entry.
+ **/
+ public Feature forcedAdd (Feature feature)
+ throws ReadOnlyException {
+ if (feature.getEntry () != null) {
+ throw new Error ("internal error - a feature must have one owner");
+ }
+
+ try {
+ return createFeature (feature.getKey (),
+ feature.getLocation (),
+ feature.getQualifiers ());
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Remove the given Feature from this Entry.
+ * @return true if and only if the Feature was in this Entry.
+ * @exception ReadOnlyException If this entry is read only.
+ **/
+ public boolean remove (Feature feature)
+ throws ReadOnlyException {
+ final boolean reallyRemoved;
+
+ if (featTable.contains (feature)) {
+ reallyRemoved = true;
+ } else {
+ reallyRemoved = false;
+ }
+
+ featTable.remove (feature);
+
+ if (reallyRemoved) {
+ ((BioJavaFeature)feature).setBioJavaEntry (null);
+
+ try
+ {
+ bioJavaSequence.removeFeature ((org.biojava.bio.seq.Feature) ((BioJavaFeature)feature).getBioJavaFeature ());
+ }
+ catch(org.biojava.utils.ChangeVetoException e)
+ {
+ throw new ReadOnlyException("read only - feature cannot be removed");
+ }
+ catch(org.biojava.bio.BioException e)
+ {
+ //A nestable biological exception.
+ }
+
+ setDirtyFlag ();
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Set lastChangeTime so that hasUnsavedChanges () will return true.
+ * lastChangeTime can be read with getLastChangeTime ().
+ **/
+ public void setDirtyFlag () {
+ if (in_constructor) {
+ // features are being added, but the entry hasn't really changed
+ } else {
+ final java.util.Calendar calendar = java.util.Calendar.getInstance ();
+
+ lastChangeTime = calendar.getTime ();
+ }
+ }
+
+ /**
+ * Return the Date when this Entry last changed or null if this Entry
+ * hasn't changed since the last save.
+ **/
+ public java.util.Date getLastChangeTime () {
+ return lastChangeTime;
+ }
+
+ void removeFromTable (final BioJavaFeature feature) {
+ featTable.remove (feature);
+ }
+
+ void addToTable (final BioJavaFeature feature) {
+ featTable.add (feature);
+ }
+
+ public String getName()
+ {
+ return bioJavaSequence.getName();
+ }
+
+ public int getFeatureCount()
+ {
+ return featTable.getFeatureCount();
+ }
+
+ public Feature getFeatureAtIndex(final int index)
+ {
+ return featTable.getFeatureAtIndex (index);
+ }
+
+ public int indexOf(final Feature feature)
+ {
+ return featTable.indexOf (feature);
+ }
+
+ public boolean contains(final Feature feature)
+ {
+ return featTable.contains(feature);
+ }
+
+ public FeatureEnumeration features()
+ {
+ return featTable.features();
+ }
+
+ public FeatureVector getFeaturesInRange(final Range range)
+ throws OutOfRangeException
+ {
+ return featTable.getFeaturesInRange(range);
+ }
+
+ public FeatureVector getAllFeatures()
+ {
+ return featTable.getAllFeatures ();
+ }
+
+ public Sequence getSequence()
+ {
+ return artemisSequence;
+ }
+
+ private void setArtemisFeatures(final FeatureHolder holder)
+ {
+ for (Iterator fi = holder.features(); fi.hasNext();) {
+ org.biojava.bio.seq.Feature f =
+ (org.biojava.bio.seq.Feature) fi.next();
+ final BioJavaFeature biojavaFeature = new BioJavaFeature(f, this);
+
+ featTable.add(biojavaFeature);
+ }
+ }
+
+
+ private void setArtemisSequence(final SymbolList symbols) {
+ artemisSequence = new BioJavaSequence(symbols);
+ }
+
+
+ /**
+ * Set to true in the constructor while features are added. setDirtyFlag ()
+ * will do nothing while this is true.
+ **/
+ private boolean in_constructor = false;
+
+
+ /**
+ * The Date when this Entry last changed or null if this Entry
+ * hasn't changed since the last save. Set to null by save ().
+ **/
+ private java.util.Date lastChangeTime = null;
+
+ /**
+ * The Document object that was passed to the constructor.
+ **/
+ private Document document = null;
+
+ private FeatureTable featTable;
+ private EntryInformation entryInformation;
+ private Sequence artemisSequence;
+ private org.biojava.bio.seq.Sequence bioJavaSequence;
+ private SequenceFormat sequenceFormat;
+ public void dispose()
+ {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/BioJavaFeature.java b/uk/ac/sanger/artemis/io/BioJavaFeature.java
new file mode 100644
index 0000000..5eb0a4e
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/BioJavaFeature.java
@@ -0,0 +1,456 @@
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.*;
+
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import org.biojava.utils.ChangeVetoException;
+import org.biojava.bio.Annotation;
+import org.biojava.bio.symbol.LocationTools;
+
+public class BioJavaFeature extends EMBLObject implements ComparableFeature
+{
+ org.biojava.bio.seq.Feature bioJavaFeature;
+
+ private BioJavaEntry nativeEntry;
+ private Key nativeKey;
+ private Location nativeLocation;
+ private QualifierVector nativeQualifiers;
+
+ private static long id_counter = 0;
+ private final long id = id_counter++;
+
+ public BioJavaFeature(org.biojava.bio.seq.Feature feature,
+ BioJavaEntry nativeEntry)
+ {
+ this.bioJavaFeature = feature;
+ this.nativeEntry = nativeEntry;
+ }
+
+// /**
+// * Create a new BioJavaFeature object with the same key, location and
+// * qualifiers as the given feature. The feature will be added to
+// * nativeEntry
+// * @param feature The feature to copy.
+// **/
+// BioJavaFeature(Feature feature,
+// BioJavaEntry nativeEntry)
+// {
+// this.bioJavaFeature = new org.biojava.bio.seq.Feature;
+// this.nativeEntry = nativeEntry;
+// }
+
+ public void set (Key key, Location loc, QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException, OutOfRangeException
+ {
+ setKey (key);
+ setLocation (loc);
+ setQualifiers (qualifiers);
+ }
+
+ public Key getKey ()
+ {
+ return new Key (bioJavaFeature.getType());
+ }
+
+ public long getNumericID()
+ {
+ return id;
+ }
+
+ public Feature copy()
+ {
+ return new BioJavaFeature(bioJavaFeature, nativeEntry);
+ }
+
+ public Location getLocation()
+ {
+ return makeNativeLocation ();
+ }
+
+ public Entry getEntry()
+ {
+ return nativeEntry;
+ }
+
+
+ /**
+ *
+ **/
+ org.biojava.bio.seq.Feature getBioJavaFeature () {
+ return bioJavaFeature;
+ }
+
+ void setBioJavaEntry(final BioJavaEntry entry)
+ {
+ this.nativeEntry = entry;
+ }
+
+ /**
+ * Return the first base of this feature.
+ **/
+ public int getFirstBase () {
+ return getLocation ().getFirstBase ();
+ }
+
+ /**
+ * Return the last base of this feature.
+ **/
+ public int getLastBase () {
+ return getLocation ().getLastBase ();
+ }
+
+
+ public Qualifier getQualifierByName(String name)
+ throws InvalidRelationException
+ {
+ org.biojava.bio.Annotation annotation = bioJavaFeature.getAnnotation();
+
+ if (!annotation.containsProperty (name)) {
+ return null;
+ }
+
+ Object value = annotation.getProperty (name);
+
+ StringVector sv = new StringVector();
+
+ if (Collection.class.isInstance(value)) {
+ for (Iterator vi = ((Collection) value).iterator(); vi.hasNext();) {
+ sv.add(vi.next().toString());
+ }
+ } else {
+ sv.add(value.toString());
+ }
+
+ return new Qualifier (name, sv);
+ }
+
+ public QualifierVector getQualifiers()
+ {
+ org.biojava.bio.Annotation annotation = bioJavaFeature.getAnnotation();
+
+ QualifierVector qualifiers = new QualifierVector();
+
+ List keys = new ArrayList();
+ keys.addAll(annotation.keys());
+ Collections.sort(keys);
+
+ for (Iterator ki = keys.iterator() ; ki.hasNext() ; ) {
+ Object key = ki.next();
+ Object value = annotation.getProperty(key);
+
+ if (key.equals (org.biojava.bio.seq.Feature.PROPERTY_DATA_KEY)) {
+ continue;
+ }
+
+ if (value instanceof Collection) {
+ StringVector sv = new StringVector();
+
+ for (Iterator vi = ((Collection) value).iterator(); vi.hasNext();) {
+ sv.add (vi.next().toString());
+ }
+
+ qualifiers.addQualifierValues (new Qualifier (key.toString(), sv));
+ } else {
+ if (value instanceof Boolean) {
+ qualifiers.setQualifier (new Qualifier (key.toString ()));
+ } else {
+ if (value instanceof String) {
+ qualifiers.addQualifierValues (new Qualifier (key.toString(), value.toString ()));
+ }
+ }
+ }
+ }
+ return qualifiers;
+ }
+
+ /**
+ *
+ **/
+ public void removeQualifierByName(String name)
+ throws EntryInformationException, ReadOnlyException
+ {
+ if (getEntry () == null) {
+ return;
+ }
+
+ // save the Entry because the call to remove () will set it to null
+ final BioJavaEntry saved_entry = nativeEntry;
+
+ // remove and then add the Feature because changing the Key may
+ // change the position of the Feature in the FeatureTable eg. changing
+ // CDS to CDS_motif can move the Feature because CDS is always sorted
+ // before CDS_motif if they have the same start base
+ saved_entry.removeFromTable (this);
+
+ try {
+ bioJavaFeature.getAnnotation ().removeProperty (name);
+ } catch (IllegalArgumentException e) {
+ throw new EntryInformationException ("cannot remove qualifier: " + name);
+ } catch (ChangeVetoException e) {
+ throw new ReadOnlyException ("cannot remove qualifier: " + name);
+ } finally {
+ saved_entry.setDirtyFlag ();
+
+ saved_entry.addToTable (this);
+ }
+ }
+
+ public void setKey(Key key)
+ throws EntryInformationException, ReadOnlyException
+ {
+ if (key == null) {
+ return;
+ }
+
+ if (getEntry () == null) {
+ return;
+ }
+
+ // save the Entry because the call to remove () will set it to null
+ final BioJavaEntry saved_entry = nativeEntry;
+
+ // remove and then add the Feature because changing the Key may
+ // change the position of the Feature in the FeatureTable eg. changing
+ // CDS to CDS_motif can move the Feature because CDS is always sorted
+ // before CDS_motif if they have the same start base
+ saved_entry.removeFromTable (this);
+
+
+ try {
+ bioJavaFeature.setType (key.toString ());
+ } catch (ChangeVetoException e) {
+ throw new ReadOnlyException ("cannot set key");
+ } finally {
+ saved_entry.setDirtyFlag ();
+
+ saved_entry.addToTable (this);
+ }
+ }
+
+ public void setLocation(Location location)
+ throws OutOfRangeException, ReadOnlyException
+ {
+ setLocation(location, null);
+ }
+
+ public void setLocation(Location location, Entry entry)
+ throws OutOfRangeException, ReadOnlyException
+ {
+ if (location == null || getEntry () == null) {
+ return;
+ }
+
+ // save the Entry because the call to remove () will set it to null
+ final BioJavaEntry saved_entry = nativeEntry;
+
+ // remove and then add the Feature because changing the Key may
+ // change the position of the Feature in the FeatureTable eg. changing
+ // CDS to CDS_motif can move the Feature because CDS is always sorted
+ // before CDS_motif if they have the same start base
+ saved_entry.removeFromTable (this);
+
+// final boolean complementFlag = loc.isComplement ();
+
+ final org.biojava.bio.symbol.Location bioJavaLocation =
+ makeBioJavaLocation (location);
+
+ try {
+ bioJavaFeature.setLocation (bioJavaLocation);
+ } catch (ChangeVetoException e) {
+ throw new ReadOnlyException ("cannot set location");
+ } finally {
+ saved_entry.setDirtyFlag ();
+
+ saved_entry.addToTable (this);
+ }
+ }
+
+ static org.biojava.bio.symbol.Location
+ makeBioJavaLocation (final Location artemisLocation)
+ {
+ final ArrayList al = new ArrayList ();
+
+ final RangeVector ranges = artemisLocation.getRanges ();
+
+ for (int i = 0 ; i < ranges.size () ; ++i) {
+ final Range range = (Range)ranges.elementAt (i);
+ al.add (LocationTools.makeLocation (range.getStart (), range.getEnd ()));
+ }
+
+ return LocationTools.union (al);
+ }
+
+
+ public void setQualifier (final Qualifier qualifier)
+ throws EntryInformationException, ReadOnlyException
+ {
+ // save the Entry because the call to remove () will set it to null
+ final BioJavaEntry saved_entry = nativeEntry;
+
+ if (getEntry () == null) {
+ return;
+ }
+
+ // remove and then add the Feature because changing the Key may
+ // change the position of the Feature in the FeatureTable eg. changing
+ // CDS to CDS_motif can move the Feature because CDS is always sorted
+ // before CDS_motif if they have the same start base
+ saved_entry.removeFromTable (this);
+
+ try {
+ setQualifierInternal (qualifier);
+ } catch (IllegalArgumentException e) {
+ throw new EntryInformationException ("cannot remove qualifier: " +
+ qualifier.getName ());
+ } catch (ChangeVetoException e) {
+ throw new ReadOnlyException ("cannot remove qualifier: " +
+ qualifier.getName ());
+ } finally {
+ saved_entry.setDirtyFlag ();
+
+ saved_entry.addToTable (this);
+ }
+ }
+
+ private void setQualifierInternal (final Qualifier qualifier)
+ throws IllegalArgumentException, ChangeVetoException {
+ final Annotation annotation = bioJavaFeature.getAnnotation ();
+
+ if (qualifier.getValues () == null) {
+ annotation.setProperty (qualifier.getName (), new Boolean (true));
+ } else {
+ if (qualifier.getValues ().size () == 1) {
+ annotation.setProperty (qualifier.getName (),
+ qualifier.getValues ().elementAt (0));
+ } else {
+ annotation.setProperty (qualifier.getName (),
+ (Collection)qualifier.getValues ());
+ }
+ }
+ }
+
+ /**
+ *
+ **/
+ private void clearAnnotation ()
+ throws ChangeVetoException {
+ org.biojava.bio.Annotation annotation = bioJavaFeature.getAnnotation();
+
+ List keys = new ArrayList();
+
+ keys.addAll(annotation.keys());
+
+ for (Iterator ki = keys.iterator() ; ki.hasNext() ; ) {
+ Object key = ki.next();
+
+ if (key.equals (org.biojava.bio.seq.Feature.PROPERTY_DATA_KEY)) {
+ continue;
+ }
+
+ annotation.removeProperty (key);
+ }
+ }
+
+ /**
+ *
+ **/
+ public void setQualifiers(QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException
+ {
+ if (qualifiers == null) {
+ return;
+ }
+
+ if (getEntry () == null) {
+ return;
+ }
+
+ // save the Entry because the call to remove () will set it to null
+ final BioJavaEntry saved_entry = nativeEntry;
+
+ // remove and then add the Feature because changing the Key may
+ // change the position of the Feature in the FeatureTable eg. changing
+ // CDS to CDS_motif can move the Feature because CDS is always sorted
+ // before CDS_motif if they have the same start base
+ saved_entry.removeFromTable (this);
+
+
+ try {
+ clearAnnotation ();
+ for (int i = 0 ; i < qualifiers.size () ; ++i) {
+ setQualifierInternal((Qualifier)qualifiers.elementAt (i));
+ }
+ } catch (ChangeVetoException e) {
+ throw new ReadOnlyException ("cannot set qualifiers");
+ } finally {
+ saved_entry.setDirtyFlag ();
+
+ saved_entry.addToTable (this);
+ }
+ }
+
+ public boolean isReadOnly () {
+ return false;
+ }
+
+ private Location makeNativeLocation()
+ {
+ org.biojava.bio.symbol.Location locs = bioJavaFeature.getLocation();
+
+ boolean complement = false;
+
+ if (org.biojava.bio.seq.StrandedFeature.class.isInstance(bioJavaFeature))
+ if (((org.biojava.bio.seq.StrandedFeature) bioJavaFeature).getStrand().getValue() == -1)
+ complement = true;
+
+ RangeVector ranges = new RangeVector();
+ for (Iterator li = locs.blockIterator(); li.hasNext();)
+ {
+ org.biojava.bio.symbol.Location thisLoc =
+ (org.biojava.bio.symbol.Location) li.next();
+
+ int min = (thisLoc.getMin() == Integer.MIN_VALUE) ?
+ 1 :
+ thisLoc.getMin();
+
+ int max = (thisLoc.getMax() == Integer.MAX_VALUE) ?
+ getEntry().getSequence().length() :
+ thisLoc.getMax();
+
+ try
+ {
+ if (min != max)
+ {
+ if (complement)
+ {
+ ranges.insertElementAt(new Range(min, max), 0);
+ }
+ else
+ {
+ ranges.add(new Range(min, max));
+ }
+ }
+ else
+ {
+ if (complement)
+ {
+ ranges.insertElementAt(new Range(min), 0);
+ }
+ else
+ {
+ ranges.add(new Range(min));
+ }
+ }
+ }
+ catch (OutOfRangeException ore)
+ {
+ System.err.println("Error converting BioJava range: "
+ + ore.getMessage());
+ }
+ }
+ return new Location(ranges, complement);
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/BioJavaSequence.java b/uk/ac/sanger/artemis/io/BioJavaSequence.java
new file mode 100644
index 0000000..960d9d4
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/BioJavaSequence.java
@@ -0,0 +1,169 @@
+
+package uk.ac.sanger.artemis.io;
+
+import java.lang.IndexOutOfBoundsException;
+import java.lang.String;
+
+import org.biojava.bio.symbol.Edit;
+import org.biojava.bio.seq.io.SymbolListCharSequence;
+import org.biojava.bio.symbol.IllegalSymbolException;
+import org.biojava.bio.symbol.IllegalAlphabetException;
+import org.biojava.bio.seq.DNATools;
+import org.biojava.utils.ChangeVetoException;
+
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+public class BioJavaSequence implements Sequence
+{
+ private org.biojava.bio.symbol.SymbolList symbols;
+
+ private int aCount;
+ private int cCount;
+ private int gCount;
+ private int tCount;
+
+ private boolean sequenceIsStale = true;
+
+ public BioJavaSequence(final org.biojava.bio.symbol.SymbolList symbols)
+ {
+ this.symbols = symbols;
+ }
+
+ public char charAt(int i)
+ {
+ return ' ';
+ }
+
+ public int length()
+ {
+ return symbols.length();
+ }
+
+ public int getACount()
+ {
+ if (sequenceIsStale)
+ countSymbols();
+
+ return aCount;
+ }
+
+ public int getCCount()
+ {
+ if (sequenceIsStale)
+ countSymbols();
+
+ return cCount;
+ }
+
+ public int getGCount()
+ {
+ if (sequenceIsStale)
+ countSymbols();
+
+ return gCount;
+ }
+
+ public int getTCount()
+ {
+ if (sequenceIsStale)
+ countSymbols();
+
+ return tCount;
+ }
+
+ public int getOtherCount()
+ {
+ if (sequenceIsStale)
+ countSymbols();
+
+ return (symbols.length() - aCount - cCount - gCount -tCount);
+ }
+
+ org.biojava.bio.symbol.SymbolList getSymbolList () {
+ return symbols;
+ }
+
+ public String getSubSequence(int index1, int index2)
+ {
+ String subSeq = "";
+
+ try
+ {
+ subSeq = symbols.subStr(index1, index2);
+ }
+ catch (IndexOutOfBoundsException ioe)
+ {
+ System.err.println("An error occurred while extracting subsequence "
+ + ioe.getMessage());
+ ioe.printStackTrace();
+ }
+
+ return subSeq;
+ }
+
+ public char[] getCharSubSequence (int start, int end)
+ {
+ return getSubSequence(start,end).toCharArray();
+ }
+
+
+ public void setFromChar(final char[] seqString)
+ throws ReadOnlyException, IllegalSymbolException
+ {
+ Edit ed = new Edit(1, length (), DNATools.createDNA(new String(seqString)));
+ try {
+ symbols.edit(ed);
+ } catch (ChangeVetoException e) {
+ throw new ReadOnlyException ("cannot set sequence - readonly");
+ } catch (IllegalAlphabetException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ public void clear()
+ {
+ }
+
+ private void countSymbols()
+ {
+ final SymbolListCharSequence slcs = new SymbolListCharSequence (symbols);
+
+ int a = 0;
+ int c = 0;
+ int g = 0;
+ int t = 0;
+
+ for (int i = 0 ; i < slcs.length () ; ++i) {
+ char token = slcs.charAt(i);
+
+ switch (token)
+ {
+ case 'a':
+ a++;
+ break;
+
+ case 'c':
+ c++;
+ break;
+
+ case 'g':
+ g++;
+ break;
+
+ case 't':
+ t++;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ aCount = a;
+ cCount = c;
+ gCount = g;
+ tCount = t;
+
+ sequenceIsStale = false;
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/BlastDocumentEntry.java b/uk/ac/sanger/artemis/io/BlastDocumentEntry.java
new file mode 100644
index 0000000..df7388b
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/BlastDocumentEntry.java
@@ -0,0 +1,116 @@
+/* BlastDocumentEntry.java
+ *
+ * created: Wed May 8 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/BlastDocumentEntry.java,v 1.2 2007-09-26 10:39:10 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+
+/**
+ * A DocumentEntry that can read an Entry from a Document containing
+ * blastall -m 8 (ie. tabular blast output).
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: BlastDocumentEntry.java,v 1.2 2007-09-26 10:39:10 tjc Exp $
+ **/
+
+public class BlastDocumentEntry extends SimpleDocumentEntry
+ implements DocumentEntry {
+ /**
+ * Create a new BlastDocumentEntry object associated with the given
+ * Document.
+ * @param document This is the file that we will read from. This is also
+ * used for saving the entry back to the file it came from and to give
+ * the new object a name.
+ * @param listener The object that will listen for ReadEvents.
+ * @exception IOException thrown if there is a problem reading the entry -
+ * most likely ReadFormatException.
+ **/
+ public BlastDocumentEntry (final Document document,
+ final ReadListener listener)
+ throws IOException, EntryInformationException {
+ super (new BlastEntryInformation (), document, listener);
+ }
+
+ /**
+ * Create a new BlastDocumentEntry that will be a copy of the given
+ * Entry and has no Document associated with it. The new
+ * BlastDocumentEntry cannot be saved to a file with save () unless
+ * save (Document) has been called first. Some qualifier and location
+ * information will be lost.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys in the new Entry will be quietly thrown away. "Invalid"
+ * means that the key/qualifier is not allowed to occur in an Entry of
+ * this type (probably determined by the EntryInformation object of this
+ * Entry). If false an EntryInformationException will be thrown for
+ * invalid keys or qualifiers.
+ **/
+ public BlastDocumentEntry (final Entry new_entry, final boolean force)
+ throws EntryInformationException {
+ super (new BlastEntryInformation (), new_entry, force);
+ }
+
+ /**
+ * Create a new empty BlastDocumentEntry object that has no Document
+ * associated with it. The new BlastDocumentEntry cannot be saved to a
+ * file with save () unless save (Document) has been called first. The
+ * save (Document) method will assign a Document.
+ **/
+ public BlastDocumentEntry (final EntryInformation entry_information) {
+ super (new BlastEntryInformation ());
+ }
+
+ /**
+ * Returns true if and only if this entry is read only. For now this
+ * always returns true - BlastDocumentEntry objects can't be changed.
+ **/
+ public boolean isReadOnly () {
+ return true;
+ }
+
+ /**
+ * If the given feature can be added directly to this Entry, then return
+ * it, otherwise create and return a new feature of the appropriate type.
+ * @param copy if true then always new a new copy of the Feature.
+ **/
+ protected Object makeNativeFeature (final Feature feature,
+ final boolean copy) {
+ if (!copy && feature instanceof BlastStreamFeature) {
+ return (BlastStreamFeature) feature;
+ } else {
+ return new BlastStreamFeature (feature);
+ }
+ }
+
+ /**
+ * If the given Sequence can be added directly to this Entry, then return a
+ * copy of it, otherwise create and return a new feature of the appropriate
+ * type for this Entry.
+ **/
+ protected StreamSequence makeNativeSequence (final Sequence sequence) {
+ return new FastaStreamSequence (sequence);
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/BlastEntryInformation.java b/uk/ac/sanger/artemis/io/BlastEntryInformation.java
new file mode 100644
index 0000000..f4c166d
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/BlastEntryInformation.java
@@ -0,0 +1,37 @@
+/* BlastEntryInformation.java
+ *
+ * created: Wed May 8 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/BlastEntryInformation.java,v 1.1 2004-06-09 09:48:55 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * An EntryInformation object for BlastDocumentEntry objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: BlastEntryInformation.java,v 1.1 2004-06-09 09:48:55 tjc Exp $
+ **/
+
+public class BlastEntryInformation extends SimpleEntryInformation {
+
+}
diff --git a/uk/ac/sanger/artemis/io/BlastStreamFeature.java b/uk/ac/sanger/artemis/io/BlastStreamFeature.java
new file mode 100644
index 0000000..0656961
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/BlastStreamFeature.java
@@ -0,0 +1,283 @@
+/* BlastStreamFeature.java
+ *
+ * created: Wed May 8 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/BlastStreamFeature.java,v 1.2 2005-10-11 14:20:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+
+/**
+ * A StreamFeature that thinks it is a Blast feature.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: BlastStreamFeature.java,v 1.2 2005-10-11 14:20:31 tjc Exp $
+ **/
+
+public class BlastStreamFeature
+ extends SimpleDocumentFeature
+ implements DocumentFeature, StreamFeature, ComparableFeature {
+ /**
+ * Create a new BlastStreamFeature object. The feature should be added
+ * to an Entry (with Entry.add ()).
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ **/
+ public BlastStreamFeature (final Key key,
+ final Location location,
+ final QualifierVector qualifiers) {
+ super (null);
+ try {
+ setKey (key);
+ setLocation (location);
+ setQualifiers (qualifiers);
+ } catch (EntryInformationException e) {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (ReadOnlyException e) {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Create a new BlastStreamFeature with the same key, location and
+ * qualifiers as the given feature. The feature should be added to an
+ * Entry (with Entry.add ()).
+ * @param feature The feature to copy.
+ **/
+ public BlastStreamFeature (final Feature feature) {
+ super (null);
+
+ if (feature instanceof BlastStreamFeature) {
+ blast_line = ((BlastStreamFeature)feature).blast_line;
+ }
+
+ try {
+ setKey (feature.getKey ());
+ setLocation (feature.getLocation ());
+ setQualifiers (feature.getQualifiers ());
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return the reference of a new copy of this Feature.
+ **/
+ public Feature copy () {
+ final Feature return_value = new BlastStreamFeature (this);
+
+ return return_value;
+ }
+
+ /**
+ * Create a new BlastStreamFeature from the given line. The String
+ * should be in gene finder format.
+ **/
+ private BlastStreamFeature (final String line)
+ throws ReadFormatException {
+ super (null);
+
+ final StringVector line_bits = StringVector.getStrings (line, "\t");
+
+ if (line_bits.size () < 12) {
+ throw new ReadFormatException ("invalid Blast line (not enough " +
+ "fields): " + line);
+ }
+
+ try {
+ int query_start = Integer.valueOf ((String)line_bits.elementAt (6)).intValue ();
+ int query_end = Integer.valueOf ((String)line_bits.elementAt (7)).intValue ();
+
+ final String percent_id = (String)line_bits.elementAt (2);
+
+ final String query_id = (String)line_bits.elementAt (0);
+ final String subject_id = (String)line_bits.elementAt (1);
+ final String subject_start_string = (String)line_bits.elementAt (8);
+ final String subject_end_string = (String)line_bits.elementAt (9);
+
+ final String score = (String)line_bits.elementAt (11);
+ final String e_value = (String)line_bits.elementAt (10);
+
+ final Qualifier blast_score_qualifier =
+ new Qualifier ("blast_score", score);
+ // score qualifier must be 1-100
+ final Qualifier score_qualifier =
+ new Qualifier ("score", percent_id);
+ final Qualifier percent_id_qualifier =
+ new Qualifier ("percent_id", percent_id);
+ final Qualifier query_id_qualifier =
+ new Qualifier ("query_id", query_id);
+ final Qualifier subject_start_qualifier =
+ new Qualifier ("subject_start", subject_start_string);
+ final Qualifier subject_end_qualifier =
+ new Qualifier ("subject_end", subject_end_string);
+ final Qualifier subject_id_qualifier =
+ new Qualifier ("subject_id", subject_id);
+
+ setQualifier (blast_score_qualifier);
+ setQualifier (score_qualifier);
+ setQualifier (percent_id_qualifier);
+ setQualifier (query_id_qualifier);
+ setQualifier (subject_start_qualifier);
+ setQualifier (subject_end_qualifier);
+ setQualifier (subject_id_qualifier);
+
+ int subject_start = Integer.valueOf (subject_start_string).intValue ();
+ int subject_end = Integer.valueOf (subject_end_string).intValue ();
+
+ final Key key = new Key ("BLASTCDS");
+
+ setKey (key);
+
+ final StringVector note_values = new StringVector ();
+
+ note_values.add ("hit to " + subject_id + " " + subject_start +
+ ".." + subject_end + " score: " + score +
+ " percent id: " + percent_id + " e-value: " +
+ e_value);
+
+ final Qualifier note_qualifier = new Qualifier ("note", note_values);
+
+ setQualifier (note_qualifier);
+
+
+ boolean complement_flag;
+
+ if (subject_end < subject_start) {
+ complement_flag = true;
+ } else {
+ complement_flag = false;
+ }
+
+ if (query_start > query_end) {
+ final int tmp = query_end;
+ query_end = query_start;
+ query_start = tmp;
+
+ complement_flag = !complement_flag;
+ }
+
+ final RangeVector ranges =
+ new RangeVector (new Range (query_start, query_end));
+
+ setLocation (new Location (ranges, complement_flag));
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ this.blast_line = line;
+ }
+
+ /**
+ * Read and return a BlastStreamFeature from a stream. A feature must
+ * be the next thing in the stream.
+ * @param stream the Feature is read from this stream
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ * @return null if in_stream is at the end of file when the method is
+ * called
+ **/
+ protected static BlastStreamFeature
+ readFromStream (LinePushBackReader stream)
+ throws IOException, InvalidRelationException {
+
+ String line = stream.readLine ();
+
+ if (line == null) {
+ return null;
+ }
+
+ try {
+ final BlastStreamFeature new_feature =
+ new BlastStreamFeature (line);
+
+ return new_feature;
+ } catch (ReadFormatException exception) {
+ // re-throw the exception with the line number added
+
+ final String new_error_string = exception.getMessage ();
+
+ throw new ReadFormatException (new_error_string,
+ stream.getLineNumber ());
+ }
+ }
+
+ /**
+ * Read the details of a feature from an EMBL stream into the current
+ * object.
+ * @param entry_information The EntryInformation object of the Entry that
+ * will contain the Feature.
+ * @param in_stream the Feature is read from this stream
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException if the stream does not contain Blast
+ * feature.
+ **/
+ public void setFromStream (final EntryInformation entry_information,
+ final LinePushBackReader in_stream)
+ throws IOException, InvalidRelationException, ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Write this Feature to the given stream.
+ * @param writer The stream to write to.
+ * @exception IOException thrown if there is an io problem while writing
+ * the Feature.
+ **/
+ public void writeToStream (final Writer writer)
+ throws IOException {
+
+ // for now Blast features are read-only so just write what we read
+ writer.write (blast_line + "\n");
+ }
+
+ /**
+ * The DocumentEntry object that contains this Feature as passed to the
+ * constructor.
+ **/
+ private DocumentEntry entry;
+
+ /**
+ * This is the line of blastall -m 8 input that was read to get this
+ * BlastStreamFeature.
+ **/
+ private String blast_line = null;
+}
diff --git a/uk/ac/sanger/artemis/io/ChadoCanonicalGene.java b/uk/ac/sanger/artemis/io/ChadoCanonicalGene.java
new file mode 100644
index 0000000..c77903f
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/ChadoCanonicalGene.java
@@ -0,0 +1,1343 @@
+/* ChadoCanonicalGene.java
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/ChadoCanonicalGene.java,v 1.34 2009-08-11 08:59:46 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.gmod.schema.sequence.FeatureLoc;
+
+/**
+ * Used by GFFStreamFeature to represent the chado canonical gene.
+ * Contains gene, transcript, exons and proteins.
+ **/
+public class ChadoCanonicalGene
+{
+ private Feature gene;
+
+ // part_of gene
+ private List<Feature> transcripts = new Vector<Feature>();
+
+ // part_of transcripts
+ private Hashtable<String, List<Feature>> splicedFeatures =
+ new Hashtable<String, List<Feature>>();
+
+ // derives_from transript
+ private Hashtable<String, Feature> proteins = new Hashtable<String, Feature>();
+
+ // utr features
+ private Hashtable<String, List<Feature>> three_prime_utr =
+ new Hashtable<String, List<Feature>>();
+ private Hashtable<String, List<Feature>> five_prime_utr =
+ new Hashtable<String, List<Feature>>();
+
+ // other child features of transcript
+ private Hashtable<String, List<Feature>> other_features =
+ new Hashtable<String, List<Feature>>();
+
+ // srcfeature
+ private int srcfeature_id;
+
+ // srcfeature length
+ private int seqlen;
+
+
+ /**
+ * Get the gene feaure object.
+ * @return
+ */
+ public Feature getGene()
+ {
+ return gene;
+ }
+
+ /**
+ * Set the gene feature object.
+ * @param gene
+ */
+ public void setGene(Feature gene)
+ {
+ this.gene = gene;
+ }
+
+ public String getGeneUniqueName()
+ {
+ try
+ {
+ return getQualifier(getGene(), "ID");
+ }
+ catch(InvalidRelationException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Add a transcript to the model
+ * @param transcript
+ */
+ public void addTranscript(Feature transcript)
+ {
+ transcripts.add(transcript);
+ }
+
+ /**
+ * Delete a transcript and child features.
+ * @param transcript_name
+ */
+ public void deleteTranscript(String transcript_name)
+ {
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ try
+ {
+ Feature transcript = (Feature)transcripts.get(i);
+
+ if( transcript_name.equals(getQualifier(transcript, "ID")) )
+ {
+ transcripts.remove(transcript);
+ splicedFeatures.remove(transcript_name);
+ three_prime_utr.remove(transcript_name);
+ five_prime_utr.remove(transcript_name);
+ other_features.remove(transcript_name);
+ proteins.remove(transcript_name);
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * This should be called if the uniqueName of a gene model
+ * feature is changed.
+ * @param oldName
+ * @param newName
+ * @param children
+ */
+ public void updateUniqueName(final String oldName,
+ final String newName,
+ final Set<Feature> children)
+ {
+ updateNames(splicedFeatures,oldName,newName);
+ updateNames(proteins,oldName,newName);
+ updateNames(three_prime_utr,oldName,newName);
+ updateNames(five_prime_utr,oldName,newName);
+ updateNames(other_features,oldName,newName);
+
+ if(children != null)
+ GeneUtils.fixParentQualifier(oldName, newName, children);
+ }
+
+ /**
+ * Utility for changing the key used in a Hashtable
+ * @param hash
+ * @param oldName
+ * @param newName
+ */
+ private static void updateNames(final Hashtable hash,
+ final String oldName,
+ final String newName)
+ {
+ Object features = hash.get(oldName);
+ if(features != null)
+ {
+ hash.remove(oldName);
+ hash.put(newName, features);
+ }
+ }
+
+ /**
+ * Delete features.
+ * @param embl_feature
+ */
+ public void deleteFeature(final Feature embl_feature)
+ {
+ try
+ {
+ final String name = getQualifier(embl_feature, "ID");
+ Object feature = getSplicedFeatures(name);
+
+ if(feature != null)
+ {
+ String transcript_name = getQualifier((Feature) feature, "Parent");
+ splicedFeatures.remove(transcript_name);
+ return;
+ }
+
+ final Enumeration<String> enum_protein = proteins.keys();
+ while(enum_protein.hasMoreElements())
+ {
+ final String transcriptName = (String)enum_protein.nextElement();
+ Feature protein = (Feature)proteins.get(transcriptName);
+ if(getQualifier(protein, "ID").equals(name))
+ {
+ proteins.remove(transcriptName);
+ return;
+ }
+ }
+
+ feature = getFeatureFromHash(name, three_prime_utr);
+ if(feature != null)
+ {
+ String transcript_name = getQualifier((Feature) feature, "Parent");
+ List<Feature> utr = get3UtrOfTranscript(transcript_name);
+ utr.remove(feature);
+ return;
+ }
+
+ feature = getFeatureFromHash(name, five_prime_utr);
+ if(feature != null)
+ {
+ String transcript_name = getQualifier((Feature) feature, "Parent");
+ List<Feature> utr = get5UtrOfTranscript(transcript_name);
+ utr.remove(feature);
+ return;
+ }
+
+ feature = getFeatureFromHash(name, other_features);
+ if(feature != null)
+ {
+ String transcript_name = getQualifier((Feature) feature, "Parent");
+ List<Feature> others = getOtherFeaturesOfTranscript(transcript_name);
+ others.remove(feature);
+ return;
+ }
+
+ deleteTranscript(name);
+ }
+ catch(InvalidRelationException e1)
+ {
+ e1.printStackTrace();
+ }
+ }
+
+ /**
+ * Get all child members of a feature
+ * @param embl_feature
+ * @return
+ */
+ public Set<Feature> getChildren(Feature embl_feature)
+ {
+ Set<Feature> children = new HashSet<Feature>();
+ try
+ {
+ String name = getQualifier(embl_feature, "ID");
+
+ String gene_name = getQualifier(getGene(), "ID");
+ if(name.equals(gene_name))
+ {
+ List<Feature> transcripts = getTranscripts();
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ Feature transcript = transcripts.get(i);
+ children.add(transcript);
+ children.addAll( getChildren(transcript) );
+ }
+ return children;
+ }
+
+ searchForChildren(splicedFeatures, name, children);
+ searchForChildren(three_prime_utr, name, children);
+ searchForChildren(five_prime_utr, name, children);
+ searchForChildren(other_features, name, children);
+
+ // protein
+ Enumeration<Feature> pep_enum = proteins.elements();
+ while(pep_enum.hasMoreElements())
+ {
+ Feature child = pep_enum.nextElement();
+ String parent = getQualifier(child, "Derives_from");
+ if(parent != null && parent.equals(name))
+ children.add(child);
+ }
+ return children;
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Search in a <code>Hashtable</code> for child Features with a
+ * matching parent ID. Child features are added to the <code>Set</code>
+ * that is passed into this method.
+ * @param hash Hashtable to search for children in
+ * @param parent_id uniquname to look for
+ * @param children collection to add child features to
+ * @throws InvalidRelationException
+ */
+ private void searchForChildren(Hashtable<String, List<Feature>> hash,
+ String parent_id,
+ Set<Feature> children)
+ throws InvalidRelationException
+ {
+ Enumeration<List<Feature>> feature_enum = hash.elements();
+ String parent;
+
+ while(feature_enum.hasMoreElements())
+ {
+ List<Feature> child_list = feature_enum.nextElement();
+
+ for(int i=0; i<child_list.size(); i++)
+ {
+ Feature child = child_list.get(i);
+ //if(children.contains(child))
+ // continue;
+
+ parent = getQualifier(child, "Parent");
+ if(parent != null && parent.equals(parent_id))
+ children.add(child);
+ else
+ {
+ parent = getQualifier(child, "Derives_from");
+ if(parent != null && parent.equals(parent_id))
+ children.add(child);
+ }
+ }
+ }
+ }
+
+ /**
+ * Add exon feature to the chado gene model.
+ * @param transcript_name
+ * @param exon
+ * @param reset
+ * @throws InvalidRelationException
+ */
+ public void addSplicedFeatures(final String transcript_name,
+ final Feature exon, boolean reset)
+ {
+ if(reset)
+ splicedFeatures.remove(transcript_name);
+ addSplicedFeatures(transcript_name, exon);
+ }
+
+ /**
+ * Add exon feature to the chado gene model.
+ * @param transcript_name
+ * @param v_spliced
+ * @throws InvalidRelationException
+ */
+ public void addSplicedFeatures(final String transcript_name,
+ final Feature spliced)
+ {
+ final List<Feature> v_spliced;
+ if(splicedFeatures.containsKey(transcript_name))
+ v_spliced = (Vector<Feature>)splicedFeatures.get(transcript_name);
+ else
+ v_spliced = new Vector<Feature>();
+
+ v_spliced.add(spliced);
+ splicedFeatures.put(transcript_name, v_spliced);
+ }
+
+ public void correctSpliceSiteAssignments()
+ {
+ Enumeration<String> enumSplicedFeatures = splicedFeatures.keys();
+ while(enumSplicedFeatures.hasMoreElements())
+ {
+ String transcriptId = enumSplicedFeatures.nextElement();
+ Vector<Feature> v_spliced = (Vector<Feature>)splicedFeatures.get(transcriptId);
+ Set<String> splicedTypes = getSpliceTypes(transcriptId);
+ Iterator<String> it = splicedTypes.iterator();
+ while(it.hasNext())
+ {
+ String type = it.next();
+ if(!type.equals(DatabaseDocument.EXONMODEL) &&
+ !type.equals("pseudogenic_exon") &&
+ !type.equals("exon"))
+ {
+ List<Feature> splicedFeatures = getSpliceSitesOfTranscript(transcriptId, type);
+ if(splicedFeatures.size() == 1)
+ {
+ Feature f = (Feature)splicedFeatures.get(0);
+ addOtherFeatures(transcriptId, f);
+ v_spliced.remove(f);
+ try
+ {
+ f.removeQualifierByName("feature_relationship_rank");
+ }
+ catch(ReadOnlyException e){}
+ catch(EntryInformationException e){}
+ }
+ }
+ }
+ splicedFeatures.put(transcriptId, v_spliced);
+ }
+ }
+
+ /**
+ * Add protein feature to the chado gene model.
+ * @param transcript_name
+ * @param protein
+ * @throws InvalidRelationException
+ */
+ public void addProtein(final String transcript_name,
+ final Feature protein)
+ {
+ proteins.put(transcript_name, protein);
+ }
+
+ /**
+ * Add 3'UTR to chado gene model.
+ * @param transcript_name
+ * @param utr
+ * @throws InvalidRelationException
+ */
+ public void add3PrimeUtr(final String transcript_name,
+ final Feature utr)
+ {
+ final List<Feature> utr_list;
+ if(three_prime_utr.containsKey(transcript_name))
+ utr_list = three_prime_utr.get(transcript_name);
+ else
+ utr_list = new Vector<Feature>();
+
+ utr_list.add(utr);
+ three_prime_utr.put(transcript_name, utr_list);
+ }
+
+ /**
+ * Add 5'UTR to chado gene model.
+ * @param transcript_name
+ * @param utr
+ * @throws InvalidRelationException
+ */
+ public void add5PrimeUtr(final String transcript_name,
+ final Feature utr)
+ {
+ final List<Feature> utr_list;
+ if(five_prime_utr.containsKey(transcript_name))
+ utr_list = (Vector<Feature>)five_prime_utr.get(transcript_name);
+ else
+ utr_list = new Vector<Feature>();
+
+ utr_list.add(utr);
+ five_prime_utr.put(transcript_name, utr_list);
+ }
+
+ /**
+ * Add other child features of a transcript to the chado
+ * gene model.
+ * @param transcript_name
+ * @param other_feature
+ */
+ public void addOtherFeatures(final String transcript_name,
+ final Feature other_feature)
+ {
+ final List<Feature> v_other_features;
+ if(other_features.containsKey(transcript_name))
+ v_other_features = (Vector<Feature>)other_features.get(transcript_name);
+ else
+ v_other_features = new Vector<Feature>();
+ v_other_features.add(other_feature);
+ other_features.put(transcript_name, v_other_features);
+ }
+
+ /**
+ * Check if this gene model contains a transcript with an ID equal to
+ * any of the names in the <code>StringVector</code>. If it does find
+ * it returns the transcript feature, otherwise it returns null.
+ * @param names
+ * @return
+ */
+ public Feature containsTranscript(final StringVector names)
+ {
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ try
+ {
+ Feature transcript = (Feature)transcripts.get(i);
+
+ if( names.contains(getQualifier(transcript, "ID")) )
+ return transcript;
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+
+ public List<Feature> getSpliceSitesOfTranscript(final String transcript_name,
+ final String type)
+ {
+ if(splicedFeatures.containsKey(transcript_name))
+ {
+ List<Feature> splicedFeaturesOfTranscript = splicedFeatures.get(transcript_name);
+ List<Feature> results = new Vector<Feature>();
+ for(int i=0; i<splicedFeaturesOfTranscript.size(); i++)
+ {
+ Feature feature = (Feature)splicedFeaturesOfTranscript.get(i);
+ if(feature.getKey().getKeyString().equals(type))
+ results.add(feature);
+ }
+ return results;
+ }
+
+ return null;
+ }
+
+ /**
+ * Get a list of the feature keys of the types that are splice sites
+ * @param transcript_name
+ * @return
+ */
+ public Set<String> getSpliceTypes(final String transcript_name)
+ {
+ if(splicedFeatures.containsKey(transcript_name))
+ {
+ List<Feature> splicedFeaturesOfTranscript = splicedFeatures.get(transcript_name);
+ Set<String> splicedTypes = new HashSet<String>();
+ for(int i=0; i<splicedFeaturesOfTranscript.size(); i++)
+ {
+ Feature feature = (Feature)splicedFeaturesOfTranscript.get(i);
+ splicedTypes.add( feature.getKey().getKeyString() );
+ }
+ return splicedTypes;
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the exons of a given transcript as a <code>List</code>.
+ * @param transcript_name
+ * @return
+ */
+ public List<Feature> getSplicedFeaturesOfTranscript(final String transcript_name)
+ {
+ if(splicedFeatures.containsKey(transcript_name))
+ {
+ return splicedFeatures.get(transcript_name);
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the transcript from the name of a constituent feature
+ * @param constituent feature name
+ * @return transcript
+ */
+ public Feature getTranscriptFeatureFromName(final String name)
+ {
+ String transcriptName = getTranscriptFromName(name);
+ if(transcriptName == null)
+ return null;
+
+ try
+ {
+ for (int i = 0; i < transcripts.size(); i++)
+ {
+ Feature feature = (Feature) transcripts.get(i);
+ if (getQualifier(feature, "ID").equals(transcriptName))
+ return feature;
+ }
+ }
+ catch (InvalidRelationException ire){}
+ return null;
+ }
+
+ /**
+ * Return the transcript from the name of a constituent feature
+ * @param constituent feature name
+ * @return transcript name
+ */
+ public String getTranscriptFromName(final String name)
+ {
+ // check transcript
+ StringVector sv = new StringVector();
+ sv.add(name);
+ Feature feature = containsTranscript(sv);
+
+ if(feature != null)
+ return name;
+
+ // check exons
+ List<String> transcriptNames = getTranscriptNames();
+ feature = getSplicedFeatures(name);
+
+ if(feature != null)
+ {
+ for(int i=0; i<transcriptNames.size(); i++)
+ {
+ String transcriptName = (String)transcriptNames.get(i);
+ List<Feature> splicedSegments = getSplicedFeaturesOfTranscript(transcriptName);
+
+ if(splicedSegments != null)
+ {
+ for(int j=0; j<splicedSegments.size(); j++)
+ {
+ Feature segment = splicedSegments.get(j);
+ try
+ {
+ String segmentName = (String)segment.getQualifierByName("ID").getValues().get(0);
+ if(name.equals(segmentName))
+ return transcriptName;
+ }
+ catch(InvalidRelationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ feature = getProtein(name);
+
+ if(feature != null)
+ {
+ for(int i=0; i<transcriptNames.size(); i++)
+ {
+ String transcriptName = (String)transcriptNames.get(i);
+ Feature protein = getProteinOfTranscript(transcriptName);
+ try
+ {
+ String proteinsName = (String)protein.getQualifierByName("ID").getValues().get(0);
+ if(name.equals(proteinsName))
+ return transcriptName;
+ }
+ catch(InvalidRelationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // search children of all transcripts
+ List<Feature> transcripts = getTranscripts();
+ for(int i=0;i<transcripts.size(); i++)
+ {
+ Feature transcript = transcripts.get(i);
+ Set<Feature> children = getChildren(transcript);
+ Iterator<Feature> it = children.iterator();
+ while(it.hasNext())
+ {
+ Feature f = it.next();
+ if(name.equals(GeneUtils.getUniqueName(f)))
+ return GeneUtils.getUniqueName(transcript);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the protein feature of a transcipt.
+ * @param transcript_name
+ * @return
+ */
+ public Feature getProteinOfTranscript(final String transcript_name)
+ {
+ if(proteins.containsKey(transcript_name))
+ return (Feature)proteins.get(transcript_name);;
+
+ return null;
+ }
+
+ /**
+ * Return the 3'UTR features of a transcriot as a <code>List</code>.
+ * @param transcript_name
+ * @return
+ */
+ public List<Feature> get3UtrOfTranscript(final String transcript_name)
+ {
+ if(three_prime_utr.containsKey(transcript_name))
+ return (List<Feature>)three_prime_utr.get(transcript_name);
+
+ return null;
+ }
+
+ /**
+ * Return the 5'UTR features of a transcriot as a <code>List</code>.
+ * @param transcript_name
+ * @return
+ */
+ public List<Feature> get5UtrOfTranscript(final String transcript_name)
+ {
+ if(five_prime_utr.containsKey(transcript_name))
+ return (List<Feature>)five_prime_utr.get(transcript_name);
+
+ return null;
+ }
+
+ /**
+ * Utility to determine if this is the first or only UTR, so that
+ * partial qualifiers can be added to the correct UTR feature.
+ * @param utrName
+ * @param isFwd
+ * @return
+ */
+ public boolean isFirstUtr(final String utrName, final boolean isFwd)
+ {
+ try
+ {
+ Feature this5Utr = getFeatureFromHash(utrName, five_prime_utr);
+ if (this5Utr != null)
+ {
+ String transcript_name = getQualifier(this5Utr, "Parent");
+ List<Feature> utrs = get5UtrOfTranscript(transcript_name);
+ if (utrs.size() == 1)
+ return true;
+
+ for (Feature utr : utrs)
+ {
+ if (isFwd && utr.getFirstBase() < this5Utr.getFirstBase())
+ return false;
+ else if (!isFwd && utr.getLastBase() > this5Utr.getLastBase())
+ return false;
+ }
+ return true;
+ }
+
+
+ Feature this3Utr = getFeatureFromHash(utrName, three_prime_utr);
+ if (this3Utr != null)
+ {
+ String transcript_name = getQualifier(this3Utr, "Parent");
+ List<Feature> utrs = get3UtrOfTranscript(transcript_name);
+ if (utrs.size() == 1)
+ return true;
+
+ for (Feature utr : utrs)
+ {
+ if (!isFwd && utr.getFirstBase() < this3Utr.getFirstBase())
+ return false;
+ else if (isFwd && utr.getLastBase() > this3Utr.getLastBase())
+ return false;
+ }
+ return true;
+ }
+ }
+ catch(InvalidRelationException ire){}
+ return false;
+ }
+
+ /**
+ * Return the other child features of a transcriot as a <code>List</code>.
+ * @param transcript_name
+ * @return
+ */
+ public List<Feature> getOtherFeaturesOfTranscript(final String transcript_name)
+ {
+ if(other_features.containsKey(transcript_name))
+ return other_features.get(transcript_name);
+ return null;
+ }
+
+ /**
+ * Get a list of trancripts.
+ * @return
+ */
+ public List<Feature> getTranscripts()
+ {
+ return transcripts;
+ }
+
+
+ /**
+ * Get a list of trancripts.
+ * @return
+ */
+ private List<String> getTranscriptNames()
+ {
+ List<String> names = new Vector<String>();
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ Feature f = (Feature)transcripts.get(i);
+ try
+ {
+ names.add( (String)f.getQualifierByName("ID").getValues().get(0) );
+ }
+ catch(InvalidRelationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ return names;
+ }
+
+ /**
+ * Test if a name is already used in this gene model
+ * @param name
+ * @return
+ */
+ private boolean isUniqueName(final String name)
+ {
+ if(isTranscript(name))
+ return false;
+ if(isSplicedFeatures(name))
+ return false;
+
+ try
+ {
+ if(getFeatureFromHash(name, three_prime_utr) != null)
+ return false;
+ if(getFeatureFromHash(name, five_prime_utr) != null)
+ return false;
+ if(getFeatureFromHash(name, other_features) != null)
+ return false;
+
+ final Enumeration<Feature> enum_pp = proteins.elements();
+ while(enum_pp.hasMoreElements())
+ {
+ final Feature pp = enum_pp.nextElement();
+ if( getQualifier(pp, "ID").equals(name) )
+ return false;
+ }
+
+ if( getQualifier(getGene(), "ID").equals(name) )
+ return false;
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+
+ return true;
+ }
+
+ /**
+ * Test if the name is a transcript in this gene model.
+ * @param feature_id
+ * @return true if a transcript
+ */
+ public boolean isTranscript(final String feature_id)
+ {
+ try
+ {
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ if(feature_id.equals(getQualifier((Feature)transcripts.get(i), "ID")))
+ return true;
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
+ /**
+ * Test if this is an exon of transcript.
+ * @param feature_id exon feature
+ * @param transcript_id transcript feature
+ * @return
+ */
+ private boolean isSplicedFeatures(final String feature_id)
+ {
+ List<Feature> splicedFeatures = new Vector<Feature>();
+ List<Feature> transcripts = getTranscripts();
+
+ try
+ {
+ for(int i = 0; i < transcripts.size(); i++)
+ {
+ Feature transcript = (Feature) transcripts.get(i);
+ String transcript_id = getQualifier(transcript, "ID");
+ List<Feature> splicedSites = getSplicedFeaturesOfTranscript(transcript_id);
+ if(splicedSites != null)
+ splicedFeatures.addAll(splicedSites);
+ }
+
+ if(splicedFeatures == null)
+ return false;
+
+ for(int i=0; i<splicedFeatures.size(); i++)
+ {
+ GFFStreamFeature feature = (GFFStreamFeature)splicedFeatures.get(i);
+ RangeVector rv = feature.getLocation().getRanges();
+ for(int j=0; j<rv.size(); j++)
+ {
+ String this_feature_id = feature.getSegmentID((Range)rv.get(j));
+ if(feature_id.equals(this_feature_id))
+ return true;
+ }
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
+ /**
+ * Method to automatically generate ID's for transcripts
+ * @param transcript_key
+ * @return
+ */
+ public String autoGenerateTanscriptName(String transcript_key)
+ {
+ try
+ {
+ String name = getQualifier(getGene(), "ID");
+ int auto = 1;
+ while( isTranscript( name + "." + auto ) &&
+ auto < 50)
+ auto++;
+ return name + "." + auto;
+ }
+ catch(InvalidRelationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Generate new names for exon features for this gene model
+ * @param transcript_id
+ * @return
+ */
+ public String autoGenerateSplicedFeatureName(final String transcript_id)
+ {
+ try
+ {
+ int index = transcript_id.lastIndexOf('.');
+ if(index == -1)
+ index = transcript_id.lastIndexOf(':');
+ int transcript_number = -1;
+ String name = (String)getGene().getQualifierByName("ID").getValues().get(0);
+
+ if(index > -1)
+ {
+ try
+ {
+ transcript_number = Integer.parseInt(transcript_id.substring(index+1));
+ }
+ catch(NumberFormatException nfe)
+ {
+ transcript_number = -1;
+ }
+ }
+
+ if(transcript_number < 1)
+ {
+ for(transcript_number = 0; transcript_number <= transcripts.size();
+ transcript_number++)
+ {
+ Feature transcript = (Feature) transcripts.get(transcript_number);
+ if(transcript_id.equals(getQualifier(transcript, "ID")))
+ break;
+ }
+ }
+ if(transcript_number == 0)
+ name = name + ":exon:";
+ else
+ name = name + "." + transcript_number + ":exon:";
+
+ int auto = 1;
+ while( isSplicedFeatures(name + auto) && auto < 50)
+ auto++;
+ return name + auto;
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ /**
+ * Generate new names for peptide features for this gene model
+ * @param transcript_id
+ * @return
+ */
+ public String autoGeneratePepName(final String transcript_id)
+ {
+ try
+ {
+ int index = transcript_id.lastIndexOf('.');
+ if(index == -1)
+ index = transcript_id.lastIndexOf(':');
+ int transcript_number = -1;
+
+ if(index > -1)
+ {
+ try
+ {
+ transcript_number = Integer.parseInt(transcript_id.substring(index+1));
+ }
+ catch(NumberFormatException nfe)
+ {
+ transcript_number = -1;
+ }
+ }
+
+ if(transcript_number < 1)
+ {
+ for(transcript_number = 1; transcript_number <= transcripts.size();
+ transcript_number++)
+ {
+ Feature transcript = (Feature) transcripts.get(transcript_number - 1);
+ if(transcript_id.equals(getQualifier(transcript, "ID")))
+ break;
+ }
+ }
+
+ String name = (String)getGene().getQualifierByName("ID").getValues().get(0);
+
+ if(isUniqueName(name+ "." + transcript_number + ":pep"))
+ return name+ "." + transcript_number + ":pep";
+ else
+ return name + "." + transcript_number + "a:pep";
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Generate new names for generic region features for this gene model
+ * @param transcript_id
+ * @return
+ */
+ public String autoGenerateFeatureName(final String transcript_id,
+ final String keyName)
+ {
+ String featureName = "";
+ try
+ {
+ featureName =
+ (String)getGene().getQualifierByName("ID").getValues().get(0);
+ }
+ catch(InvalidRelationException e){}
+
+ final Pattern pattern = Pattern.compile("\\d+$");
+ final Matcher matcher = pattern.matcher(transcript_id);
+ if(matcher.find())
+ featureName = featureName+"."+matcher.group()+":"+keyName;
+ else
+ featureName = featureName+":"+keyName;
+
+ if(!isUniqueName(featureName))
+ {
+ int num = 1;
+ while(!isUniqueName(featureName + ":" + num) && num < 100)
+ num++;
+ featureName = featureName + ":" + num;
+ }
+
+ return featureName;
+ }
+
+ /**
+ * Search for the feature with a particular uniquename
+ * @param name uniquename
+ * @return
+ */
+ public Object getFeatureFromId(final String name)
+ {
+ Object feature = null;
+
+ // check gene
+ try
+ {
+ final String uniquename = getQualifier(gene, "ID");
+
+ if(uniquename.equals(name))
+ return gene;
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+
+ // check transcript
+ StringVector sv = new StringVector();
+ sv.add(name);
+
+ feature = containsTranscript(sv);
+
+ if(feature != null)
+ return feature;
+
+ // check exons
+ feature = getSplicedFeatures(name);
+
+ if(feature != null)
+ return feature;
+
+ feature = getProtein(name);
+
+ if(feature != null)
+ return feature;
+
+ try
+ {
+ feature = getFeatureFromHash(name, three_prime_utr);
+ if(feature != null)
+ return feature;
+
+ feature = getFeatureFromHash(name, five_prime_utr);
+ if(feature != null)
+ return feature;
+
+ feature = getFeatureFromHash(name, other_features);
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+
+ return feature;
+ }
+
+ /**
+ * Routine to look for a exon with a particular
+ * uniquename
+ * @param name
+ * @return
+ */
+ private Feature getSplicedFeatures(final String name)
+ {
+ Enumeration<List<Feature>> enum_exons = splicedFeatures.elements();
+ try
+ {
+ while(enum_exons.hasMoreElements())
+ {
+ List<Feature> exons = enum_exons.nextElement();
+
+ for(int i=0; i<exons.size(); i++)
+ {
+ String uniquename = getQualifier((Feature)exons.get(i), "ID");
+
+ if(uniquename.equals(name))
+ return (Feature)exons.get(i);
+ }
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private Feature getProtein(final String id)
+ {
+ Enumeration<Feature> enum_proteins = proteins.elements();
+ try
+ {
+ while(enum_proteins.hasMoreElements())
+ {
+ Feature protein = enum_proteins.nextElement();
+ if(getQualifier(protein, "ID").equals(id))
+ return protein;
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Search for a feature uniquename
+ * @param id
+ * @param UTR
+ * @return
+ * @throws InvalidRelationException
+ */
+ private Feature getFeatureFromHash
+ (final String id,
+ final Hashtable<String, List<Feature>> UTR)
+ throws InvalidRelationException
+ {
+ Enumeration<List<Feature>> enum_utr = UTR.elements();
+
+ while(enum_utr.hasMoreElements())
+ {
+ List<Feature> utrs = enum_utr.nextElement();
+
+ for(int i=0; i<utrs.size(); i++)
+ {
+ Feature utr = utrs.get(i);
+ if(getQualifier(utr, "ID").equals(id))
+ return utr;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Utility for get feature ID and Parent qualifiers.
+ * @param feature
+ * @param name
+ * @return
+ * @throws InvalidRelationException
+ */
+ public String getQualifier(final Feature feature,
+ final String name)
+ throws InvalidRelationException
+ {
+ Qualifier qualifier = feature.getQualifierByName(name);
+ if(qualifier == null)
+ return null;
+
+ return (String)(qualifier.getValues().get(0));
+ }
+
+ /**
+ * Get the srcfeature residue length
+ * @return
+ */
+ public int getSeqlen()
+ {
+ return seqlen;
+ }
+
+ /**
+ * Set the srcfeature residue length
+ * @param seqlen
+ */
+ public void setSeqlen(int seqlen)
+ {
+ this.seqlen = seqlen;
+ }
+
+ public int getSrcfeature_id()
+ {
+ return srcfeature_id;
+ }
+
+ public void setSrcfeature_id(int srcfeature_id)
+ {
+ this.srcfeature_id = srcfeature_id;
+ }
+
+ public Hashtable<String, List<Feature>> getSplicedFeatures()
+ {
+ return splicedFeatures;
+ }
+
+ /**
+ * Get the nucleotide location for a featureloc in amino acid
+ * coordinates.
+ * @param proteinFeature
+ * @param featureLocToProtein
+ * @return
+ * @throws LocationParseException
+ */
+ public Location getNucLocation(final Feature proteinFeature,
+ final FeatureLoc featureLocToProtein)
+ throws LocationParseException
+ {
+ String transcriptName = getTranscriptFromName(
+ GeneUtils.getUniqueName(proteinFeature));
+ List<Feature> spliced = getSplicedFeaturesOfTranscript(transcriptName);
+ if(spliced == null)
+ return null;
+
+ RangeVector ranges = new RangeVector();
+ for(int i=0; i<spliced.size(); i++)
+ {
+ Feature f = spliced.get(i);
+ if(f.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL))
+ ranges.addAll(f.getLocation().getRanges());
+ }
+
+ int start = proteinFeature.getLocation().getFirstBase();
+ int fmin = start+(featureLocToProtein.getFmin()*3)+1;
+ int fmax = start+(featureLocToProtein.getFmax()*3);
+
+ int len = proteinFeature.getEntry().getSequence().length();
+ if(fmax > len)
+ fmax = len;
+
+ if(ranges.size()>1)
+ {
+ Collections.sort(ranges, new RangeComparator());
+
+ for(int i=0;i<ranges.size()-1; i++)
+ {
+ Range range1 = (Range) ranges.get(i);
+ Range range2 = (Range) ranges.get(i+1);
+ if(fmin > range1.getEnd())
+ fmin += range2.getStart()-range1.getEnd();
+ if(fmax > range1.getEnd())
+ fmax += range2.getStart()-range1.getEnd();
+ }
+ }
+
+ Location location;
+ if(proteinFeature.getLocation().isComplement())
+ location = new Location("complement("+fmin+".."+fmax+")");
+ else
+ location = new Location(fmin+".."+fmax);
+ return location;
+ }
+
+
+ class RangeComparator implements Comparator<Range>
+ {
+ public int compare(Range o1, Range o2)
+ {
+ int start1 = o1.getStart();
+ int start2 = o2.getStart();
+ return start1-start2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/ComparableFeature.java b/uk/ac/sanger/artemis/io/ComparableFeature.java
new file mode 100644
index 0000000..7735522
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/ComparableFeature.java
@@ -0,0 +1,51 @@
+/* ComparableFeature.java
+ *
+ * created: Tue Sep 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/ComparableFeature.java,v 1.1 2004-06-09 09:48:58 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * Features that implement this interface can be compared with a
+ * FeatureComparator object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ComparableFeature.java,v 1.1 2004-06-09 09:48:58 tjc Exp $
+ **/
+
+interface ComparableFeature extends Feature {
+ /**
+ * Return the unique identifier of this Feature.
+ **/
+ long getNumericID ();
+
+ /**
+ * Return the first base of this feature.
+ **/
+ int getFirstBase ();
+
+ /**
+ * Return the last base of this feature.
+ **/
+ int getLastBase ();
+}
diff --git a/uk/ac/sanger/artemis/io/CorbaEntry.java b/uk/ac/sanger/artemis/io/CorbaEntry.java
new file mode 100644
index 0000000..c26934c
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/CorbaEntry.java
@@ -0,0 +1,243 @@
+/* CorbaEntry.java
+ *
+ * created: Wed Dec 30 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/CorbaEntry.java,v 1.2 2008-06-11 15:12:20 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import nsdb.EmblSeq;
+import nsdb.NucFeature;
+import type.NoResult;
+
+import java.util.Vector;
+import java.io.IOException;
+import java.util.NoSuchElementException;
+
+/**
+ * This class extends the Entry class with the data for the entry coming from
+ * a Corba server.
+ *
+ * @author Kim Rutherford
+ * @version $Id: CorbaEntry.java,v 1.2 2008-06-11 15:12:20 tjc Exp $
+ **/
+
+public class CorbaEntry extends ReadOnlyEntry
+ implements Entry {
+ /**
+ * Create a new CorbaEntry object from the given handle.
+ * @param entry_information The EntryInformation object of the new Entry.
+ * @param data This is the corba object that we will read from.
+ * @exception EntryInformationException Thrown if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ public CorbaEntry (final EntryInformation entry_information,
+ final EmblSeq corba_handle)
+ throws LocationParseException, InvalidKeyException, NoResult {
+ this.corba_handle = corba_handle;
+ this.sequence = new CorbaSequence (corba_handle);
+ this.entry_information = entry_information;
+
+ grabFeatures ();
+ }
+
+ /**
+ * Return the text of the EMBL header of this Entry or null if there is no
+ * header.
+ **/
+ public String getHeaderText () {
+ return null;
+ }
+
+ /**
+ * Return the name of this Entry.
+ **/
+ public String getName () {
+ return corba_handle.getBioSeqId ();
+ }
+
+ /**
+ * Return a count of the number of Feature objects in this Entry.
+ **/
+ public int getFeatureCount () {
+ return features.size ();
+ }
+
+ /**
+ * Return the ith Feature from this Entry. This Features are returned in a
+ * consistent order, sorted by the first base of each Feature.
+ **/
+ public Feature getFeatureAtIndex (int arg_index) {
+
+ final FeatureEnumeration enumerator = features.features ();
+
+ int i = 0;
+
+ while (enumerator.hasMoreFeatures ()) {
+ final Feature this_feature = enumerator.nextFeature ();
+
+ if (i == arg_index) {
+ return this_feature;
+ }
+
+ ++i;
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the index of the given Feature. This does the reverse of
+ * getFeatureAtIndex ().
+ **/
+ public int indexOf (Feature feature) {
+ final FeatureEnumeration enumerator = features.features ();
+
+ int i = 0;
+
+ while (enumerator.hasMoreFeatures ()) {
+ final Feature this_feature = enumerator.nextFeature ();
+
+ if (this_feature == feature) {
+ return i;
+ }
+ ++i;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns true if and only if this Entry contains the given feature.
+ **/
+ public boolean contains (Feature feature) {
+ return features.contains (feature);
+ }
+
+ /**
+ * Returns an enumeration of the Feature objects in this Entry. The
+ * returned Enumeration object will generate all features in this object in
+ * turn. The first item generated is the item at index 0, then the item at
+ * index 1, and so on.
+ **/
+ public FeatureEnumeration features () {
+ return features.features ();
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ public FeatureVector getFeaturesInRange (Range range) {
+ return features.getFeaturesInRange (range);
+ }
+
+ /**
+ * Return a vector containing the references of all the Feature objects in
+ * this Entry.
+ * @return The features of this Entry. The returned object
+ * is a copy - changes will not effect the Entry object itself.
+ **/
+ public FeatureVector getAllFeatures () {
+ final FeatureVector return_features = new FeatureVector ();
+
+ final FeatureEnumeration enumerator = features.features ();
+
+ while (enumerator.hasMoreFeatures ()) {
+ final Feature this_feature = enumerator.nextFeature ();
+
+ return_features.add (this_feature);
+ }
+
+ return return_features;
+ }
+
+ /**
+ * Return the Sequence object from this entry or null if it does not
+ * contain one.
+ * @return a Sequence object for this Entry. the returned object is
+ * not a copy - changes to it will change the Entry object itself
+ **/
+ public Sequence getSequence () {
+ return sequence;
+ }
+
+ /**
+ * Return the EntryInformation object that was passed to the constructor.
+ **/
+ public EntryInformation getEntryInformation () {
+ return entry_information;
+ }
+
+ /**
+ * Read the features from the corba_handle and put them in a FeatureTree.
+ **/
+ private void grabFeatures ()
+ throws LocationParseException, InvalidKeyException, NoResult {
+ final NucFeature [] feature_handles = corba_handle.getNucFeatures ();
+
+ for (int i = 0 ; i < feature_handles.length ; ++i) {
+ try {
+ final Feature new_feature = new CorbaFeature (feature_handles [i]);
+
+ features.add (new_feature);
+ } catch (InvalidRelationException e) {
+ System.out.println ("exception while reading: " + e);
+ }
+ }
+ }
+
+ /**
+ * The EmblSeq object that was passed to the constructor.
+ **/
+ private EmblSeq corba_handle;
+
+ /**
+ * This is created in the constructor
+ **/
+ private CorbaSequence sequence = null;
+
+ /**
+ * The FeatureTree containing the features of this Entry.
+ **/
+ private FeatureTree features = new FeatureTree (new FeatureComparator ());
+
+ /**
+ * The EntryInformation object that was passed to the constructor.
+ **/
+ final private EntryInformation entry_information;
+
+ public void dispose()
+ {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/CorbaFeature.java b/uk/ac/sanger/artemis/io/CorbaFeature.java
new file mode 100644
index 0000000..1e6b566
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/CorbaFeature.java
@@ -0,0 +1,389 @@
+/* CorbaFeature.java
+ *
+ * created: Wed Feb 10 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/CorbaFeature.java,v 1.2 2005-11-28 16:46:38 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import nsdb.NucFeature;
+import type.NoResult;
+
+/**
+ * This is a subclass of Feature that can read itself from the EMBL CORBA
+ * server.
+ *
+ * @author Kim Rutherford
+ * @version $Id: CorbaFeature.java,v 1.2 2005-11-28 16:46:38 tjc Exp $
+ **/
+
+public class CorbaFeature extends EMBLObject
+ implements ComparableFeature {
+ /**
+ * Create a new CorbaFeature object.
+ * @param feature_handle The corba handle of the feature
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * one of the qualifiers that was read from the server.
+ **/
+ public CorbaFeature (final NucFeature feature_handle)
+ throws LocationParseException, InvalidKeyException, NoResult,
+ InvalidRelationException {
+ this.feature_handle = feature_handle;
+
+ final String location_string =
+ feature_handle.getLocation ().getLocationString ();
+
+ this.location = new Location (location_string);
+ this.key = new Key (feature_handle.getKey ());
+ grabQualifiers ();
+ }
+
+ /**
+ * Set the value of this object.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * any of the given Qualifier objects.
+ **/
+ public void set (Key key,
+ Location location,
+ QualifierVector qualifiers)
+ throws InvalidRelationException, ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Set the owning Entry of this Feature. Other objects should call
+ * setCorbaEntry () to change the owner.
+ * @param entry The Entry that now owns this Feature.
+ **/
+ private void setEntry (CorbaEntry entry) {
+ this.entry = entry;
+ }
+
+ /**
+ * Set the owning CorbaEntry of this Feature.
+ * @param entry The CorbaEntry that now owns this Feature.
+ **/
+ void setCorbaEntry (final CorbaEntry entry) {
+ setEntry (entry);
+ }
+
+ /**
+ * Set the key field of this object.
+ * @param key The new feature key
+ **/
+ public void setKey (Key key)
+ throws ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ public void setLocation (Location location, Entry entry)
+ throws ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Set the location of this object.
+ * @param location The Location object for the new feature
+ **/
+ public void setLocation (Location location)
+ throws ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Set the qualifiers of this object discarding all the current qualifiers.
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Throw if this Feature cannot contain
+ * any of the given Qualifier objects.
+ **/
+ public void setQualifiers (QualifierVector qualifiers)
+ throws InvalidRelationException, ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Add the given Qualifier to this Feature. If this Feature contains a
+ * Qualifier with the same name as the new Qualifier it will be replaced.
+ * @param qualifier The new qualifier to add.
+ * @exception InvalidRelationException Throw if this Feature cannot contain
+ * the given Qualifier.
+ **/
+ public void setQualifier (Qualifier qualifier)
+ throws InvalidRelationException, ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Remove the Qualifier with the given name. If there is no Qualifier with
+ * that name then return immediately.
+ * @param name The qualifier name to look for.
+ **/
+ public void removeQualifierByName (final String name)
+ throws ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Add the values from the given qualifier to the Qualifier object with the
+ * same name in this Feature or otherwise add a copy of the argument.
+ * @param qualifier This object contians name and values to add.
+ * @param values The values to add to the Qualifier.
+ * @return The Qualifier that was changed or created.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ **/
+ public Qualifier addQualifierValues (Qualifier qualifier)
+ throws InvalidRelationException, ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Return the unique identifier of this StreamFeature.
+ **/
+ public long getNumericID () {
+ return id;
+ }
+
+ /**
+ * Return the key of this feature, as passed to the constructor.
+ **/
+ public Key getKey () {
+ return key;
+ }
+
+ /**
+ * Return the Location of this Feature, as passed to the constructor.
+ **/
+ public Location getLocation () {
+ return location;
+ }
+
+ /**
+ * Return a QualifierVector object containing the qualifiers for this
+ * feature. This method does not return a copy of the qualifier vector so
+ * changing the vector will change the feature.
+ **/
+ public QualifierVector getQualifiers () {
+ return qualifiers;
+ }
+
+ /**
+ * Return the Qualifier in this Feature with the given name or null if
+ * there no such Qualifier.
+ **/
+ public Qualifier getQualifierByName (final String name) {
+ return getQualifiers ().getQualifierByName (name);
+ }
+
+ /**
+ * Return the first base of this feature.
+ **/
+ public int getFirstBase () {
+ return getLocation ().getFirstBase ();
+ }
+
+ /**
+ * Return the last base of this feature.
+ **/
+ public int getLastBase () {
+ return getLocation ().getLastBase ();
+ }
+
+ /**
+ * Return the Entry object that contains this Feature.
+ **/
+ public Entry getEntry () {
+ return entry;
+ }
+
+ /**
+ * Return the reference of a new copy of this Feature. The new Feature
+ * will be an EmblStreamFeature and will not be in any Entry, so it should
+ * be explicitly added with a call to Entry.add ().
+ **/
+ public Feature copy () {
+ return new EmblStreamFeature (this);
+ }
+
+ /**
+ * Returns true if and only if this Feature can't be changed and can't be
+ * removed from it's entry.
+ **/
+ public boolean isReadOnly () {
+ return true;
+ }
+
+ /**
+ * Read the qualifiers from the given NucFeature, create an embl.Qualifier
+ * for each one and put them in the qualifiers vector.
+ **/
+ private void grabQualifiers ()
+ throws InvalidRelationException {
+ qualifiers = new QualifierVector ();
+
+ try {
+ final nsdb.NucFeaturePackage.Qualifier [] corba_qualifier_handles =
+ feature_handle.getQualifiers ();
+
+ for (int i = 0 ; i < corba_qualifier_handles.length ; ++i) {
+ final nsdb.NucFeaturePackage.Qualifier this_corba_qualifier =
+ corba_qualifier_handles[i];
+
+ final StringVector values =
+ getQualifierStringValues (this_corba_qualifier);
+
+ EntryInformation entry_info = getEntryInformation ();
+
+ final String qualifier_name = this_corba_qualifier.name;
+
+ if (!entry_info.isValidQualifier (getKey (),
+ qualifier_name)) {
+ final String message =
+ getKey () + " cannot have " + qualifier_name +
+ " as a qualifier";
+ throw new InvalidRelationException (message, getKey (),
+ new Qualifier (qualifier_name,
+ values));
+ }
+
+ if (values.size () == 0) {
+ qualifiers.setQualifier (new Qualifier (this_corba_qualifier.name));
+ } else {
+ qualifiers.setQualifier (new Qualifier (this_corba_qualifier.name,
+ values));
+ }
+ }
+ } catch (NoResult e) {
+ System.out.println ("unexpected NoResult exception while reading " +
+ "from corba " + e);
+ }
+ }
+
+ /**
+ * Return (from corba) the values of the given qualifier as String objects.
+ **/
+ private StringVector
+ getQualifierStringValues (nsdb.NucFeaturePackage.Qualifier qualifier) {
+
+ final nsdb.NucFeaturePackage.QualifierValue_u [] corba_values =
+ qualifier.values;
+
+ final StringVector return_array = new StringVector ();
+
+ for (int value_index = 0 ;
+ value_index < corba_values.length ;
+ ++value_index) {
+
+ final nsdb.NucFeaturePackage.QualifierValue_u corba_value =
+ corba_values[value_index];
+
+ /*final*/ String this_value_string;
+
+ switch (corba_value.discriminator ()) {
+ case NucFeature.string_qtc:
+ return_array.add (corba_value.text ());
+ break;
+ case NucFeature.integer_qtc:
+ return_array.add (String.valueOf (corba_value.integer ()));
+ break;
+ case NucFeature.real_qtc:
+ return_array.add (String.valueOf (corba_value.real ()));
+ break;
+ case NucFeature.TranslationException_qtc:
+ return_array.add (String.valueOf (corba_value.translation_exception ()));
+ break;
+ case NucFeature.CodonTranslation_qtc:
+ return_array.add (String.valueOf (corba_value.codon_translation ()));
+ break;
+ case NucFeature.Anticodon_qtc:
+ return_array.add (String.valueOf (corba_value.anti_codon ()));
+ break;
+ case NucFeature.SpliceConsensus_qtc:
+ return_array.add (String.valueOf (corba_value.splice_consensus ()));
+ break;
+ case NucFeature.RepeatUnit_qtc:
+ return_array.add (String.valueOf (corba_value.repeat_unit ()));
+ break;
+ case NucFeature.DbXref_qtc:
+ return_array.add (String.valueOf (corba_value.db_xref ()));
+ break;
+ }
+ }
+
+ return return_array;
+ }
+
+ /**
+ * Return the EntryInformation object from the Entry of this feature (if
+ * there is an Entry) or SimpleEntryInformation.default_entry_information
+ * otherwise.
+ **/
+ private EntryInformation getEntryInformation () {
+ if (getEntry () == null) {
+ return SimpleEntryInformation.getDefaultEntryInformation ();
+ } else {
+ return getEntry ().getEntryInformation ();
+ }
+ }
+
+ /**
+ * The NucFeature object that was passed to the constructor.
+ **/
+ private NucFeature feature_handle;
+
+ /**
+ * The Location of this Feature, set by the constructor.
+ **/
+ private Location location;
+
+ /**
+ * The Key of this Feature, set by the constructor.
+ **/
+ private Key key;
+
+ /**
+ * The qualifiers of this Feature, set by the first call to the
+ * getQualifiers () method.
+ **/
+ private QualifierVector qualifiers = null;
+
+ /**
+ * The CorbaEntry object that contains this Feature as passed to the
+ * constructor.
+ **/
+ private CorbaEntry entry;
+
+ /**
+ * A unique identifier for this feature.
+ **/
+ private final long id = id_counter++;
+
+ /**
+ * This is incremented each time a constructor is called.
+ **/
+ private static long id_counter = 0;
+}
diff --git a/uk/ac/sanger/artemis/io/CorbaSequence.java b/uk/ac/sanger/artemis/io/CorbaSequence.java
new file mode 100644
index 0000000..c8239c5
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/CorbaSequence.java
@@ -0,0 +1,155 @@
+/* CorbaSequence.java
+ *
+ * created: Mon Feb 8 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/CorbaSequence.java,v 1.4 2008-06-10 15:33:13 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+import java.io.IOException;
+
+/**
+ * This is a subclass of Sequence that can read itself from a Corba embl
+ * object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: CorbaSequence.java,v 1.4 2008-06-10 15:33:13 tjc Exp $
+ **/
+
+public class CorbaSequence implements Sequence {
+ /**
+ * Create a new CorbaSequence object from the given handle.
+ * @param data This is the corba object that we will read from.
+ **/
+ public CorbaSequence (final nsdb.EmblSeq corba_handle) {
+ this.corba_handle = corba_handle;
+
+ sequence = corba_handle.getSeq();
+ }
+
+ /**
+ * Return this Sequence as a String.
+ **/
+ public String toString () {
+ return sequence;
+ }
+
+ /**
+ * Return a the given range of bases as a String.
+ * @param start The start base of the range.
+ * @param end The end base of the range.
+ **/
+ public String getSubSequence (int start, int end) {
+ if (start == 1 && end == length ()) {
+ return sequence;
+ }
+
+ if (end < start) {
+ // empty range
+ return "";
+ } else {
+ return sequence.substring (start - 1, end);
+ }
+ }
+
+ public char[] getCharSubSequence (int start, int end)
+ {
+ char[] dst = new char[end-start+1];
+ StringBuffer buff = new StringBuffer(sequence);
+ buff.getChars(start-1, end, dst, 0);
+ return dst;
+ }
+
+ public char charAt(int i)
+ {
+ return sequence.charAt(i);
+ }
+
+ /**
+ * Set this sequence to hold the bases in the given String - throws
+ * ReadOnlyException for CorbaSequence objects.
+ * @exception ReadOnlyException If this Sequence cannot be changed.
+ **/
+ public void setFromChar(final char[] new_sequence)
+ throws ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Returns the length of the sequence in bases.
+ **/
+ public int length () {
+ return sequence.length ();
+ }
+
+ /**
+ * Return the count of c bases in the whole sequence.
+ **/
+ public int getCCount () {
+ return corba_handle.getCountC();
+ }
+
+ /**
+ * Return the count of g bases in the whole sequence.
+ **/
+ public int getGCount () {
+ return corba_handle.getCountG();
+ }
+
+ /**
+ * Return the count of a bases in the whole sequence.
+ **/
+ public int getACount () {
+ return corba_handle.getCountA();
+ }
+
+ /**
+ * Return the count of t bases in the whole sequence.
+ **/
+ public int getTCount () {
+ return corba_handle.getCountT();
+ }
+
+ /**
+ * Return the count of non-g,c,t,a bases in the whole sequence.
+ **/
+ public int getOtherCount () {
+ return
+ length () - (getCCount () + getACount () + getTCount () + getGCount ());
+ }
+
+ public void clear() {}
+
+ /**
+ * The sequence that was read from the Corba object by the constructor.
+ **/
+ private String sequence;
+
+ /**
+ * The corba object that was passed to the constructor.
+ **/
+ private nsdb.EmblSeq corba_handle;
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/DatabaseDocumentEntry.java b/uk/ac/sanger/artemis/io/DatabaseDocumentEntry.java
new file mode 100644
index 0000000..0b05aed
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/DatabaseDocumentEntry.java
@@ -0,0 +1,138 @@
+/* DatabaseDocumentEntry.java
+ *
+ * created: Mar 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+
+/**
+ * A DatabaseDocumentEntry that can read an Entry from a Document containing
+ * relational database entry
+ **/
+
+public class DatabaseDocumentEntry extends GFFDocumentEntry
+ implements DocumentEntry
+{
+ private PartialSequence sequence;
+ private ChadoTransactionManager ctm = new ChadoTransactionManager();
+
+ public DatabaseDocumentEntry(final Document doc,
+ final ReadListener listener)
+ throws EntryInformationException, IOException
+ {
+ super(doc,listener);
+ }
+
+ public DatabaseDocumentEntry()
+ {
+ super(new GFFEntryInformation());
+ }
+
+
+
+ /**
+ * If the given Sequence can be added directly to this Entry, then return a
+ * copy of it, otherwise create and return a new feature of the appropriate
+ * type for this Entry.
+ **/
+ protected StreamSequence makeNativeSequence(final Sequence sequence)
+ {
+ return new FastaStreamSequence(sequence);
+ }
+
+
+ /**
+ * If the given feature can be added directly to this Entry, then return
+ * it, otherwise create and return a new feature of the appropriate type.
+ * @param copy if true then always new a new copy of the Feature.
+ **/
+ protected Object makeNativeFeature(final Feature feature,
+ final boolean copy)
+ {
+ if (!copy)
+ {
+ if(feature instanceof DatabaseStreamFeature)
+ return (DatabaseStreamFeature)feature;
+ else if(feature instanceof GFFStreamFeature)
+ return (GFFStreamFeature)feature;
+ else
+ {
+ QualifierVector qualifiers = feature.getQualifiers().copy();
+ if(qualifiers.getQualifierByName("ID") == null)
+ {
+ String idStr = null;
+ StringVector v = Options.getOptions().getSystematicQualifierNames();
+ for(int i=0; i<v.size(); i++)
+ {
+ final String sysName = (String)v.get(i);
+ if(qualifiers.getQualifierByName(sysName) != null)
+ {
+ idStr = (String)qualifiers.getQualifierByName(sysName).getValues().get(0);
+ break;
+ }
+ }
+ // autogenerate ID
+ if(idStr == null)
+ idStr = feature.getKey().getKeyString()+":"+feature.getLocation().toString();
+ qualifiers.add(new Qualifier("ID", idStr));
+ }
+
+ return new DatabaseStreamFeature(feature.getKey(),
+ feature.getLocation(), qualifiers);
+ }
+ }
+ else
+ return new DatabaseStreamFeature(feature);
+ }
+
+ public void setPartialSequence(final PartialSequence sequence)
+ {
+ this.sequence = sequence;
+ }
+
+ public Sequence getSequence()
+ {
+ if(sequence == null)
+ return super.getSequence();
+ return sequence;
+ }
+
+ /**
+ * Returns true if and only if there have been some changes to this Entry
+ * since the last save.
+ **/
+ public boolean hasUnsavedChanges()
+ {
+ return ctm.hasTransactions();
+ }
+
+ public ChadoTransactionManager getChadoTransactionManager()
+ {
+ return ctm;
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/DatabaseInferredFeature.java b/uk/ac/sanger/artemis/io/DatabaseInferredFeature.java
new file mode 100644
index 0000000..5ec0179
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/DatabaseInferredFeature.java
@@ -0,0 +1,466 @@
+/* DatabaseInferredFeature.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.List;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.FeatureChangeEvent;
+import uk.ac.sanger.artemis.FeatureChangeListener;
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+/**
+ * A feature (CDS and UTRs) that is inferred from a chado
+ * database hierarchy (gene, transcript, exon, and polypeptide)
+ * defined by the <code>ChadoCanonicalGene</code>.
+ */
+public class DatabaseInferredFeature
+ extends GFFStreamFeature implements FeatureChangeListener
+{
+
+ public DatabaseInferredFeature(Key key,
+ Location location,
+ QualifierVector qualifiers,
+ ChadoCanonicalGene gene)
+ {
+ super(key, location, qualifiers);
+ setChadoGene(gene);
+ }
+
+ public void addFeatureListeners()
+ {
+ if(!getKey().equals(Key.CDS))
+ return;
+ String parent = (String) getQualifierByName("Parent").getValues().get(0);
+ if(getChadoGene().getProteinOfTranscript(parent) == null)
+ return;
+
+ Feature protein = getChadoGene().getProteinOfTranscript(parent);
+ ((uk.ac.sanger.artemis.Feature)protein.getUserData()).addFeatureChangeListener(this);
+
+ List list = getChadoGene().getSpliceSitesOfTranscript(parent, "exon");
+ for(int i=0; i<list.size(); i++)
+ {
+ uk.ac.sanger.artemis.Feature f =
+ ((uk.ac.sanger.artemis.Feature)((Feature)list.get(i)).getUserData());
+ f.addFeatureChangeListener(this);
+ }
+
+ updateInferredLocations();
+ }
+
+ private void removeFeatureListeners()
+ {
+ if(!getKey().equals(Key.CDS))
+ return;
+ String parent = (String) getQualifierByName("Parent").getValues().get(0);
+ if(getChadoGene().getProteinOfTranscript(parent) == null)
+ return;
+
+ Feature protein = getChadoGene().getProteinOfTranscript(parent);
+ ((uk.ac.sanger.artemis.Feature)protein.getUserData()).removeFeatureChangeListener(this);
+
+ List list = getChadoGene().getSpliceSitesOfTranscript(parent, "exon");
+ for(int i=0; i<list.size(); i++)
+ {
+ uk.ac.sanger.artemis.Feature f =
+ ((uk.ac.sanger.artemis.Feature)((Feature)list.get(i)).getUserData());
+ f.removeFeatureChangeListener(this);
+ }
+ }
+
+ public void featureChanged(FeatureChangeEvent event)
+ {
+ if(event.getType() == FeatureChangeEvent.LOCATION_CHANGED ||
+ event.getType() == FeatureChangeEvent.SEGMENT_CHANGED)
+ {
+ if(getKey().equals(Key.CDS))
+ updateInferredLocations();
+ }
+ }
+
+ public void updateInferredLocations()
+ {
+ String transcriptID = (String) getQualifierByName("Parent").getValues().get(0);
+ if(getChadoGene().getProteinOfTranscript(transcriptID) == null)
+ return;
+
+ Range proteinRange =
+ getChadoGene().getProteinOfTranscript(transcriptID).getLocation().getTotalRange();
+
+ Feature exonFeature =
+ ((Feature)(getChadoGene().getSpliceSitesOfTranscript(transcriptID, "exon").get(0)));
+
+ RangeVector r_old = exonFeature.getLocation().getRanges();
+ RangeVector r_cds = new RangeVector();
+ RangeVector r_diff_5 = new RangeVector();
+ RangeVector r_diff_3 = new RangeVector();
+ boolean isComplement = exonFeature.getLocation().isComplement();
+
+ for(int i=0; i<r_old.size(); i++)
+ {
+ Range exonRange = (Range) r_old.get(i);
+ findRanges(proteinRange, exonRange, isComplement,
+ r_diff_5, r_diff_3, r_cds);
+ }
+
+ //
+ // update CDS location
+ try
+ {
+ if(!containsAll(r_cds, getLocation().getRanges()))
+ super.setLocation(new Location(r_cds, getLocation().isComplement()));
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+
+ //
+ // update UTR locations
+ Entry entry =
+ ((uk.ac.sanger.artemis.Feature) getUserData()).getEntry();
+ try
+ {
+ if (r_diff_5.size() > 0)
+ {
+ List listUTR = getChadoGene().get5UtrOfTranscript(transcriptID);
+ boolean isFound = containsAll(listUTR, r_diff_5);
+
+ if(!isFound)
+ {
+ Feature utrFeature = addUTR(transcriptID, "five_prime_UTR", r_diff_5,
+ getChadoGene().get5UtrOfTranscript(transcriptID), entry);
+ getChadoGene().add5PrimeUtr(transcriptID, utrFeature);
+ }
+ }
+ else
+ removeUTR(getChadoGene().get5UtrOfTranscript(transcriptID), entry);
+
+ if(r_diff_3.size() > 0)
+ {
+ List listUTR = getChadoGene().get3UtrOfTranscript(transcriptID);
+ boolean isFound = containsAll(listUTR, r_diff_3);
+
+ if(!isFound)
+ {
+ Feature utrFeature = addUTR(transcriptID, "three_prime_UTR", r_diff_3,
+ getChadoGene().get3UtrOfTranscript(transcriptID), entry);
+ getChadoGene().add3PrimeUtr(transcriptID, utrFeature);
+ }
+ }
+ else
+ removeUTR(getChadoGene().get3UtrOfTranscript(transcriptID), entry);
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Classify the ranges based on the exon and protein ranges.
+ * The range is divided into 5'UTR, 3'UTR and CDS ranges.
+ * @param proteinRange
+ * @param exonRange
+ * @param isComplement
+ * @param r_diff_5
+ * @param r_diff_3
+ * @param r_cds
+ */
+ private void findRanges(Range proteinRange,
+ Range exonRange,
+ boolean isComplement,
+ RangeVector r_diff_5,
+ RangeVector r_diff_3,
+ RangeVector r_cds)
+ {
+ if( !proteinRange.contains(exonRange) )
+ {
+ if(exonRange.getStart () < proteinRange.getStart () &&
+ exonRange.getEnd () > proteinRange.getEnd ())
+ {
+ try
+ {
+ if(isComplement)
+ {
+ r_diff_3.add(new Range(exonRange.getStart(), proteinRange.getStart()-1));
+ r_diff_5.add(new Range(proteinRange.getEnd()+1, exonRange.getEnd()));
+ }
+ else
+ {
+ r_diff_5.add(new Range(exonRange.getStart(), proteinRange.getStart()-1));
+ r_diff_3.add(new Range(proteinRange.getEnd()+1, exonRange.getEnd()));
+ }
+ r_cds.add(new Range(proteinRange.getStart(), proteinRange.getEnd()));
+ }
+ catch (OutOfRangeException e){ e.printStackTrace(); }
+ }
+ else if(exonRange.getStart() >= proteinRange.getStart() &&
+ exonRange.getStart() < proteinRange.getEnd())
+ {
+ // exon is partially CDS and UTR
+ try
+ {
+ r_cds.add(new Range(exonRange.getStart(), proteinRange.getEnd()));
+
+ if(isComplement)
+ r_diff_5.add(new Range(proteinRange.getEnd()+1, exonRange.getEnd()));
+ else
+ r_diff_3.add(new Range(proteinRange.getEnd()+1, exonRange.getEnd()));
+ }
+ catch (OutOfRangeException e){ e.printStackTrace(); }
+ }
+ else if(exonRange.getEnd() > proteinRange.getStart() &&
+ exonRange.getEnd() <= proteinRange.getEnd())
+ {
+ try
+ {
+ r_cds.add(new Range(proteinRange.getStart(), exonRange.getEnd()));
+
+ if(isComplement)
+ r_diff_3.add(new Range(exonRange.getStart(), proteinRange.getStart()-1));
+ else
+ r_diff_5.add(new Range(exonRange.getStart(), proteinRange.getStart()-1));
+ }
+ catch (OutOfRangeException e){ e.printStackTrace(); }
+ }
+ else if(exonRange.getStart() < proteinRange.getStart())
+ r_diff_5.add(exonRange);
+ else if(exonRange.getStart() > proteinRange.getStart())
+ r_diff_3.add(exonRange);
+ }
+ else
+ r_cds.add(exonRange);
+ }
+
+ /**
+ * Override to ensure that associated features get updated.
+ */
+ public void setLocation (final Location location)
+ throws ReadOnlyException, OutOfRangeException
+ {
+ if(getQualifierByName("Parent") != null)
+ removeFeatureListeners();
+ super.setLocation(location);
+
+ if(getKey().equals(Key.CDS) && getQualifierByName("Parent") != null)
+ {
+ // update the exon feature
+ String transcriptID = (String) getQualifierByName("Parent").getValues().get(0);
+ Feature exonFeature =
+ ((Feature)(getChadoGene().getSpliceSitesOfTranscript(transcriptID, "exon").get(0)));
+
+ RangeVector exonRanges = exonFeature.getLocation().getRanges();
+ RangeVector cdsRanges = getLocation().getRanges();
+ FeatureSegmentVector segments =
+ ((uk.ac.sanger.artemis.Feature)exonFeature.getUserData()).getSegments();
+
+ for(int i=0; i<cdsRanges.size(); i++)
+ {
+ Range cdsRange = (Range) cdsRanges.get(i);
+ if(!exonRanges.containsRange(cdsRange))
+ {
+ for(int j=0; j<segments.size(); j++)
+ {
+ Range exonRange = segments.elementAt(j).getRawRange();
+ if( exonRange.overlaps(cdsRange) &&
+ (exonRange.getStart() == cdsRange.getStart() ||
+ exonRange.getEnd() == cdsRange.getEnd()) )
+ {
+ segments.elementAt(j).setRange(cdsRange);
+ }
+ }
+ }
+ }
+
+ }
+ else if(getKey().getKeyString().equals("five_prime_UTR"))
+ {
+
+ }
+
+ if(getQualifierByName("Parent") != null)
+ addFeatureListeners();
+ }
+
+ /**
+ * Add a UTR to an entry.
+ * @param transcriptName
+ * @param keyName
+ * @param ranges
+ * @param existingUTRs
+ * @param entry
+ * @return
+ * @throws ReadOnlyException
+ * @throws EntryInformationException
+ */
+ private Feature addUTR(String transcriptName,
+ String keyName,
+ RangeVector ranges,
+ List existingUTRs,
+ Entry entry)
+ throws ReadOnlyException, EntryInformationException
+ {
+ removeUTR(existingUTRs, entry);
+
+ QualifierVector qualifiers = new QualifierVector();
+ qualifiers.add(new Qualifier("ID", transcriptName+
+ ( keyName.equals("five_prime_UTR") ? ":5UTR" : ":3UTR" ) ));
+ qualifiers.add(new Qualifier("Parent", transcriptName));
+
+ Location l = new Location(ranges, getLocation().isComplement());
+ DatabaseInferredFeature utrFeature = new DatabaseInferredFeature(
+ new Key(keyName), l, qualifiers, getChadoGene());
+
+ uk.ac.sanger.artemis.Feature f = new uk.ac.sanger.artemis.Feature(utrFeature);
+ f.setEntry(entry);
+
+ return entry.getEMBLEntry().add(utrFeature);
+ }
+
+ /**
+ * Remove a list of UTRs from an entry.
+ * @param existingUTRs
+ * @param entry
+ * @throws ReadOnlyException
+ */
+ private void removeUTR(List existingUTRs,
+ Entry entry) throws ReadOnlyException
+ {
+ if (existingUTRs == null)
+ return;
+
+ for (int i = 0; i < existingUTRs.size(); i++)
+ entry.getEMBLEntry().remove((Feature) existingUTRs.get(i));
+ existingUTRs.clear();
+ }
+
+ /**
+ * Check that a list of UTRs contains all the ranges in a
+ * new <code>RangeVector</code>.
+ * @param listUTR
+ * @param newRanges
+ * @return
+ */
+ private boolean containsAll(List listUTR,
+ RangeVector newRanges)
+ {
+ RangeVector oldRanges = new RangeVector();
+ if(listUTR != null)
+ {
+ for(int i=0;i<listUTR.size(); i++)
+ {
+ Feature featureUTR = (Feature) listUTR.get(i);
+ oldRanges.addAll(featureUTR.getLocation().getRanges());
+ }
+ }
+ return containsAll(newRanges, oldRanges);
+ }
+
+ /**
+ * Check if a <code>RangeVector</code> contains all the ranges
+ * in a second <code>RangeVector</code>.
+ * @param newRanges
+ * @param oldRanges
+ * @return
+ */
+ private boolean containsAll(RangeVector newRanges,
+ RangeVector oldRanges)
+ {
+ if(newRanges.size() != oldRanges.size())
+ return false;
+ for(int i=0; i<oldRanges.size(); i++)
+ {
+ if(!newRanges.containsRange( (Range)oldRanges.get(i) ))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Create a CDS
+ * @param transcriptId
+ * @param exonFeature
+ * @param chadoGene
+ * @param entry
+ */
+ public static void createFeature(final String transcriptId,
+ final Feature exonFeature,
+ final ChadoCanonicalGene chadoGene,
+ final Entry entry)
+ {
+ QualifierVector qualifiers = new QualifierVector();
+ qualifiers.add(new Qualifier("ID", transcriptId+":CDS"));
+ qualifiers.add(new Qualifier("Parent", transcriptId));
+
+ DatabaseInferredFeature cdsFeature = new DatabaseInferredFeature(
+ Key.CDS, exonFeature.getLocation(), qualifiers, chadoGene);
+ uk.ac.sanger.artemis.Feature cds = new uk.ac.sanger.artemis.Feature(cdsFeature);
+
+ try
+ {
+ chadoGene.addSplicedFeatures(transcriptId, cdsFeature);
+ entry.add(cds, true);
+ cdsFeature.addFeatureListeners();
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ *
+ * @param entryGroup
+ */
+ public static void addListenersToEntryGroup(EntryGroup entryGroup)
+ {
+ FeatureVector features = entryGroup.getAllFeatures();
+ for(int i=0; i<features.size(); i++)
+ if(features.elementAt(i).getEmblFeature() instanceof DatabaseInferredFeature)
+ ((DatabaseInferredFeature)features.elementAt(i).getEmblFeature()).addFeatureListeners();
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/DatabaseStreamFeature.java b/uk/ac/sanger/artemis/io/DatabaseStreamFeature.java
new file mode 100644
index 0000000..de3145c
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/DatabaseStreamFeature.java
@@ -0,0 +1,65 @@
+/* DatbaseStreamFeature.java
+ *
+ * created: Mar 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This is an implementation of Feature that can read and write itself to a
+ * CHADO stream.
+ *
+ * @version $Id: DatabaseStreamFeature.java,v 1.7 2007-06-05 09:33:41 tjc Exp $
+ **/
+
+public class DatabaseStreamFeature
+ extends GFFStreamFeature
+ implements DocumentFeature, StreamFeature, ComparableFeature
+{
+
+ public DatabaseStreamFeature(final Key key, final Location location,
+ final QualifierVector qualifiers)
+ {
+ super(key, location, qualifiers);
+ }
+
+ /**
+ * Create a new DatabaseStreamFeature with the same key, location and
+ * qualifiers as the given feature. The feature should be added to an
+ * Entry (with Entry.add ()).
+ * @param feature The feature to copy.
+ **/
+ public DatabaseStreamFeature (final Feature feature)
+ {
+ super(feature);
+ }
+
+ /**
+ * Return the reference of a new copy of this Feature.
+ **/
+ public Feature copy()
+ {
+ final Feature return_value = new DatabaseStreamFeature(this);
+ return return_value;
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/DateStampFeature.java b/uk/ac/sanger/artemis/io/DateStampFeature.java
new file mode 100644
index 0000000..adeadd0
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/DateStampFeature.java
@@ -0,0 +1,104 @@
+/* DateStampFeature.java
+ *
+ * created: Mon Jul 12 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/DateStampFeature.java,v 1.1 2004-06-09 09:49:02 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.util.Date;
+
+/**
+ * This interface is a Feature with set () methods that take a datestamp as
+ * an argument.
+ *
+ * @author Kim Rutherford
+ * @version $Id: DateStampFeature.java,v 1.1 2004-06-09 09:49:02 tjc Exp $
+ **/
+
+public interface DateStampFeature extends Feature {
+
+ /**
+ * Set the value of this object.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * any of the given Qualifier objects.
+ * @exception OutOfDate If the key has changed in the server since the time
+ * given by datestamp.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfRangeException Thrown if any part of the location is out
+ * of range for the sequence.
+ **/
+ void set (final Date datestamp,
+ final Key key,
+ final Location location,
+ final QualifierVector qualifiers)
+ throws InvalidRelationException, OutOfRangeException, ReadOnlyException,
+ OutOfDateException;
+ /**
+ * Set the key field of this object.
+ * @param key The new feature key
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfDate If the key has changed in the server since the time
+ * given by datestamp.
+ **/
+ void setKey (final Date datestamp, final Key key)
+ throws InvalidRelationException, ReadOnlyException,
+ OutOfDateException;
+
+ /**
+ * Set the location of this object.
+ * @param location The Location object for the new feature
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfDate If the key has changed in the server since the time
+ * given by datestamp.
+ * @exception OutOfRangeException Thrown if any part of the location is out
+ * of range for this sequence.
+ **/
+ void setLocation (final Date datestamp,
+ final Location location)
+ throws ReadOnlyException, OutOfDateException, OutOfRangeException;
+
+ /**
+ * Set the qualifiers of this object discarding all the current qualifiers.
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Throw if this Feature cannot contain
+ * any of the given Qualifier objects.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfDateException If the key has changed in the server since
+ * the time given by datestamp.
+ **/
+ void setQualifiers (final Date datestamp,
+ final QualifierVector qualifiers)
+ throws InvalidRelationException, ReadOnlyException, OutOfDateException;
+
+ /**
+ * Return the datestamp of this feature - that is, the time it was last
+ * changed.
+ **/
+ Date getDatestamp ();
+}
+
diff --git a/uk/ac/sanger/artemis/io/DocumentEntry.java b/uk/ac/sanger/artemis/io/DocumentEntry.java
new file mode 100644
index 0000000..e1317ac
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/DocumentEntry.java
@@ -0,0 +1,88 @@
+/* DocumentEntry.java
+ *
+ * created: Wed Dec 30 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/DocumentEntry.java,v 1.2 2005-02-03 15:17:50 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+import java.util.Date;
+
+/**
+ * This class extends the Entry class with the data for the entry coming from
+ * a Document object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: DocumentEntry.java,v 1.2 2005-02-03 15:17:50 tjc Exp $
+ **/
+
+public interface DocumentEntry extends Entry {
+ /**
+ * Write this Entry to the Document it came from. This method uses the
+ * current Document reference (as given by getDocument () or the
+ * constructor). This method will throw a NullPointerException if the is
+ * no current Document ie. if getDocument () return null. Use
+ * save (Document) to save and set the current Document.
+ * @exception IOException thrown if there is a problem saving the entry.
+ **/
+ void save ()
+ throws IOException;
+
+ /**
+ * Write this Entry to the given Document.
+ * @param document This is the file that we will write to.
+ * @exception IOException thrown if there is a problem saving the entry.
+ **/
+ void save (final Document document)
+ throws IOException;
+
+ /**
+ * Write this Entry to the given stream.
+ * @param writer The stream to write to.
+ * @exception IOException thrown if there is a problem writing the entry.
+ **/
+ void writeToStream (final Writer writer)
+ throws IOException;
+
+
+ /**
+ * Arrange for hasUnsavedChanges () to return true until the next save.
+ **/
+ void setDirtyFlag ();
+
+ /**
+ * Return the Date when this Entry last changed or null if this Entry
+ * hasn't changed since the last save.
+ **/
+ Date getLastChangeTime ();
+
+ /**
+ * Return the File reference that was passed to the constructor or null if
+ * none was passed.
+ **/
+ Document getDocument ();
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/DocumentEntryAutosaveThread.java b/uk/ac/sanger/artemis/io/DocumentEntryAutosaveThread.java
new file mode 100644
index 0000000..cc0a3c7
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/DocumentEntryAutosaveThread.java
@@ -0,0 +1,155 @@
+/* DocumentEntryAutosaveThread.java
+ *
+ * created: Wed Aug 16 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/DocumentEntryAutosaveThread.java,v 1.8 2008-08-01 12:50:16 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+import java.io.*;
+
+/**
+ * This is a Thread that automatically saves a DocumentEntry to a backup file
+ * called #entry_name# every 120 seconds.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: DocumentEntryAutosaveThread.java,v 1.8 2008-08-01 12:50:16 tjc Exp $
+ **/
+
+public class DocumentEntryAutosaveThread extends Thread {
+ /**
+ * Create a new DocumentEntryAutosaveThread with MIN_PRIORITY.
+ **/
+ public DocumentEntryAutosaveThread (final DocumentEntry document_entry) {
+ this.document_entry = document_entry;
+
+ setPriority (Thread.MIN_PRIORITY);
+ }
+
+ /**
+ * The length of time (in seconds) to sleep for.
+ **/
+ private final static int SLEEP_TIME = 2 * 60 * 1000;
+
+ /**
+ * The main code for DocumentEntryAutosaveThread. Attempts to autosave the
+ * DocumentEntry every 2 minutes if it has unsaved changes.
+ **/
+ public void run () {
+ java.util.Date last_save_time = null;
+
+ // Set to true the first time we save.
+ boolean have_saved = false;
+
+ try {
+ // sleep for 240 seconds before starting - there is no point in attempting
+ // to save straight away
+ Thread.sleep (240000);
+ } catch (InterruptedException _) {
+ }
+
+ while (true) {
+ final String entry_name = document_entry.getName ();
+
+ if (entry_name == null) {
+ continue;
+ }
+
+ final File save_file;
+
+ if(document_entry.getDocument() instanceof RemoteFileDocument ||
+ isMac())
+ save_file = new File(System.getProperty("user.dir")+
+ System.getProperty("file.separator")+
+ "#" + entry_name + "#");
+ else
+ save_file = new File ("#" + entry_name + "#");
+
+ final java.util.Date last_change_time =
+ document_entry.getLastChangeTime ();
+
+ // only save if there are unsaved changes and we haven't autosaved
+ // since the last change
+ if (document_entry.hasUnsavedChanges () &&
+ (last_save_time == null ||
+ last_change_time != null &&
+ last_change_time.after (last_save_time))) {
+ final Document save_document = new FileDocument (save_file);
+ try {
+ final Writer out_file = save_document.getWriter ();
+ document_entry.writeToStream (out_file);
+ out_file.close ();
+ have_saved = true;
+ } catch (IOException e) {
+ System.err.println ("warning: could not auto save to: " +
+ save_document.getName () +
+ " (will try again later)");
+ } catch (java.util.ConcurrentModificationException e) {
+ // this Exception means that the tree that stores the features has
+ // changed since we started writing. since this thread doesn't
+ // change the tree, this exception is harmless, so we ignore it
+ // and try again later
+ }
+ catch(NullPointerException npe)
+ {
+ break;
+ }
+ } else {
+ if (have_saved) {
+ // auto save file isn't needed now so turn it into a backup file
+ final File new_name;
+
+ if(document_entry.getDocument() instanceof RemoteFileDocument ||
+ isMac())
+ new_name = new File (System.getProperty("user.dir")+
+ System.getProperty("file.separator")+
+ entry_name + "~");
+ else
+ new_name = new File (entry_name + "~");
+
+ save_file.renameTo (new_name);
+ } else {
+ // the file wasn't made by us so leave it
+ }
+ }
+
+ last_save_time = last_change_time;
+
+ try {
+ Thread.sleep (SLEEP_TIME);
+ } catch (InterruptedException _) {
+ }
+ }
+ }
+
+ private boolean isMac()
+ {
+ return System.getProperty("mrj.version") != null ||
+ System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0;
+ }
+
+ /**
+ * The DocumentEntry we will save.
+ **/
+ private DocumentEntry document_entry;
+}
diff --git a/uk/ac/sanger/artemis/io/DocumentEntryFactory.java b/uk/ac/sanger/artemis/io/DocumentEntryFactory.java
new file mode 100644
index 0000000..f530a08
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/DocumentEntryFactory.java
@@ -0,0 +1,227 @@
+/* DocumentEntryFactory.java
+ *
+ * created: Sat Sep 11 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/DocumentEntryFactory.java,v 1.3 2007-04-11 13:47:09 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.File;
+import java.io.IOException;
+
+
+/**
+ * This class contains the method makeDocumentEntry (), which creates a
+ * DocumentEntry object of the appropriate class from a Document object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: DocumentEntryFactory.java,v 1.3 2007-04-11 13:47:09 tjc Exp $
+ **/
+
+abstract public class DocumentEntryFactory
+{
+
+ /** use if format of the entry is unknown */
+ final public static int UNKNOWN_FORMAT = 0;
+
+ /** use if format of the entry is not important */
+ final public static int ANY_FORMAT = UNKNOWN_FORMAT;
+
+ /** use for an entry that is in EMBL format */
+ final public static int EMBL_FORMAT = 1;
+
+ /** use for an entry that is in GENBANK format */
+ final public static int GENBANK_FORMAT = 2;
+
+ /** use for an entry that is in GFF format */
+ final public static int GFF_FORMAT = 3;
+
+ public static boolean REMOVE_PRODUCT_FROM_PSEUDOGENE = false;
+
+
+ /**
+ * Read a DocumentEntry object from the given Document.
+ * @param entry_information The EntryInformation to use when reading. This
+ * supplies the list of valid keys and qualifiers
+ * @param listener The object that will listen for ReadEvents.
+ * @exception EntryInformationException Thrown if an Entry using the given
+ * EntryInformation object cannot contain the Key, Qualifier or
+ * Key/Qualifier combination of one of the features in the Document.
+ **/
+ public static DocumentEntry makeDocumentEntry (final EntryInformation entry_information,
+ final Document document,
+ final ReadListener listener)
+ throws IOException, EntryInformationException
+ {
+ if(!System.getProperty("java.version").startsWith("1.5.") &&
+ document.getInputStream() instanceof net.sf.samtools.util.BlockCompressedInputStream)
+ {
+ if(IndexedGFFDocumentEntry.isIndexed( ((File)document.getLocation()) ))
+ return new IndexedGFFDocumentEntry(document);
+ }
+
+ final LinePushBackReader document_reader =
+ document.getLinePushBackReader();
+
+ final String first_line = document_reader.readLine();
+
+ if (first_line == null) // empty file - create an empty EmblDocumentEntry
+ return new EmblDocumentEntry(entry_information, document, listener);
+
+ final int first_line_type = LineGroup.getLineType(first_line);
+ document_reader.pushBack(first_line);
+
+ switch (first_line_type) {
+ case LineGroup.GFF_FEATURE:
+ case LineGroup.GFF_MISC:
+ {
+ final SimpleDocumentEntry document_entry =
+ new GFFDocumentEntry (document, listener);
+ return document_entry;
+ }
+ case LineGroup.MSPCRUNCH_FEATURE:
+ {
+ final SimpleDocumentEntry document_entry =
+ new MSPcrunchDocumentEntry (document, listener);
+ return document_entry;
+ }
+ case LineGroup.BLAST_FEATURE:
+ {
+ final SimpleDocumentEntry document_entry =
+ new BlastDocumentEntry (document, listener);
+ return document_entry;
+ }
+ case LineGroup.GENBANK_MISC:
+ case LineGroup.GENBANK_FEATURE:
+ {
+ final SimpleDocumentEntry document_entry =
+ new GenbankDocumentEntry (entry_information, document, listener);
+ return document_entry;
+ }
+// case LineGroup.GAME_XML:
+// return new BioJavaEntry (document,
+// new GAMEFormat ());
+ default:
+ {
+ final SimpleDocumentEntry document_entry =
+ new EmblDocumentEntry (entry_information, document, listener);
+ return document_entry;
+ }
+ }
+ }
+
+ /**
+ * Make a new (nameless) DocumentEntry from the given Entry.
+ * @param entry_information The EntryInformation to use for the new object.
+ * This supplies the list of valid keys and qualifiers. Note that this
+ * argument is ignored by some DocumentEntry types, because some formats
+ * (like GFF) have limitations on the possible keys and qualifiers.
+ * @param destination_type This parameter control the type of DocumentEntry
+ * that is created. It should be a DocumentEntry type that can be
+ * constructed from any Entry eg. one of EMBL_FORMAT, GENBANK_FORMAT.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys in the new Entry will be quietly thrown away. "Invalid"
+ * means that the key/qualifier is not allowed to occur in an Entry of
+ * this type (probably determined by the EntryInformation object of this
+ * Entry). If false an EntryInformationException will be thrown for
+ * invalid keys or qualifiers.
+ * @exception EntryInformationException Thrown if an Entry using the given
+ * EntryInformation object cannot contain the Key, Qualifier or
+ * Key/Qualifier combination of one of the features in the source Entry.
+ **/
+ public static DocumentEntry makeDocumentEntry (final EntryInformation
+ entry_information,
+ final Entry entry,
+ int destination_type,
+ final boolean force)
+ throws EntryInformationException
+ {
+
+ if(destination_type == ANY_FORMAT)
+ {
+ if (entry instanceof EmblDocumentEntry)
+ destination_type = EMBL_FORMAT;
+ else
+ {
+ if (entry instanceof GenbankDocumentEntry)
+ destination_type = GENBANK_FORMAT;
+ else
+ {
+ if (entry instanceof GFFDocumentEntry)
+ destination_type = GFF_FORMAT;
+ else
+ destination_type = EMBL_FORMAT;
+ }
+ }
+ }
+
+ switch (destination_type) {
+ case EMBL_FORMAT:
+ {
+ EmblDocumentEntry ee = new EmblDocumentEntry (entry_information, entry, force);
+ if(force)
+ removeProductFromPseudogene(ee, destination_type);
+ return ee;
+ }
+ case GENBANK_FORMAT:
+ return new GenbankDocumentEntry (entry_information, entry, force);
+ case GFF_FORMAT:
+ return new GFFDocumentEntry (entry, force);
+// case BSML_FORMAT:
+// case GAME_FORMAT:
+// case AGAVE_FORMAT:
+// ...
+ default:
+ throw new Error ("internal error - unknown DocumentEntry type");
+ }
+ }
+
+ /**
+ * Remove product qualifier from CDS features with pseudogene qualifier
+ * @param document_entry
+ * @param destination_type
+ * @throws EntryInformationException
+ */
+ private static void removeProductFromPseudogene(final DocumentEntry entry,
+ final int destination_type)
+ throws EntryInformationException
+ {
+ if( !REMOVE_PRODUCT_FROM_PSEUDOGENE )
+ return;
+
+ final FeatureVector features = entry.getAllFeatures();
+ for(Feature f: features)
+ {
+ if(f.getKey().equals("CDS"))
+ {
+ Qualifier q = f.getQualifierByName("product");
+ if( q != null &&
+ (f.getQualifierByName("pseudogene") != null ||
+ f.getQualifierByName("pseudo") != null))
+ f.getQualifiers().remove(q);
+ }
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/DocumentFeature.java b/uk/ac/sanger/artemis/io/DocumentFeature.java
new file mode 100644
index 0000000..ca5b5a2
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/DocumentFeature.java
@@ -0,0 +1,37 @@
+/* DocumentFeature.java
+ *
+ * created: Thu Feb 17 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This is a Faeture that is owned by a DocumentEntry.
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ **/
+
+public interface DocumentFeature extends Feature {
+ /**
+ * Return the DocumentEntry that contains this DocumentFeature. A
+ * DocumentFeature will always be owned by a DocumentEntry.
+ **/
+ DocumentEntry getDocumentEntry ();
+}
diff --git a/uk/ac/sanger/artemis/io/EMBLObject.java b/uk/ac/sanger/artemis/io/EMBLObject.java
new file mode 100644
index 0000000..a5a3421
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/EMBLObject.java
@@ -0,0 +1,72 @@
+/* EMBLObject.java
+ *
+ * created: Tue Nov 17 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/EMBLObject.java,v 1.1 2004-06-09 09:49:08 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This class is the base class for objects in the embl package.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EMBLObject.java,v 1.1 2004-06-09 09:49:08 tjc Exp $
+ **/
+
+class EMBLObject
+{
+
+ /**
+ * An arbitrary data reference, set with a call to setUserData ().
+ **/
+ private Object user_data;
+
+ /**
+ * Create a new EMBLObject object.
+ **/
+ public EMBLObject()
+ {
+
+ }
+
+ /**
+ * Set the user data reference for this object. The user data object is
+ * chosen by the user of this class. This facility is provided to make it
+ * easier to wrap this class in more powerful classes.
+ * @param user_data The new value of the user data reference of this object.
+ * The existing user data reference (if any) is lost.
+ **/
+ public void setUserData(Object user_data)
+ {
+ this.user_data = user_data;
+ }
+
+ /**
+ * Return the user data reference for this object, as set by a call to
+ * setUserData().
+ **/
+ public Object getUserData()
+ {
+ return user_data;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/io/EmblDocumentEntry.java b/uk/ac/sanger/artemis/io/EmblDocumentEntry.java
new file mode 100644
index 0000000..ec30c9c
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/EmblDocumentEntry.java
@@ -0,0 +1,124 @@
+/* EmblDocumentEntry.java
+ *
+ * created: Sat Sep 11 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/EmblDocumentEntry.java,v 1.2 2009-03-06 12:12:24 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+import java.io.IOException;
+
+/**
+ * A DocumentEntry that can read an EMBL entry from a Document.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EmblDocumentEntry.java,v 1.2 2009-03-06 12:12:24 tjc Exp $
+ **/
+public class EmblDocumentEntry extends PublicDBDocumentEntry
+{
+ /**
+ * Create a new EmblDocumentEntry object associated with the given
+ * Document.
+ * @param document This is the file that we will read from. This is also
+ * used for saving the entry back to the file it came from and to give
+ * the new object a name.
+ * @param entry_information The EntryInformation object to use for the new
+ * Entry.
+ * @param listener The object that will listen for ReadEvents.
+ * @exception IOException thrown if there is a problem reading the entry -
+ * most likely ReadFormatException.
+ * @exception EntryInformationException Thrown if force is false and if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ public EmblDocumentEntry(final EntryInformation entry_information,
+ final Document document,
+ final ReadListener listener)
+ throws IOException, EntryInformationException
+ {
+ super(entry_information, document, listener);
+ }
+
+ /**
+ * Create a new EmblDocumentEntry that will be a copy of the given
+ * Entry and has no Document associated with it. The new
+ * EmblDocumentEntry cannot be saved to a file with save () unless save
+ * (Document) has been called first. The save (Document) method will
+ * assign a Document..
+ * @param entry_information The EntryInformation object for this Entry.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys in the new Entry will be quietly thrown away. "Invalid"
+ * means that the key/qualifier is not allowed to occur in an Entry of
+ * this type (probably determined by the EntryInformation object of this
+ * Entry). If false an EntryInformationException will be thrown for
+ * invalid keys or qualifiers.
+ * @exception EntryInformationException Thrown if force is false and if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ public EmblDocumentEntry(final EntryInformation entry_information,
+ final Entry new_entry, final boolean force)
+ throws EntryInformationException
+ {
+ super(entry_information, new_entry, force);
+
+ try
+ {
+ if(getHeaderText() == null &&
+ new_entry.getHeaderText() != null &&
+ new_entry.getHeaderText().startsWith("ID"))
+ setHeaderText(new_entry.getHeaderText());
+ }
+ catch (Exception e){}
+ }
+
+ /**
+ * Create a new EmblDocumentEntry that will be a copy of the given
+ * Entry and has no Document associated with it. The new
+ * EmblDocumentEntry cannot be saved to a file with save () unless save
+ * (Document) has been called first. The save (Document) method will
+ * assign a Document. The new DocumentEntry will use a copy of the
+ * EntryInformation object from the given Entry.
+ * @exception EntryInformationException Thrown if this Entry cannot contain
+ * the Key, Qualifier or Key/Qualifier combination of one of the features
+ * in the given Entry.
+ **/
+ public EmblDocumentEntry(final Entry new_entry)
+ throws EntryInformationException
+ {
+ super(new SimpleEntryInformation(new_entry.getEntryInformation ()),
+ new_entry, false);
+ }
+
+ /**
+ * Create a new empty EmblDocumentEntry object that has no Document
+ * associated with it. The new EmblDocumentEntry cannot be saved to a
+ * file with save () unless save (Document) has been called first. The
+ * save (Document) method will assign a Document.
+ * @param entry_information The EntryInformation object for this Entry.
+ **/
+ public EmblDocumentEntry(final EntryInformation entry_information)
+ {
+ super(entry_information);
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/EmblMisc.java b/uk/ac/sanger/artemis/io/EmblMisc.java
new file mode 100644
index 0000000..4249295
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/EmblMisc.java
@@ -0,0 +1,57 @@
+/* EmblMisc.java
+ *
+ * created: Sun Sep 12 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/EmblMisc.java,v 1.1 2004-06-09 09:49:10 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+
+/**
+ * This class is used to store EMBL entry lines that are not handled by other
+ * classes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EmblMisc.java,v 1.1 2004-06-09 09:49:10 tjc Exp $
+ **/
+
+public class EmblMisc extends MiscLineGroup {
+ /**
+ * Create a new EmblMisc object. One line is read from the in_stream
+ * and stored.
+ **/
+ public EmblMisc (final LinePushBackReader in_stream)
+ throws IOException {
+ super (in_stream);
+ }
+
+ /**
+ * Create a new EmblMisc object that consists of the given String
+ * (which should not be terminated with a newline).
+ **/
+ public EmblMisc (final String line) {
+ super (line);
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/EmblStreamFeature.java b/uk/ac/sanger/artemis/io/EmblStreamFeature.java
new file mode 100644
index 0000000..c6ee373
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/EmblStreamFeature.java
@@ -0,0 +1,111 @@
+/* EmblStreamFeature.java
+ *
+ * created: Mon Sep 13 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/EmblStreamFeature.java,v 1.1 2004-06-09 09:49:11 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.*;
+
+/**
+ * A StreamFeature that thinks it is a EMBL feature.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EmblStreamFeature.java,v 1.1 2004-06-09 09:49:11 tjc Exp $
+ **/
+
+public class EmblStreamFeature extends PublicDBStreamFeature {
+ /**
+ * Create a new EmblStreamFeature object.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ **/
+ public EmblStreamFeature (Key key,
+ Location location,
+ QualifierVector qualifiers)
+ throws InvalidRelationException {
+ super (key, location, qualifiers);
+ }
+
+ /**
+ * Create a new EmblStreamFeature with the same key, location and
+ * qualifiers as the given feature. The feature should be added to an
+ * Entry (with Entry.add ()).
+ * @param feature The feature to copy.
+ **/
+ public EmblStreamFeature (final Feature feature) {
+ super (feature);
+ }
+
+ /**
+ * Create a new, blank EMBL feature. It will have no qualifiers, a key of
+ * CDS and a location of 1.
+ **/
+ public EmblStreamFeature () {
+ super (makeBlankFeature ());
+ }
+
+ /**
+ * Called by the constructor.
+ **/
+ private static EmblStreamFeature makeBlankFeature () {
+ try {
+ return new EmblStreamFeature (Key.CDS, new Location ("1"),
+ new QualifierVector ());
+ } catch (InvalidRelationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (LocationParseException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Read and return a EmblStreamFeature from a stream. A feature must be
+ * the next thing in the stream.
+ * @param stream the Feature is read from this stream
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException.
+ * @return null if in_stream is at the end of file when the method is called
+ *
+ **/
+ public static EmblStreamFeature
+ readFromStream (final LinePushBackReader in_stream)
+ throws IOException {
+ return (EmblStreamFeature) readFromStream (in_stream,
+ LineGroup.EMBL_FEATURE);
+ }
+
+ /**
+ * Return the reference of a new copy of this Feature.
+ **/
+ public Feature copy () {
+ final Feature return_value = new EmblStreamFeature (this);
+
+ return return_value;
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/EmblStreamSequence.java b/uk/ac/sanger/artemis/io/EmblStreamSequence.java
new file mode 100644
index 0000000..03b663c
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/EmblStreamSequence.java
@@ -0,0 +1,321 @@
+/* EmblStreamSequence.java
+ *
+ * created: Mon Jun 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/EmblStreamSequence.java,v 1.6 2006-01-10 10:09:07 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This is a subclass of StreamSequence containing EMBL sequence.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EmblStreamSequence.java,v 1.6 2006-01-10 10:09:07 tjc Exp $
+ **/
+
+public class EmblStreamSequence extends StreamSequence
+{
+
+ /** The header line(s) of this sequence(set by setHeader()). */
+ private String header_line = null;
+
+ /**
+ * Create a new EmblStreamSequence object from a stream. The next line to
+ * read from the stream should be the header line of the sequence.
+ * @param in_stream The stream to read from. When the constructor returns
+ * the stream will at the next line after the sequence.
+ **/
+ public EmblStreamSequence(final LinePushBackReader in_stream)
+ throws IOException
+ {
+ readHeader(in_stream);
+ readSequence(in_stream);
+ }
+
+ /**
+ * Make a new EmblStreamSequence containing the same sequence as the given
+ * Sequence.
+ **/
+ public EmblStreamSequence(final Sequence sequence)
+ {
+ setFromChar(((StreamSequence)sequence).getCharSequence());
+ }
+
+ /**
+ * Make a new EmblStreamSequence containing the same sequence as the given
+ * String.
+ * @param sequence_string The String containing the sequence for the new
+ * EmblStreamSequence.
+ **/
+ public EmblStreamSequence(final String sequence_string)
+ {
+ setFromChar(sequence_string.toCharArray());
+ }
+
+ /**
+ * Return a new StreamSequence object that is a copy of this one.
+ **/
+ public StreamSequence copy()
+ {
+ return new EmblStreamSequence(this);
+ }
+
+ /**
+ * Return the sequence type(EMBL_FORMAT for this class).
+ **/
+ public int getFormatType()
+ {
+ return StreamSequenceFactory.EMBL_FORMAT;
+ }
+
+ /**
+ * Read the header for this sequence(if any).
+ **/
+ protected void readHeader(final LinePushBackReader in_stream)
+ throws IOException
+ {
+ final String seq_header_line = in_stream.readLine();
+ setHeader(seq_header_line);
+ }
+
+ /**
+ * Return the header line(s) of this Sequence or null if there is no
+ * header. The returned String will not end in a newline character.
+ **/
+ public String getHeader()
+ {
+ return header_line;
+ }
+
+ /**
+ * Set the header line(s) of this Sequence. The argument should not end in
+ * a newline character.
+ **/
+ public void setHeader(final String sequence_string)
+ {
+ header_line = sequence_string;
+ }
+
+ /**
+ * This method will read EMBL sequence from the stream in this object
+ * removing whitespace as it goes.
+ **/
+ protected void readSequence(final LinePushBackReader in_stream)
+ throws IOException
+ {
+
+// long startT = System.currentTimeMillis();
+ final StringBuffer this_line_sequence_buffer = new StringBuffer(81);
+ final String seq_header_line = getHeader();
+ final int header_base_count = getHeaderBaseCount(seq_header_line);
+
+ final int buffer_capacity;
+
+ if(header_base_count > 50000)
+ {
+ // add 100 for good luck
+ buffer_capacity = header_base_count + 100;
+ }
+ else
+ {
+ // try to pick an initial that covers a large number of cases
+ buffer_capacity = 50000;
+ }
+
+ // we buffer up the sequence bases then assign them to sequence once all
+ // bases are read
+ setSequencePackingCapacity(buffer_capacity);
+
+ String line;
+ String this_line_sequence;
+ char[] char_array = new char[60];
+
+ while((line = in_stream.readLine()) != null)
+ {
+ if(line.startsWith("//") || !line.startsWith(" "))
+ {
+ // end of Sequence -
+ in_stream.pushBack(line);
+ break;
+ }
+
+ if(line.length() < 72)
+ throw new ReadFormatException("line too short while reading " +
+ "embl sequence data",
+ in_stream.getLineNumber());
+
+ this_line_sequence_buffer.setLength(0);
+
+ // if the input file is a well formatted embl entry, then the bases
+ // should be in 6 columns of 10 bases each (with a single space
+ // separating them). The first column starts at index 5 of the input
+ // line.
+
+ line = line.toLowerCase();
+ int nbase = 0;
+ for(int i = 0 ; i < 6 ; ++i)
+ {
+ final int first_base = 5 + i * 11;
+ for(int j=first_base; j<first_base+10; j++)
+ {
+ char c = line.charAt(j);
+ char_array[nbase] = c;
+ nbase++;
+ }
+ }
+
+ if(Character.isSpaceChar(char_array[59]))
+ {
+ int j = 0;
+ for(j=0; j<60; j++)
+ {
+ if(Character.isSpaceChar(char_array[j]))
+ break;
+ }
+ appendChar((new String(char_array,0,j)).toCharArray());
+ }
+ else
+ appendChar(char_array);
+ }
+
+ setCounts();
+// long endT = System.currentTimeMillis() - startT;
+// System.out.println(endT);
+ }
+
+ /**
+ * Write this Sequence to the given stream.
+ * @param writer The stream to write to.
+ **/
+ public synchronized void writeToStream(final Writer writer)
+ throws IOException
+ {
+
+ final String sequence = toString();
+
+ // first count A,C,G,T and other bases
+
+ final int SEQUENCE_LINE_BASE_COUNT = 60;
+
+ if(header_line != null)
+ writer.write(header_line);
+
+ if(header_line == null || !header_line.startsWith("SQ "))
+ writer.write("SQ Sequence " +
+ length() + " BP; " +
+ getACount() + " A; " +
+ getCCount() + " C; " +
+ getGCount() + " G; " +
+ getTCount() + " T; " +
+ getOtherCount() + " other;\n");
+ else
+ writer.write("\n");
+
+ int line_length_so_far = 0;
+ final int BLOCK_LENGTH = 10;
+
+ for(int i = 0 ; i < length() ; i += SEQUENCE_LINE_BASE_COUNT)
+ {
+ // get the bases in chunks of at most 60
+ final int this_line_length;
+
+ if(length() - i < SEQUENCE_LINE_BASE_COUNT)
+ this_line_length = length() - i;
+ else
+ this_line_length = SEQUENCE_LINE_BASE_COUNT;
+
+ writer.write(" ");
+ line_length_so_far += 4;
+
+ for(int j = 0 ; j < this_line_length ; j += BLOCK_LENGTH)
+ {
+ final int this_block_length;
+ writer.write(' ');
+
+ if(this_line_length - j < BLOCK_LENGTH)
+ {
+ this_block_length = this_line_length - j;
+ forceReset();
+ }
+ else
+ this_block_length = BLOCK_LENGTH;
+
+ writer.write(getCharSubSequence(i + j + 1, i + j + this_block_length));
+ line_length_so_far += this_block_length + 1;
+ }
+
+ // the base counter to write at the end of each line
+ final int base_count = i + this_line_length;
+ final String string_base_count = String.valueOf(base_count);
+
+ // now pad the line with spaces
+ final int count_width = string_base_count.length();
+ final int LINE_WIDTH = 80;
+
+ for(int char_index = 0 ;
+ char_index < LINE_WIDTH - count_width - line_length_so_far ;
+ ++char_index)
+ writer.write(' ');
+
+ writer.write(string_base_count);
+ line_length_so_far = 0;
+ writer.write("\n");
+ }
+ }
+
+ /**
+ * Take a EMBL sequence header line and extract and return the base count.
+ * If the line cannot be parsed as a header line then -1 is returned.
+ **/
+ private int getHeaderBaseCount(final String line)
+ {
+
+ if(line.startsWith("SQ Sequence "))
+ {
+ final String temp_line = line.substring(14);
+ final int space_index = temp_line.indexOf(' ');
+
+ if(space_index == -1)
+ return -1;
+
+ final String count_string = temp_line.substring(0, space_index);
+
+ try
+ {
+ return Integer.parseInt(count_string);
+ }
+ catch(NumberFormatException e)
+ {
+ return -1;
+ }
+ }
+ else
+ return -1;
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/io/Entry.java b/uk/ac/sanger/artemis/io/Entry.java
new file mode 100644
index 0000000..0d8a806
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/Entry.java
@@ -0,0 +1,222 @@
+/* Entry.java
+ *
+ * created: Mon Oct 12 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/Entry.java,v 1.2 2008-06-11 15:12:20 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+import java.io.*;
+
+/**
+ * Objects of this class represent one EMBL entry.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Entry.java,v 1.2 2008-06-11 15:12:20 tjc Exp $
+ *
+ **/
+
+public interface Entry
+{
+ /**
+ * Write this entry to the file/database/whatever it came from. If this
+ * object is read only a ReadOnlyException will be thrown.
+ **/
+ void save() throws IOException;
+
+ /**
+ * Returns true if and only if there have been some changes to this Entry
+ * since the last save.
+ **/
+ boolean hasUnsavedChanges();
+
+ /**
+ * Returns true if and only if this entry is read only.
+ **/
+ boolean isReadOnly();
+
+ /**
+ * Return the text of the EMBL header of this Entry or null if there is no
+ * header.
+ **/
+ String getHeaderText();
+
+ /**
+ * Set the header of this Entry to be the given text.
+ * @return true if and only if the header was successfully set. Not all
+ * Entry objects can change their header, so it is up to the calling
+ * function to check the return value.
+ * @exception IOException thrown if there is a problem reading the header
+ * from the String - most likely ReadFormatException.
+ **/
+ boolean setHeaderText (final String new_header)
+ throws IOException;
+
+ /**
+ * Return the name of this Entry.
+ **/
+ String getName();
+
+ /**
+ * Set the name of this Entry - if possible (the return value will let the
+ * caller know).
+ * @return true if and only if the name was successfully set. Not all
+ * Entry objects can change their name, so it is up to the calling
+ * function to check the return value.
+ **/
+ boolean setName(final String name);
+
+ /**
+ * Create a new Feature object of an appropriate type in this Entry.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature (can be null if
+ * there are no qualifiers).
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ * @exception EntryInformationException Thrown if a Feature in this Entry
+ * cannot contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ Feature createFeature(Key key,
+ Location location,
+ QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException, OutOfRangeException;
+
+ /**
+ * Return a count of the number of Feature objects in this Entry.
+ **/
+ int getFeatureCount();
+
+ /**
+ * Add the given Feature to this Entry. If the Feature is already in an
+ * Entry then Entry.remove () should be called on that Entry before calling
+ * Entry.add (). An Error will be thrown otherwise.
+ * @exception ReadOnlyException If this entry is read only.
+ * @exception EntryInformationException Thrown if this Entry
+ * cannot contain the Key, Qualifier or Key/Qualifier combination of the
+ * given Feature.
+ * @return A reference that was passed to add (), if that Feature can be
+ * stored directly in this Entry, otherwise returns a reference to a new
+ * Feature, that is a copy of the argument. The argument reference
+ * should not be used after the call to add (), unless the return
+ * reference happens to be the same as the argument.
+ **/
+ Feature add(Feature feature)
+ throws EntryInformationException, ReadOnlyException;
+
+ /**
+ * Add the given Feature to this Entry. If the Feature is already in an
+ * Entry then Entry.remove () should be called on that Entry before calling
+ * Entry.add (). An Error will be thrown otherwise. Invalid qualifiers
+ * will be quietly thrown away. Features with invalid keys will not be
+ * added (and null will be returned). "Invalid" means that the
+ * key/qualifier is non allowed to occur in an Entry of this type (probably
+ * determined by the EntryInformation object of this Entry).
+ * @exception ReadOnlyException If this entry is read only.
+ * @exception EntryInformationException Thrown if this Entry
+ * cannot contain the Key, Qualifier or Key/Qualifier combination of the
+ * given Feature
+ * @return A reference that was passed to add (), if that Feature can be
+ * stored directly in this Entry, otherwise returns a reference to a new
+ * Feature, that is a copy of the argument. The argument reference
+ * should not be used after the call to add (), unless the return
+ * reference happens to be the same as the argument. Returns null if and
+ * only if the new Feature has a key that is invalid for this Entry.
+ **/
+ Feature forcedAdd(Feature feature)
+ throws ReadOnlyException;
+
+ /**
+ * Remove the given Feature from this Entry.
+ * @return true if and only if the Feature was in this Entry.
+ * @exception ReadOnlyException If this entry is read only.
+ **/
+ boolean remove(Feature feature)
+ throws ReadOnlyException;
+
+ /**
+ * Return the ith Feature from this Entry. The features are returned in a
+ * consistent order, sorted by the first base of each Feature.
+ **/
+ Feature getFeatureAtIndex (int i);
+
+ /**
+ * Return the index of the given Feature. This does the reverse of
+ * getFeatureAtIndex ().
+ **/
+ int indexOf(Feature feature);
+
+ /**
+ * Returns true if and only if this Entry contains the given feature.
+ **/
+ boolean contains(Feature feature);
+
+ /**
+ * Returns an enumeration of the Feature objects in this Entry. The
+ * returned Enumeration object will generate all features in this object in
+ * turn. The first item generated is the item at index 0, then the item at
+ * index 1, and so on.
+ **/
+ FeatureEnumeration features();
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ FeatureVector getFeaturesInRange(Range range)
+ throws OutOfRangeException;
+
+ /**
+ * Return a vector containing the references of all the Feature objects in
+ * this Entry.
+ * @return The features of this Entry. The returned object
+ * is a copy - changes will not effect the Entry object itself.
+ **/
+ FeatureVector getAllFeatures();
+
+ /**
+ * Return the Sequence object from this entry or null if it does not
+ * contain one.
+ * @return a Sequence object for this Entry. the returned object is
+ * not a copy - changes to it will change the Entry object itself
+ **/
+ Sequence getSequence();
+
+ /**
+ * Return the EntryInformation object for this Entry.
+ **/
+ EntryInformation getEntryInformation();
+
+ /**
+ * Dispose of entry objects
+ */
+ void dispose();
+}
diff --git a/uk/ac/sanger/artemis/io/EntryInformation.java b/uk/ac/sanger/artemis/io/EntryInformation.java
new file mode 100644
index 0000000..1c9549e
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/EntryInformation.java
@@ -0,0 +1,150 @@
+/* EntryInformation.java
+ *
+ * created: Tue Jan 5 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/EntryInformation.java,v 1.3 2008-01-24 14:15:30 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+/**
+ * This interface contains methods that give information about the possible
+ * keys and qualifiers of an entry.
+ *
+ * @author Kim Rutherford
+ * @version $Id: EntryInformation.java,v 1.3 2008-01-24 14:15:30 tjc Exp $
+ **/
+
+public interface EntryInformation {
+ /**
+ * Add a new feature key to the list of keys returned by getValidKeys ()
+ * and getSortedValidKeys ().
+ **/
+ void addKey (final Key key);
+
+ /**
+ * Add a QualifierInfo object to this EntryInformation object. This will
+ * change the return values of getSortedValidKeys() and
+ * getValidKeys() if the new QualifierInfo object refers to a new key.
+ * @exception QualifierInfoException Thrown if a QualifierInfo object with
+ * the same name, but conflicting types has already been added to this
+ * EntryInformation object.
+ **/
+ void addQualifierInfo (final QualifierInfo qualifier_info)
+ throws QualifierInfoException;
+
+ /**
+ * Return a vector containing the valid feature keys. The returned
+ * Vector is a copy.
+ * @return null if and only if all keys are valid.
+ **/
+ KeyVector getValidKeys ();
+
+ /**
+ * Return a alphanumerically sorted vector of the valid keys.
+ * @return null if and only if all keys are valid.
+ **/
+ KeyVector getSortedValidKeys ();
+
+ /**
+ * Returns the keys that where added with addKey ()
+ **/
+ KeyVector getUserKeys ();
+
+ /**
+ * Return the Key that should be used when a new (empty) feature is created.
+ **/
+ Key getDefaultKey ();
+
+ /**
+ * Return a vector containing all valid qualifiers for a feature with
+ * the given key. The returned StringVector is a copy.
+ **/
+ StringVector getValidQualifierNames (final Key key);
+
+ /**
+ * Return a vector containing the required feature qualifiers for the given
+ * key.
+ **/
+ StringVector getRequiredQualifiers (final Key key);
+
+ /**
+ * Return true if and only if the given String contains a valid
+ * qualifier name.
+ **/
+ boolean isValidQualifier (final String name);
+
+ /**
+ * Return true if and only if given qualifier_name is a legal qualifier
+ * name for the given key.
+ **/
+ boolean isValidQualifier (final Key key,
+ final String qualifier_name);
+
+ /**
+ * Return true if and only if given qualifier_name is a legal qualifier
+ * name for the given key and must be present in a feature with that key.
+ **/
+ boolean isRequiredQualifier (final Key key,
+ final String qualifier_name);
+
+ /**
+ * Return true if and only if given Key is a valid.
+ **/
+ boolean isValidKey (final Key key);
+
+ /**
+ * Return the QualifierInfo object for the given qualifier name, or null if
+ * there are no legal qualifiers with the given name.
+ **/
+ QualifierInfo getQualifierInfo (final String qualifier_name);
+
+ /**
+ * Return a Vector contains all the QualifierInfo objects that this
+ * EntryInformation knows about.
+ **/
+ QualifierInfoHash getAllQualifierInfo ();
+
+ /**
+ * Returns true if strict EMBL format will be used when writing. If true
+ * qualifiers will always wrap at 80 columns.
+ * EMBL format locations have the complement (if any) inside the join. eg:
+ * join(complement(1..100),complement(200..300)).
+ * The default format has the complement on the outside, which is much more
+ * readable. eg: complement(join(1..100,200..300))
+ **/
+ boolean useEMBLFormat ();
+
+ /**
+ * Set the use_embl_format flag (see useEMBLFormat ()). true means the
+ * strict EMBL format should be used when writing.
+ **/
+ void setEMBLFormat (final boolean use_embl_format);
+
+ /**
+ * Fix this EntryInformation so that the given exception won't happen
+ * again.
+ **/
+ void fixException (final EntryInformationException exception);
+}
diff --git a/uk/ac/sanger/artemis/io/EntryInformationException.java b/uk/ac/sanger/artemis/io/EntryInformationException.java
new file mode 100644
index 0000000..c1b9197
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/EntryInformationException.java
@@ -0,0 +1,48 @@
+/* EntryInformationException.java
+ *
+ * created: Fri Feb 11 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/EntryInformationException.java,v 1.1 2004-06-09 09:49:17 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This exception is thrown when a Key, Qualifier or Key/Qualifier
+ * combination is used in a way that is not allowed in the current
+ * EntryInformation environment. This could happen if a required qualifier
+ * is removed.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: EntryInformationException.java,v 1.1 2004-06-09 09:49:17 tjc Exp $
+ **/
+
+public class EntryInformationException extends Exception {
+ /**
+ * Create a new EntryInformationException object with the given String as
+ * the message.
+ * @param message the detail message
+ **/
+ public EntryInformationException (final String message) {
+ super (message);
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/io/EntryStreamEvent.java b/uk/ac/sanger/artemis/io/EntryStreamEvent.java
new file mode 100644
index 0000000..a96499a
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/EntryStreamEvent.java
@@ -0,0 +1,42 @@
+/* EntryStreamEvent.java
+ *
+ * created: Fri Aug 15 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/EntryStreamEvent.java,v 1.1 2004-06-09 09:49:18 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * EntryStreamEvent class
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: EntryStreamEvent.java,v 1.1 2004-06-09 09:49:18 tjc Exp $
+ **/
+
+public class EntryStreamEvent {
+ /**
+ *
+ **/
+ public EntryStreamEvent () {
+
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/FastaStreamSequence.java b/uk/ac/sanger/artemis/io/FastaStreamSequence.java
new file mode 100644
index 0000000..5e8c159
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/FastaStreamSequence.java
@@ -0,0 +1,126 @@
+/* FastaStreamSequence.java
+ *
+ * created: Mon Jun 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/FastaStreamSequence.java,v 1.1 2004-06-09 09:49:19 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This is a subclass of StreamSequence containing Fasta sequence.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FastaStreamSequence.java,v 1.1 2004-06-09 09:49:19 tjc Exp $
+ **/
+
+public class FastaStreamSequence extends RawStreamSequence {
+ /**
+ * Create a new FastaStreamSequence object from a stream. The next line to
+ * read from the stream should be the header line of the sequence.
+ * @param in_stream The stream to read from. When the constructor returns
+ * the stream will at the next line after the sequence.
+ **/
+ public FastaStreamSequence (final LinePushBackReader in_stream)
+ throws IOException {
+ super (in_stream);
+ }
+
+ /**
+ * Make a new FastaStreamSequence containing the same sequence as the given
+ * Sequence. The header will be ">artemis_sequence" unless the Sequence is
+ * a FastaStreamSequence.
+ **/
+ public FastaStreamSequence (final Sequence sequence) {
+ super (sequence);
+ }
+
+ /**
+ * Make a new FastaStreamSequence containing the same sequence as the given
+ * String.
+ * @param sequence_string The String containing the sequence for the new
+ * FastaStreamSequence.
+ * @param header The header to use for the new object.
+ **/
+ public FastaStreamSequence (final String sequence_string,
+ final String header) {
+ super (sequence_string);
+ this.header = header;
+ }
+
+ /**
+ * Make a new FastaStreamSequence containing the same sequence as the given
+ * String. The header will be ">"
+ * @param sequence_string The String containing the sequence for the new
+ * FastaStreamSequence.
+ **/
+ public FastaStreamSequence (final String sequence_string) {
+ this (sequence_string, "");
+ }
+
+ /**
+ * Return a new StreamSequence object that is a copy of this one.
+ **/
+ public StreamSequence copy () {
+ return new FastaStreamSequence (this);
+ }
+
+ /**
+ * Return the sequence type (EMBL_FORMAT for this class).
+ **/
+ public int getFormatType () {
+ return StreamSequenceFactory.FASTA_FORMAT;
+ }
+
+ /**
+ * Write this Sequence to the given stream. The output will be in FASTA
+ * format.
+ * @param writer The stream to write to.
+ **/
+ public void writeToStream (final Writer writer)
+ throws IOException {
+ if (getHeader () != null) {
+ writer.write (">" + getHeader () + "\n");
+ }
+
+ super.writeToStream (writer);
+ }
+
+ /**
+ * Return the header that was passed to the FastaStreamSequence (final
+ * String sequence_string, final String header) constructor.
+ **/
+ private String getHeader () {
+ return header;
+ }
+
+ /**
+ * The header to use in writeToStream() if the FastaStreamSequence was
+ * created with the (final String sequence_string, final String header)
+ * constructor.
+ **/
+ private String header = null;
+}
diff --git a/uk/ac/sanger/artemis/io/Feature.java b/uk/ac/sanger/artemis/io/Feature.java
new file mode 100644
index 0000000..1df2419
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/Feature.java
@@ -0,0 +1,176 @@
+/* Feature.java
+ *
+ * created: Mon Oct 12 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/Feature.java,v 1.2 2005-11-28 16:46:38 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+/**
+ * Feature interface
+ *
+ * @author Kim Rutherford
+ * @version $Id: Feature.java,v 1.2 2005-11-28 16:46:38 tjc Exp $
+ *
+ */
+
+public interface Feature {
+ /**
+ * Set the value of this object.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception EntryInformationException Thrown if this Feature cannot contain
+ * the given Key, Qualifier or Key/Qualifier combination.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * any of the given Qualifier objects.
+ **/
+ void set (final Key key,
+ final Location location,
+ final QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException, OutOfRangeException;
+
+ /**
+ * Set the key field of this object.
+ * @param key The new feature key
+ * @exception InvalidKeyException Thrown if the given Key is not a valid
+ * Key for this type of feature.
+ * @exception EntryInformationException Thrown if this Feature cannot contain
+ * the given Key, Qualifier or Key/Qualifier combination.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * any of the given Qualifier objects.
+ **/
+ void setKey (final Key key)
+ throws EntryInformationException, ReadOnlyException;
+
+ /**
+ * Set the location of this object.
+ * @param location The Location object for the new feature
+ **/
+ void setLocation (final Location location)
+ throws OutOfRangeException, ReadOnlyException;
+
+ void setLocation (final Location location, Entry saved_entry)
+ throws OutOfRangeException, ReadOnlyException;
+
+ /**
+ * Set the qualifiers of this object discarding all the current qualifiers.
+ * @param qualifiers The qualifiers for the new feature
+ * @exception EntryInformationException Thrown if this Feature cannot contain
+ * the given Key, Qualifier or Key/Qualifier combination.
+ * @exception InvalidRelationException Throw if this Feature cannot contain
+ * any of the given Qualifier objects.
+ **/
+ void setQualifiers (final QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException;
+
+ /**
+ * Add the given Qualifier to this Feature. If this Feature contains a
+ * Qualifier with the same name as the new Qualifier it will be replaced.
+ * @param qualifier The new qualifier to add.
+ * @exception EntryInformationException Thrown if this Feature cannot contain
+ * the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ void setQualifier (final Qualifier qualifier)
+ throws EntryInformationException, ReadOnlyException;
+
+ /**
+ * Remove the Qualifier with the given name. If there is no Qualifier with
+ * that name then return immediately.
+ * @exception EntryInformationException Thrown if this Feature cannot contain
+ * the given Key, Qualifier or Key/Qualifier combination.
+ * @param name The qualifier name to look for.
+ **/
+ void removeQualifierByName (final String name)
+ throws EntryInformationException, ReadOnlyException;
+
+ /**
+ * Return the key of this feature, as passed to the constructor.
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ Key getKey ();
+
+ /**
+ * Return the Location of this Feature, as passed to the constructor.
+ **/
+ Location getLocation ();
+
+ /**
+ * Return a QualifierVector object containing the qualifiers for this
+ * feature. This method does not return a copy of the qualifier vector so
+ * changing the vector will change the feature.
+ **/
+ QualifierVector getQualifiers ();
+
+ /**
+ * Return a copy of the Qualifier in this Feature with the given name or
+ * null if there no such Qualifier.
+ **/
+ Qualifier getQualifierByName (final String name)
+ throws InvalidRelationException;
+
+ /**
+ * Return the first base of this feature.
+ **/
+ int getFirstBase ();
+
+ /**
+ * Return the last base of this feature.
+ **/
+ int getLastBase ();
+
+ /**
+ * Return the Entry object that contains this Feature.
+ **/
+ Entry getEntry ();
+
+ /**
+ * Return the reference of a new copy of this Feature. The new Feature
+ * will not be in any Entry, so it should be explicitly added with a call
+ * to Entry.add ().
+ **/
+ Feature copy ();
+
+ /**
+ * Set the user data reference for this object. The user data object is
+ * chosen by the user of this class. This facility is provided to make it
+ * easier to wrap this class in more powerful classes.
+ * @param user_data The new value of the user data reference of this object.
+ * The existing user data reference (if any) is lost.
+ **/
+ void setUserData (final Object user_data);
+
+ /**
+ * Return the user data reference for this object, as set by a call to
+ * setUserData ().
+ **/
+ Object getUserData ();
+
+ /**
+ * Returns true if and only if this Feature can't be changed or can't be
+ * removed from it's entry.
+ **/
+ boolean isReadOnly ();
+}
diff --git a/uk/ac/sanger/artemis/io/FeatureComparator.java b/uk/ac/sanger/artemis/io/FeatureComparator.java
new file mode 100644
index 0000000..4c8f0df
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/FeatureComparator.java
@@ -0,0 +1,173 @@
+/* FeatureComparator.java
+ *
+ * created: Tue Sep 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/FeatureComparator.java,v 1.2 2008-09-08 10:36:30 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.Comparator;
+
+/**
+ * Objects of this class implement the Comparator interface for
+ * ComparableFeature objects. The features are ordered by first base, then
+ * last base then using ComparableFeature.getNumericID (). This is an
+ * example ordering: 1..100, 1..200, 50..100, 150..250.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureComparator.java,v 1.2 2008-09-08 10:36:30 tjc Exp $
+ **/
+
+public class FeatureComparator implements Comparator {
+ /**
+ * Compare two ComparableFeature Objects with respect to ordering.
+ *
+ * @param fst first argument
+ * @param snd second argument
+ * @return a negative number if first is less than second; a
+ * positive number if first is greater than second; else 0
+ **/
+ public int compare (final Object first, final Object second) {
+ if (first instanceof Integer && second instanceof Integer) {
+ final int first_integer = ((Integer)first).intValue ();
+ final int second_integer = ((Integer)second).intValue ();
+
+ if (first_integer < second_integer) {
+ return -1;
+ } else {
+ if (first_integer > second_integer) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ // this nonsense exists so that FeatureTree.findByBase() will work
+ if (first instanceof Integer) {
+ final int first_integer = ((Integer)first).intValue ();
+
+ final ComparableFeature second_feature = (ComparableFeature) second;
+ final int second_feature_first_base = second_feature.getFirstBase ();
+
+ if (first_integer < second_feature_first_base) {
+ return -1;
+ } else {
+ if (first_integer > second_feature_first_base) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ if (second instanceof Integer) {
+ final int second_integer = ((Integer)second).intValue ();
+
+ final ComparableFeature first_feature = (ComparableFeature) first;
+ final int first_feature_first_base = first_feature.getFirstBase ();
+
+ if (first_feature_first_base < second_integer) {
+ return -1;
+ } else {
+ if (first_feature_first_base > second_integer) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ final ComparableFeature first_feature = (ComparableFeature) first;
+ final ComparableFeature second_feature = (ComparableFeature) second;
+
+ final int first_feature_first_base = first_feature.getFirstBase ();
+ final int second_feature_first_base = second_feature.getFirstBase ();
+
+ final int first_feature_last_base = first_feature.getLastBase ();
+ final int second_feature_last_base = second_feature.getLastBase ();
+
+ if (first_feature_first_base < second_feature_first_base) {
+ return -1;
+ }
+
+ if (first_feature_first_base > second_feature_first_base) {
+ return 1;
+ }
+
+ final int index_of_first =
+ indexOfKey (first_feature.getKey ());
+ final int index_of_second =
+ indexOfKey (second_feature.getKey ());
+
+ if (index_of_first != index_of_second) {
+ if (index_of_first < index_of_second) {
+ return -1;
+ } else {
+ if (index_of_first > index_of_second) {
+ return 1;
+ }
+ }
+ } else {
+ // fall through
+ }
+
+ if (first_feature_last_base < second_feature_last_base) {
+ return -1;
+ }
+
+ if (first_feature_last_base > second_feature_last_base) {
+ return 1;
+ }
+
+ // we use the id counter as a tie breaker so that compare () will only
+ // return 0 if the two ComparableFeature objects are the same
+ if (first_feature.getNumericID () < second_feature.getNumericID ()) {
+ return -1;
+ } else {
+ if (first_feature.getNumericID () > second_feature.getNumericID ()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Return the index of the given Key in the keys_to_sort_first array.
+ **/
+ private int indexOfKey (final Key key) {
+ for (int i = 0 ; i < keys_to_sort_first.length ; ++i) {
+ if (keys_to_sort_first[i].equals (key.toString ())) {
+ return i;
+ }
+ }
+
+ return keys_to_sort_first.length;
+ }
+
+ final private String keys_to_sort_first[] = {
+ "source",
+ "gene",
+ "CDS"
+ };
+}
diff --git a/uk/ac/sanger/artemis/io/FeatureEnumeration.java b/uk/ac/sanger/artemis/io/FeatureEnumeration.java
new file mode 100644
index 0000000..e54d3a7
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/FeatureEnumeration.java
@@ -0,0 +1,56 @@
+/* FeatureEnumeration.java
+ *
+ * created: Sun Jan 31 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/FeatureEnumeration.java,v 1.1 2004-06-09 09:49:23 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.NoSuchElementException;
+
+/**
+ * An object that implements the FeatureEnumeration interface generates a
+ * series of Feature objects, one at a time. Successive calls to the
+ * nextFeature method return successive elements of the series.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureEnumeration.java,v 1.1 2004-06-09 09:49:23 tjc Exp $
+ *
+ **/
+
+public interface FeatureEnumeration {
+ /**
+ * Tests if this enumeration contains more features.
+ * @return true if and only if this enumeration object contains at least
+ * one more feature to provide; false otherwise.
+ **/
+ boolean hasMoreFeatures ();
+
+ /**
+ * Returns the next Feature object of this enumeration if this enumeration
+ * object has at least one more element to provide.
+ * @return The next Feature of this enumeration.
+ * @exception NoSuchElementException If no more elements exist.
+ **/
+ Feature nextFeature ()
+ throws NoSuchElementException;
+}
diff --git a/uk/ac/sanger/artemis/io/FeatureHeader.java b/uk/ac/sanger/artemis/io/FeatureHeader.java
new file mode 100644
index 0000000..cc53ccd
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/FeatureHeader.java
@@ -0,0 +1,74 @@
+/* FeatureHeader.java
+ *
+ * created: Thu Jul 20 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/FeatureHeader.java,v 1.1 2004-06-09 09:49:24 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+
+/**
+ * Class used to store FH lines.
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: FeatureHeader.java,v 1.1 2004-06-09 09:49:24 tjc Exp $
+ **/
+
+public class FeatureHeader extends EmblMisc {
+ /**
+ * Create a new FeatureHeader object by reading the current "FH" line and
+ * the ones directly after it.
+ **/
+ public FeatureHeader (final LinePushBackReader in_stream)
+ throws IOException {
+ super (getLines (in_stream));
+ }
+
+ /**
+ * Read the next "FH" lines from the stream.
+ **/
+ private static String getLines (final LinePushBackReader in_stream)
+ throws IOException {
+ final StringBuilder buffer = new StringBuilder ();
+ while (true) {
+ final String this_line = in_stream.readLine ();
+
+ if (this_line == null) {
+ break;
+ }
+
+ if (getLineType (this_line) == EMBL_FEATURE_HEADER) {
+ if (buffer.length () != 0) {
+ buffer.append ("\n");
+ }
+ buffer.append (this_line);
+ } else {
+ in_stream.pushBack (this_line);
+ break;
+ }
+ }
+
+ return buffer.toString ();
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/FeatureTable.java b/uk/ac/sanger/artemis/io/FeatureTable.java
new file mode 100644
index 0000000..a4fd875
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/FeatureTable.java
@@ -0,0 +1,233 @@
+/* FeatureTable.java
+ *
+ * created: Mon Oct 12 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/FeatureTable.java,v 1.2 2009-06-01 09:45:32 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This object contains all the features in an entry.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureTable.java,v 1.2 2009-06-01 09:45:32 tjc Exp $
+ */
+
+abstract class FeatureTable extends LineGroup {
+ /**
+ * Create a new empty FeatureTable object.
+ **/
+ FeatureTable () {
+
+ }
+
+ /**
+ * Return the feature tree from this feature table.
+ * @return The features of this feature table. The returned
+ * object is not a copy - changes to it will change the FeatureTable
+ * object itself
+ */
+ private FeatureTree getFeatures () {
+ return features;
+ }
+
+ /**
+ * Return a count of the number of Feature obejcts in this FeatureTable.
+ **/
+ int getFeatureCount () {
+ return getFeatures ().size ();
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects whose
+ * location overlap the given range.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ FeatureVector getFeaturesInRange (Range range) {
+ return getFeatures ().getFeaturesInRange (range);
+ }
+
+ /**
+ * Return a vector containing the references of the all Feature objects in
+ * this FeatureTable.
+ * @return The features of this feature table. The returned
+ * object is a copy - changes will not effect the FeatureTable object
+ * itself.
+ **/
+ FeatureVector getAllFeatures () {
+ final FeatureVector return_features = new FeatureVector ();
+
+ final FeatureEnumeration enumerator = features ();
+
+ while (enumerator.hasMoreFeatures ()) {
+ final Feature this_feature = enumerator.nextFeature ();
+
+ return_features.add (this_feature);
+ }
+
+ return return_features;
+ }
+
+ /**
+ * Add a feature to the feature table. The features are ordered by first
+ * base and then last base. This is an example ordering: 1..100, 1..200,
+ * 50..100, 150..250. This method resets cached_feature_index and
+ * cached_feature.
+ * @param new_feature The feature to add
+ **/
+ void add (final Feature new_feature) {
+ cached_feature = null;
+ cached_feature_index = -999;
+
+ features.add (new_feature);
+ }
+
+ /**
+ * Remove the given Feature from this FeatureTable. This method resets
+ * cached_feature_index and cached_feature.
+ **/
+ Feature remove (final Feature feature) {
+ cached_feature = null;
+ cached_feature_index = -999;
+
+ if (getFeatures ().contains (feature)) {
+ getFeatures ().remove (feature);
+ return feature;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the ith Feature from this FeatureTable. This Features are
+ * returned in a consistent order, sorted by the first base of each
+ * Feature. This method uses and updates cached_feature_index and
+ * cached_feature.
+ **/
+ Feature getFeatureAtIndex (final int arg_index) {
+ if (cached_feature != null) {
+ if (arg_index == cached_feature_index) {
+ return cached_feature;
+ }
+
+ if (arg_index == cached_feature_index + 1) {
+ final Feature next_feature =
+ getFeatures ().getNextFeature (cached_feature);
+
+ if (next_feature == null) {
+ throw new ArrayIndexOutOfBoundsException (arg_index);
+ } else {
+ cached_feature_index = arg_index;
+ cached_feature = next_feature;
+
+ return cached_feature;
+ }
+ }
+ }
+
+ final FeatureEnumeration enumerator = features ();
+
+ int i = 0;
+
+ while (enumerator.hasMoreFeatures ()) {
+ final Feature this_feature = enumerator.nextFeature ();
+
+ if (i == arg_index) {
+ cached_feature = this_feature;
+ cached_feature_index = i;
+
+ return this_feature;
+ }
+
+ ++i;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns true if and only if this FeatureTable contains the given feature.
+ **/
+ public boolean contains (final Feature feature) {
+ return getFeatures ().contains (feature);
+ }
+
+ /**
+ * Return the index of the given Feature. This does the reverse of
+ * getFeatureAtIndex ().
+ **/
+ int indexOf (final Feature feature) {
+ if (cached_feature == feature) {
+ return cached_feature_index;
+ }
+
+ final FeatureEnumeration enumerator = features ();
+
+ int i = 0;
+
+ while (enumerator.hasMoreFeatures ()) {
+ final Feature this_feature = enumerator.nextFeature ();
+
+ if (this_feature == feature) {
+ return i;
+ }
+ ++i;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns an enumeration of the Feature objects in this FeatureTable. The
+ * returned Enumeration object will generate all features in this object in
+ * turn. The first item generated is the item at index 0, then the item at
+ * index 1, and so on.
+ **/
+ public FeatureEnumeration features () {
+ // return an enumeration from the FeatureTree
+ return getFeatures ().features ();
+ }
+
+ /**
+ * This holds the last Feature that was returned from getFeatureAtIndex ()
+ * or the last Feature that was passed to indexOf ().
+ **/
+ private Feature cached_feature = null;
+
+ /**
+ * This holds the last index that was passed to getFeatureAtIndex () or the
+ * last index that was returned from indexOf (). This is used to improve
+ * the speed of those two methods.
+ **/
+ private int cached_feature_index = -999;
+
+ /**
+ * This holds the features of this FeatureTable
+ **/
+ final private FeatureTree features =
+ new FeatureTree (new FeatureComparator ());
+}
diff --git a/uk/ac/sanger/artemis/io/FeatureTree.java b/uk/ac/sanger/artemis/io/FeatureTree.java
new file mode 100644
index 0000000..061822d
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/FeatureTree.java
@@ -0,0 +1,272 @@
+/* FeatureTree.java
+ *
+ * created: Thu Jan 28 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/FeatureTree.java,v 1.3 2005-11-28 16:46:38 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.*;
+
+/**
+ * A tree that stores StreamFeature objects ordered with a StreamFeatureComparator
+ * object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureTree.java,v 1.3 2005-11-28 16:46:38 tjc Exp $
+ **/
+
+public class FeatureTree extends TreeSet {
+ /**
+ * Create a new (empty) FeatureTree.
+ **/
+ public FeatureTree (final Comparator comparator) {
+ super (comparator);
+
+ this.comparator = comparator;
+ }
+
+ /**
+ * Wrapper for TreeSet.add () which sets size_of_largest_feature_seen.
+ **/
+ public synchronized boolean add (final Object element) {
+ final Feature this_feature = (Feature) element;
+
+ getBucket (this_feature).add (this_feature);
+
+ return super.add (element);
+ }
+
+ /**
+ * Wrapper for TreeSet.() which removes the Feature from
+ * rbtree_buckets.
+ **/
+ public synchronized boolean remove (Object element) {
+ final Feature this_feature = (Feature) element;
+
+ getBucket (this_feature).remove (this_feature);
+
+ return super.remove (element);
+ }
+
+ /**
+ * Find the sub-tree which has a first base that is >= the given base.
+ **/
+ private static SortedSet findByBase (final TreeSet tree, final int base) {
+ final ComparableFeature test_feature = new ComparableFeature () {
+ public void set (final Key k, final Location l,
+ final QualifierVector qv) {}
+ public void setKey (final Key _) {}
+ public void setLocation (final Location _) {}
+ public void setLocation (final Location _, Entry entry) {}
+ public void setQualifiers (final QualifierVector _) {}
+ public void setQualifier (final Qualifier _) {}
+ public void removeQualifierByName (final String _) {}
+ private final Key dummy_key = new Key ("_dummy_key_");
+ public Key getKey () {return dummy_key;}
+ public Location getLocation () {return null;}
+ public QualifierVector getQualifiers () {return null;}
+ public Qualifier getQualifierByName (final String _) {return null;}
+ public int getFirstBase () {return base;}
+ public int getLastBase () {return base;}
+ public long getNumericID () {return -1;}
+ public Entry getEntry () {return null;}
+ public Feature copy () {return null;}
+ public void setUserData (final Object _) {}
+ public Object getUserData () {return null;}
+ public boolean isReadOnly () {return false;}
+ };
+
+ return tree.tailSet (test_feature);
+ }
+
+ /**
+ * Add to features_in_range all the features in the given TreeSet which are
+ * within the given Range.
+ * @param max_feature_length the maximum length in bases of the features in
+ * the TreeSet.
+ **/
+ private void getFeaturesInRange (final TreeSet tree,
+ final FeatureVector features_in_range,
+ final Range range,
+ final int max_feature_length) {
+ // find the leftmost node in the range
+ final SortedSet tail_set =
+ findByBase (tree, range.getStart () - max_feature_length);
+
+ final Iterator tail_set_iterator = tail_set.iterator ();
+
+ // now loop over all the features that could possibly be in the range
+ // (ie. those between range.getStart () - max_feature_length and
+ // range.getEnd ())
+ while (tail_set_iterator.hasNext ()) {
+ final Feature this_feature = (Feature) tail_set_iterator.next ();
+
+ if (this_feature.getFirstBase () > range.getEnd ()) {
+ return;
+ }
+
+ if (this_feature.getLocation ().getTotalRange ().overlaps (range)) {
+ features_in_range.add (this_feature);
+ }
+ }
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The features that are within the given range. The returned
+ * object is a copy - changes will not effect the FeatureTree object
+ * itself.
+ **/
+ public synchronized FeatureVector getFeaturesInRange (final Range range) {
+ // this default size will cover many common cases
+ final FeatureVector return_features = new FeatureVector();
+
+ for (int i = 0 ;
+ i < rbtree_buckets.size () && rbtree_buckets.elementAt (i) != null ;
+ ++i) {
+ getFeaturesInRange ((TreeSet) rbtree_buckets.elementAt (i),
+ return_features, range,
+ (int) Math.pow (BUCKET_MULTIPLIER, i + 1));
+ }
+
+ return return_features;
+ }
+
+ /**
+ * Returns an enumeration of the Feature objects in this FeatureTree. The
+ * returned Enumeration object will generate all features in this object in
+ * turn.
+ **/
+ public FeatureEnumeration features () {
+ return new FeatureEnumerator ();
+ }
+
+ /**
+ * Return the reference of the Feature that is immediately after the
+ * argument Feature in the tree or null if there is no next Feature.
+ * The tree must contain this_feature.
+ **/
+ public Feature getNextFeature (final Feature this_feature) {
+ final SortedSet tail_set = tailSet (this_feature);
+
+ final Iterator tail_set_iterator = tail_set.iterator ();
+
+ // must contain this_feature
+ tail_set_iterator.next ();
+
+ if (tail_set_iterator.hasNext ()) {
+ return (Feature) tail_set_iterator.next ();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * An Enumeration of Feature objects.
+ **/
+ public class FeatureEnumerator implements FeatureEnumeration {
+ /**
+ * Create a new FeatureEnumeration that will enumerate the enclosing
+ * DocumentEntry object. The DocumentEntry object must not be changed
+ * while the enumeration is active.
+ **/
+ public FeatureEnumerator () {
+ iterator = iterator ();
+ }
+
+ /**
+ * See the FeatureEnumeration interface for details.
+ **/
+ public boolean hasMoreFeatures () {
+ return iterator.hasNext ();
+ }
+
+ /**
+ * See the FeatureEnumeration interface for details.
+ **/
+ public Feature nextFeature ()
+ throws NoSuchElementException {
+ return (Feature) iterator.next ();
+ }
+
+ /**
+ * This is the underlying enumeration that does all the work for us
+ **/
+ private Iterator iterator;
+ }
+
+ /**
+ * Features are stored in buckets.
+ * Bucket 0 will hold a reference to all features that are less than
+ * BUCKET_MULTIPLIER in length. Bucket 1 will hold those from
+ * BUCKET_MULTIPLIER to BUCKET_MULTIPLIER*BUCKET_MULTIPLIER-1 (inclusive)
+ * in length. Bucket 2 will hold those from BUCKET_MULTIPLIER^2 to
+ * BUCKET_MULTIPLIER^3-1 (inclusive) in length. etc.
+ **/
+ private TreeSet getBucket (final Feature feature) {
+ final int feature_length =
+ feature.getLocation ().getTotalRange ().getCount ();
+
+ final int feature_bucket;
+
+ if (feature_length <= BUCKET_MULTIPLIER) {
+ feature_bucket = 0;
+ } else {
+ // subtract 0.5 to feature_length to make sure that rounding errors go
+ // in out favour
+ feature_bucket =
+ (int) (Math.log (feature_length + 0.5) / Math.log (BUCKET_MULTIPLIER));
+ }
+
+ // make the bucket we need and all smaller buckets
+ while (rbtree_buckets.size () <= feature_bucket) {
+ rbtree_buckets.addElement (new TreeSet (comparator));
+ }
+
+ return (TreeSet) rbtree_buckets.elementAt (feature_bucket);
+ }
+
+ /**
+ * The maximum number of buckets.
+ **/
+ private final int BUCKET_COUNT = 10;
+
+ /**
+ * See comment on getBucket().
+ **/
+ private final int BUCKET_MULTIPLIER = 4;
+
+ /**
+ * See comment above.
+ **/
+ private final Vector rbtree_buckets = new Vector (BUCKET_COUNT);
+
+ /**
+ * The Comparator that was passed to the constructor.
+ **/
+ private Comparator comparator;
+}
diff --git a/uk/ac/sanger/artemis/io/FeatureVector.java b/uk/ac/sanger/artemis/io/FeatureVector.java
new file mode 100644
index 0000000..897658f
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/FeatureVector.java
@@ -0,0 +1,54 @@
+/* FeatureVector.java
+ *
+ * created: Tue Oct 13 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.Vector;
+
+/**
+ * This class implements a Vector of Feature objects.
+ * @author Kim Rutherford
+ * @version $Id: FeatureVector.java,v 1.2 2004-11-24 11:55:52 tjc Exp $
+ */
+
+public class FeatureVector extends Vector<Feature>
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new vector of Feature objects with an initial capacity of 100.
+ **/
+ public FeatureVector()
+ {
+ super(100);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt()
+ **/
+ public Feature featureAt(int index)
+ {
+ return elementAt(index);
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/io/FuzzyRange.java b/uk/ac/sanger/artemis/io/FuzzyRange.java
new file mode 100644
index 0000000..8a48147
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/FuzzyRange.java
@@ -0,0 +1,339 @@
+/* FuzzyRange.java
+ *
+ * created: Wed Apr 28 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999,2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/FuzzyRange.java,v 1.1 2004-06-09 09:49:28 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+/**
+ * This is a Range with a non-exact upper or lower bound. It represents EMBL
+ * locations like (100.200)..300 or (10.20)..(40.50)
+ * An object of this class can be constructed with a Range object for the
+ * start or end or both.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FuzzyRange.java,v 1.1 2004-06-09 09:49:28 tjc Exp $
+ **/
+
+public class FuzzyRange extends Range {
+ /**
+ * Create a new FuzzyRange object.
+ * @param start_object the start of the FuzzyRange
+ * @param end_object the end of the FuzzyRange
+ **/
+ private FuzzyRange (final Object start_object, final Object end_object)
+ throws OutOfRangeException {
+ super (getStartPosition (start_object),
+ getEndPosition (end_object));
+ this.start_object = start_object;
+ this.end_object = end_object;
+ }
+
+ /**
+ * Create a new FuzzyRange object.
+ * @param position_object the position of the FuzzyRange
+ **/
+ private FuzzyRange (final Object position_object) {
+ super (getPosition (position_object));
+ this.start_object = position_object;
+ this.end_object = null;
+ }
+
+ /**
+ * Make and return a new FuzzyRange or Range object.
+ **/
+ public static Range makeRange (final Object start_object,
+ final Object end_object)
+ throws OutOfRangeException {
+ if (end_object == null) {
+ return makeRange (start_object);
+ }
+
+ if (start_object instanceof Integer &&
+ end_object instanceof Integer) {
+ int start_pos = ((Integer) start_object).intValue ();
+ int end_pos = ((Integer) end_object).intValue ();
+
+ if (start_pos <= end_pos) {
+ return new Range (start_pos, end_pos);
+ } else {
+ return new Range (end_pos, start_pos);
+ }
+ } else {
+ return new FuzzyRange (start_object, end_object);
+ }
+ }
+
+ /**
+ * Make and return a new FuzzyRange or Range object.
+ **/
+ public static Range makeRange (final Object start_object) {
+ if (start_object instanceof Integer) {
+ return new Range (((Integer) start_object).intValue ());
+ } else {
+ return new FuzzyRange (start_object);
+ }
+ }
+
+ /**
+ * Return the integer position of the given object, which should be the
+ * start object of a range.
+ **/
+ private static int getStartPosition (final Object start_object) {
+ if (start_object instanceof Integer) {
+ return ((Integer) start_object).intValue ();
+ } else {
+ if (start_object instanceof LowerInteger) {
+ return ((LowerInteger) start_object).getPosition ();
+ } else {
+ if (start_object instanceof Range) {
+ return ((Range) start_object).getStart ();
+ } else {
+ throw new Error ("internal error: object is not recognised: " +
+ start_object);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the integer position of the given object, which should be the
+ * end object of a range.
+ **/
+ private static int getEndPosition (final Object end_object) {
+ if (end_object instanceof Integer) {
+ return ((Integer) end_object).intValue ();
+ } else {
+ if (end_object instanceof UpperInteger) {
+ return ((UpperInteger) end_object).getPosition ();
+ } else {
+ if (end_object instanceof Range) {
+ return ((Range) end_object).getEnd ();
+ } else {
+ throw new Error ("internal error: object is not recognised: " +
+ end_object);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the integer position of the given object.
+ **/
+ private static int getPosition (final Object position_object) {
+ if (position_object instanceof Integer) {
+ return ((Integer) position_object).intValue ();
+ } else {
+ if (position_object instanceof LowerInteger) {
+ return ((LowerInteger) position_object).getPosition ();
+ } else {
+ if (position_object instanceof UpperInteger) {
+ return ((UpperInteger) position_object).getPosition ();
+ } else {
+ if (position_object instanceof Range) {
+ return ((Range) position_object).getStart ();
+ } else {
+ throw new Error ("internal error: object is not recognised: " +
+ position_object);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Return true if and only if the argument equals this Range.
+ **/
+ public boolean equals (final Range test_range) {
+ if (test_range instanceof FuzzyRange) {
+
+ final FuzzyRange fuzzy_range = (FuzzyRange) test_range;
+
+ if (getStart () != fuzzy_range.getStart ()) {
+ return false;
+ }
+
+ if (getEnd () != fuzzy_range.getEnd ()) {
+ return false;
+ }
+
+ if (start_object instanceof Integer &&
+ fuzzy_range.start_object instanceof Integer) {
+
+ if (((Integer) start_object).intValue () !=
+ ((Integer) fuzzy_range.start_object).intValue ()) {
+ return false;
+ }
+ }
+
+ if (start_object instanceof LowerInteger &&
+ fuzzy_range.start_object instanceof LowerInteger) {
+
+ if (((LowerInteger) start_object).getPosition () !=
+ ((LowerInteger) fuzzy_range.start_object).getPosition ()) {
+ return false;
+ }
+ }
+
+ if (start_object instanceof Range &&
+ fuzzy_range.start_object instanceof Range) {
+
+ if (((Range) start_object).getStart () !=
+ ((Range) fuzzy_range.start_object).getStart ()) {
+ return false;
+ }
+ }
+
+ if (end_object instanceof Integer &&
+ fuzzy_range.end_object instanceof Integer) {
+
+ if (((Integer) end_object).intValue () !=
+ ((Integer) fuzzy_range.end_object).intValue ()) {
+ return false;
+ }
+ }
+
+ if (end_object instanceof UpperInteger &&
+ fuzzy_range.end_object instanceof UpperInteger) {
+
+ if (((UpperInteger) end_object).getPosition () !=
+ ((UpperInteger) fuzzy_range.end_object).getPosition ()) {
+ return false;
+ }
+ }
+
+ if (end_object instanceof Range &&
+ fuzzy_range.end_object instanceof Range) {
+
+ if (((Range) end_object).getEnd () !=
+ ((Range) fuzzy_range.end_object).getEnd ()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return a String representation of this FuzzyRange object.
+ **/
+ public String toString () {
+ final String start_string;
+
+ if (start_object instanceof Range) {
+ start_string ="(" +
+ ((Range) start_object).getStart () + "." +
+ ((Range) start_object).getEnd () +
+ ")";
+ } else {
+ start_string = start_object.toString ();
+ }
+
+ if (end_object == null) {
+ return start_string;
+ } else {
+ final String end_string;
+
+ if (end_object instanceof Range) {
+ end_string ="(" +
+ ((Range) end_object).getStart () + "." +
+ ((Range) end_object).getEnd () +
+ ")";
+ } else {
+ end_string = end_object.toString ();
+ }
+
+ return start_string + ".." + end_string;
+ }
+ }
+
+ /**
+ * Return a copy of this object with the start and end changed. Currently
+ * if the start or end object is a Range then that object will be turned
+ * into an Integer.
+ **/
+ public Range change (final int start, final int end)
+ throws OutOfRangeException {
+ if ((start_object instanceof Integer ||
+ start_object instanceof LowerInteger) &&
+ (end_object instanceof Integer ||
+ end_object instanceof UpperInteger)) {
+ final Object new_start_object;
+
+ if (start_object instanceof Integer) {
+ new_start_object = new Integer (start);
+ } else {
+ new_start_object = new LowerInteger (new Integer (start));
+ }
+
+ final Object new_end_object;
+
+ if (end_object instanceof Integer) {
+ new_end_object = new Integer (end);
+ } else {
+ new_end_object = new UpperInteger (new Integer (end));
+ }
+
+ return new FuzzyRange (new_start_object, new_end_object);
+ } else {
+ return new Range (start, end);
+ }
+ }
+
+ /**
+ * Return a copy of this object.
+ **/
+ public Range copy () {
+ try {
+ return makeRange (start_object, end_object);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception");
+ }
+ }
+
+ public Object getStartObject()
+ {
+ return start_object;
+ }
+
+ public Object getEndObject()
+ {
+ return end_object;
+ }
+
+ /**
+ * The start Object of this FuzzyRange.
+ **/
+ private Object start_object = null;
+
+ /**
+ * The end Range of this FuzzyRange.
+ **/
+ private Object end_object = null;
+}
+
diff --git a/uk/ac/sanger/artemis/io/GFFDocumentEntry.java b/uk/ac/sanger/artemis/io/GFFDocumentEntry.java
new file mode 100644
index 0000000..3ef88f2
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GFFDocumentEntry.java
@@ -0,0 +1,1009 @@
+/* GFFDocumentEntry.java
+ *
+ * created: Tue Sep 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999-2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/GFFDocumentEntry.java,v 1.69 2009-09-03 13:47:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.chado.FeatureLocLazyQualifierValue;
+import uk.ac.sanger.artemis.components.Splash;
+import uk.ac.sanger.artemis.components.filetree.LocalAndRemoteFileManager;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+import java.sql.Timestamp;
+
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.sequence.FeatureLoc;
+
+/**
+ * A DocumentEntry that can read an GFF entry from a Document.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GFFDocumentEntry.java,v 1.69 2009-09-03 13:47:31 tjc Exp $
+ **/
+
+public class GFFDocumentEntry extends SimpleDocumentEntry
+ implements DocumentEntry
+{
+ private boolean finished_constructor = false;
+ private boolean isReadOnly = false;
+
+ /**
+ * Create a new GFFDocumentEntry object associated with the given
+ * Document.
+ * @param document This is the file that we will read from. This is also
+ * used for saving the entry back to the file it came from and to give
+ * the new object a name.
+ * @param listener The object that will listen for ReadEvents.
+ * @exception IOException thrown if there is a problem reading the entry -
+ * most likely ReadFormatException.
+ **/
+ GFFDocumentEntry(final Document document, final ReadListener listener)
+ throws IOException, EntryInformationException
+ {
+ super(new GFFEntryInformation(), document, listener);
+ super.in_constructor = true;
+ // join the separate exons into one feature (if appropriate)
+ final FeatureVector original_features = getAllFeatures();
+ if(original_features.size() > 0 && GFFStreamFeature.isGTF((Feature)original_features.get(0)))
+ {
+ // GTF
+ mergeGtfFeatures(original_features, "CDS");
+ mergeGtfFeatures(original_features, "exon");
+ }
+ else
+ {
+ // GFF
+ combineGeneFeatures(original_features);
+ }
+ super.in_constructor = false;
+ finished_constructor = true;
+ }
+
+ /**
+ * Create a new GFFDocumentEntry that will be a copy of the given Entry and
+ * has no Document associated with it. The new GFFDocumentEntry cannot be
+ * saved to a file with save() unless save(Document) has been called
+ * first. Some qualifier and location information will be lost.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys in the new Entry will be quietly thrown away. "Invalid"
+ * means that the key/qualifier is not allowed to occur in an Entry of
+ * this type (probably determined by the EntryInformation object of this
+ * Entry). If false an EntryInformationException will be thrown for
+ * invalid keys or qualifiers.
+ **/
+ public GFFDocumentEntry(final Entry new_entry, final boolean force)
+ throws EntryInformationException
+ {
+ super(new GFFEntryInformation(), new_entry, force);
+ finished_constructor = true;
+ }
+
+ /**
+ * Create a new empty GFFDocumentEntry object that has no Document
+ * associated with it. The new GFFDocumentEntry cannot be saved to a
+ * file with save() unless save(Document) has been called first. The
+ * save(Document) method will assign a Document.
+ **/
+ public GFFDocumentEntry(final EntryInformation entry_information)
+ {
+ super(new GFFEntryInformation());
+ finished_constructor = true;
+ }
+
+ /**
+ * Returns true if and only if this entry is read only. For now this
+ * always returns true - GFFDocumentEntry objects can't be changed.
+ **/
+ /**
+ * Returns true if and only if this entry is read only. For now this
+ * always returns true - BlastDocumentEntry objects can't be changed.
+ **/
+ public boolean isReadOnly()
+ {
+ return isReadOnly;
+ }
+
+ public void setReadOnly(final boolean isReadOnly)
+ {
+ this.isReadOnly = isReadOnly;
+ }
+
+ /**
+ * If the given feature can be added directly to this Entry, then return
+ * it, otherwise create and return a new feature of the appropriate type.
+ * @param copy if true then always new a new copy of the Feature.
+ **/
+ protected Object makeNativeFeature(final Feature feature,
+ final boolean copy)
+ {
+ if(!copy && feature instanceof GFFStreamFeature)
+ return (GFFStreamFeature)feature;
+ else
+ {
+ if(PublicDBDocumentEntry.IGNORE_OBSOLETE_FEATURES)
+ {
+ Qualifier isObsoleteQualifier =
+ feature.getQualifiers().getQualifierByName("isObsolete");
+ if(isObsoleteQualifier != null)
+ {
+ String value = (String)isObsoleteQualifier.getValues().get(0);
+ if(Boolean.parseBoolean(value))
+ return null;
+ }
+ }
+ return new GFFStreamFeature(feature);
+ }
+ }
+
+ /**
+ * If the given Sequence can be added directly to this Entry, then return a
+ * copy of it, otherwise create and return a new feature of the appropriate
+ * type for this Entry.
+ **/
+ protected StreamSequence makeNativeSequence(final Sequence sequence)
+ {
+ return new FastaStreamSequence(sequence);
+ }
+
+ private void combineGeneFeatures(FeatureVector original_features)
+ {
+ Feature this_feature;
+ Hashtable chado_gene = new Hashtable();
+ try
+ {
+ // find the genes
+ for(int i = 0 ; i < original_features.size() ; ++i)
+ {
+ this_feature = original_features.featureAt(i);
+ final String key = this_feature.getKey().getKeyString();
+ if(this_feature instanceof GFFStreamFeature &&
+ (GeneUtils.isHiddenFeature(key) ||
+ GeneUtils.isObsolete((GFFStreamFeature)this_feature)))
+ ((GFFStreamFeature)this_feature).setVisible(false);
+
+ if(key.equals("gene") || key.equals("pseudogene"))
+ {
+ final Qualifier idQualifier = this_feature.getQualifierByName("ID");
+ if(idQualifier != null)
+ {
+ String id = (String)this_feature.getQualifierByName("ID").getValues().get(0);
+ ChadoCanonicalGene gene = new ChadoCanonicalGene();
+ gene.setGene(this_feature);
+ chado_gene.put(id, gene);
+ ((GFFStreamFeature)this_feature).setChadoGene(gene);
+ }
+ }
+ }
+
+ // find the transcripts
+ Hashtable transcripts_lookup = new Hashtable();
+ for(int i = 0 ; i < original_features.size() ; ++i)
+ {
+ this_feature = original_features.featureAt(i);
+
+ // transcript
+ Qualifier parent_qualifier = this_feature.getQualifierByName("Parent");
+
+ if(parent_qualifier == null)
+ continue;
+
+ StringVector parents = parent_qualifier.getValues();
+ for(int j=0; j<parents.size(); j++)
+ {
+ String parent = (String)parents.get(j);
+
+ if(chado_gene.containsKey(parent))
+ {
+ // store transcript
+ ChadoCanonicalGene gene = (ChadoCanonicalGene)chado_gene.get(parent);
+ gene.addTranscript(this_feature);
+ ((GFFStreamFeature)this_feature).setChadoGene(gene);
+
+ // store the transcript ID with its ChadoCanonicalGene object
+
+ if(this_feature.getQualifierByName("ID") != null)
+ transcripts_lookup.put((String)this_feature.getQualifierByName("ID").getValues().get(0),
+ gene);
+ continue;
+ }
+ }
+ }
+
+
+ // find exons & protein
+ String key;
+ for(int i = 0 ; i < original_features.size() ; ++i)
+ {
+ this_feature = original_features.featureAt(i);
+ // exons
+ key = this_feature.getKey().getKeyString();
+
+ final Qualifier parent_qualifier = this_feature.getQualifierByName("Parent");
+ final Qualifier derives_qualifier = this_feature.getQualifierByName("Derives_from");
+ if(parent_qualifier == null && derives_qualifier == null)
+ continue;
+
+ final Qualifier featureRelationship =
+ this_feature.getQualifierByName("feature_relationship_rank");
+ // compare this features parent_id's to transcript id's in the
+ // chado gene hash to decide if it is part of it
+ final StringVector parent_id;
+
+ if(parent_qualifier != null)
+ parent_id = parent_qualifier.getValues();
+ else
+ parent_id = derives_qualifier.getValues();
+
+ for(int j=0; j<parent_id.size(); j++)
+ {
+ final String parent = (String)parent_id.get(j);
+
+ if(transcripts_lookup.containsKey(parent))
+ {
+ final ChadoCanonicalGene gene = (ChadoCanonicalGene)transcripts_lookup.get(parent);
+ ((GFFStreamFeature)this_feature).setChadoGene(gene);
+
+ if(parent_qualifier == null)
+ {
+ //((GFFStreamFeature)this_feature).setVisible(false);
+ gene.addProtein(parent, this_feature);
+ }
+ else if(key.equals("three_prime_UTR"))
+ gene.add3PrimeUtr(parent, this_feature);
+ else if(key.equals("five_prime_UTR"))
+ gene.add5PrimeUtr(parent, this_feature);
+ else if(key.equals(DatabaseDocument.EXONMODEL) || key.equals("exon") ||
+ featureRelationship != null ||
+ key.equals("pseudogenic_exon"))
+ gene.addSplicedFeatures(parent, this_feature);
+ else
+ gene.addOtherFeatures(parent, this_feature);
+ }
+ }
+
+ }
+
+ //
+ //
+ if(getDocument() instanceof DatabaseDocument)
+ {
+ DatabaseDocument doc = (DatabaseDocument)getDocument();
+ loadFeatureLocLazyData(original_features);
+ if(doc.isLazyFeatureLoad())
+ {
+ // using lazy loading - add the lazy chado feature to GFFStreamFeature
+ final Hashtable idFeatureStore = doc.getIdFeatureStore();
+ for(int i = 0 ; i < original_features.size() ; ++i)
+ {
+ this_feature = original_features.featureAt(i);
+ String featureId = (String) this_feature.getQualifierByName("feature_id").getValues().get(0);
+ org.gmod.schema.sequence.Feature chadoLazyFeature =
+ (org.gmod.schema.sequence.Feature)idFeatureStore.get(featureId);
+ ((GFFStreamFeature)this_feature).setChadoLazyFeature(chadoLazyFeature);
+ }
+ idFeatureStore.clear();
+ }
+ }
+
+ // now join exons
+ //combineFeatures();
+ Enumeration enum_genes = chado_gene.elements();
+ while(enum_genes.hasMoreElements())
+ {
+ ChadoCanonicalGene gene = (ChadoCanonicalGene)enum_genes.nextElement();
+ combineChadoExons(gene);
+
+ // inferring CDS and UTRs
+ if(DatabaseDocument.CHADO_INFER_CDS)
+ {
+ final Vector transcripts = (Vector)gene.getTranscripts();
+ gene.correctSpliceSiteAssignments();
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ GFFStreamFeature transcript = (GFFStreamFeature)transcripts.get(i);
+ String transcript_id = null;
+ transcript_id = GeneUtils.getUniqueName(transcript);
+
+ List exons = gene.getSpliceSitesOfTranscript(transcript_id, "exon");
+ if(exons == null)
+ continue;
+
+ Iterator it = exons.iterator();
+ while(it.hasNext())
+ {
+ final GFFStreamFeature exonFeature = (GFFStreamFeature)it.next();
+
+ QualifierVector qualifiers = new QualifierVector();
+ qualifiers.add(new Qualifier("ID", transcript_id+":CDS"));
+ qualifiers.add(new Qualifier("Parent", transcript_id));
+
+ DatabaseInferredFeature cdsFeature = new DatabaseInferredFeature(
+ Key.CDS, exonFeature.getLocation(), qualifiers, gene);
+
+ try
+ {
+ gene.addSplicedFeatures(transcript_id, cdsFeature);
+ forcedAdd(cdsFeature);
+ }
+ catch (ReadOnlyException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ }
+ }
+ }
+
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Get 'similarity', polypeptide_domain qualifiers
+ * @param fv
+ * @throws InvalidRelationException
+ */
+ private void loadFeatureLocLazyData(final FeatureVector fv)
+ throws InvalidRelationException
+ {
+ final DatabaseDocument doc = (DatabaseDocument)getDocument();
+ List matches;
+
+ if(fv.size() < 30 && fv.size() > 0) // if just a few features to look up e.g. for gene editor
+ {
+ List featureIds = new Vector(fv.size());
+ for(int i=0;i<fv.size(); i++)
+ {
+ Qualifier featureIdQualifier = fv.featureAt(i).getQualifierByName("feature_id");
+ featureIds.add( (String)featureIdQualifier.getValues().get(0) );
+ }
+ matches = doc.getSimilarityMatches(featureIds);
+ }
+ else
+ matches = doc.getSimilarityMatches(null);
+
+ if(matches == null || matches.size() < 1)
+ return;
+ final Hashtable temp_lookup_hash = new Hashtable(matches.size()/2);
+ String f_id;
+ for(int i=0; i<fv.size(); i++)
+ {
+ Feature f = (Feature)fv.elementAt(i);
+ Qualifier qualifier = ((Feature)f).getQualifierByName("feature_id");
+
+ if(qualifier != null)
+ {
+ f_id = (String)qualifier.getValues().get(0);
+ temp_lookup_hash.put(f_id, f);
+ }
+ }
+
+ // bulk load featureLocs and create lookup hash
+ final Hashtable hashFeatureLocs = getFeatureLocsHash(doc, matches);
+ if(hashFeatureLocs == null)
+ return;
+
+ final Hashtable cvTermCache = new Hashtable();
+ final Feature f = (Feature)fv.elementAt(0);
+ for(int i=0; i<matches.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature matchFeature =
+ (org.gmod.schema.sequence.Feature)matches.get(i);
+
+ final CvTerm cvTerm = getCvTermFromCache(cvTermCache, matchFeature, f);
+ final String qualifierName;
+ if(cvTerm.getName().indexOf("match") > -1)
+ qualifierName = "similarity";
+ else
+ qualifierName = cvTerm.getName();
+
+
+ final List featureLocs =
+ (List) hashFeatureLocs.get(new Integer(matchFeature.getFeatureId()));
+ if(featureLocs == null)
+ continue;
+
+ matchFeature.setFeatureLocsForFeatureId(featureLocs);
+ //java.util.Collection featureLocs = matchFeature.getFeatureLocsForFeatureId();
+ java.util.Iterator it = featureLocs.iterator();
+ while(it.hasNext())
+ {
+ final FeatureLoc featureLoc = (FeatureLoc)it.next();
+ final Feature queryFeature =
+ (Feature)temp_lookup_hash.get(Integer.toString(featureLoc.getSrcFeatureId()));
+
+ if(queryFeature != null)
+ {
+ Qualifier qualifier = queryFeature.getQualifierByName(qualifierName);
+ final FeatureLocLazyQualifierValue sim =
+ new FeatureLocLazyQualifierValue(matchFeature, featureLoc.getSrcFeatureId());
+
+ if(qualifier == null || !(qualifier instanceof QualifierLazyLoading))
+ qualifier = new QualifierLazyLoading(qualifierName, sim);
+ else
+ ((QualifierLazyLoading)qualifier).addValue(sim);
+
+ try
+ {
+ queryFeature.setQualifier(qualifier);
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch(EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+
+ if(qualifierName.equals("polypeptide_domain") &&
+ LocalAndRemoteFileManager.domainLoad.isSelected())
+ addDomain(queryFeature, featureLoc, matchFeature);
+
+ break;
+ }
+ }
+ }
+ cvTermCache.clear();
+ temp_lookup_hash.clear();
+ }
+
+ /**
+ * Retrieve a CvTerm from a Hashtable with keys equal to the cvterm_id
+ * and values of the corresponding CvTerm. If the term is not in the cache
+ * then look it up in the main DatabaseDocument cache.
+ * @param cvTermCache
+ * @param matchFeature
+ * @param f
+ * @return
+ */
+ private CvTerm getCvTermFromCache(final Hashtable cvTermCache,
+ final org.gmod.schema.sequence.Feature matchFeature,
+ final Feature f)
+ {
+ final Integer cvTermId = new Integer(matchFeature.getCvTerm().getCvTermId());
+ final CvTerm cvTerm;
+
+ if(cvTermCache.containsKey(cvTermId))
+ cvTerm = (CvTerm) cvTermCache.get(cvTermId);
+ else
+ {
+ cvTerm = DatabaseDocument.getCvTermByCvTermId(
+ matchFeature.getCvTerm().getCvTermId(), f);
+ cvTermCache.put(cvTermId, cvTerm);
+ }
+
+ matchFeature.setCvTerm(cvTerm);
+ return cvTerm;
+ }
+
+ /**
+ * Add domain features as read-only features
+ * @param queryFeature
+ * @param featureLoc
+ * @param matchFeature
+ */
+ private void addDomain(final Feature queryFeature,
+ final FeatureLoc featureLoc,
+ org.gmod.schema.sequence.Feature matchFeature)
+ {
+ try
+ {
+ int start = queryFeature.getLocation().getFirstBase();
+ Location location = null;
+
+ ChadoCanonicalGene chadoGene = ((GFFStreamFeature)queryFeature).getChadoGene();
+ if(chadoGene != null)
+ location = chadoGene.getNucLocation(queryFeature, featureLoc);
+ else if(queryFeature.getLocation().isComplement())
+ location = new Location("complement("+
+ (start+(featureLoc.getFmin()*3)+1)+".."+(start+(featureLoc.getFmax()*3))+")");
+ if(location == null)
+ location = new Location(
+ (start+(featureLoc.getFmin()*3)+1)+".."+(start+(featureLoc.getFmax()*3)));
+
+ final GFFStreamFeature newFeature = new GFFStreamFeature(
+ new Key("polypeptide_domain"),
+ location, null);
+ newFeature.setReadOnlyFeature(true);
+ newFeature.setChadoLazyFeature(matchFeature);
+ add(newFeature);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+
+ /**
+ * Bulk load match features featureLoc's and create a Hashtable with the
+ * feature_id's as keys and a list of the corresponding featureLocs as values.
+ * @param doc
+ * @param matches
+ * @return the hashtable; null if no featureLocs are found
+ */
+ private Hashtable getFeatureLocsHash(final DatabaseDocument doc, final List matches)
+ {
+ final List matchFeatureIds = new Vector(matches.size());
+ for(int i=0; i< matches.size(); i++)
+ {
+ String matchFeatureId = Integer.toString(
+ ((org.gmod.schema.sequence.Feature)matches.get(i)).getFeatureId() );
+ matchFeatureIds.add( matchFeatureId );
+ }
+
+ final List allFeatureLocs = doc.getFeatureLocsByListOfIds(matchFeatureIds);
+ if(allFeatureLocs == null)
+ return null;
+
+ Hashtable hashFeatureLocs = new Hashtable();
+ for(int i=0;i<allFeatureLocs.size(); i++)
+ {
+ FeatureLoc featureLoc = (FeatureLoc)allFeatureLocs.get(i);
+ Integer featureId = new Integer(featureLoc.getFeatureByFeatureId().getFeatureId());
+ List list;
+ if(hashFeatureLocs.containsKey(featureId))
+ list = (List) hashFeatureLocs.get(featureId);
+ else
+ list = new Vector();
+ list.add(featureLoc);
+ hashFeatureLocs.put(featureId, list);
+ }
+ return hashFeatureLocs;
+ }
+
+ /**
+ * Combine the features (which are exons) and delete the orignals from this
+ * Entry. The key of this hash will be the group name and the value is a
+ * FeatureVector containing the feature that are in that group. Groups
+ * that have more than one member will be combined.
+ **/
+ private void combineChadoExons(ChadoCanonicalGene gene)
+ {
+ final List<Feature> transcripts = gene.getTranscripts();
+ gene.correctSpliceSiteAssignments();
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ GFFStreamFeature transcript = (GFFStreamFeature)transcripts.get(i);
+ String transcript_id = null;
+
+ if(transcript.getQualifierByName("ID") == null)
+ continue;
+ transcript_id = (String)(transcript.getQualifierByName("ID").getValues().get(0));
+
+ Set<String> splicedSiteTypes = gene.getSpliceTypes(transcript_id);
+ if(splicedSiteTypes == null)
+ continue;
+
+ Iterator<String> it = splicedSiteTypes.iterator();
+ Vector<Feature> new_set = new Vector<Feature>();
+ while(it.hasNext())
+ {
+ String type = it.next();
+ List<Feature> splicedSites = gene.getSpliceSitesOfTranscript(transcript_id, type);
+
+ if(splicedSites == null)
+ continue;
+
+ mergeFeatures(splicedSites, new_set,
+ (String)(transcript.getQualifierByName("ID").getValues().get(0)),
+ transcript.getLocation().isComplement());
+ }
+
+ for(int j=0; j<new_set.size(); j++)
+ {
+ if(j == 0)
+ gene.addSplicedFeatures(transcript_id,
+ new_set.get(j), true );
+ else
+ gene.addSplicedFeatures(transcript_id,
+ new_set.get(j));
+ }
+
+ }
+ }
+
+
+ private void mergeFeatures(final List<Feature> gffFeatures,
+ final List<Feature> new_set,
+ final String transcript_id,
+ final boolean isComplement)
+ {
+ final Hashtable<String, Integer> feature_relationship_rank_store = new Hashtable<String, Integer>();
+ final Hashtable<String, Range> id_range_store = new Hashtable<String, Range>();
+ final RangeVector new_range_vector = new RangeVector();
+ QualifierVector qualifiers = new QualifierVector();
+ Timestamp lasttimemodified = null;
+
+ final Qualifier codon_start = getCodonStart(gffFeatures, isComplement);
+ for(int j = 0; j < gffFeatures.size(); j++)
+ {
+ final GFFStreamFeature this_feature = (GFFStreamFeature)gffFeatures.get(j);
+
+ Integer rank;
+ Qualifier rankQualifier = this_feature
+ .getQualifierByName("feature_relationship_rank");
+ if(rankQualifier == null)
+ rank = new Integer(0);
+ else
+ {
+ rank = new Integer((String) (rankQualifier.getValues().get(0)));
+ this_feature.getQualifiers().removeQualifierByName("feature_relationship_rank");
+ }
+
+ // use the most current lastmodified datestamp
+ if(this_feature.getLastModified() != null
+ && (lasttimemodified == null || this_feature.getLastModified()
+ .compareTo(lasttimemodified) > 0))
+ lasttimemodified = this_feature.getLastModified();
+
+ final Location this_feature_location = this_feature.getLocation();
+
+ if(this_feature_location.getRanges().size() > 1)
+ {
+ String id= "";
+ try
+ {
+ id = (String)this_feature.getQualifierByName("ID").getValues().get(0);
+ }
+ catch(Exception e){}
+ throw new Error("internal error - new location should have "
+ + "exactly one range (there may be non-unique ID's):\n"+
+ this_feature_location.toStringShort()+"\n"+id);
+ }
+
+ final Range new_range = (Range) this_feature_location.getRanges()
+ .elementAt(0);
+
+ Qualifier id_qualifier = this_feature.getQualifierByName("ID");
+ if(id_qualifier != null)
+ {
+ String id = (String) (id_qualifier.getValues()).elementAt(0);
+ id_range_store.put(id, new_range);
+ feature_relationship_rank_store.put(id, rank);
+ }
+ else
+ Splash.logger4j.warn("NO ID FOUND FOR FEATURE AT: "+
+ this_feature.getLocation().toString());
+
+ if(this_feature_location.isComplement())
+ new_range_vector.insertElementAt(new_range, 0);
+ else
+ new_range_vector.add(new_range);
+
+ removeInternal(this_feature);
+ qualifiers.addAll(this_feature.getQualifiers());
+ }
+
+ final GFFStreamFeature first_old_feature = (GFFStreamFeature)gffFeatures.get(0);
+
+ final Location new_location = new Location(new_range_vector,
+ first_old_feature.getLocation().isComplement());
+
+ if(codon_start != null)
+ {
+ QualifierVector tmp_qualifier_vector = new QualifierVector();
+
+ for(Qualifier q: qualifiers)
+ if(!q.getName().equals("codon_start"))
+ tmp_qualifier_vector.addElement(q);
+ qualifiers = tmp_qualifier_vector;
+ qualifiers.setQualifier(codon_start);
+ }
+
+ qualifiers = mergeQualifiers(qualifiers);
+
+ final GFFStreamFeature new_feature = new GFFStreamFeature(first_old_feature
+ .getKey(), new_location, qualifiers);
+
+ if(lasttimemodified != null)
+ new_feature.setLastModified(lasttimemodified);
+
+ if(first_old_feature.getChadoGene() != null)
+ new_feature.setChadoGene(first_old_feature.getChadoGene());
+
+ new_feature.setSegmentRangeStore(id_range_store);
+ new_feature
+ .setFeature_relationship_rank_store(feature_relationship_rank_store);
+ new_feature.setGffSource(first_old_feature.getGffSource());
+ new_feature.setGffSeqName(first_old_feature.getGffSeqName());
+
+// set the ID
+ String ID;
+ try
+ {
+ ID = new_feature.getSegmentID(new_feature.getLocation().getRanges());
+ }
+ catch(NullPointerException npe)
+ {
+ if(new_feature.getQualifierByName("Parent") != null)
+ ID = ((String)new_feature.getQualifierByName("Parent").getValues().get(0)) +
+ ":"+new_feature.getKey().getKeyString();
+ else
+ ID = new_feature.getKey().getKeyString();
+ }
+ final Qualifier id_qualifier = new_feature.getQualifierByName("ID");
+ id_qualifier.removeValue((String)(id_qualifier.getValues()).elementAt(0));
+ id_qualifier.addValue(ID);
+
+
+ // set visibility
+ if(GeneUtils.isHiddenFeature(new_feature.getKey().getKeyString()) ||
+ GeneUtils.isObsolete(new_feature))
+ new_feature.setVisible(false);
+
+ try
+ {
+ new_feature.setLocation(new_location);
+
+ final Qualifier gene_qualifier = new_feature.getQualifierByName("gene");
+
+ if(gene_qualifier != null
+ && gene_qualifier.getValues().size() > 0
+ && ((String) (gene_qualifier.getValues()).elementAt(0))
+ .startsWith("Phat"))
+ {
+ // special case to handle incorrect output of the Phat gene
+ // prediction tool
+ new_feature.removeQualifierByName("codon_start");
+ }
+
+ forcedAdd(new_feature);
+ //gene.addExon(transcript_id, new_feature, true );
+ new_set.add(new_feature);
+ }
+ catch(ReadOnlyException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(EntryInformationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ private QualifierVector mergeQualifiers(QualifierVector qualifier_vector)
+ {
+ QualifierVector merge_qualifier_vector = new QualifierVector();
+ boolean seen = false;
+
+ for(int i = 0 ; i < qualifier_vector.size() ; ++i)
+ {
+ Qualifier qual = (Qualifier)qualifier_vector.elementAt(i);
+
+ if(qual.getName().equals("codon_start"))
+ {
+ if(!seen)
+ {
+ merge_qualifier_vector.addElement(qual);
+ seen = true;
+ }
+ }
+ else if(qual.getName().equals("Alias"))
+ {
+ final Qualifier id_qualifier =
+ merge_qualifier_vector.getQualifierByName("Alias");
+
+ if(id_qualifier == null)
+ merge_qualifier_vector.addElement(qual);
+ else
+ {
+ String id1 = (String)(id_qualifier.getValues()).elementAt(0);
+ String id2 = (String)(qual.getValues()).elementAt(0);
+ id_qualifier.removeValue(id1);
+ id_qualifier.addValue(id1+","+id2);
+ }
+ }
+ else if(!qual.getName().equals("ID") &&
+ !qual.getName().equals("feature_id"))
+ merge_qualifier_vector.setQualifier(qual);
+ }
+ return merge_qualifier_vector;
+ }
+
+ /**
+ * Merge function for GTF features
+ * @param original_features
+ * @param keyStr
+ * @throws ReadOnlyException
+ */
+ private void mergeGtfFeatures(FeatureVector original_features, String keyStr) throws ReadOnlyException
+ {
+ Hashtable<String, Vector<GFFStreamFeature>> group = new Hashtable<String, Vector<GFFStreamFeature>>();
+ for(int i=0; i<original_features.size(); i++)
+ {
+ GFFStreamFeature feature = (GFFStreamFeature)original_features.get(i);
+ if(!feature.getKey().getKeyString().equals(keyStr))
+ continue;
+ String transcriptId =
+ ((String) feature.getQualifierByName("transcript_id").getValues().get(0)).replaceAll("'", "");
+ if(group.containsKey(transcriptId))
+ group.get(transcriptId).add(feature);
+ else
+ {
+ Vector<GFFStreamFeature> this_group = new Vector<GFFStreamFeature>();
+ this_group.add(feature);
+ group.put(transcriptId, this_group);
+ }
+ }
+
+ Enumeration<String> enumGroup = group.keys();
+ while(enumGroup.hasMoreElements())
+ {
+ String transcriptId = enumGroup.nextElement();
+ Vector<GFFStreamFeature> this_group = group.get(transcriptId);
+ QualifierVector qualifier_vector = new QualifierVector();
+ final RangeVector new_range_vector = new RangeVector();
+
+ for(GFFStreamFeature this_feature: this_group)
+ {
+ removeInternal(this_feature);
+ qualifier_vector.addAll(this_feature.getQualifiers());
+
+ final Range new_range = (Range) this_feature.getLocation().getRanges().elementAt(0);
+ if(this_feature.getLocation().isComplement())
+ new_range_vector.insertElementAt(this_feature.getLocation().getTotalRange(), 0);
+ else
+ new_range_vector.add(new_range);
+ }
+ final GFFStreamFeature old_feature = (GFFStreamFeature)this_group.get(0);
+
+ final Location new_location = new Location(new_range_vector,
+ old_feature.getLocation().isComplement());
+
+ qualifier_vector = mergeQualifiers(qualifier_vector);
+ if(qualifier_vector.getQualifierByName("gene_id") != null)
+ qualifier_vector.addQualifierValues(new Qualifier("ID",
+ keyStr+":"+qualifier_vector.getQualifierByName("gene_id").getValues().get(0)));
+
+ final GFFStreamFeature new_feature = new GFFStreamFeature(old_feature
+ .getKey(), new_location, qualifier_vector);
+ forcedAdd(new_feature);
+ }
+ }
+
+ /**
+ * Get the phase/codon_start for the first feature segment
+ * @param gffFeatures
+ * @param isComplement
+ * @return
+ */
+ private Qualifier getCodonStart(final List<Feature> gffFeatures, final boolean isComplement)
+ {
+ int fstart = (isComplement ? 0 : Integer.MAX_VALUE);
+ Feature firstFeature = null;
+ for(Feature f: gffFeatures)
+ {
+ final GFFStreamFeature this_feature = (GFFStreamFeature)f;
+ if(isComplement && this_feature.getFirstBase() > fstart)
+ {
+ firstFeature = this_feature;
+ fstart = this_feature.getFirstBase();
+ }
+ else if(!isComplement && this_feature.getFirstBase() < fstart)
+ {
+ firstFeature = this_feature;
+ fstart = this_feature.getFirstBase();
+ }
+ }
+
+ if(firstFeature == null)
+ return null;
+ try
+ {
+ Qualifier codon_start = firstFeature.getQualifierByName("codon_start");
+ if(codon_start != null)
+ return codon_start.copy();
+ }
+ catch (InvalidRelationException e){}
+ return null;
+ }
+
+ /**
+ * Adjust feature coordinates to match the contig positions when loaded
+ * with a multiple fasta.
+ * @param sequenceEntry sequence entry
+ */
+ public void adjustCoordinates(uk.ac.sanger.artemis.Entry sequenceEntry)
+ {
+ final Entry entry;
+ if(sequenceEntry != null)
+ entry = sequenceEntry.getEMBLEntry();
+ else
+ entry = this;
+ if(entry instanceof SimpleDocumentEntry)
+ {
+ // adjust feature coordinates to match contig positions
+ final Hashtable<String, Range> contig_ranges = ((SimpleDocumentEntry)entry).contig_ranges;
+ if(contig_ranges != null)
+ {
+ final FeatureVector gff_regions = getAllFeatures();
+ final Enumeration<Feature> gff_features = gff_regions.elements();
+
+ while(gff_features.hasMoreElements())
+ {
+ final Feature f = gff_features.nextElement();
+ if( !(f instanceof GFFStreamFeature) )
+ continue;
+
+ final String name = ((GFFStreamFeature)f).getGffSeqName();
+ if(name == null)
+ continue;
+ if(contig_ranges.containsKey(name))
+ {
+
+ try
+ {
+ final Range new_range = contig_ranges.get(name);
+ final RangeVector new_ranges = new RangeVector();
+ final RangeVector ranges = f.getLocation().getRanges();
+ for(int i = 0 ; i<ranges.size () ; ++i)
+ {
+ final Range r = (Range)ranges.elementAt(i);
+
+ new_ranges.add(new Range(r.getStart()+new_range.getStart()-1,
+ r.getEnd()+new_range.getStart()-1));
+ }
+
+ Location l = new Location(new_ranges, f.getLocation().isComplement());
+ f.setLocation(l);
+ ((uk.ac.sanger.artemis.Feature)f.getUserData()).setLocation(l);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ // store so these can be used when writing out
+ GFFStreamFeature.contig_ranges = contig_ranges;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/GFFEntryInformation.java b/uk/ac/sanger/artemis/io/GFFEntryInformation.java
new file mode 100644
index 0000000..ef0b524
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GFFEntryInformation.java
@@ -0,0 +1,208 @@
+/* GFFEntryInformation.java
+ *
+ * created: Thu Mar 30 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/GFFEntryInformation.java,v 1.6 2007-06-14 16:01:38 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.Options;
+
+/**
+ * An EntryInformation object for GFFDocumentEntry objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: GFFEntryInformation.java,v 1.6 2007-06-14 16:01:38 tjc Exp $
+ **/
+
+public class GFFEntryInformation extends SimpleEntryInformation
+{
+
+ public GFFEntryInformation()
+ {
+ super();
+
+ try
+ {
+ makeEntryInformation();
+ }
+ catch(QualifierInfoException e)
+ {
+ System.err.println("could not initialise the embl package: " +
+ e.getMessage());
+ System.exit(1);
+ }
+ catch(IOException e)
+ {
+ System.err.println("could not initialise the embl package: " +
+ e.getMessage());
+ System.exit(1);
+ }
+ }
+
+
+ /**
+ * Return an EntryInformation object that is suitable for EMBL and GENBANK
+ * entries.
+ **/
+ private void makeEntryInformation()
+ throws IOException, QualifierInfoException
+ {
+ final InputStream feature_keys_stream =
+ Options.class.getResourceAsStream("/etc/feature_keys_gff");
+
+ final InputStream qualifier_types_stream =
+ Options.class.getResourceAsStream("/etc/qualifier_types_gff");
+
+ QualifierInfoVector qualifier_info_vector =
+ readQualifierInfo(qualifier_types_stream, feature_keys_stream);
+
+ for(int i = 0 ; i < qualifier_info_vector.size() ; ++i)
+ {
+ final QualifierInfo qualifier_info =
+ qualifier_info_vector.elementAt(i);
+
+ addQualifierInfo(qualifier_info);
+ }
+
+ //
+ // include extra keys and qualifiers
+ final QualifierInfoVector extra_qualifiers =
+ Options.getOptions().getExtraGffQualifiers();
+ final StringVector extra_keys =
+ Options.getOptions().getOptionValues("extra_keys_gff");
+
+ for(int i = 0 ; i < extra_keys.size() ; ++i)
+ {
+ final Key new_key = new Key((String)extra_keys.elementAt(i));
+ //System.out.println(new_key.toString());
+ addKey(new_key);
+ }
+
+ for(int i = 0 ; i < extra_qualifiers.size() ; ++i)
+ {
+ final QualifierInfo new_qualifier_info = extra_qualifiers.elementAt(i);
+ //System.out.println(new_qualifier_info.getName());
+ addQualifierInfo(new_qualifier_info);
+ }
+
+// entry_information.setEMBLFormat(true);
+ }
+
+ /**
+ * Read the possible feature key and qualifier names and types from the two
+ * given streams (see etc/feature_keys and etc/qualifier_types for details
+ * on the formats).
+ **/
+ private static QualifierInfoVector
+ readQualifierInfo(final InputStream qualifier_types_stream,
+ final InputStream feature_keys_stream)
+ throws IOException
+ {
+
+ final QualifierInfoVector return_vector = new QualifierInfoVector();
+
+ Properties feature_properties = new Properties();
+ final Properties qualifier_properties = new Properties();
+
+ feature_properties.load(feature_keys_stream);
+ qualifier_properties.load(qualifier_types_stream);
+
+ // parse the feature_properties
+
+ {
+ final Properties new_feature_properties = new Properties();
+ final Enumeration feature_enum = feature_properties.propertyNames();
+
+ while(feature_enum.hasMoreElements())
+ {
+ String current_feature_name = (String) feature_enum.nextElement();
+
+ final StringVector property_values =
+ Options.getPropertyValues(feature_properties, current_feature_name);
+
+ new_feature_properties.put(current_feature_name, property_values);
+ }
+
+ feature_properties = new_feature_properties;
+ }
+
+ final Enumeration qualifier_enum = qualifier_properties.propertyNames();
+
+ while(qualifier_enum.hasMoreElements())
+ {
+ String current_qualifier_name = (String) qualifier_enum.nextElement();
+
+ final StringVector current_qualifier_values =
+ Options.getPropertyValues(qualifier_properties,
+ current_qualifier_name);
+
+ final boolean once_only =
+ current_qualifier_values.elementAt(0).equals("yes");
+
+ final String type_string = (String)current_qualifier_values.elementAt(1);
+
+ // find the keys for which this qualifier name is valid or required
+
+ final KeyVector valid_keys = new KeyVector();
+ final KeyVector required_keys = new KeyVector();
+
+ final Enumeration features_enum = feature_properties.propertyNames();
+
+ while(features_enum.hasMoreElements())
+ {
+ final String current_key_string = (String)features_enum.nextElement();
+
+ final Key current_key = new Key(current_key_string);
+
+ final StringVector current_feature_qualifiers =
+ (StringVector) feature_properties.get(current_key_string);
+
+ if(current_feature_qualifiers.contains(current_qualifier_name))
+ valid_keys.add(current_key);
+ else
+ if(current_feature_qualifiers.contains("@" +
+ current_qualifier_name))
+ {
+ valid_keys.add(current_key);
+ required_keys.add(current_key);
+ }
+ }
+
+ final int type = QualifierInfo.getQualifierTypeID(type_string);
+
+ final QualifierInfo qualifier_info =
+ new QualifierInfo(current_qualifier_name, type, valid_keys,
+ required_keys, once_only);
+
+ return_vector.add(qualifier_info);
+ }
+
+ return return_vector;
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/GFFMisc.java b/uk/ac/sanger/artemis/io/GFFMisc.java
new file mode 100644
index 0000000..eafb157
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GFFMisc.java
@@ -0,0 +1,48 @@
+/* GFFMisc.java
+ *
+ * created: Thu Feb 17 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/GFFMisc.java,v 1.1 2004-06-09 09:49:32 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+
+/**
+ * GFFMisc class
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: GFFMisc.java,v 1.1 2004-06-09 09:49:32 tjc Exp $
+ **/
+
+public class GFFMisc extends MiscLineGroup {
+ /**
+ * Create a new MiscLineGroup object. One line is read from the in_stream
+ * and stored.
+ **/
+ public GFFMisc (LinePushBackReader in_stream)
+ throws IOException {
+ super (in_stream);
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/GFFStreamFeature.java b/uk/ac/sanger/artemis/io/GFFStreamFeature.java
new file mode 100644
index 0000000..8e68ac3
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GFFStreamFeature.java
@@ -0,0 +1,1359 @@
+/* GFFStreamFeature.java
+ *
+ * created: Tue Sep 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999,2000,2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/GFFStreamFeature.java,v 1.72 2009-08-28 10:33:12 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+
+import java.util.Hashtable;
+import java.util.HashSet;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.io.IOException;
+import java.io.Writer;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.chado.ClusterLazyQualifierValue;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.genebuilder.ProteinMapPanel;
+import uk.ac.sanger.artemis.components.genebuilder.ortholog.MatchPanel;
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+/**
+ * A StreamFeature that thinks it is a GFF feature.
+ * @author Kim Rutherford
+ **/
+public class GFFStreamFeature extends SimpleDocumentFeature
+ implements DocumentFeature, StreamFeature, ComparableFeature
+{
+
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(GFFStreamFeature.class);
+
+ /** store for spliced features containing id and range of each segment */
+ private Hashtable<String, Range> id_range_store;
+
+ /** store a record of the new and old uniquenames that have been changed */
+ private Hashtable<String, String> newIdMapToOldId;
+
+ /** store the Timestamp for the feature */
+ private Timestamp timelastmodified;
+
+ private ChadoCanonicalGene chadoGene;
+
+ private boolean visible = true;
+
+ /** combined feature_relationship.rank store for exons */
+ private Hashtable<String, Integer> feature_relationship_rank_store;
+
+ /** first tabbed parameter */
+ private String gffSeqName;
+ /** second tabbed parameter */
+ private String gffSource;
+ /** duplication count */
+ private short duplicate = 0;
+
+ protected static Hashtable<String, Range> contig_ranges;
+ private boolean lazyLoaded = false;
+ private org.gmod.schema.sequence.Feature chadoLazyFeature;
+ private boolean readOnlyFeature = false;
+
+
+ private static String MAP_DECODE[][] = {
+ { " ", "%20" }, // white space
+ { ",", "%2C" }, // comma
+ { ";", "%3B" }, // semi-colon
+ { "=", "%3D" }, // equals
+ { "\t", "%09" }, // tab
+ { " ", "+" }, // white space
+ { "+", "%2B" },
+ { "(", "%28" }, // left bracket
+ { ")", "%29" }, // right bracket
+ { "'", "\"" }
+ };
+
+ private static String MAP_ENCODE[][] = {
+// { " ", "%20" }, // white space
+ { ",", "%2C" }, // comma
+ { ";", "%3B" }, // semi-colon
+ { "=", "%3D" }, // equals
+ { "\t", "%09" }, // tab
+ { "+", "%2B" },
+ { " ", "+" }, // white space
+ { "(", "%28" }, // left bracket
+ { ")", "%29" }, // right bracket
+ { "\n", "%5C" } // new-line
+ };
+
+ private static Set<String> attrs_to_filter = new HashSet<String>();
+
+ /**
+ * Registers an attribute not to be included in the GFF3 output for
+ * GFFStreamFeatures
+ * @param attr The GFF3 attribute to remove
+ **/
+ public static void removeAttribute(String attr)
+ {
+ attrs_to_filter.add(attr);
+ }
+
+ /**
+ * Registers an attribute to be included in the GFF3 output for
+ * GFFStreamFeatures
+ * @param attr The GFF3 attribute to include
+ **/
+ public static void includeAttribute(String attr)
+ {
+ attrs_to_filter.remove(attr);
+ }
+
+ /**
+ * Create a new GFFStreamFeature object. The feature should be added
+ * to an Entry (with Entry.add()).
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ **/
+ public GFFStreamFeature(final Key key, final Location location,
+ final QualifierVector qualifiers)
+ {
+ super(null);
+
+ try
+ {
+ setKey(key);
+ setLocation(location);
+ setQualifiers(qualifiers);
+
+ if(getQualifierByName("ID") == null)
+ {
+ String idStr = null;
+ StringVector v = Options.getOptions().getSystematicQualifierNames();
+ for(int i=0; i<v.size(); i++)
+ {
+ final String sysName = (String)v.get(i);
+ if(getQualifierByName(sysName) != null)
+ {
+ idStr = (String)getQualifierByName(sysName).getValues().get(0);
+ break;
+ }
+ }
+ // autogenerate ID
+ if(idStr == null)
+ idStr = key.getKeyString()+":"+location.toString();
+ setQualifier(new Qualifier("ID", idStr));
+ }
+
+ }
+ catch(EntryInformationException e)
+ {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(ReadOnlyException e)
+ {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(OutOfRangeException e)
+ {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ public GFFStreamFeature(final Feature feature)
+ {
+ this(feature, false);
+ }
+
+ /**
+ * Create a new GFFStreamFeature with the same key, location and
+ * qualifiers as the given feature. The feature should be added to an
+ * Entry (with Entry.add()).
+ * @param feature The feature to copy.
+ **/
+ public GFFStreamFeature(final Feature feature, final boolean isDuplicatedInChado)
+ {
+ this(feature.getKey(), feature.getLocation(), feature.getQualifiers());
+
+ if(feature instanceof GFFStreamFeature)
+ {
+ if(((GFFStreamFeature)feature).id_range_store != null)
+ this.id_range_store =
+ (Hashtable)(((GFFStreamFeature)feature).id_range_store).clone();
+
+ if(((GFFStreamFeature)feature).feature_relationship_rank_store != null)
+ this.feature_relationship_rank_store =
+ (Hashtable)(((GFFStreamFeature)feature).feature_relationship_rank_store).clone();
+
+ this.setGffSeqName(((GFFStreamFeature)feature).getGffSeqName());
+ this.setGffSource(((GFFStreamFeature)feature).getGffSource());
+
+ if(isDuplicatedInChado)
+ {
+ try
+ {
+ final String uniquename;
+ final String duplicatePrefix;
+
+ if(feature instanceof GFFStreamFeature)
+ {
+ ((GFFStreamFeature)feature).duplicate++;
+ duplicatePrefix = "DUP"+Short.toString(((GFFStreamFeature)feature).duplicate)+"-";
+ }
+ else
+ duplicatePrefix = "DUP";
+ if(id_range_store != null)
+ {
+ final Hashtable<String, Range> new_id_range_store = new Hashtable<String, Range>(id_range_store.size());
+ final Enumeration<String> enumIdRangeStore = id_range_store.keys();
+ while(enumIdRangeStore.hasMoreElements())
+ {
+ final String keyId = enumIdRangeStore.nextElement();
+ final Range range = id_range_store.get(keyId);
+ new_id_range_store.put(duplicatePrefix+keyId, range);
+ }
+ id_range_store.clear();
+ this.id_range_store = (Hashtable) new_id_range_store.clone();
+
+
+ if(getLocation().getRanges().size() > 1)
+ uniquename = getSegmentID(getLocation().getRanges());
+ else
+ {
+ if( ((String)getQualifierByName("ID").getValues().get(0)).endsWith("}") )
+ uniquename = id_range_store.keys().nextElement();
+ else
+ uniquename = duplicatePrefix+ (String)getQualifierByName("ID").getValues().get(0);
+ }
+ }
+ else
+ uniquename = duplicatePrefix+ (String)getQualifierByName("ID").getValues().get(0);
+ setQualifier(new Qualifier("ID", uniquename));
+
+ if(getQualifierByName("Parent") != null)
+ {
+ final String parent =
+ (String) getQualifierByName("Parent").getValues().get(0);
+ setQualifier(new Qualifier("Parent", duplicatePrefix+parent));
+ }
+
+ if(getQualifierByName("Derives_from") != null)
+ {
+ final String derives_from =
+ (String) getQualifierByName("Derives_from").getValues().get(0);
+ setQualifier(new Qualifier("Derives_from", duplicatePrefix+derives_from));
+ }
+
+ // remove qualifiers that don't get transferred to duplicate
+ final String removeQualifierNames[] =
+ { "feature_id",
+ "timelastmodified",
+ "feature_relationship_rank",
+ ProteinMapPanel.POLYPEPTIDE_DOMAIN,
+ ProteinMapPanel.TMHMM[0],
+ ProteinMapPanel.TMHMM[1],
+ ProteinMapPanel.TMHMM[2],
+ ProteinMapPanel.TMHMM[3],
+ MatchPanel.ORTHOLOG,
+ MatchPanel.ORTHOLOG
+ };
+
+ for(int i=0;i<removeQualifierNames.length; i++)
+ removeQualifierByName(removeQualifierNames[i]);
+ }
+ catch(ReadOnlyException e){}
+ catch(EntryInformationException e){}
+ }
+ else
+ {
+ chadoGene = ((GFFStreamFeature)feature).chadoGene;
+ }
+ }
+ }
+
+ /**
+ * Create a new GFFStreamFeature from the given line. The String should be
+ * in gene finder format.
+ **/
+ public GFFStreamFeature(final String line)
+ throws ReadFormatException
+ {
+ super(null);
+
+ final StringVector line_bits = StringVector.getStrings(line, "\t", true);
+ if(line_bits.size() < 8)
+ throw new ReadFormatException("invalid GFF line: 8 fields needed " +
+ "(got " + line_bits.size () +
+ " fields) from: " + line);
+
+ final String start_base_str = line_bits.elementAt(3).trim();
+ final String end_base_str = line_bits.elementAt(4).trim();
+
+ final int start_base;
+ final int end_base;
+ try
+ {
+ start_base = Integer.parseInt(start_base_str);
+ end_base = Integer.parseInt(end_base_str);
+ }
+ catch(NumberFormatException e)
+ {
+ throw new ReadFormatException("Could not understand the start or end base " +
+ "of a GFF feature: " + start_base_str +
+ " " + end_base_str);
+ }
+
+ // start of qualifier parsing and setting
+ try
+ {
+ final boolean complement_flag;
+ if(line_bits.elementAt(6).equals("+"))
+ complement_flag = false;
+ else if(line_bits.elementAt(6).equals("-"))
+ complement_flag = true;
+ else
+ {
+ // must be unstranded
+ complement_flag = false;
+ }
+
+ if(line_bits.size() == 9)
+ {
+ final String rest_of_line = line_bits.elementAt(8);
+ final Hashtable<String, StringVector> attributes = parseAttributes(rest_of_line);
+ for(final Enumeration<String> attribute_enum = attributes.keys();
+ attribute_enum.hasMoreElements();)
+ {
+ String name = attribute_enum.nextElement();
+ final StringVector values = attributes.get(name);
+
+ if(MatchPanel.isClusterTag(name))
+ {
+ List<ClusterLazyQualifierValue> lazyValues = new Vector<ClusterLazyQualifierValue>();
+ for(int i=0; i<values.size(); i++)
+ lazyValues.add(
+ new ClusterLazyQualifierValue( (String)values.get(i), name,
+ this ));
+ setQualifier(new QualifierLazyLoading(name, lazyValues));
+ }
+ else
+ {
+ if(values.size() == 0)
+ setQualifier(new Qualifier(name));
+ else
+ setQualifier(new Qualifier(name, values));
+ }
+ }
+ }
+
+ if( !line_bits.elementAt(0).equals("null") )
+ setGffSeqName( decode(line_bits.elementAt(0)) );
+
+ setKey(new Key(line_bits.elementAt(2)));
+ setGffSource(line_bits.elementAt(1));
+
+ if( !line_bits.elementAt(5).equals(".") )
+ {
+ final Qualifier score_qualifier =
+ new Qualifier("score", line_bits.elementAt(5));
+ setQualifier(score_qualifier);
+ }
+
+ String frame = line_bits.elementAt(7);
+
+ if(frame.equals ("0"))
+ frame = "1";
+ else if(frame.equals("1"))
+ frame = "2";
+ else if(frame.equals("2"))
+ frame = "3";
+ else
+ frame = ".";
+
+ if(!frame.equals("."))
+ {
+ final Qualifier codon_start_qualifier =
+ new Qualifier("codon_start", frame);
+
+ setQualifier(codon_start_qualifier);
+ }
+
+ if(start_base > end_base)
+ throw new ReadFormatException("start position is greater than end " +
+ "position: " + start_base + " > " +
+ end_base+"\n"+line);
+
+ if(start_base < 0)
+ throw new ReadFormatException("start position must be positive: " +
+ start_base);
+
+ final Range location_range = new Range(start_base, end_base);
+ final RangeVector location_ranges = new RangeVector(location_range);
+ setLocation(new Location(location_ranges, complement_flag));
+ }
+ catch(ReadOnlyException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(EntryInformationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ //this.gff_lines = new StringVector(line);
+ }
+
+ /**
+ *
+ * Store for spliced regions of segments ID's and ranges.
+ *
+ */
+ public void setSegmentRangeStore(Hashtable<String, Range> id_range_store)
+ {
+ this.id_range_store = id_range_store;
+ }
+
+ public Hashtable<String, Range> getSegmentRangeStore()
+ {
+ if(id_range_store == null)
+ {
+ id_range_store = new Hashtable<String, Range>();
+ id_range_store.put((String)this.getQualifierByName("ID").getValues().get(0),
+ this.getLocation().getTotalRange());
+ }
+ return id_range_store;
+ }
+
+ public Hashtable<String, String> getNewIdMapToOldId()
+ {
+ return newIdMapToOldId;
+ }
+
+ /**
+ * Used when changing spliced feature uniquenames
+ * @param newIdMapToOldId
+ */
+ public void setNewIdMapToOldId(Hashtable<String, String> newIdMapToOldId)
+ {
+ this.newIdMapToOldId = newIdMapToOldId;
+ }
+
+ /**
+ * Store for ID's and CHADO feature_relationship.rank
+ * @param feature_relationship_rank_store
+ */
+ public void setFeature_relationship_rank_store(
+ Hashtable<String, Integer> feature_relationship_rank_store)
+ {
+ this.feature_relationship_rank_store = feature_relationship_rank_store;
+ }
+
+ /**
+ * Store for ID's and CHADO feature_relationship.rank
+ * @return
+ */
+ public Hashtable<String, Integer> getFeature_relationship_rank_store()
+ {
+ return feature_relationship_rank_store;
+ }
+
+
+ /**
+ * Get the chado uniquename
+ * @param r
+ * @return
+ */
+ public String getSegmentID(final Range r)
+ {
+ if(id_range_store != null)
+ {
+ int offset = 0;
+ if(getGffSeqName() != null && contig_ranges != null &&
+ contig_ranges.containsKey(getGffSeqName()))
+ {
+ // adjust for coordinates in multi-sequence GFF
+ Range offset_range = contig_ranges.get(getGffSeqName());
+ offset = offset_range.getStart()-1;
+ }
+
+ Enumeration<String> enum_ranges = id_range_store.keys();
+ while(enum_ranges.hasMoreElements())
+ {
+ String key = enum_ranges.nextElement();
+ Range range = id_range_store.get(key);
+ if(range.getStart() == r.getStart()-offset &&
+ range.getEnd() == r.getEnd()-offset)
+ return key;
+ }
+ }
+ else if (getQualifierByName("ID") != null)
+ {
+ return (String)getQualifierByName("ID").getValues().get(0);
+ }
+
+ logger4j.warn("RANGE NOT FOUND "+r.toString());
+
+ return null;
+ }
+
+ /**
+ * Get the feature ID based on the segments chado
+ * uniquename's.
+ * @param rv
+ * @return
+ */
+ public String getSegmentID(final RangeVector rv)
+ {
+ String id = "";
+ if(id_range_store != null)
+ {
+ String id_new;
+ Range range;
+ int index;
+ for(int i=0; i<rv.size(); i++)
+ {
+ range = (Range)rv.get(i);
+ id_new = getSegmentID(range);
+
+ String prefix[] = getPrefix(id_new, ':');
+ if(prefix[0] != null)
+ {
+ index = id.indexOf(prefix[0]);
+ if(id.equals("") || index < 0)
+ {
+ if(!id.equals(""))
+ id = id +",";
+ id = id+prefix[0] + "{" + prefix[1] + "}";
+ continue;
+ }
+
+ index = id.indexOf('}', index);
+ id = id.substring(0,index) + "," +
+ prefix[1] + id.substring(index);
+ }
+ else if(id_new != null)
+ {
+ if(!id.equals(""))
+ id = id +",";
+ id = id+id_new;
+ }
+ }
+ }
+
+ return id;
+ }
+
+ /**
+ * Get the ID prefix, e.g. for SPAC1556.06.1:exon:2
+ * returns SPAC1556.06.1:exon as the prefix and 2 as the
+ * index.
+ * @param id
+ * @return
+ */
+ public String[] getPrefix(final String id,
+ final char separator)
+ {
+ String prefix[] = new String[2];
+ int index = id.lastIndexOf(separator);
+
+ if(index > -1)
+ {
+ prefix[0] = id.substring(0,index);
+ prefix[1] = id.substring(index+1);
+ }
+ return prefix;
+ }
+
+ /**
+ * Used to automatically generate
+ * @param prefix
+ * @return
+ */
+ public int getAutoNumber(final String prefix,
+ final char separator)
+ {
+ int auto = 1;
+ String val = prefix + separator + auto;
+ while(id_range_store.containsKey(val))
+ {
+ auto++;
+ val = prefix + separator + auto;
+ }
+ return auto;
+ }
+
+
+ /**
+ * For gff-version 3:
+ * http://www.sequenceontology.org/gff3.shtml
+ * Remove URL escaping rule (e.g. space="%20" or "+")
+ */
+ public static String decode(String s)
+ {
+ int ind;
+ String enc;
+ String dec;
+
+ for(int i=0; i<MAP_DECODE.length; i++)
+ {
+ enc = MAP_DECODE[i][1];
+ dec = MAP_DECODE[i][0];
+ while( (ind = s.indexOf(enc)) > -1)
+ s = s.substring(0,ind) + dec + s.substring(ind+enc.length());
+ }
+ return s;
+ }
+
+
+ /**
+ * For gff-version 3:
+ * http://www.sequenceontology.org/gff3.shtml
+ * Add URL escaping rule (e.g. space="%20" or "+")
+ */
+ public static String encode(String s)
+ {
+ int ind;
+ String enc;
+ String dec;
+
+ for(int i=0; i<MAP_ENCODE.length; i++)
+ {
+ enc = MAP_ENCODE[i][1];
+ dec = MAP_ENCODE[i][0];
+ while( (ind = s.indexOf(dec)) > -1 )
+ s = s.substring(0,ind) + enc + s.substring(ind+1);
+ }
+ return s;
+ }
+
+
+ /**
+ * Return the reference of a new copy of this Feature.
+ **/
+ public Feature copy()
+ {
+ final Feature return_value = new GFFStreamFeature(this);
+ return return_value;
+ }
+
+ /**
+ * Read and return a GFFStreamFeature from a stream. A feature must be the
+ * next thing in the stream.
+ * @param stream the Feature is read from this stream
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ * @return null if in_stream is at the end of file when the method is
+ * called
+ */
+ protected static GFFStreamFeature readFromStream(LinePushBackReader stream)
+ throws IOException, InvalidRelationException
+ {
+ final String line = stream.readLine();
+ if(line == null)
+ return null;
+
+ try
+ {
+ return new GFFStreamFeature(line);
+ }
+ catch(ReadFormatException exception)
+ {
+ // re-throw the exception with the line number added
+ final String new_error_string = exception.getMessage();
+
+ throw new ReadFormatException(new_error_string,
+ stream.getLineNumber());
+ }
+ }
+
+ /**
+ * Read the details of a feature from an EMBL stream into the current
+ * object.
+ * @param entry_information The EntryInformation object of the Entry that
+ * will contain the Feature.
+ * @param in_stream the Feature is read from this stream
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException if the stream does not contain GFF
+ * feature.
+ **/
+ public void setFromStream(final EntryInformation entry_information,
+ final LinePushBackReader in_stream)
+ throws IOException, InvalidRelationException, ReadOnlyException
+ {
+ throw new ReadOnlyException();
+ }
+
+ /**
+ * Write this Feature to the given stream.
+ * @param writer The stream to write to.
+ * @exception IOException thrown if there is an io problem while writing
+ * the Feature.
+ **/
+ public void writeToStream(final Writer writer)
+ throws IOException
+ {
+ final RangeVector ranges = getLocation().getRanges();
+ final int ranges_size = ranges.size();
+
+// final Hashtable contig_ranges = SimpleDocumentEntry.getContigRanges();
+ for(int i = 0; i < ranges_size; ++i)
+ {
+ Range this_range = (Range)ranges.elementAt(i);
+
+ String seqname = getGffSeqName();
+ String source = getGffSource();
+ Qualifier score = getQualifierByName("score");
+ Qualifier group = getQualifierByName("group");
+
+ // source becomes a Dbxref in chado
+ String source_str = null;
+ if(getQualifierByName("Dbxref") != null)
+ {
+ source_str = getDbxrefGFFSource(getQualifierByName("Dbxref"));
+ }
+
+ int start = this_range.getStart();
+ int end = this_range.getEnd();
+
+ if(seqname == null && ((GFFDocumentEntry)getEntry()).getDocument() != null)
+ seqname = ((GFFDocumentEntry)getEntry()).getDocument().getName();
+ if(seqname == null)
+ seqname = deriveSeqName(start);
+
+ if(source == null)
+ source = "artemis";
+
+ if(score == null)
+ score = new Qualifier("score", ".");
+
+ if(seqname != null && contig_ranges != null &&
+ contig_ranges.containsKey(seqname))
+ {
+ Range offset_range = contig_ranges.get(seqname);
+ start = start-offset_range.getStart()+1;
+ end = end-offset_range.getStart()+1;
+ }
+
+ if(group == null || group.getValues() == null ||
+ group.getValues().elementAt(0).equals(""))
+ {
+ final Qualifier gene = getQualifierByName("gene");
+
+ if(gene == null)
+ group = new Qualifier("group", "");
+ else
+ group = gene;
+ }
+
+ String frame = ".";
+ final Qualifier codon_start = getQualifierByName("codon_start");
+
+ if(codon_start != null)
+ {
+ frame = (String)(codon_start.getValues()).elementAt(0);
+
+ if(frame.equals ("1"))
+ frame = "0";
+ else if(frame.equals("2"))
+ frame = "1";
+ else if(frame.equals("3"))
+ frame = "2";
+ else
+ frame = ".";
+ }
+
+ // phase is REQUIRED for all CDS features
+ if(getKey().equals("CDS") && frame.equals("."))
+ frame = "0";
+
+ final String myId = getSegmentID(this_range);
+ String attribute_string = unParseAttributes(myId);
+
+ if(source_str == null && source != null)
+ source_str = source;
+
+ final String translation = getTranslation();
+ if(translation != null)
+ attribute_string = attribute_string + ";" + translation;
+ writer.write(seqname + "\t" +
+ source_str + "\t" +
+ getKey().getKeyString() + "\t" +
+ start + "\t" +
+ end + "\t" +
+ score.getValues() .elementAt(0)+ "\t" +
+ (getLocation().isComplement() ? "-\t" : "+\t") +
+ frame + "\t" +
+ attribute_string + "\n");
+ }
+ }
+
+ /**
+ * If the seqname is not set for this feature try to derive the contig/chromosome
+ * it is located on
+ * @param start
+ * @return
+ */
+ private String deriveSeqName(int start)
+ {
+ String seqname = null;
+ if(contig_ranges != null)
+ {
+ final Enumeration<String> contigEnum = contig_ranges.keys();
+ while(contigEnum.hasMoreElements())
+ {
+ final String key = contigEnum.nextElement();
+ final Range r = contig_ranges.get(key);
+ if(r.getStart() > start)
+ continue;
+ if(r.getEnd() > start)
+ return key;
+ }
+ }
+ else
+ {
+ try
+ {
+ seqname = ((GFFStreamFeature)(getEntry().getAllFeatures().elementAt(0))).getGffSeqName();
+ }
+ catch(Exception e) {}
+ }
+
+ if(seqname == null)
+ seqname = "gff_seqname";
+ return seqname;
+ }
+
+ /**
+ * Return a String containing the qualifiers of this feature in a form
+ * suitable for using as the last field of a GFF line. The codon_start
+ * attribute is not included since GFF has a frame field. gff_seqname,
+ * gff_source and score aren't included since they have corresponding
+ * fields.
+ **/
+ private String unParseAttributes(final String myId)
+ {
+ final StringBuffer buffer = new StringBuffer();
+ final QualifierVector qualifiers = getQualifiers();
+
+ final String names[] = { "ID", "Name", "Alias", "Parent",
+ "Derives_from",
+ "Target", "Gap", "Note",
+ "Dbxref", "Ontology_term",
+ "Start_range", "End_range",
+ "Is_circular"};
+ int count = 0;
+ final int names_length = names.length;
+
+ if(myId != null)
+ {
+ buffer.append("ID=");
+ buffer.append(encode(myId));
+ count++;
+ }
+
+ for(int i=1; i<names_length; i++)
+ {
+ Qualifier this_qualifier = qualifiers.getQualifierByName(names[i]);
+
+ if(this_qualifier == null)
+ continue;
+
+ final String this_qualifier_str = getQualifierString(this_qualifier, true);
+ if(this_qualifier_str == null)
+ continue;
+
+ if(count != 0)
+ buffer.append(";");
+ buffer.append(this_qualifier_str);
+ count++;
+ }
+
+ boolean lname;
+ for(Qualifier this_qualifier: qualifiers)
+ {
+ lname = false;
+ for(int j=0; j<names_length; j++)
+ if(this_qualifier.getName().equals(names[j]))
+ lname = true;
+
+ if(lname)
+ continue;
+
+ if(attrs_to_filter.contains(this_qualifier.getName()))
+ continue;
+
+ if( (this_qualifier.getName().equals("private") && System.getProperty("noprivate") != null) ||
+ (this_qualifier.getName().equals("history") && System.getProperty("nohistory") != null) )
+ continue;
+
+ final String this_qualifier_str = getQualifierString(this_qualifier, false);
+
+ if(this_qualifier_str == null)
+ continue;
+
+ if(count != 0)
+ buffer.append(";");
+ buffer.append(this_qualifier_str);
+ }
+
+ return buffer.toString();
+ }
+
+
+ /**
+ * Get the translation qualifier string for polypeptide features.
+ */
+ private String getTranslation()
+ {
+ if (! getKey().getKeyString().equals("polypeptide"))
+ return null;
+ if (chadoGene != null)
+ {
+ if(getUserData() == null)
+ new uk.ac.sanger.artemis.Feature(this);
+ // the above line constructs the appropriate userData within this current GFFStreamFeature object,
+ // which is required by the following GeneUtils.deriveResidues()
+ String residues = GeneUtils.deriveResidues(this);
+ if (residues != null)
+ return "translation="+residues;
+ }
+ return null;
+ }
+
+ /**
+ * Used to write out the GFF attributes.
+ * @param q the qualifier to represent as a <code>String</code>
+ * @param reserved indicate if this is one of the reserved tags or not
+ * @return the <code>String</code> representation
+ */
+ private String getQualifierString(Qualifier q, boolean reserved )
+ {
+ StringBuffer buffer = new StringBuffer();
+ final String name = q.getName();
+
+ if(name.equals("codon_start") || name.equals("gff_source") ||
+ name.equals("gff_seqname") || name.equals("score"))
+ return null;
+
+ final StringVector values = q.getValues();
+
+ /* ignore qualifiers with just one empty value, will mess up GFF3 output */
+ if(values != null && values.size() == 1)
+ {
+ if (values.elementAt(0).replaceAll("\\s+","").equals(""))
+ return null;
+ }
+
+ /*
+ * GSV :
+ * The Bio::FeatureIO perl module falls over if there are Uppercased
+ * attribute names for tags which aren't part of the standard reserved
+ * set. So we lowercase these, since in the specification it says :
+ *
+ * "All attributes that begin with an uppercase letter are reserved for
+ * later use. Attributes that begin with a lowercase letter can be used
+ * freely by applications."
+ * see http://www.sequenceontology.org/gff3.shtml
+ */
+ String nameToBuffer = encode(name);
+
+ if (! reserved)
+ nameToBuffer = Character.toLowerCase(nameToBuffer.charAt(0)) + nameToBuffer.substring(1);
+ buffer.append(nameToBuffer);
+
+ if(values != null && values.size() > 0)
+ {
+ buffer.append('=');
+ for(int value_index = 0; value_index < values.size();
+ ++value_index)
+ {
+ final String this_value;
+ if(name.equals("class"))
+ {
+ int index = values.elementAt(value_index).indexOf("::");
+ if(index > -1)
+ this_value = encode(values.elementAt(value_index).substring(0,index));
+ else
+ this_value = encode(values.elementAt(value_index));
+ }
+ else
+ this_value = encode(values.elementAt(value_index));
+
+ if(value_index>0)
+ buffer.append("%2C");
+
+ if(name.equals("Parent"))
+ buffer.append(this_value);
+ else
+ {
+ try
+ {
+ buffer.append(Integer.valueOf(this_value));
+ }
+ catch(NumberFormatException _)
+ {
+ // not an integer
+ try
+ {
+ buffer.append(Double.valueOf(this_value));
+ }
+ catch (NumberFormatException __)
+ {
+ // not a double or integer so quote it
+ buffer.append(this_value);
+ }
+ }
+ }
+ }
+ }
+ if (buffer.toString().charAt(buffer.toString().length()-1) == '=')
+ System.out.println(buffer.toString() + " ----- values length was " + values.size() + ": '" + values.elementAt(0) + "'");
+ return buffer.toString();
+ }
+
+ /**
+ * Parse the given String as ACeDB format attributes.
+ * Adapted from code by Matthew Pocock for the BioJava project.
+ *
+ * Modified for gff-version 3.
+ * @return Return a Hashtable. Each key is an attribute name and each value
+ * of the Hashtable is a StringVector containing the attribute values.
+ * If the attribute has no value then the Hashtable value will be a zero
+ * length vector.
+ **/
+ private Hashtable<String, StringVector> parseAttributes(final String att_val_list)
+ {
+ final Hashtable<String, StringVector> attr = new Hashtable<String, StringVector>();
+
+ int ind_start = 0;
+ int ind_end;
+ while( (ind_end = att_val_list.indexOf(";",ind_start)) > -1 ||
+ ind_start < att_val_list.length() )
+ {
+ if(ind_end < 0)
+ ind_end = att_val_list.length();
+
+ final String this_token = decode(att_val_list.substring(ind_start, ind_end).trim());
+ ind_start = ind_end+1;
+
+ int index_of_first_space = this_token.indexOf(" ");
+
+ final String att_name;
+ StringVector att_values = new StringVector();
+
+ if( this_token.indexOf("=") > -1 &&
+ (this_token.indexOf("=") < index_of_first_space ||
+ index_of_first_space == -1) )
+ {
+ index_of_first_space = this_token.indexOf("=");
+ att_name = this_token.substring(0, index_of_first_space);
+ att_values.add(this_token.substring(index_of_first_space+1).trim());
+ }
+ else if(index_of_first_space == -1)
+ att_name = this_token;
+ else
+ {
+ att_name = this_token.substring(0, index_of_first_space);
+
+ String rest_of_token =
+ this_token.substring(index_of_first_space+1).trim();
+
+ while(rest_of_token.length() > 0)
+ {
+ if(rest_of_token.startsWith("\""))
+ {
+ int quote_index = 0;
+ do
+ {
+ quote_index++;
+ quote_index = rest_of_token.indexOf("\"", quote_index);
+ } while(quote_index > -1 &&
+ rest_of_token.charAt(quote_index - 1) == '\\');
+
+ if(quote_index < 0)
+ {
+ // no closing quote - panic
+ final Hashtable<String, StringVector> panic_attributes =
+ new Hashtable<String, StringVector>();
+ final StringVector notes = new StringVector();
+ notes.add(att_val_list);
+ panic_attributes.put("note", notes);
+
+ return panic_attributes;
+ }
+
+ final String next_bit = rest_of_token.substring(1, quote_index);
+ att_values.add(next_bit);
+ rest_of_token = rest_of_token.substring(quote_index + 1).trim();
+ }
+ else
+ {
+ final int index_of_next_space = rest_of_token.indexOf(" ");
+
+ if(index_of_next_space == -1)
+ {
+ att_values.add(rest_of_token);
+ rest_of_token = "";
+ }
+ else
+ {
+ final String next_bit =
+ rest_of_token.substring(0, index_of_next_space);
+
+ att_values.add(next_bit);
+ rest_of_token =
+ rest_of_token.substring(index_of_next_space).trim();
+ }
+ }
+ }
+
+ if(!rest_of_token.equals(""))
+ att_values.add(rest_of_token);
+ }
+
+ if(att_name.equals("Dbxref") || att_name.equals("Alias")) // convert to multi-line
+ {
+ StringTokenizer stok =
+ new StringTokenizer((String)att_values.get(0), ",");
+ StringVector str_values = new StringVector();
+ while(stok.hasMoreTokens())
+ str_values.add(stok.nextToken());
+
+ att_values = str_values;
+ }
+
+ if(att_name.equals("timelastmodified"))
+ {
+ try
+ {
+ this.timelastmodified =
+ new Timestamp( Long.parseLong((String)att_values.get(0)) );
+ SimpleDateFormat date_format =
+ new SimpleDateFormat("dd.MM.yyyy hh:mm:ss z");
+ att_values.set(0,date_format.format(timelastmodified));
+ }
+ catch(NumberFormatException e)
+ {
+ att_values.set(0,(String)att_values.get(0));
+ }
+ }
+
+ if(attr.get(att_name) != null)
+ attr.get(att_name).add(att_values);
+ else
+ attr.put(att_name, att_values);
+ }
+
+ return attr;
+ }
+
+ /**
+ * Get the feature time last modified timestamp.
+ * @return
+ */
+ public Timestamp getLastModified()
+ {
+ return timelastmodified;
+ }
+
+ /**
+ * Get the GFF_source value of a Dbxref qualifier.
+ * @param qualifier
+ * @return the gff_source value or NULL
+ */
+ private String getDbxrefGFFSource(final Qualifier qualifier)
+ {
+ StringVector qualifier_strings =
+ StreamQualifier.toStringVector(null, qualifier);
+
+ for(int i=0; i<qualifier_strings.size(); i++)
+ {
+ String qualifier_string = (String)qualifier_strings.elementAt(i);
+
+ if(qualifier_string.indexOf("GFF_source:") >-1)
+ {
+ int index = qualifier_string.indexOf(":")+1;
+ int len = qualifier_string.length();
+ if(qualifier_string.endsWith("\""))
+ len--;
+ return qualifier_string.substring(index, len);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Set the feature time last modified timestamp.
+ * @param timelastmodified
+ */
+ public void setLastModified(final Timestamp timelastmodified)
+ {
+ this.timelastmodified = timelastmodified;
+
+ // now update the qualifier value itself
+ QualifierVector qualifiers = getQualifiers();
+ Qualifier qualifier = qualifiers.getQualifierByName("timelastmodified");
+ SimpleDateFormat date_format =
+ new SimpleDateFormat("dd.MM.yyyy hh:mm:ss z");
+
+ if(qualifier != null)
+ qualifier.removeValue((String)qualifier.getValues().get(0));
+ else
+ {
+ try
+ {
+ qualifier = new Qualifier("timelastmodified",
+ date_format.format(timelastmodified));
+ setQualifier(qualifier);
+ return;
+ }
+ catch(EntryInformationException eie)
+ {}
+ catch(ReadOnlyException roe)
+ {}
+ }
+
+ qualifier.addValue(date_format.format(timelastmodified));
+ }
+
+ /**
+ * Returns true if and only if this Feature can't be changed or can't be
+ * removed from it's entry.
+ **/
+ public boolean isReadOnly ()
+ {
+ if(readOnlyFeature)
+ return true;
+ return super.isReadOnly();
+ }
+
+ public void setReadOnlyFeature(boolean readOnlyFeature)
+ {
+ this.readOnlyFeature = readOnlyFeature;
+ }
+
+ public ChadoCanonicalGene getChadoGene()
+ {
+ return chadoGene;
+ }
+
+ public void setChadoGene(ChadoCanonicalGene chadoGene)
+ {
+ this.chadoGene = chadoGene;
+ }
+
+ public boolean isVisible()
+ {
+ return visible;
+ }
+
+ public void setVisible(boolean visible)
+ {
+ this.visible = visible;
+ }
+
+ public String getGffSeqName()
+ {
+ return gffSeqName;
+ }
+
+ public void setGffSeqName(String gffSeqName)
+ {
+ this.gffSeqName = gffSeqName;
+ }
+
+ public String getGffSource()
+ {
+ return gffSource;
+ }
+
+ public void setGffSource(String gffSource)
+ {
+ this.gffSource = gffSource;
+ }
+
+ public boolean isLazyLoaded()
+ {
+ return lazyLoaded;
+ }
+
+ public void setLazyLoaded(boolean lazyLoaded)
+ {
+ this.lazyLoaded = lazyLoaded;
+ }
+
+ public org.gmod.schema.sequence.Feature getChadoLazyFeature()
+ {
+ return chadoLazyFeature;
+ }
+
+ public void setChadoLazyFeature(
+ org.gmod.schema.sequence.Feature chadoLazyFeature)
+ {
+ this.chadoLazyFeature = chadoLazyFeature;
+ }
+
+ protected static boolean isGTF(Feature feature)
+ {
+ if(!(feature instanceof GFFStreamFeature))
+ return false;
+
+ final String names[] = { "ID", "Name", "Alias", "Parent",
+ "Derives_from",
+ "Target", "Gap", "Note",
+ "Dbxref", "Ontology_term" };
+
+ for(String name: names)
+ {
+ if(feature.getQualifiers().getQualifierByName(name) != null)
+ return false;
+ }
+
+ if(feature.getQualifiers().getQualifierByName("gene_id") != null &&
+ feature.getQualifiers().getQualifierByName("transcript_id") != null)
+ {
+ if(feature.getEntry() != null)
+ logger4j.debug(feature.getEntry().getName()+" is in GTF format");
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/GFFUtils.java b/uk/ac/sanger/artemis/io/GFFUtils.java
new file mode 100644
index 0000000..96cefa4
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GFFUtils.java
@@ -0,0 +1,125 @@
+/* GFFUtils.java
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import java.util.Vector;
+
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+
+
+public class GFFUtils
+{
+ /**
+ * Test if this is feature is marked as having a stop codon
+ * redefined as selenocysteine
+ * @param f
+ * @return
+ */
+ public static boolean isSelenocysteine(Feature f)
+ {
+ if(!(f instanceof GFFStreamFeature))
+ return false;
+ try
+ {
+ ChadoCanonicalGene gffGene = ((GFFStreamFeature)f).getChadoGene();
+ if(gffGene == null)
+ return false;
+ String transcript = gffGene.getTranscriptFromName(
+ GeneUtils.getUniqueName(f));
+ if(transcript == null)
+ return false;
+ Feature pep = gffGene.getProteinOfTranscript(transcript);
+ if(pep == null)
+ return false;
+ if(pep.getQualifierByName("stop_codon_redefined_as_selenocysteine") != null)
+ return true;
+ }
+ catch (Exception e){}
+
+ return false;
+ }
+
+ /**
+ * Update the segment range store for GFFStreamFeature with a new location
+ * @param gff
+ * @param oldLocation
+ * @param newLocation
+ */
+ public static void updateSegmentRangeStore(final GFFStreamFeature gff,
+ final Location oldLocation,
+ final Location newLocation)
+ {
+ final RangeVector rv_new = newLocation.getRanges();
+ final RangeVector rv_old = oldLocation.getRanges();
+ if(rv_new.size() != rv_old.size())
+ {
+ final RangeVector rangesToAdd = new RangeVector();
+ for(Range r: rv_new)
+ if(!rv_old.containsRange(r))
+ rangesToAdd.add(r);
+
+ final Vector<Integer> deleted = new Vector<Integer>();
+ for(int ideleted = 0; ideleted < rv_old.size(); ideleted++)
+ if(!rv_new.containsRange(rv_old.get(ideleted)))
+ deleted.add(ideleted);
+
+ try
+ {
+ if(gff.getQualifierByName("Parent") != null)
+ GeneUtils.addSegment(gff, rangesToAdd,
+ gff.getQualifierByName("Parent").getValues().get(0));
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ for(Integer d: deleted)
+ gff.getSegmentRangeStore().remove(
+ gff.getSegmentID(rv_old.elementAt(d)));
+ }
+ else if(gff.getSegmentRangeStore() != null)
+ {
+ Vector<Integer> changes = new Vector<Integer>();
+ for(int i=0; i<rv_old.size(); i++)
+ {
+ Range rnew = rv_new.elementAt(i);
+ Range rold = rv_old.elementAt(i);
+
+ if(rnew.getStart() != rold.getStart() ||
+ rnew.getEnd() != rold.getEnd() ||
+ (oldLocation.isComplement(rold) !=
+ newLocation.isComplement(rnew)))
+ changes.add(i);
+ }
+
+ for(Integer c: changes)
+ {
+ Range rnew = rv_new.elementAt(c);
+ String segId = gff.getSegmentID(rnew);
+ if(segId == null)
+ segId = gff.getSegmentID(rv_old.elementAt(c));
+ gff.getSegmentRangeStore().put(segId, rnew);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/GenbankDocumentEntry.java b/uk/ac/sanger/artemis/io/GenbankDocumentEntry.java
new file mode 100644
index 0000000..a885c66
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GenbankDocumentEntry.java
@@ -0,0 +1,110 @@
+/* GenbankDocumentEntry.java
+ *
+ * created: Sun Sep 12 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/GenbankDocumentEntry.java,v 1.1 2004-06-09 09:49:35 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+import java.io.IOException;
+
+/**
+ * A DocumentEntry that can read a GENBANK entry from a Document.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GenbankDocumentEntry.java,v 1.1 2004-06-09 09:49:35 tjc Exp $
+ **/
+
+public class GenbankDocumentEntry extends PublicDBDocumentEntry {
+ /**
+ * Create a new GenbankDocumentEntry object associated with the given
+ * Document.
+ * @param entry_information The EntryInformation object for this Entry.
+ * @param document This is the file that we will read from. This is also
+ * used for saving the entry back to the file it came from and to give
+ * the new object a name.
+ * @param listener The object that will listen for ReadEvents.
+ * @exception IOException thrown if there is a problem reading the entry -
+ * most likely ReadFormatException.
+ * @exception EntryInformationException Thrown if force is false and if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ public GenbankDocumentEntry (final EntryInformation entry_information,
+ final Document document,
+ final ReadListener listener)
+ throws IOException, EntryInformationException {
+ super (entry_information, document, listener);
+ }
+
+ /**
+ * Create a new GenbankDocumentEntry that will be a copy of the given
+ * Entry and has no Document associated with it. The new
+ * GenbankDocumentEntry cannot be saved to a file with save () unless save
+ * (Document) has been called first. The save (Document) method will
+ * assign a Document..
+ * @param entry_information The EntryInformation object for this Entry.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys in the new Entry will be quietly thrown away. "Invalid"
+ * means that the key/qualifier is not allowed to occur in an Entry of
+ * this type (probably determined by the EntryInformation object of this
+ * Entry). If false an EntryInformationException will be thrown for
+ * invalid keys or qualifiers.
+ * @exception EntryInformationException Thrown if force is false and if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ public GenbankDocumentEntry (final EntryInformation entry_information,
+ final Entry new_entry, final boolean force)
+ throws EntryInformationException {
+ super (entry_information, new_entry, force);
+ }
+
+ /**
+ * Create a new GenbankDocumentEntry that will be a copy of the given
+ * Entry and has no Document associated with it. The new
+ * GenbankDocumentEntry cannot be saved to a file with save () unless save
+ * (Document) has been called first. The save (Document) method will
+ * assign a Document. The new DocumentEntry will use a copy of the
+ * EntryInformation object from the given Entry.
+ * @exception EntryInformationException Thrown if this Entry cannot contain
+ * the Key, Qualifier or Key/Qualifier combination of one of the features
+ * in the given Entry.
+ **/
+ public GenbankDocumentEntry (final Entry new_entry)
+ throws EntryInformationException {
+ super (new SimpleEntryInformation (new_entry.getEntryInformation ()),
+ new_entry, false);
+ }
+
+ /**
+ * Create a new empty GenbankDocumentEntry object that has no Document
+ * associated with it. The new GenbankDocumentEntry cannot be saved to a
+ * file with save () unless save (Document) has been called first. The
+ * save (Document) method will assign a Document.
+ * @param entry_information The EntryInformation object for this Entry.
+ **/
+ public GenbankDocumentEntry (final EntryInformation entry_information) {
+ super (entry_information);
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/GenbankMisc.java b/uk/ac/sanger/artemis/io/GenbankMisc.java
new file mode 100644
index 0000000..41ab351
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GenbankMisc.java
@@ -0,0 +1,76 @@
+/* GenbankMisc.java
+ *
+ * created: Sun Sep 12 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/GenbankMisc.java,v 1.1 2004-06-09 09:49:36 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This class is used to store EMBL entry lines that are not handled by other
+ * classes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GenbankMisc.java,v 1.1 2004-06-09 09:49:36 tjc Exp $
+ **/
+
+public class GenbankMisc extends MiscLineGroup {
+ /**
+ * Create a new GenbankMisc object. One or more lines are read from the
+ * in_stream and stored.
+ **/
+ public GenbankMisc (LinePushBackReader in_stream)
+ throws IOException {
+ super (in_stream.readLine ());
+
+ if (getLine ().startsWith ("FEATURES ")) {
+ // special case (hack) for "FEATURES" line - just read one line.
+ return;
+ }
+ while (true) {
+ final String temp_line = in_stream.readLine ();
+
+ if (temp_line != null && temp_line.startsWith (" ")) {
+ setLine (getLine () + "\n" + temp_line);
+ } else {
+ // end of line group
+ in_stream.pushBack (temp_line);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Write the line stored in this GenbankMisc object to the given stream.
+ * @param writer The stream to write to.
+ **/
+ public void writeToStream (final Writer writer)
+ throws IOException {
+
+ writer.write (toString ());
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/GenbankStreamFeature.java b/uk/ac/sanger/artemis/io/GenbankStreamFeature.java
new file mode 100644
index 0000000..00c3bff
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GenbankStreamFeature.java
@@ -0,0 +1,114 @@
+/* GenbankStreamFeature.java
+ *
+ * created: Mon Sep 13 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/GenbankStreamFeature.java,v 1.1 2004-06-09 09:49:37 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.*;
+
+/**
+ * A StreamFeature that thinks it is a GENBANK feature.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GenbankStreamFeature.java,v 1.1 2004-06-09 09:49:37 tjc Exp $
+ **/
+
+public class GenbankStreamFeature extends PublicDBStreamFeature {
+ /**
+ * Create a new GenbankStreamFeature object.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ **/
+ public GenbankStreamFeature (Key key,
+ Location location,
+ QualifierVector qualifiers)
+ throws InvalidRelationException {
+ super (key, location, qualifiers);
+ }
+
+ /**
+ * Create a new GenbankStreamFeature with the same key, location and
+ * qualifiers as the given feature. The feature should be added to an
+ * Entry (with Entry.add ()).
+ * @param feature The feature to copy.
+ **/
+ public GenbankStreamFeature (final Feature feature) {
+ super (feature);
+ }
+
+
+ /**
+ * Create a new, blank GENBANK feature. It will have no qualifiers, a key
+ * of CDS and a location of 1.
+ **/
+ public GenbankStreamFeature () {
+ super (makeBlankFeature ());
+ }
+
+ /**
+ * Called by the constructor.
+ **/
+ private static EmblStreamFeature makeBlankFeature () {
+ try {
+ return new EmblStreamFeature (Key.CDS, new Location ("1"),
+ new QualifierVector ());
+ } catch (InvalidRelationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (LocationParseException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Read and return a GenbankStreamFeature from a stream. A feature must be
+ * the next thing in the stream.
+ * @param stream the Feature is read from this stream
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException.
+ * @return null if in_stream is at the end of file when the method is called
+ *
+ **/
+ public static GenbankStreamFeature
+ readFromStream (final LinePushBackReader in_stream)
+ throws IOException {
+ return (GenbankStreamFeature) readFromStream (in_stream,
+ LineGroup.GENBANK_FEATURE);
+ }
+
+ /**
+ * Return the reference of a new copy of this Feature.
+ **/
+ public Feature copy () {
+ final Feature return_value = new GenbankStreamFeature (this);
+
+// System.out.println (return_value.getEntry ());
+
+ return return_value;
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/GenbankStreamSequence.java b/uk/ac/sanger/artemis/io/GenbankStreamSequence.java
new file mode 100644
index 0000000..add8407
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GenbankStreamSequence.java
@@ -0,0 +1,315 @@
+/* GenbankStreamSequence.java
+ *
+ * created: Mon Jun 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/GenbankStreamSequence.java,v 1.6 2008-03-25 13:49:30 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This is a subclass of StreamSequence containing GENBANK format sequence.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GenbankStreamSequence.java,v 1.6 2008-03-25 13:49:30 tjc Exp $
+ **/
+
+public class GenbankStreamSequence extends StreamSequence
+{
+
+ /** The header line(s) of this sequence(set by setHeader()). */
+ private String header_line = null;
+
+ /**
+ * Create a new GenbankStreamSequence object from a stream. The next line
+ * to read from the stream should be the header line of the sequence.
+ * @param in_stream The stream to read from. When the constructor returns
+ * the stream will at the next line after the sequence.
+ **/
+ public GenbankStreamSequence(final LinePushBackReader in_stream)
+ throws IOException
+ {
+ readHeader(in_stream);
+ readSequence(in_stream);
+ }
+
+ /**
+ * Make a new GenbankStreamSequence containing the same sequence as the
+ * given Sequence.
+ **/
+ public GenbankStreamSequence(final Sequence sequence)
+ {
+ setFromChar(((StreamSequence)sequence).getCharSequence());
+ }
+
+ /**
+ * Make a new GenbankStreamSequence containing the same sequence as the
+ * given String.
+ * @param sequence_string The String containing the sequence for the new
+ * GenbankStreamSequence.
+ **/
+ public GenbankStreamSequence(final String sequence_string)
+ {
+ setFromChar(sequence_string.toCharArray());
+ }
+
+ /**
+ * Return a new StreamSequence object that is a copy of this one.
+ **/
+ public StreamSequence copy()
+ {
+ return new GenbankStreamSequence(this);
+ }
+
+ /**
+ * Return the sequence type(GENBANK_FORMAT for this class).
+ **/
+ public int getFormatType()
+ {
+ return StreamSequenceFactory.GENBANK_FORMAT;
+ }
+
+ /**
+ * Read the header for this sequence(if any).
+ **/
+ protected void readHeader(final LinePushBackReader in_stream)
+ throws IOException
+ {
+ String sequence_header = in_stream.readLine();
+
+ // GenBank format has changed - BASE COUNT no longer appears
+ if(sequence_header.startsWith("BASE COUNT"))
+ {
+ final String next_line = in_stream.readLine();
+
+ if(next_line.startsWith("ORIGIN"))
+ sequence_header = sequence_header + next_line;
+ else
+ throw new ReadFormatException("Genbank sequence data should have " +
+ "\"ORIGIN\" on the second line");
+ }
+ else
+ {
+ if(!sequence_header.startsWith("ORIGIN"))
+ throw new ReadFormatException("Genbank sequence data should begin " +
+ "with \"BASE COUNT\" or \"ORIGIN\"");
+ }
+
+ setHeader(sequence_header);
+ }
+
+ /**
+ * Return the header line(s) of this Sequence or null if there is no
+ * header. The returned String will not end in a newline character.
+ **/
+ public String getHeader()
+ {
+ return header_line;
+ }
+
+ /**
+ * Set the header line(s) of this Sequence. The argument should not end in
+ * a newline character.
+ **/
+ public void setHeader(final String sequence_string)
+ {
+ header_line = sequence_string;
+ }
+
+ /**
+ * This method will read raw sequence from the stream in this object
+ * removing whitespace as it goes. No checks are made on the format of the
+ * sequence, apart from checking that the stream contains only letters.
+ **/
+ protected void readSequence(final LinePushBackReader in_stream)
+ throws IOException
+ {
+
+ final int buffer_capacity = 50000;
+
+ // we buffer up the sequence bases then assign them to sequence once all
+ // bases are read
+ String line;
+ setSequencePackingCapacity(buffer_capacity);
+ final StringBuffer this_line_sequence_buffer = new StringBuffer(100);
+ while((line = in_stream.readLine()) != null)
+ {
+ if(line.equals("//"))
+ {
+ // end of the sequence
+ in_stream.pushBack(line);
+ break;
+ }
+
+ // set to true when we see a base - numbers and spaces are allowed before
+ // the first base is seen in the line
+ boolean seen_base = false;
+ this_line_sequence_buffer.setLength(0);
+
+ for(int i = 0 ; i < line.length() ; ++i)
+ {
+ final char this_char = line.charAt(i);
+
+ if(!seen_base)
+ {
+ if(Character.isDigit(this_char) ||
+ Character.isSpaceChar(this_char))
+ {
+ continue;
+ // ok - ignore it
+ }
+ }
+
+ if(Character.isLetter(this_char) ||
+ this_char == '.' ||
+ this_char == '-' ||
+ this_char == '*')
+ {
+ seen_base = true;
+ this_line_sequence_buffer.append(this_char);
+ }
+ else
+ {
+ if(Character.isSpaceChar(this_char)) {
+ // just ignore it
+ }
+ else
+ throw new ReadFormatException("GENBANK sequence file contains " +
+ "a character that is not a " +
+ "letter: " + this_char,
+ in_stream.getLineNumber());
+ }
+ }
+
+ if(this_line_sequence_buffer.length() > 0)
+ appendChar(this_line_sequence_buffer.toString().toLowerCase().toCharArray());
+ }
+ setCounts();
+ }
+
+ /**
+ * Write this Sequence to the given stream.
+ * @param writer The stream to write to.
+ **/
+ public synchronized void writeToStream(final Writer writer)
+ throws IOException
+ {
+ final StringBuffer line_buffer = new StringBuffer(90);
+
+ // first count A,C,G,T and other bases
+
+ final int SEQUENCE_LINE_BASE_COUNT = 60;
+
+ line_buffer.setLength(0);
+ line_buffer.ensureCapacity(90);
+
+ line_buffer.append("BASE COUNT ");
+
+ final int MAX_WIDTH = 7;
+
+ appendAndPad(line_buffer, MAX_WIDTH, String.valueOf(getACount()));
+ line_buffer.append(" a");
+
+ appendAndPad(line_buffer, MAX_WIDTH, String.valueOf(getCCount()));
+ line_buffer.append(" c");
+
+ appendAndPad(line_buffer, MAX_WIDTH, String.valueOf(getGCount()));
+ line_buffer.append(" g");
+
+ appendAndPad(line_buffer, MAX_WIDTH, String.valueOf(getTCount()));
+ line_buffer.append(" t");
+
+ writer.write(line_buffer + "\nORIGIN\n");
+
+ for(int i = 0 ; i < length() ; i += SEQUENCE_LINE_BASE_COUNT)
+ {
+ // get the bases in chunks of at most 60
+ final int this_line_length;
+
+ if(length() - i < SEQUENCE_LINE_BASE_COUNT)
+ this_line_length = length() - i;
+ else
+ this_line_length = SEQUENCE_LINE_BASE_COUNT;
+
+ line_buffer.setLength(0);
+ line_buffer.ensureCapacity(90);
+
+ // the base counter to write at the end of each line
+ final int base_count = i + 1;
+
+ final String string_base_count = String.valueOf(base_count);
+
+ // now pad the line with spaces
+ final int LINE_WIDTH = 80;
+
+ final int MAX_COUNT_WIDTH = 9;
+
+ appendAndPad(line_buffer, MAX_COUNT_WIDTH, string_base_count);
+
+ final int BLOCK_LENGTH = 10;
+ final int BLOCK_COUNT = 6;
+
+ for(int j = 0 ; j < this_line_length ; j += BLOCK_LENGTH)
+ {
+ final int this_block_length;
+
+ line_buffer.append(' ');
+
+ if(this_line_length - j < BLOCK_LENGTH)
+ this_block_length = this_line_length - j;
+ else
+ this_block_length = BLOCK_LENGTH;
+
+ line_buffer.append(getCharSubSequence(i + j + 1, i + j + this_block_length));
+
+//sequence.substring(i + j,
+// i + j + this_block_length));
+ }
+
+ line_buffer.append("\n");
+ writer.write(line_buffer.toString());
+ }
+ }
+
+ /**
+ * Append string to line_buffer, but pad it (in front) with spaces so that
+ * it takes max_width characters.
+ **/
+ private void appendAndPad(final StringBuffer line_buffer,
+ final int max_width,
+ final String string)
+ {
+ final int string_width = string.length();
+
+ for(int char_index = 0; char_index < max_width - string_width;
+ ++char_index)
+ line_buffer.append(' ');
+
+ line_buffer.append(string);
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/io/GenbankTblOutputStream.java b/uk/ac/sanger/artemis/io/GenbankTblOutputStream.java
new file mode 100644
index 0000000..82ae21e
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GenbankTblOutputStream.java
@@ -0,0 +1,184 @@
+/* GenbankTblOutputStream.java
+ *
+ * created: 2008
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+/**
+ * Handle writing tbl format:
+ * http://www.ncbi.nlm.nih.gov/Sequin/table.html
+ */
+public class GenbankTblOutputStream
+{
+
+ /**
+ * Write out an entry as tbl format.
+ * @param entry
+ * @param f
+ */
+ public static void writeEntryAsTbl(final Entry entry, final JFrame f)
+ {
+ final JFileChooser chooser = new JFileChooser();
+ chooser.setMultiSelectionEnabled(false);
+ int returnVal = chooser.showSaveDialog(f);
+ if(returnVal == JFileChooser.CANCEL_OPTION)
+ return;
+
+ final File fileSave = chooser.getSelectedFile();
+ if(fileSave.exists())
+ {
+ returnVal = JOptionPane.showConfirmDialog(f,
+ "File exists. Overwrite?",
+ "File Exists",
+ JOptionPane.YES_NO_OPTION);
+ if(returnVal == JOptionPane.CANCEL_OPTION ||
+ returnVal == JOptionPane.NO_OPTION)
+ return;
+ }
+
+ final FileDocument fileDocument = new FileDocument(fileSave);
+ try
+ {
+ final Writer writer = fileDocument.getWriter();
+ writer.write(">Feature "+entry.getName()+"\n");
+
+ final FeatureVector features = entry.getAllFeatures();
+ final EntryInformation entry_information = entry.getEntryInformation ();
+
+ int count = 0;
+ for(int i=0; i<features.size(); i++)
+ {
+ final Feature feature = features.elementAt(i);
+ if(feature.getKey().getKeyString().equals("source"))
+ continue;
+ if(count > 0)
+ writer.write("\n");
+
+ count++;
+ writeRanges(feature, writer);
+ writeQualifiers(feature, entry_information, writer);
+ }
+ writer.close();
+ }
+ catch(IOException e1)
+ {
+ JOptionPane.showMessageDialog(f, e1.getMessage());
+ }
+ }
+
+ /**
+ * Write out ranges and feature key
+ * @param feature
+ * @param writer
+ * @throws IOException
+ */
+ private static void writeRanges(final Feature feature, final Writer writer)
+ throws IOException
+ {
+ final RangeVector ranges = feature.getLocation().getRanges();
+ if(!feature.isForwardFeature() && ranges.size() > 1)
+ ranges.reverse();
+
+ Range r = (Range)ranges.elementAt(0);
+ if(feature.isForwardFeature())
+ writer.write(r.getStart()+"\t"+r.getEnd());
+ else
+ writer.write(r.getEnd()+"\t"+r.getStart());
+
+ writer.write("\t"+feature.getKey().getKeyString());
+
+ for(int j=1; j<ranges.size(); j++)
+ {
+ writer.write("\n");
+ r = (Range)ranges.elementAt(j);
+ if(feature.isForwardFeature())
+ writer.write(r.getStart()+"\t"+r.getEnd());
+ else
+ writer.write(r.getEnd()+"\t"+r.getStart());
+ }
+ }
+
+ /**
+ * Write out qualifiers
+ * @param feature
+ * @param entry_information
+ * @param writer
+ * @throws IOException
+ */
+ private static void writeQualifiers(final Feature feature,
+ final EntryInformation entry_information,
+ final Writer writer) throws IOException
+ {
+ QualifierVector qualifiers = feature.getQualifiers();
+ for(int j=0; j<qualifiers.size(); j++)
+ {
+ Qualifier current_qualifier = (Qualifier) qualifiers.elementAt(j);
+
+ if(feature.getKey().getKeyString().equals("CDS") &&
+ current_qualifier.getName ().equals("translation"))
+ continue;
+
+ final QualifierInfo qualifier_info =
+ entry_information.getQualifierInfo (current_qualifier.getName ());
+
+ // this will contain one String for each /name=value pair
+ final StringVector qualifier_strings =
+ StreamQualifier.toStringVector (qualifier_info, current_qualifier);
+
+ for(int k=0; k<qualifier_strings.size(); k++)
+ {
+ String qualifierStr = (String)qualifier_strings.get(k);
+ int index = qualifierStr.indexOf("=");
+ if(index < 1)
+ {
+ if(current_qualifier.getName().equals("pseudo"))
+ writer.write("\n\t\t\tpseudo");
+ continue;
+ }
+ qualifierStr = qualifierStr.substring(index+1);
+ if(qualifierStr.startsWith("\""))
+ qualifierStr = qualifierStr.substring(1);
+ if(qualifierStr.endsWith("\""))
+ qualifierStr = qualifierStr.substring(0, qualifierStr.length()-1);
+
+ writer.write("\n\t\t\t"+current_qualifier.getName()+"\t"+
+ qualifierStr);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/GffToEMBL.java b/uk/ac/sanger/artemis/io/GffToEMBL.java
new file mode 100644
index 0000000..838247b
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/GffToEMBL.java
@@ -0,0 +1,286 @@
+/*
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Vector;
+
+import javax.swing.JOptionPane;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+class GffToEMBL
+{
+ /**
+ * Convert GFF to EMBL
+ * @param inGff
+ * @param outDir
+ * @param emblSubmission
+ * @param flatten
+ * @param gzip
+ */
+ public GffToEMBL(final String inGff,
+ final String outDir,
+ final boolean emblSubmission,
+ final boolean flatten,
+ final boolean gzip)
+ {
+ final Entry entry = getEntry(inGff);
+ if(entry == null || !(entry instanceof GFFDocumentEntry) )
+ {
+ JOptionPane.showMessageDialog(null,
+ "No GFF entry found.", "Error", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ EntryGroup egrp = null;
+ try
+ {
+ uk.ac.sanger.artemis.Entry artEntry = new uk.ac.sanger.artemis.Entry(entry);
+ egrp = new SimpleEntryGroup(artEntry.getBases());
+ egrp.add(artEntry);
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ catch (NoSequenceException e) {} // no sequence
+
+
+ if(egrp == null) // no sequence found
+ {
+ final FeatureVector features = entry.getAllFeatures();
+ final HashSet<String> seqNames = new HashSet<String>();
+ for(Feature f: features)
+ seqNames.add(((GFFStreamFeature)f).getGffSeqName());
+
+ for(String seq: seqNames)
+ {
+ Entry newEntry = getEntryForSeqName(seq, features);
+ writeEMBL(newEntry, seq, outDir, emblSubmission, flatten, gzip);
+ }
+ }
+ else
+ {
+ writeEMBL(entry, entry.getName(), outDir, emblSubmission, flatten, gzip);
+ }
+ }
+
+ /**
+ * Write EMBL file
+ * @param entry
+ * @param seqName
+ * @param outDir
+ * @param emblSubmission
+ * @param gzip
+ */
+ private void writeEMBL(
+ final Entry entry,
+ final String seqName,
+ final String outDir,
+ final boolean emblSubmission,
+ final boolean flatten,
+ final boolean gzip)
+ {
+ final EntryInformation entryInfo;
+ if(emblSubmission)
+ entryInfo =Options.getDBEntryInformation();
+ else
+ entryInfo = Options.getArtemisEntryInformation();
+
+ if(!flatten)
+ {
+ final FeatureVector features = entry.getAllFeatures();
+ for(int i=0; i<features.size(); i++)
+ ReadAndWriteEntry.addAllKeysQualifiers(entryInfo, features.elementAt(i));
+
+ if(entry instanceof GFFDocumentEntry)
+ ReadAndWriteEntry.addQualifierToEntryInfo(entryInfo,
+ (String)PublicDBDocumentEntry.getDatabaseQualifiersToRemove()[0]);
+ }
+
+ try
+ {
+ final EmblDocumentEntry emblEntry =
+ new EmblDocumentEntry (entryInfo, entry, true);
+ FileDocument out = new FileDocument(new File(outDir+File.separator+
+ seqName+".embl"+(gzip ? ".gz" : "")));
+ emblEntry.save(out);
+
+ System.out.println("Written... "+out.getFile().getAbsolutePath());
+ }
+ catch (EntryInformationException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IOException e)
+ {
+ JOptionPane.showMessageDialog(null,
+ e.getMessage(), "I/O Error", JOptionPane.ERROR_MESSAGE);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Get a new entry for a sequence
+ * @param seqName
+ * @param features
+ * @return
+ */
+ private Entry getEntryForSeqName(
+ final String seqName,
+ final FeatureVector features)
+ {
+ final GFFDocumentEntry newEntry = new GFFDocumentEntry(null);
+ for(Feature f: features)
+ {
+ final GFFStreamFeature gff = (GFFStreamFeature)f;
+ if(gff.getGffSeqName().equals(seqName))
+ try
+ {
+ newEntry.forcedAdd(new GFFStreamFeature(gff));
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return newEntry;
+ }
+
+ private Entry getEntry(final String fileName)
+ {
+ try
+ {
+ // move away index file
+ File f = new File(fileName);
+ if(IndexedGFFDocumentEntry.isIndexed( f ))
+ {
+ f = new File(f.getAbsolutePath() + ".tbi");
+ File tmp = new File(f.getAbsolutePath() + ".old");
+ f.renameTo(tmp);
+ f = tmp;
+ }
+ else
+ f = null;
+
+ final Document doc = DocumentFactory.makeDocument(fileName);
+ final Entry entry = DocumentEntryFactory.makeDocumentEntry(
+ Options.getArtemisEntryInformation(),doc,null);
+
+ // move back index file
+ if(f != null)
+ {
+ String name = f.getAbsolutePath();
+ f.renameTo(new File(name.substring(0, name.length()-4)));
+ }
+ return entry;
+ }
+ catch(EntryInformationException e)
+ {
+ JOptionPane.showMessageDialog(null,
+ e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ catch(IOException e)
+ {
+ JOptionPane.showMessageDialog(null,
+ e.getMessage(), "I/O Error", JOptionPane.ERROR_MESSAGE);
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static void main(String args[])
+ {
+ if( (args != null && args.length == 1 && args[0].startsWith("-h")) ||
+ (args == null || args.length < 1))
+ {
+ System.out.println("-h\tshow help");
+ System.out.println("-s\tspace separated list of sequences to read and write out");
+ System.out.println("-o\toutput directory");
+ System.out.println("-f\t[y|n] flatten the gene model, default is y");
+ System.out.println("-z\t[y|n] gzip output, default is y");
+ System.out.println("-a\t[y|n] for EMBL submission format change to n, default is y");
+ System.exit(0);
+ }
+
+ boolean gzip = true;
+ boolean emblSubmission = true;
+ boolean flatten = true;
+ Vector<String> files = new java.util.Vector<String>();;
+ String outDir = System.getProperty("user.dir"); // working directory
+ for(int i=0; i<args.length; i++)
+ {
+ String s = args[i];
+ if(s.equals("-z"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("n"))
+ gzip = false;
+ }
+ else if(s.equals("-a"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("y"))
+ emblSubmission = false;
+ }
+ else if(s.equals("-f"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("n"))
+ flatten = false;
+ }
+ else if(s.equals("-o"))
+ {
+ if(i + 1 < args.length)
+ outDir= args[i + 1];
+ }
+ else if(args[i].toLowerCase().equals("-s"))
+ {
+ for(int j = i + 1; j < args.length; j++)
+ {
+ if(args[j].startsWith("-"))
+ break;
+ files.add(args[j]);
+ }
+ }
+ }
+
+ final File outDirFile = new File(outDir);
+ if(!outDirFile.exists() && !outDirFile.mkdir())
+ {
+ JOptionPane.showMessageDialog(null, "Problems writing to "+
+ outDirFile.getAbsolutePath(), "Error", JOptionPane.ERROR_MESSAGE);
+ System.exit(0);
+ }
+
+ for(String fn: files)
+ new GffToEMBL(fn, outDir, emblSubmission, flatten, gzip);
+ System.exit(0);
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/IndexFastaStream.java b/uk/ac/sanger/artemis/io/IndexFastaStream.java
new file mode 100644
index 0000000..479f030
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/IndexFastaStream.java
@@ -0,0 +1,360 @@
+/*
+ * created: 2010
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.JOptionPane;
+
+import net.sf.picard.reference.FastaSequenceIndex;
+import net.sf.picard.reference.IndexedFastaSequenceFile;
+import net.sf.picard.reference.ReferenceSequence;
+import net.sf.picard.reference.ReferenceSequenceFileFactory;
+
+import uk.ac.sanger.artemis.io.Entry;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.util.CacheHashMap;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.URLDocument;
+
+public class IndexFastaStream extends StreamSequence
+{
+ private IndexedFastaSequenceFile indexSeqFile;
+ private FastaSequenceIndex fastaIndex;
+ private int len;
+ private String contig;
+ private CacheHashMap basesCache; // used by charAt()
+
+ public IndexFastaStream(Entry entry)
+ {
+ DocumentEntry doc = (DocumentEntry)entry;
+ if(doc instanceof URLDocument)
+ {
+ //URL url = (URL)((URLDocument)doc).getLocation();
+ // not supported yet
+ }
+ else
+ {
+ File fasta = ((FileDocument)doc.getDocument()).getFile();
+ File parentDir = fasta.getParentFile();
+ File fastaIndexFile;
+ if(parentDir != null)
+ fastaIndexFile = new File(parentDir.getAbsolutePath(), fasta.getName() + ".fai");
+ else
+ fastaIndexFile = new File(fasta.getName() + ".fai");
+
+ fastaIndex = new FastaSequenceIndex(fastaIndexFile);
+
+ try
+ {
+ indexSeqFile = new IndexedFastaSequenceFile(fasta, fastaIndex);
+ }
+ catch(IllegalArgumentException ie)
+ {
+ JOptionPane.showConfirmDialog(null,
+ "Expecting fasta extensions:\n"+
+ ReferenceSequenceFileFactory.FASTA_EXTENSIONS.toString()+
+ "\n"+ie.getMessage(),
+ "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ setContigByIndex(0);
+ }
+
+ /**
+ * Test if the entry contains an indexed sequence
+ * @param entry
+ * @return
+ */
+ protected static boolean isIndexed(Entry entry)
+ {
+ try
+ {
+ if (entry instanceof DocumentEntry
+ && ((DocumentEntry) entry).getDocument() instanceof FileDocument)
+ {
+ File fasta = ((FileDocument) ((DocumentEntry) entry).getDocument()).getFile();
+ File parentDir = fasta.getParentFile();
+ File fastaIndexFile;
+ if(parentDir != null)
+ fastaIndexFile = new File(parentDir.getAbsolutePath(), fasta.getName() + ".fai");
+ else
+ fastaIndexFile = new File(fasta.getName() + ".fai");
+ if (fastaIndexFile.exists())
+ {
+ try
+ {
+ setExtensions();
+ }
+ catch(UnsupportedClassVersionError e)
+ {
+ System.err.println("Java version "+System.getProperty("java.version")+
+ " does not support indexed fasta - use Java 1.6 or higher.");
+ return false;
+ }
+ return true;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ protected boolean useIndex()
+ {
+ if(fastaIndex.size() == 1)
+ return true;
+
+ Object[] possibleValues = { "Use index", "Concatenate Sequences" };
+ int sel = JOptionPane.showOptionDialog(null,
+ "Use the FASTA index file (to increase the performance)\n"+
+ "or concatenate the sequences together?",
+ "Indexed FASTA Found",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
+ null, possibleValues, possibleValues[0]);
+
+ return sel == 0;
+ }
+
+ /**
+ * Add to supported FASTA allowed suffixes.
+ */
+ private static void setExtensions()
+ {
+ if(ReferenceSequenceFileFactory.FASTA_EXTENSIONS.contains(".dna"))
+ return;
+ ReferenceSequenceFileFactory.FASTA_EXTENSIONS.add(".dna");
+ ReferenceSequenceFileFactory.FASTA_EXTENSIONS.add(".seq");
+ ReferenceSequenceFileFactory.FASTA_EXTENSIONS.add(".fas");
+ ReferenceSequenceFileFactory.FASTA_EXTENSIONS.add(".ffn");
+ }
+
+ public void setContigByIndex(int seqIndex)
+ {
+ /*ReferenceSequence ref = getReferenceSequence(seqIndex);
+ len = ref.length();
+ contig = ref.getName();*/
+
+ len = getLengthByIndex(seqIndex);
+ contig = getContigByIndex(seqIndex);
+ basesCache = null;
+ }
+
+ /**
+ * Return a the given range of bases as a String. Returns an empty
+ * sequence if the end position is less than the start position.
+ * @param start The start base of the range.
+ * @param end The end base of the range.
+ **/
+ public String getSubSequence(int start, int end)
+ {
+ byte b[] = indexSeqFile.getSubsequenceAt(contig, start, end).getBases();
+ return new String(b).toLowerCase();
+ }
+
+ public char[] getCharSubSequence(int start, int end)
+ {
+ return getSubSequence(start, end).toCharArray();
+ }
+
+ /**
+ * Used by AddMenu.markAmbiguities() to retrieve the sequence character
+ * at a specified position
+ */
+ public char charAt(final int i)
+ {
+ if(basesCache == null)
+ basesCache = new CacheHashMap(250,50);
+
+ if(!basesCache.containsKey(i))
+ {
+ int end = i+249;
+ if(end > length())
+ end = length();
+ String seq = getSubSequence(i, end);
+ for(int idx=0; idx<seq.length(); idx++)
+ basesCache.put(i+idx, seq.charAt(idx));
+ }
+
+ return (Character)basesCache.get(i);
+ }
+
+ private int getLengthByIndex(int seqIndex)
+ {
+ Iterator it = fastaIndex.iterator();
+ int i = 0;
+ while(it.hasNext())
+ {
+ Object obj = it.next();
+ if(i == seqIndex)
+ {
+ String parts[] = obj.toString().split(";");
+ for(String part: parts)
+ {
+ if(part.trim().startsWith("size"))
+ return Integer.parseInt(part.substring(5).trim());
+ }
+ }
+ i++;
+ }
+ return -1;
+ }
+
+ private String getContigByIndex(int seqIndex)
+ {
+ Iterator it = fastaIndex.iterator();
+ int i = 0;
+ while(it.hasNext())
+ {
+ Object obj = it.next();
+ if(i == seqIndex)
+ {
+ String c = obj.toString().split(" ")[1];
+ if(c.endsWith(";"))
+ c = c.substring(0, c.length()-1);
+ return c;
+ }
+ i++;
+ }
+ return null;
+ }
+
+ public Vector<String> getContigs()
+ {
+ Iterator it = fastaIndex.iterator();
+ Vector<String> contigs = new Vector<String>();
+ while(it.hasNext())
+ {
+ String contig = it.next().toString().split(";")[0];
+ if(contig.startsWith("contig "))
+ contig = contig.substring(6).trim();
+ contigs.add( contig );
+ }
+ return contigs;
+ }
+
+ public ReferenceSequence getReferenceSequence(int seqIndex)
+ {
+ int i = 0;
+ ReferenceSequence ref;
+ indexSeqFile.reset();
+ while( (ref=indexSeqFile.nextSequence()) != null )
+ {
+ if(i == seqIndex)
+ return ref;
+ i++;
+ }
+ return null;
+ }
+
+
+ /**
+ * Returns the length of the sequence in bases.
+ **/
+ public int length()
+ {
+ return len;
+ }
+
+ @Override
+ public StreamSequence copy()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getFormatType()
+ {
+ return StreamSequenceFactory.INDEXED_FASTA_FORMAT;
+ }
+
+ public void setFromChar(final char dna[])
+ {
+ JOptionPane.showMessageDialog(null,"Read only sequence.",
+ "Warning", JOptionPane.WARNING_MESSAGE);
+ throw new RuntimeException(new ReadOnlyException());
+ }
+
+ @Override
+ public void writeToStream(Writer writer) throws IOException
+ {
+ // TODO Auto-generated method stub
+ }
+
+ public IndexedFastaSequenceFile getIndexSeqFile()
+ {
+ return indexSeqFile;
+ }
+
+ public FastaSequenceIndex getFastaIndex()
+ {
+ return fastaIndex;
+ }
+
+ public String getContig()
+ {
+ return contig;
+ }
+
+ public static void main(String args[])
+ {
+ EntryInformation new_entry_information =
+ new SimpleEntryInformation(Options.getArtemisEntryInformation());
+
+ try
+ {
+ Entry emblEntry = null;
+ if(args[0].startsWith("http:"))
+ {
+
+
+ }
+ else
+ {
+ final uk.ac.sanger.artemis.Entry entry =
+ new uk.ac.sanger.artemis.Entry(EntryFileDialog.getEntryFromFile(
+ null, new FileDocument(new File(args[0])),
+ new_entry_information, true));
+ emblEntry = entry.getEMBLEntry();
+ }
+
+ IndexFastaStream istream = new IndexFastaStream(emblEntry);
+ System.out.println(istream.getCharSubSequence(1, 8000));
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/IndexedGFFDocumentEntry.java b/uk/ac/sanger/artemis/io/IndexedGFFDocumentEntry.java
new file mode 100644
index 0000000..746012c
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/IndexedGFFDocumentEntry.java
@@ -0,0 +1,1391 @@
+/* IndexedGFFDocumentEntry.java
+ *
+ * created: 2012
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2012 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+package uk.ac.sanger.artemis.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.Vector;
+
+import net.sf.samtools.util.BlockCompressedInputStream;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.components.FeatureDisplay;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.variant.FeatureContigPredicate;
+import uk.ac.sanger.artemis.components.variant.TabixReader;
+import uk.ac.sanger.artemis.util.CacheHashMap;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+
+public class IndexedGFFDocumentEntry implements DocumentEntry
+{
+ private TabixReader reader;
+ private String sequenceNames[];
+ private LinkedHashMap<String, IndexContig> contigHash;
+ private String name;
+
+ private String contig;
+ private boolean combinedReference = false;
+
+ private Document document;
+ private EntryInformation entryInfo;
+ private EntryGroup entryGroup;
+ private int featureCount = -1;
+
+ private boolean isGTF = false;
+ // cache used by getFeatureAtIndex() and indexOf()
+ private CacheHashMap gffCache = new CacheHashMap(150,5);
+
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(IndexedGFFDocumentEntry.class);
+
+ /**
+ * Create a new IndexedGFFDocumentEntry object associated with the given
+ * Document.
+ * @param document This is the file that we will read from. This is also
+ * used for saving the entry back to the file it came from and to give
+ * the new object a name.
+ * @param listener The object that will listen for ReadEvents.
+ * @exception IOException thrown if there is a problem reading the entry -
+ * most likely ReadFormatException.
+ **/
+ public IndexedGFFDocumentEntry(final Document document)
+ {
+ this.document = document;
+ entryInfo = new GFFEntryInformation();
+
+ try
+ {
+ final File gffFile = ((FileDocument)getDocument()).getFile();
+ setName(gffFile.getName());
+
+ reader = new TabixReader(gffFile.getAbsolutePath());
+ sequenceNames = reader.getSeqNames();
+
+ final BlockCompressedInputStream in =
+ (BlockCompressedInputStream) getDocument().getInputStream();
+ String ln;
+ contigHash = new LinkedHashMap<String, IndexContig>(sequenceNames.length);
+ int offset = 0;
+ int cnt = 0;
+ while( (ln = in.readLine()) != null && ln.startsWith("#"))
+ {
+ // ##sequence-region seqid start end
+ if(ln.startsWith("##sequence-region "))
+ {
+ logger4j.debug(ln);
+ final String parts[] = ln.split(" ");
+
+ try
+ {
+ contigHash.put(parts[1], new IndexContig(parts[1], 1, Integer.parseInt(parts[3]), offset));
+ offset+=Integer.parseInt(parts[3]);
+ cnt++;
+ }
+ catch(Exception ae)
+ {
+ contigHash.clear();
+ cnt = 0;
+ break;
+ }
+ }
+ }
+ in.close();
+
+ // no GFF header found
+ if(cnt < 1)
+ {
+ logger4j.debug("No GFF header found for "+gffFile.getAbsolutePath());
+ for(int i=0; i<sequenceNames.length; i++)
+ contigHash.put(sequenceNames[i], new IndexContig(sequenceNames[i], 1, Integer.MAX_VALUE, 0));
+ }
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Used when editing a subsequence and features.
+ * @param constraint base range to edit
+ * @param entry new entry to add features to
+ */
+ public void truncate(final Range constraint, final uk.ac.sanger.artemis.Entry entry)
+ {
+ final FeatureVector features = getFeaturesInRange(constraint);
+ try
+ {
+ for(int i=0; i<features.size(); i++)
+ {
+ final GFFStreamFeature f = (GFFStreamFeature)features.get(i);
+ f.setLocation(f.getLocation().truncate(constraint));
+ f.setReadOnlyFeature(false);
+ entry.getEMBLEntry().forcedAdd(f);
+ }
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ catch (OutOfRangeException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ public FeatureVector getFeaturesInRange(Range range)
+ {
+ if(contig == null)
+ initContig();
+
+ final FeatureVector featuresInRange = new FeatureVector();
+ final List<IndexContig> contigs = getContigsInRange(range);
+
+ for(IndexContig c: contigs)
+ {
+ try
+ {
+ getFeaturesInRange(c, range, featuresInRange);
+ }
+ catch(IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ }
+
+ if(featuresInRange.size() > 0 && GFFStreamFeature.isGTF((Feature)featuresInRange.get(0)))
+ {
+ isGTF = true;
+ // GTF
+ try
+ {
+ mergeGtfFeatures(featuresInRange, "CDS");
+ mergeGtfFeatures(featuresInRange, "exon");
+ }
+ catch (ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+
+ }
+ else
+ {
+ // GFF
+ combineGeneFeatures(featuresInRange);
+ }
+
+ //combineGeneFeatures(featuresInRange);
+ return featuresInRange;
+ }
+
+ private void getFeaturesInRange(IndexContig c, Range range, FeatureVector features) throws NumberFormatException, IOException
+ {
+ int start = getCoordInContigCoords(range.getStart(), c);
+ int end = getCoordInContigCoords(range.getEnd(), c);
+
+ if(isGTF)
+ {
+ // for GTF grab a larger range so Artemis knows about any
+ // connecting exons outside the view
+ start -= 500000;
+ if(start < 1)
+ start = 1;
+ end += 500000;
+ try
+ {
+ range = new Range(start, end);
+ }
+ catch (OutOfRangeException e){}
+ if(end < start)
+ return;
+ }
+
+ String r = c.chr+":"+start+"-"+end;
+ TabixReader.Iterator tabixIterator = null;
+ try
+ {
+ tabixIterator = reader.query(r);
+ }
+ catch(NullPointerException npe){}
+
+ if(tabixIterator == null)
+ return;
+
+ FeatureVector featuresInRange = new FeatureVector();
+ int pos[] = iterate(c, range.getStart(), range.getEnd(), tabixIterator, featuresInRange);
+
+ if(pos[0] < range.getStart() || pos[1] > range.getEnd())
+ {
+ start = getCoordInContigCoords(pos[0], c);
+ end = getCoordInContigCoords(pos[1], c);
+ r = c.chr+":"+start+"-"+end;
+
+ tabixIterator = reader.query(r);
+ if(tabixIterator == null)
+ return;
+ featuresInRange.clear();
+
+ iterate(c, pos[0], pos[1], tabixIterator, featuresInRange);
+ }
+ features.addAll(featuresInRange);
+ }
+
+ private int[] iterate(final IndexContig c,
+ int min, int max,
+ final TabixReader.Iterator tabixIterator,
+ final FeatureVector features) throws NumberFormatException, ReadFormatException, IOException
+ {
+ String ln;
+ while( (ln = tabixIterator.next()) != null )
+ {
+ StringVector parts = StringVector.getStrings(ln, "\t", true);
+ ln = getGffInArtemisCoordinates(ln, parts, c);
+ parts = StringVector.getStrings(ln, "\t", true);
+
+ int sbeg = Integer.parseInt(parts.elementAt(3).trim());
+ int send = Integer.parseInt(parts.elementAt(4).trim());
+
+ if( (sbeg < min && send < min) || (sbeg > max && send > max) )
+ continue;
+
+ GFFStreamFeature gff = new GFFStreamFeature(ln);
+ gff.setReadOnlyFeature(true);
+ features.add(gff);
+
+ if( parts.elementAt(2).equals("gene") )
+ {
+ if(sbeg < min)
+ min = sbeg;
+ if(send > max)
+ max = send;
+ }
+ }
+ return new int[]{min, max};
+ }
+
+ /**
+ * Return the sequences that lie within a given range
+ * @param range
+ * @return
+ */
+ private List<IndexContig> getContigsInRange(Range range)
+ {
+ final List<IndexContig> list = new Vector<IndexContig>();
+ if(!combinedReference)
+ {
+ if(contig != null)
+ {
+ if(contigHash.get(contig) == null)
+ System.err.println(contig+" not found in "+this.getName());
+ else
+ list.add(contigHash.get(contig));
+ }
+ else
+ list.add(contigHash.get(sequenceNames[0]));
+ return list;
+ }
+
+ for (String key : contigHash.keySet())
+ {
+ IndexContig contig = contigHash.get(key);
+
+ if( (range.getStart() >= contig.getOffsetStart() && range.getStart() <= contig.getOffsetEnd()) ||
+ (range.getEnd() >= contig.getOffsetStart() && range.getEnd() <= contig.getOffsetEnd()) ||
+ (contig.getOffsetStart() >= range.getStart() && contig.getOffsetStart() <= range.getEnd()) ||
+ (contig.getOffsetEnd() >= range.getStart() && contig.getOffsetEnd() <= range.getEnd()) )
+ {
+ list.add(contig);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Get the list of contigs in the feature display.
+ * @return
+ */
+ private List<IndexContig> getListOfContigs()
+ {
+ List<IndexContig> contigs = new Vector<IndexContig>();
+ for (String key : contigHash.keySet())
+ {
+ IndexContig c = contigHash.get(key);
+ if(combinedReference || c.chr.equals(contig))
+ contigs.add(c);
+ }
+ return contigs;
+ }
+
+ /**
+ * Get the features start coordinate.
+ * @param gffParts
+ * @param c
+ * @return
+ */
+ private int getStartInArtemisCoords(final StringVector gffParts, final IndexContig c)
+ {
+ int sbeg = Integer.parseInt(((String)gffParts.elementAt(3)).trim());
+ if(combinedReference)
+ sbeg += c.getOffsetStart() - 1;
+ return sbeg;
+ }
+
+ /**
+ * Get the features start coordinate.
+ * @param gffParts
+ * @param c
+ * @return
+ */
+ private int getEndInArtemisCoords(final StringVector gffParts, final IndexContig c)
+ {
+ int send = Integer.parseInt(((String)gffParts.elementAt(4)).trim());
+ if(combinedReference)
+ send += c.getOffsetStart() - 1;
+ return send;
+ }
+
+ /**
+ * Get coordinate on the contig.
+ * @param start
+ * @param c
+ * @return
+ */
+ private int getCoordInContigCoords(int coord, final IndexContig c)
+ {
+ if(combinedReference)
+ coord+=-c.getOffsetStart()+1;
+ if(coord<1)
+ coord = 1;
+ return coord;
+ }
+
+ /**
+ * Get the GFF line for this feature, adjusting the coordinates if contigs
+ * are concatenated.
+ * @param ln
+ * @param gffParts
+ * @param c
+ * @return
+ */
+ private String getGffInArtemisCoordinates(String gffLine, final StringVector gffParts, final IndexContig c)
+ {
+ if(combinedReference)
+ {
+ int sbeg = Integer.parseInt(((String)gffParts.elementAt(3)).trim());
+ int send = Integer.parseInt(((String)gffParts.elementAt(4)).trim());
+
+ sbeg += c.getOffsetStart() - 1;
+ send += c.getOffsetStart() - 1;
+ final StringBuffer newLn = new StringBuffer();
+ for(int i=0; i<gffParts.size(); i++)
+ {
+ if(i==3)
+ newLn.append(sbeg);
+ else if(i==4)
+ newLn.append(send);
+ else
+ newLn.append((String)gffParts.elementAt(i));
+ newLn.append("\t");
+ }
+ gffLine = newLn.toString();
+ }
+ return gffLine;
+ }
+
+ private boolean isTranscript(Key key)
+ {
+ if(key.getKeyString().indexOf("RNA") > -1 ||
+ key.getKeyString().indexOf("transcript") > -1)
+ return true;
+ if(GeneUtils.isNonCodingTranscripts(key))
+ return true;
+ return false;
+ }
+
+ private void combineGeneFeatures(FeatureVector original_features)
+ {
+ Feature this_feature;
+ HashMap<String, ChadoCanonicalGene> chado_gene = new HashMap<String, ChadoCanonicalGene>();
+ try
+ {
+ // find the genes
+ for(int i = 0 ; i < original_features.size() ; ++i)
+ {
+ this_feature = original_features.featureAt(i);
+ final String key = this_feature.getKey().getKeyString();
+ if(this_feature instanceof GFFStreamFeature &&
+ (GeneUtils.isHiddenFeature(key) ||
+ GeneUtils.isObsolete((GFFStreamFeature)this_feature)))
+ ((GFFStreamFeature)this_feature).setVisible(false);
+
+ if(key.equals("gene") || key.equals("pseudogene"))
+ {
+ final Qualifier idQualifier = this_feature.getQualifierByName("ID");
+ if(idQualifier != null)
+ {
+ String id = (String)this_feature.getQualifierByName("ID").getValues().get(0);
+ ChadoCanonicalGene gene = new ChadoCanonicalGene();
+ gene.setGene(this_feature);
+ chado_gene.put(id, gene);
+ ((GFFStreamFeature)this_feature).setChadoGene(gene);
+ }
+ }
+ }
+
+ // find the transcripts
+ HashMap<String, ChadoCanonicalGene> transcripts_lookup = new HashMap<String, ChadoCanonicalGene>();
+ for(int i = 0 ; i < original_features.size() ; ++i)
+ {
+ this_feature = original_features.featureAt(i);
+ // transcript
+ Qualifier parent_qualifier = this_feature.getQualifierByName("Parent");
+ if(parent_qualifier == null || !isTranscript(this_feature.getKey()))
+ continue;
+
+ StringVector parents = parent_qualifier.getValues();
+ for(int j=0; j<parents.size(); j++)
+ {
+ String parent = (String)parents.get(j);
+ if(chado_gene.containsKey(parent))
+ {
+ ChadoCanonicalGene gene = (ChadoCanonicalGene)chado_gene.get(parent);
+
+ // store the transcript ID with its ChadoCanonicalGene object
+ try
+ {
+ transcripts_lookup.put((String)this_feature.getQualifierByName("ID").getValues().get(0),
+ gene);
+ ((GFFStreamFeature)this_feature).setChadoGene(gene);
+ gene.addTranscript(this_feature);
+ }
+ catch(NullPointerException npe)
+ {
+ System.err.println(gene.getGeneUniqueName()+" "+this_feature.getKey().toString()+" "+this_feature.getLocation());
+ }
+ continue;
+ }
+ }
+ }
+
+ // find exons & protein
+ String key;
+ for(int i = 0 ; i < original_features.size() ; ++i)
+ {
+ this_feature = original_features.featureAt(i);
+ // exons
+ key = this_feature.getKey().getKeyString();
+
+ final Qualifier parent_qualifier = this_feature.getQualifierByName("Parent");
+ final Qualifier derives_qualifier = this_feature.getQualifierByName("Derives_from");
+ if(parent_qualifier == null && derives_qualifier == null)
+ continue;
+
+ final Qualifier featureRelationship =
+ this_feature.getQualifierByName("feature_relationship_rank");
+ // compare this features parent_id's to transcript id's in the
+ // chado gene hash to decide if it is part of it
+ final StringVector parent_id;
+
+ if(parent_qualifier != null)
+ parent_id = parent_qualifier.getValues();
+ else
+ parent_id = derives_qualifier.getValues();
+
+ for(int j=0; j<parent_id.size(); j++)
+ {
+ final String parent = (String)parent_id.get(j);
+
+ if(transcripts_lookup.containsKey(parent))
+ {
+ final ChadoCanonicalGene gene = (ChadoCanonicalGene)transcripts_lookup.get(parent);
+ ((GFFStreamFeature)this_feature).setChadoGene(gene);
+
+ if(parent_qualifier == null)
+ gene.addProtein(parent, this_feature);
+ else if(key.equals("three_prime_UTR"))
+ gene.add3PrimeUtr(parent, this_feature);
+ else if(key.equals("five_prime_UTR"))
+ gene.add5PrimeUtr(parent, this_feature);
+ else if(key.equals(DatabaseDocument.EXONMODEL) || key.equals("exon") ||
+ featureRelationship != null ||
+ key.equals("pseudogenic_exon"))
+ gene.addSplicedFeatures(parent, this_feature);
+ else
+ gene.addOtherFeatures(parent, this_feature);
+ }
+ }
+ }
+
+ // now join exons
+ Iterator<String> enum_genes = chado_gene.keySet().iterator();
+ while(enum_genes.hasNext())
+ {
+ ChadoCanonicalGene gene = chado_gene.get(enum_genes.next());
+ combineChadoExons(gene, original_features);
+ }
+
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Combine the features (which are exons) and delete the orignals from this
+ * Entry. The key of this hash will be the group name and the value is a
+ * FeatureVector containing the feature that are in that group. Groups
+ * that have more than one member will be combined.
+ **/
+ private void combineChadoExons(ChadoCanonicalGene gene, FeatureVector features)
+ {
+ final List<Feature> transcripts = gene.getTranscripts();
+ gene.correctSpliceSiteAssignments();
+
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ GFFStreamFeature transcript = (GFFStreamFeature)transcripts.get(i);
+ String transcript_id = (String)(transcript.getQualifierByName("ID").getValues().get(0));
+ Set<String> splicedSiteTypes = gene.getSpliceTypes(transcript_id);
+ if(splicedSiteTypes == null)
+ continue;
+
+ Iterator<String> it = splicedSiteTypes.iterator();
+ Vector<Feature> new_set = new Vector<Feature>();
+ while(it.hasNext())
+ {
+ String type = (String)it.next();
+ List<Feature> splicedSites = gene.getSpliceSitesOfTranscript(transcript_id, type);
+ if(splicedSites == null)
+ continue;
+
+ mergeFeatures(splicedSites, new_set,
+ (String)(transcript.getQualifierByName("ID").getValues().get(0)));
+ features.removeAll(splicedSites);
+ }
+
+ for(int j=0; j<new_set.size(); j++)
+ {
+ features.add(new_set.get(j));
+ if(j == 0)
+ gene.addSplicedFeatures(transcript_id, new_set.get(j), true );
+ else
+ gene.addSplicedFeatures(transcript_id, new_set.get(j));
+ }
+ }
+ }
+
+ private void mergeFeatures(final List<Feature> gffFeatures,
+ final List<Feature> new_set,
+ final String transcript_id)
+ {
+ final Hashtable<String, Range> id_range_store = new Hashtable<String, Range>();
+ final RangeVector new_range_vector = new RangeVector();
+ QualifierVector qualifier_vector = new QualifierVector();
+
+ for (int j = 0; j < gffFeatures.size(); j++)
+ {
+ final GFFStreamFeature this_feature = (GFFStreamFeature) gffFeatures.get(j);
+ final Location this_feature_location = this_feature.getLocation();
+
+ if (this_feature_location.getRanges().size() > 1)
+ {
+ System.err.println("error - new location should have "
+ + "exactly one range " + transcript_id + " "
+ + this_feature.getKey().toString() + " "
+ + this_feature_location.toStringShort());
+ return;
+ }
+
+ final Range new_range = (Range) this_feature_location.getRanges().elementAt(0);
+
+ Qualifier id_qualifier = this_feature.getQualifierByName("ID");
+ if (id_qualifier != null)
+ {
+ String id = (String) (id_qualifier.getValues()).elementAt(0);
+ id_range_store.put(id, new_range);
+ }
+ else
+ logger4j.warn("NO ID FOUND FOR FEATURE AT: "
+ + this_feature.getLocation().toString());
+
+ if (this_feature_location.isComplement())
+ new_range_vector.insertElementAt(new_range, 0);
+ else
+ new_range_vector.add(new_range);
+ qualifier_vector.addAll(this_feature.getQualifiers());
+ }
+
+ final GFFStreamFeature first_old_feature = (GFFStreamFeature) gffFeatures.get(0);
+
+ final Location new_location = new Location(new_range_vector,
+ first_old_feature.getLocation().isComplement());
+
+ qualifier_vector = mergeQualifiers(qualifier_vector, first_old_feature.getLocation().isComplement());
+
+ final GFFStreamFeature new_feature = new GFFStreamFeature(
+ first_old_feature.getKey(), new_location, qualifier_vector);
+
+ if (first_old_feature.getChadoGene() != null)
+ new_feature.setChadoGene(first_old_feature.getChadoGene());
+
+ new_feature.setSegmentRangeStore(id_range_store);
+ new_feature.setGffSource(first_old_feature.getGffSource());
+ new_feature.setGffSeqName(first_old_feature.getGffSeqName());
+ new_feature.setReadOnlyFeature(first_old_feature.isReadOnly());
+
+ // set the ID
+ String ID;
+ try
+ {
+ ID = new_feature.getSegmentID(new_feature.getLocation().getRanges());
+ }
+ catch (NullPointerException npe)
+ {
+ if (new_feature.getQualifierByName("Parent") != null)
+ ID = ((String) new_feature.getQualifierByName("Parent").getValues()
+ .get(0))
+ + ":"
+ + new_feature.getKey().getKeyString()
+ + ":"
+ + new_feature.getLocation().getFirstBase();
+ else
+ ID = new_feature.getKey().getKeyString();
+ }
+ final Qualifier id_qualifier = new_feature.getQualifierByName("ID");
+ id_qualifier.removeValue((String) (id_qualifier.getValues()).elementAt(0));
+ id_qualifier.addValue(ID);
+
+ // set visibility
+ if (GeneUtils.isHiddenFeature(new_feature.getKey().getKeyString())
+ || GeneUtils.isObsolete(new_feature))
+ new_feature.setVisible(false);
+
+ try
+ {
+ new_feature.setLocation(new_location);
+ final Qualifier gene_qualifier = new_feature.getQualifierByName("gene");
+
+ if (gene_qualifier != null
+ && gene_qualifier.getValues().size() > 0
+ && ((String) (gene_qualifier.getValues()).elementAt(0))
+ .startsWith("Phat"))
+ {
+ // special case to handle incorrect output of the Phat gene
+ // prediction tool
+ new_feature.removeQualifierByName("codon_start");
+ }
+
+ new_set.add(new_feature);
+ }
+ catch (ReadOnlyException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch (OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch (EntryInformationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ private QualifierVector mergeQualifiers(final QualifierVector qualifier_vector,
+ final boolean complement)
+ {
+ QualifierVector merge_qualifier_vector = new QualifierVector();
+ boolean seen = false;
+
+ for (int i = 0; i < qualifier_vector.size(); ++i)
+ {
+ Qualifier qual = (Qualifier) qualifier_vector.elementAt(i);
+ if (qual.getName().equals("codon_start"))
+ {
+ if (!complement && !seen)
+ {
+ merge_qualifier_vector.addElement(qual);
+ seen = true;
+ }
+ else if (complement)
+ merge_qualifier_vector.setQualifier(qual);
+ }
+ else if (qual.getName().equals("Alias"))
+ {
+ final Qualifier id_qualifier = merge_qualifier_vector.getQualifierByName("Alias");
+ if (id_qualifier == null)
+ merge_qualifier_vector.addElement(qual);
+ else
+ {
+ String id1 = (String) (id_qualifier.getValues()).elementAt(0);
+ String id2 = (String) (qual.getValues()).elementAt(0);
+ id_qualifier.removeValue(id1);
+ id_qualifier.addValue(id1 + "," + id2);
+ }
+ }
+ else if (!qual.getName().equals("ID")
+ && !qual.getName().equals("feature_id"))
+ merge_qualifier_vector.setQualifier(qual);
+ }
+ return merge_qualifier_vector;
+ }
+
+ /**
+ * Merge function for GTF features
+ * @param original_features
+ * @param keyStr
+ * @throws ReadOnlyException
+ */
+ private void mergeGtfFeatures(FeatureVector original_features, String keyStr) throws ReadOnlyException
+ {
+ Hashtable<String, Vector<GFFStreamFeature>> group = new Hashtable<String, Vector<GFFStreamFeature>>();
+ for(int i=0; i<original_features.size(); i++)
+ {
+ GFFStreamFeature feature = (GFFStreamFeature)original_features.get(i);
+ if(!feature.getKey().getKeyString().equals(keyStr))
+ continue;
+ String transcriptId =
+ ((String) feature.getQualifierByName("transcript_id").getValues().get(0)).replaceAll("'", "");
+ if(group.containsKey(transcriptId))
+ group.get(transcriptId).add(feature);
+ else
+ {
+ Vector<GFFStreamFeature> this_group = new Vector<GFFStreamFeature>();
+ this_group.add(feature);
+ group.put(transcriptId, this_group);
+ }
+ }
+
+ Enumeration<String> enumGroup = group.keys();
+ while(enumGroup.hasMoreElements())
+ {
+ String transcriptId = enumGroup.nextElement();
+ Vector<GFFStreamFeature> this_group = group.get(transcriptId);
+ QualifierVector qualifier_vector = new QualifierVector();
+ final RangeVector new_range_vector = new RangeVector();
+
+ for(GFFStreamFeature this_feature: this_group)
+ {
+ qualifier_vector.addAll(this_feature.getQualifiers());
+
+ final Range new_range = (Range) this_feature.getLocation().getRanges().elementAt(0);
+ if(this_feature.getLocation().isComplement())
+ new_range_vector.insertElementAt(this_feature.getLocation().getTotalRange(), 0);
+ else
+ new_range_vector.add(new_range);
+
+ original_features.remove(this_feature);
+ }
+ final GFFStreamFeature old_feature = (GFFStreamFeature)this_group.get(0);
+
+ final Location new_location = new Location(new_range_vector,
+ old_feature.getLocation().isComplement());
+
+ qualifier_vector = mergeQualifiers(qualifier_vector, new_location.isComplement());
+ if(qualifier_vector.getQualifierByName("gene_id") != null)
+ qualifier_vector.addQualifierValues(new Qualifier("ID",
+ keyStr+":"+qualifier_vector.getQualifierByName("gene_id").getValues().get(0)));
+
+ final GFFStreamFeature new_feature = new GFFStreamFeature(old_feature
+ .getKey(), new_location, qualifier_vector);
+ original_features.add(new_feature);
+ }
+ }
+
+ public boolean hasUnsavedChanges()
+ {
+ return false;
+ }
+
+ public boolean isReadOnly()
+ {
+ return true;
+ }
+
+ public String getHeaderText()
+ {
+ return null;
+ }
+
+ public boolean setHeaderText(String new_header) throws IOException
+ {
+ return true;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public boolean setName(String name)
+ {
+ this.name = name;
+ return true;
+ }
+
+ public Feature createFeature(Key key, Location location,
+ QualifierVector qualifiers) throws EntryInformationException,
+ ReadOnlyException, OutOfRangeException
+ {
+ // not for read only entry
+ return null;
+ }
+
+ public int getFeatureCount()
+ {
+ if(contig == null)
+ initContig();
+
+ if(featureCount > -1)
+ return featureCount;
+
+ featureCount = 0;
+ List<IndexContig> contigs = getListOfContigs();
+ for(IndexContig c: contigs)
+ {
+ int nfeatures = 0;
+ final String r = c.chr+":"+1+"-"+Integer.MAX_VALUE;
+
+ TabixReader.Iterator tabixIterator = reader.query(r);
+ if(tabixIterator == null)
+ continue;
+
+ try
+ {
+ while( tabixIterator.next() != null )
+ {
+ featureCount++;
+ nfeatures++;
+ }
+ c.nfeatures = nfeatures;
+ }
+ catch(IOException ioe){}
+ }
+
+ return featureCount;
+ }
+
+ public Feature add(Feature feature) throws EntryInformationException,
+ ReadOnlyException
+ {
+ return null;
+ }
+
+ public Feature forcedAdd(Feature feature) throws ReadOnlyException
+ {
+ return null;
+ }
+
+ public boolean remove(Feature feature) throws ReadOnlyException
+ {
+ return false;
+ }
+
+
+ public Feature getFeatureAtIndex(int idx)
+ {
+ Object cachedGFF = gffCache.get(idx);
+ if(cachedGFF != null)
+ return (GFFStreamFeature)cachedGFF;
+
+ int cnt = 0;
+ int start = 1;
+
+ final List<IndexContig> contigs = getListOfContigs();
+
+ for(IndexContig c: contigs)
+ {
+ int nfeatures = c.nfeatures;
+ if(idx > cnt+nfeatures)
+ {
+ cnt+=nfeatures;
+ continue;
+ }
+ String r = c.chr+":"+start+"-"+Integer.MAX_VALUE;
+
+ TabixReader.Iterator tabixIterator = reader.query(r);
+ if(tabixIterator == null)
+ return null;
+ try
+ {
+ String ln;
+ while( (ln = tabixIterator.next()) != null )
+ {
+ if(idx == cnt++)
+ {
+ StringVector parts = StringVector.getStrings(ln, "\t", true);
+ final GFFStreamFeature gff = new GFFStreamFeature(
+ getGffInArtemisCoordinates(ln, parts, c));
+
+ gffCache.put(idx, gff);
+
+ // see if the following line is cached and if not cache the
+ // next block of lines - this speeds up the generation of the
+ // feature list
+ if(gffCache.get(idx+1) == null)
+ {
+ cnt = 1;
+ while(cnt < 32 && (ln = tabixIterator.next()) != null)
+ {
+ parts = StringVector.getStrings(ln, "\t", true);
+ gffCache.put(idx+cnt, new GFFStreamFeature(
+ getGffInArtemisCoordinates(ln, parts, c)));
+ cnt++;
+ }
+ }
+ return gff;
+ }
+ }
+ }
+ catch(IOException ioe){}
+ }
+
+ return null;
+ }
+
+ public int indexOf(Feature feature)
+ {
+ if(gffCache.containsValue(feature))
+ {
+ // retrieve from GFF cache
+ for (Object key : gffCache.keySet())
+ {
+ Feature f = (Feature)gffCache.get(key);
+ if(f.equals(feature))
+ return (Integer)key;
+ }
+ }
+
+ final List<IndexContig> contigs = getListOfContigs();
+ int cnt = 0;
+
+ final String keyStr = feature.getKey().getKeyString();
+ final int sbeg1 = feature.getFirstBase();
+ final int send1 = feature.getLastBase();
+
+ for(IndexContig c: contigs)
+ {
+ if(combinedReference && sbeg1 > c.getOffsetEnd() && send1 > c.getOffsetStart())
+ {
+ cnt+=c.nfeatures;
+ continue;
+ }
+
+ String r = c.chr+":"+1+"-"+Integer.MAX_VALUE;
+ TabixReader.Iterator tabixIterator = reader.query(r);
+ if(tabixIterator == null)
+ continue;
+ try
+ {
+ String ln;
+ while( (ln = tabixIterator.next()) != null )
+ {
+ final StringVector parts = StringVector.getStrings(ln, "\t", true);
+ int sbeg2 = getStartInArtemisCoords(parts, c);
+ int send2 = getEndInArtemisCoords(parts, c);
+
+ if(sbeg1 == sbeg2 && parts.get(2).equals(keyStr))
+ {
+ if(send1 == send2 || feature.getLocation().getRanges().size() > 1)
+ {
+ if(gffCache.get(cnt) == null)
+ {
+ // add to cache
+ final GFFStreamFeature gff = new GFFStreamFeature(
+ getGffInArtemisCoordinates(ln, parts, c));
+ gffCache.put(cnt, gff);
+ }
+ return cnt;
+ }
+ }
+ cnt++;
+ }
+ }
+ catch(IOException ioe){}
+ }
+ return -1;
+ }
+
+ public boolean contains(Feature feature)
+ {
+ return (indexOf(feature)>-1);
+ }
+
+ public FeatureEnumeration features()
+ {
+ return new IndexGFFFeatureEnumeration();
+ }
+
+ public FeatureVector getAllFeatures()
+ {
+ return new FeatureVector(){
+ private static final long serialVersionUID = 1L;
+
+ public int size()
+ {
+ return getFeatureCount();
+ }
+
+ public Feature featureAt(int index)
+ {
+ return getFeatureAtIndex(index);
+ }
+ };
+ }
+
+ public Sequence getSequence()
+ {
+ return null;
+ }
+
+
+ public void dispose()
+ {
+ }
+
+ public void save() throws IOException
+ {
+ save(getDocument());
+ }
+
+ public void save(Document document) throws IOException
+ {
+ try
+ {
+ final Writer out = document.getWriter();
+ writeToStream(out);
+ out.close();
+ }
+ catch(NullPointerException npe)
+ {
+ return;
+ }
+ }
+
+ public void writeToStream(Writer writer) throws IOException
+ {
+ }
+
+ public void setDirtyFlag()
+ {
+ }
+
+ public Date getLastChangeTime()
+ {
+ return null;
+ }
+
+ public Document getDocument()
+ {
+ return document;
+ }
+
+ public EntryInformation getEntryInformation()
+ {
+ return entryInfo;
+ }
+
+ /**
+ * Test if the tabix (.tbi) index is present.
+ * @param f
+ * @return
+ */
+ public static boolean isIndexed(File f)
+ {
+ File index = new File(f.getAbsolutePath() + ".tbi");
+ return index.exists();
+ }
+
+ public void updateReference(String contig, boolean combinedReference)
+ {
+ this.contig = contig;
+ this.combinedReference = combinedReference;
+ featureCount = -1;
+
+ gffCache.clear();
+ }
+
+ public void setEntryGroup(EntryGroup entryGroup)
+ {
+ this.entryGroup = entryGroup;
+ }
+
+ private void initContig()
+ {
+ Entry entry = entryGroup.getSequenceEntry().getEMBLEntry();
+ if(entry.getSequence() instanceof IndexFastaStream)
+ updateReference(((IndexFastaStream)entry.getSequence()).getContig(), false);
+ else
+ {
+ int len = 0;
+ int off = 0;
+ for (String key : contigHash.keySet())
+ {
+ IndexContig contig = contigHash.get(key);
+ int clen = contig.getOffsetEnd();
+ if(clen > len)
+ len = clen;
+ off = contig.offset;
+ }
+
+ if(contigHash.size() > 1 && (len == entry.getSequence().length() || off == 0))
+ {
+ // check the order of the contigs/chromosomes
+ checkOffset();
+ updateReference(sequenceNames[0], true);
+ }
+ else
+ updateReference(sequenceNames[0], false);
+ }
+ }
+
+ /**
+ * For concatenated sequences check the offset is the same as that in
+ * the header of the GFF.
+ */
+ private void checkOffset()
+ {
+ if(entryGroup.getSequenceEntry().getFeatureCount() == sequenceNames.length)
+ {
+ final List<IndexContig> list = new Vector<IndexContig>();
+ uk.ac.sanger.artemis.FeatureVector features = entryGroup.getSequenceEntry().getAllFeatures();
+
+ for (String key : contigHash.keySet())
+ {
+ IndexContig contig = contigHash.get(key);
+ list.add(contig);
+ FeatureContigPredicate predicate = new FeatureContigPredicate(contig.chr);
+ for(int j=0; j<features.size(); j++)
+ {
+ if(predicate.testPredicate(features.elementAt(j)))
+ {
+ // correct offset
+ if(contig.getOffsetStart() != features.elementAt(j).getFirstBase() ||
+ contig.offset == 0)
+ {
+ contig.offset = features.elementAt(j).getFirstBase()-1;
+
+ // this needs to be set when the GFF header is missing
+ contig.end = features.elementAt(j).getLastBase()-contig.offset;
+ }
+
+ break;
+ }
+ }
+ }
+ // sort the list by the contig offset
+/* Collections.sort(list, new ContigCompare());
+ contigHash = new LinkedHashMap<String, IndexContig>(sequenceNames.length);
+ for(IndexContig c: list)
+ contigHash.put(c.chr, c);*/
+ }
+ }
+
+ /**
+ * Return true if the FeatureVector contains the given Feature.
+ **/
+ public static boolean contains(final uk.ac.sanger.artemis.Feature f, final uk.ac.sanger.artemis.FeatureVector fs)
+ {
+ final String id = f.getIDString();
+ final String keyStr = f.getKey().toString();
+ final String pId = FeatureDisplay.getParentQualifier(f);
+ final String loc = f.getLocation().toStringShort();
+
+ for(int i=0; i<fs.size(); i++)
+ if(contains(fs.elementAt(i), id, keyStr, pId, loc))
+ return true;
+ return false;
+ }
+
+ private static boolean contains(final uk.ac.sanger.artemis.Feature f,
+ final String id,
+ final String keyStr,
+ final String pId,
+ final String loc)
+ {
+ if(keyStr.equals(f.getKey().getKeyString()))
+ {
+ final String thisParentId = FeatureDisplay.getParentQualifier(f);
+ if( f.getIDString().equals(id) &&
+ (pId != null && thisParentId.equals(pId)) )
+ return true;
+ else if(id.indexOf("{")>-1)
+ {
+ int ind = f.getIDString().lastIndexOf(":");
+ if( ind > -1 && id.startsWith(f.getIDString().substring(0, ind)) &&
+ (pId == null || thisParentId.equals(pId)) )
+ return true;
+ }
+ else if(f.getIDString().indexOf("{")>-1)
+ {
+ int ind = id.lastIndexOf(":");
+ if( ind > -1 && f.getIDString().startsWith(id.substring(0, ind)) &&
+ (pId == null || thisParentId.equals(pId)) )
+ return true;
+ }
+ else if(id != null && id.equals(f.getIDString()))
+ {
+ if(loc.equals(f.getLocation().toStringShort()))
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ class IndexContig
+ {
+ private String chr;
+ private int start;
+ private int end;
+ private int offset;
+ private int nfeatures = 0;
+
+ IndexContig(String chr, int s, int e, int off)
+ {
+ this.chr = chr;
+ this.start = s;
+ this.end = e;
+ this.offset = off;
+ }
+
+ public boolean equals(Object obj)
+ {
+ IndexContig c = (IndexContig)obj;
+ if(chr.equals(c.chr) && start == c.start && end == c.end)
+ return true;
+ return false;
+ }
+
+ private int getOffsetStart()
+ {
+ return start+offset;
+ }
+
+ private int getOffsetEnd()
+ {
+ return end+offset;
+ }
+ }
+
+ class ContigCompare implements Comparator<IndexContig>
+ {
+ public int compare(IndexContig c1, IndexContig c2)
+ {
+ if(c1.offset < c2.offset)
+ return -1;
+ else if(c2.offset > c1.offset)
+ return 1;
+ return 0;
+ }
+ }
+
+ class IndexGFFFeatureEnumeration implements FeatureEnumeration
+ {
+ private FeatureVector features;
+ private int idx = 0;
+ private int contigIdx = 0;
+ private List<IndexContig> contigs;
+
+ public boolean hasMoreFeatures()
+ {
+ if(features == null)
+ {
+ if(entryGroup == null)
+ return false;
+
+ if(contigs == null)
+ {
+ try
+ {
+ contigs = getContigsInRange(
+ new Range(1, entryGroup.getSequenceLength()));
+ }
+ catch (OutOfRangeException e){}
+ }
+ getFeaturesInContig();
+ }
+
+ if(idx < features.size())
+ return true;
+ else
+ {
+ idx = 0;
+ contigIdx++;
+ if(contigIdx < contigs.size())
+ {
+ getFeaturesInContig();
+ if(idx < features.size())
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Feature nextFeature() throws NoSuchElementException
+ {
+ idx++;
+ return features.elementAt(idx-1);
+ }
+
+ private void getFeaturesInContig()
+ {
+ try
+ {
+ features = getFeaturesInRange(
+ new Range(contigs.get(contigIdx).getOffsetStart(),
+ contigs.get(contigIdx).getOffsetEnd()));
+ }
+ catch (OutOfRangeException e){}
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/InvalidKeyException.java b/uk/ac/sanger/artemis/io/InvalidKeyException.java
new file mode 100644
index 0000000..c5eb0c3
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/InvalidKeyException.java
@@ -0,0 +1,58 @@
+/* InvalidKeyException.java
+ *
+ * created: Sun Jan 3 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/InvalidKeyException.java,v 1.1 2004-06-09 09:49:40 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This exception is thrown when a String passed to the Key constructor is
+ * not a valid feature key.
+ *
+ * @author Kim Rutherford
+ * @version $Id: InvalidKeyException.java,v 1.1 2004-06-09 09:49:40 tjc Exp $
+ **/
+
+public class InvalidKeyException extends EntryInformationException {
+ /**
+ * Create a new InvalidKeyException object with the given String as
+ * the message.
+ * @param message the detail message
+ **/
+ public InvalidKeyException (String message, Key key) {
+ super (message);
+ this.key = key;
+ }
+
+ /**
+ * Return the Key that was passed to the constructor.
+ **/
+ public Key getKey () {
+ return key;
+ }
+
+ /**
+ * The Key that was passed to the constructor.
+ **/
+ final private Key key;
+}
diff --git a/uk/ac/sanger/artemis/io/InvalidQualifierException.java b/uk/ac/sanger/artemis/io/InvalidQualifierException.java
new file mode 100644
index 0000000..0e20717
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/InvalidQualifierException.java
@@ -0,0 +1,64 @@
+/* InvalidQualifierException.java
+ *
+ * created: Wed Jan 6 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/InvalidQualifierException.java,v 1.1 2004-06-09 09:49:41 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This exception is thrown when a String passed as the name to the Qualifier
+ * constructor is not a valid feature qualifier name or the the value passed
+ * to the qualifier is invalid.
+ *
+ * @author Kim Rutherford
+ * @version $Id: InvalidQualifierException.java,v 1.1 2004-06-09 09:49:41 tjc Exp $
+ **/
+
+public class InvalidQualifierException extends EntryInformationException {
+ /**
+ * Create a new InvalidQualifierException object with the given String as
+ * the message.
+ * @param message the detail message
+ * @param qualifier The Qualifier that caused the exception.
+ **/
+ public InvalidQualifierException (final String message,
+ final Qualifier qualifier) {
+ super (message);
+
+ this.qualifier = qualifier;
+ }
+
+ /**
+ * Return the Qualifier that was passed to the constructor.
+ **/
+ public Qualifier getQualifier () {
+ return qualifier;
+ }
+
+ /**
+ * The Qualifier that was passed to the constructor.
+ **/
+ final private Qualifier qualifier;
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/InvalidRelationException.java b/uk/ac/sanger/artemis/io/InvalidRelationException.java
new file mode 100644
index 0000000..1fd6b28
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/InvalidRelationException.java
@@ -0,0 +1,89 @@
+/* InvalidRelationException.java
+ *
+ * created: Fri Jan 1 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/InvalidRelationException.java,v 1.1 2004-06-09 09:49:42 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This exception is thrown when an inappropriate Qualifier is added to or
+ * accessed in a Feature. For example adding a /codon_start qualifier to a
+ * misc_feature Feature.
+ *
+ * @author Kim Rutherford
+ * @version $Id: InvalidRelationException.java,v 1.1 2004-06-09 09:49:42 tjc Exp $
+ **/
+
+public class InvalidRelationException extends EntryInformationException {
+ /**
+ * Create a new InvalidRelationException object with the given String as
+ * the message.
+ * @param message the detail message
+ * @param key The key that caused the exception.
+ * @param qualifier The Qualifier that caused the exception.
+ **/
+ public InvalidRelationException (String message, Key key,
+ Qualifier qualifier) {
+ super (message);
+ this.key = key;
+ this.qualifier = qualifier;
+ }
+
+ /**
+ * Create a new InvalidRelationException object with the given String as
+ * the message. This constructor should be used only when the qualifier
+ * that caused the problem is not known.
+ * @param message the detail message
+ * @param key The key that caused the exception.
+ **/
+ public InvalidRelationException (String message, Key key) {
+ super (message);
+ this.key = key;
+ this.qualifier = null;
+ }
+
+ /**
+ * Return the Key that was passed to the constructor.
+ **/
+ public Key getKey () {
+ return key;
+ }
+
+ /**
+ * The Key that was passed to the constructor.
+ **/
+ final private Key key;
+
+ /**
+ * Return the Qualifier that was passed to the constructor (which could be
+ * null if the second qualifier was called).
+ **/
+ public Qualifier getQualifier () {
+ return qualifier;
+ }
+
+ /**
+ * The Qualifier that was passed to the constructor.
+ **/
+ final private Qualifier qualifier;
+}
diff --git a/uk/ac/sanger/artemis/io/Key.java b/uk/ac/sanger/artemis/io/Key.java
new file mode 100644
index 0000000..e4f749c
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/Key.java
@@ -0,0 +1,130 @@
+/* Key.java
+ *
+ * created: Sun Jan 3 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/Key.java,v 1.3 2006-09-01 10:05:04 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+
+/**
+ * Each object in this class represents a feature key. A key is a String
+ * with a limited number of possible values.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Key.java,v 1.3 2006-09-01 10:05:04 tjc Exp $
+ **/
+
+public class Key
+{
+ /** A convenience copy of the CDS Key. */
+ public static final Key CDS = new Key ("CDS", true);
+
+ /** The String that was passed to the constructor. */
+ private String key_string;
+
+ /**
+ * Create a new Key object from the given String.
+ * @param key_string The text of the new Key.
+ **/
+ public Key (final String key_string)
+ {
+ this.key_string = key_string;
+ }
+
+ /**
+ * Create a new Key object from the given String without checking to see if
+ * the key_string is valid.
+ * @param key_string The text of the new Key.
+ * @param dummy This is present only to distinguish this constructor from
+ * the previous one.
+ **/
+ Key (final String key_string, final boolean dummy)
+ {
+ this.key_string = key_string;
+ }
+
+ /**
+ * Return the String reference that was passed to the constructor.
+ **/
+ public String getKeyString ()
+ {
+ return key_string;
+ }
+
+ /**
+ * Return a String representation of this Key. This currently does this
+ * same as getKeyString ().
+ **/
+ public String toString ()
+ {
+ return getKeyString ();
+ }
+
+ /**
+ * Compares this Key to the given Object. Returns true if and only if the
+ * test_object is a String or Key with the same value as this Key.
+ **/
+ public boolean equals (final Object test_object)
+ {
+ if(test_object instanceof String)
+ return key_string.equals(test_object);
+ else if(test_object instanceof Key)
+ return key_string.equals (((Key) test_object).getKeyString ());
+ else
+ return false;
+ }
+
+ /**
+ * Returns a hash code for this Object.
+ *
+ * @return a hash code value for this object.
+ */
+ public int hashCode()
+ {
+ return getKeyString().hashCode();
+ }
+
+ /**
+ * Return the length of the String that was passed to the constructor.
+ **/
+ public int length ()
+ {
+ return getKeyString ().length ();
+ }
+
+ /**
+ * Compares two strings lexicographically. The comparison is based on the
+ * Unicode value of each character in the strings.
+ * @param another_key The Key to be compared.
+ * @return The value 0 if the argument Key is equal to this Key; a
+ * value less than 0 if this Key is lexicographically less than
+ * the Key argument; and a value greater than 0 if this Key is
+ * lexicographically greater than the Key argument.
+ **/
+ public int compareTo (final Key another_key)
+ {
+ return key_string.compareTo (another_key.key_string);
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/io/KeyVector.java b/uk/ac/sanger/artemis/io/KeyVector.java
new file mode 100644
index 0000000..95aa455
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/KeyVector.java
@@ -0,0 +1,92 @@
+/* KeyVector.java
+ *
+ * created: Fri Apr 16 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/KeyVector.java,v 1.2 2006-08-09 16:35:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.FastVector;
+
+/**
+ * This class implements a Vector of Key objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: KeyVector.java,v 1.2 2006-08-09 16:35:31 tjc Exp $
+ **/
+
+public class KeyVector extends FastVector
+{
+
+ public KeyVector ()
+ {
+ super();
+ }
+
+ /**
+ * Create a new vector which contains only the given Key.
+ **/
+ public KeyVector (final Key new_key)
+ {
+ super();
+ add (new_key);
+ }
+
+ /**
+ * Return a new copy of this object.
+ **/
+ public KeyVector copy ()
+ {
+ final KeyVector new_key_vector = (KeyVector)clone();
+
+ return new_key_vector;
+ }
+
+ /**
+ * Sorts the elements of the vector using a simple O(n^2) selection
+ * sort.
+ */
+ public void sort()
+ {
+ int smallest;
+
+ for (int i = 0; i < size (); ++i)
+ {
+ //find smallest remaining element
+ smallest = i;
+ for(int j = i + 1 ; j < size () ; ++j)
+ {
+ if(((Key)get(j)).compareTo( (Key)get(smallest)) < 0)
+ smallest = j;
+ }
+ //exchange smallest and i
+ if (smallest != i)
+ {
+ final Key tmp = (Key)get(i);
+ setElementAt (get(smallest), i);
+ setElementAt (tmp, smallest);
+ }
+ }
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/io/LazyQualifierValue.java b/uk/ac/sanger/artemis/io/LazyQualifierValue.java
new file mode 100644
index 0000000..ff45291
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/LazyQualifierValue.java
@@ -0,0 +1,8 @@
+package uk.ac.sanger.artemis.io;
+
+public interface LazyQualifierValue
+{
+ public abstract String getString();
+ public abstract void setForceLoad(boolean lazyLoad);
+ public abstract boolean isLazyLoaded();
+}
diff --git a/uk/ac/sanger/artemis/io/LineGroup.java b/uk/ac/sanger/artemis/io/LineGroup.java
new file mode 100644
index 0000000..d66d685
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/LineGroup.java
@@ -0,0 +1,470 @@
+/* LineGroup.java
+ *
+ * created: Mon Oct 12 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.io.Writer;
+import java.io.IOException;
+import java.util.Hashtable;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+/**
+ * This class corresponds to a group of associated lines in an EMBL entry.
+ * An example of a group of associated lines is all the lines in an entry
+ * that start with FT.
+ *
+ * @author Kim Rutherford
+ */
+
+abstract class LineGroup
+ extends EMBLObject
+{
+
+ /**
+ * The tag used for unidentified input.
+ **/
+ final static private int UNKNOWN = 0;
+
+ /**
+ * The tag for the end of entry line: "//"
+ **/
+ final static int END_OF_ENTRY = 1;
+ final static String END_OF_ENTRY_STRING = "//";
+
+ /**
+ * The tag for the start of sequence line
+ **/
+ final static int SEQUENCE = 2;
+ final static String EMBL_SEQUENCE_STRING = "SQ";
+
+ /**
+ * The tag for an EMBL feature table line
+ **/
+ final static int EMBL_FEATURE = 3;
+ final static String EMBL_FEATURE_STRING = "FT";
+
+ /**
+ * The tag for an EMBL feature header lines (FH ...)
+ **/
+ final static int EMBL_FEATURE_HEADER = 4;
+ final static String EMBL_FEATURE_HEADER_STRING = "FH";
+
+ /**
+ * The tag for a GENBANK feature table line
+ **/
+ final static int GENBANK_FEATURE = 5;
+
+ /**
+ * This is the tag for an EMBL LineGroup that we don't have a handler for.
+ * It will be stored in an object of type EmblMisc.
+ **/
+ final static int EMBL_MISC = 6;
+
+ /**
+ * This is the tag for an Genbank LineGroup that we don't have a handler
+ * for. It will be stored in an object of type GenbankMisc.
+ **/
+ final static int GENBANK_MISC = 7;
+
+ /**
+ * This is the tag for a GFF LineGroup (generally a comment line) that we
+ * don't have a handler for. It will be stored in an object of type
+ * GFFMisc.
+ **/
+ final static int GFF_MISC = 8;
+
+ /**
+ * This is the tag for a GFF format line.
+ **/
+ final static int GFF_FEATURE = 9;
+
+ /**
+ * This is the tag for lines generated by MSPcrunch -d
+ **/
+ final static int MSPCRUNCH_FEATURE = 10;
+
+ /**
+ * This is the tag for lines generated by blast
+ **/
+ final static int BLAST_FEATURE = 11;
+
+ /**
+ * The tag for files that look like binary.
+ **/
+ final static int BINARY_CHARACTERS = 12;
+
+ /**
+ * The tag for BSML XML files.
+ **/
+ final static int BSML_XML = 13;
+
+ /**
+ * The tag for AGAVE XML files.
+ **/
+ final static int AGAVE_XML = 14;
+
+ /**
+ * The tag for GAME XML files.
+ **/
+ final static int GAME_XML = 15;
+
+ /**
+ * This hash table contains the GENBANK start of line keywords (LOCUS,
+ * DEFINITION, FEATURES etc.)
+ **/
+ private static Hashtable<String, String> genbank_hash = null;
+
+ static
+ {
+ genbank_hash = new Hashtable<String, String> ();
+ genbank_hash.put ("LOCUS","LOCUS");
+ genbank_hash.put ("DEFINITION","DEFINITION");
+ genbank_hash.put ("ACCESSION","ACCESSION");
+ genbank_hash.put ("NID","NID");
+ genbank_hash.put ("VERSION","VERSION");
+ genbank_hash.put ("KEYWORDS","KEYWORDS");
+ genbank_hash.put ("SOURCE","SOURCE");
+ genbank_hash.put ("REFERENCE","REFERENCE");
+ genbank_hash.put ("PROJECT","PROJECT");
+ genbank_hash.put ("COMMENT","COMMENT");
+ genbank_hash.put ("FEATURES","FEATURES");
+ genbank_hash.put ("SEGMENT","SEGMENT");
+ genbank_hash.put ("PRIMARY","PRIMARY");
+ genbank_hash.put ("DBLINK","DBLINK");
+ genbank_hash.put ("CONTIG","CONTIG");
+ }
+
+ /**
+ * Try to read and return a new LineGroup object from a stream.
+ * @param reader The stream to read from.
+ * @return A new LineGroup object or null if stream is at the end of file.
+ * @exception IOException Thrown if exception occurs while reading.
+ * @exception ReadFormatException Thrown if the format of the input is in
+ * error.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * a particular Qualifier.
+ **/
+ protected static LineGroup readNextLineGroup (final LinePushBackReader reader, final Entry entry)
+ throws IOException, InvalidRelationException
+ {
+
+ String line;
+
+ // read until we get to a non-blank line
+ LINES: while(true)
+ {
+ line = reader.readLine ();
+
+ if(line == null)
+ return null; // end of file
+
+ // check for and ignore blank lines
+ for(int i = 0 ; i < line.length () ; ++i)
+ {
+ final char letter = line.charAt (i);
+
+ if(letter != ' ' && letter != '\t')
+ break LINES;
+ }
+ }
+
+ final int line_type = LineGroup.getLineType (line);
+
+ reader.pushBack (line);
+
+ switch (line_type)
+ {
+ case SEQUENCE:
+ return StreamSequenceFactory.makeStreamSequence (reader, entry);
+
+ case EMBL_FEATURE:
+ return EmblStreamFeature.readFromStream (reader);
+
+ case EMBL_FEATURE_HEADER:
+ return new FeatureHeader (reader);
+
+ case GENBANK_FEATURE:
+ return GenbankStreamFeature.readFromStream (reader);
+
+ case GFF_FEATURE:
+ return GFFStreamFeature.readFromStream (reader);
+
+ case BLAST_FEATURE:
+ return BlastStreamFeature.readFromStream (reader);
+
+ case MSPCRUNCH_FEATURE:
+ return MSPcrunchStreamFeature.readFromStream (reader);
+
+ case END_OF_ENTRY:
+ // in this case we do want to read the line (which will be //) so that
+ // the next call to readNextEntry () starts on the next entry
+ reader.readLine ();
+ return null;
+
+ case EMBL_MISC:
+ return new EmblMisc (reader);
+
+ case GENBANK_MISC:
+ return new GenbankMisc (reader);
+
+ case GFF_MISC:
+ return new GFFMisc (reader);
+
+ case BINARY_CHARACTERS:
+ throw new ReadFormatException ("cannot recognise format of binary file");
+
+ default:
+ throw new ReadFormatException ("reader got confused - " +
+ "unknown line type",
+ reader.getLineNumber ());
+ }
+ }
+
+ /**
+ * Return the embl line type of the line contained in the argument String.
+ */
+ protected static int getLineType(final String line)
+ {
+ if(line.startsWith ("<?xml"))
+ return GAME_XML;
+
+ if(line.startsWith ("#"))
+ return GFF_MISC;
+
+ if(line.length () >= 2 &&
+ (line.charAt (0) == '/' || Character.isLetter (line.charAt (0))) &&
+ (line.charAt (1) == '/' || Character.isLetter (line.charAt (1))) &&
+ (line.length () == 2 ||
+ line.length () == 3 && line.endsWith (" ") ||
+ line.length () == 4 && line.endsWith (" ") ||
+ (line.length () >= 5 && line.substring (2,5).equals (" ") ||
+ line.substring (2,5).equals (" * ")) )) // EMBL pre-submission line
+ {
+
+ if(line.startsWith(EMBL_FEATURE_STRING))
+ return EMBL_FEATURE;
+
+ if(line.startsWith(END_OF_ENTRY_STRING))
+ return END_OF_ENTRY;
+
+ if(line.startsWith(EMBL_SEQUENCE_STRING))
+ return SEQUENCE;
+
+ if(line.startsWith(EMBL_FEATURE_HEADER_STRING))
+ return EMBL_FEATURE_HEADER;
+
+ // this covers all the lines in the header
+ return EMBL_MISC;
+ }
+
+ if(line.length () > 21 &&
+ ((line.startsWith (" ") &&
+ (Character.isLetter (line.charAt (5)) ||
+ Character.isDigit (line.charAt (5)) ||
+ line.charAt (5) == '-') &&
+ line.charAt (20) == ' ') ||
+ (line.startsWith (" ") &&
+ line.trim ().length () > 0)))
+ return GENBANK_FEATURE;
+
+ final int genbank_type = getGenbankType(line);
+
+ if(genbank_type != UNKNOWN)
+ return GENBANK_MISC;
+
+ if(isGFFLine(line))
+ return GFF_FEATURE;
+
+ if(isBlastLine(line))
+ return BLAST_FEATURE;
+
+ if(isMSPcrunchLine(line))
+ return MSPCRUNCH_FEATURE;
+
+// if(isBlastLine(line))
+// return BLAST_FEATURE;
+
+ if(looksLikeBinary(line))
+ return BINARY_CHARACTERS;
+
+ // default is sequence
+ return SEQUENCE;
+ }
+
+ /**
+ * Return true if and only if the argument contains more than 30% binary
+ * characters. "binary" means a control character before space in ascii
+ * (except for tab, new line and form feed) and characters with the high
+ * bit set. This is supposed to approximate the Perl -B test.
+ **/
+ private static boolean looksLikeBinary (final String line)
+ {
+ int count = 0;
+
+ if(line.length () == 0)
+ return false;
+
+ for(int i = 0 ; i < line.length () ; ++i)
+ {
+ final char this_char = line.charAt (i);
+
+ if (Character.isISOControl (this_char) &&
+ this_char != '\t' &&
+ this_char != ' ' &&
+ this_char != '\r' &&
+ this_char != '\n' ||
+ this_char >= 128) {
+ ++count;
+ }
+ }
+
+ if (count * 100 / line.length () >= 30) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return true if and only if the given String appears to be a feature
+ * generated by MSPcrunch -d
+ **/
+ private static boolean isMSPcrunchLine (final String line)
+ {
+ final String trim_line = line.trim ();
+
+ if (trim_line.length () > 0 &&
+ Character.isDigit (trim_line.charAt (0)) &&
+ trim_line.indexOf (' ') != -1)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if the given String appears to be a feature
+ * generated by blast. This method is easily fooled.
+ **/
+ private static boolean isBlastLine (final String line)
+ {
+ if (line.length () > 0 && countChars (line, '\t') == 11)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if the given String appears to be a GFF feature.
+ * This method is easily fooled.
+ **/
+ private static boolean isGFFLine (final String line)
+ {
+ if (line.length () > 0)
+ {
+ final int tab_count = countChars (line.trim (), '\t');
+
+ if (tab_count == 7 || tab_count == 8 || tab_count == 9 ||
+ tab_count == 10)
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the number of occurrences of the character c in the String s.
+ **/
+ private static int countChars (final String s, final char c)
+ {
+ int count = 0;
+ int index = 0;
+ while((index = s.indexOf(c, index)) > -1)
+ {
+ count++;
+ index++;
+ }
+ return count;
+ }
+
+ /**
+ * Return the type of GENBANK LineGroup that starts with the given String
+ * or UNKNOWN if the String isn't the
+ **/
+ private static int getGenbankType (final String line)
+ {
+ if (line.length () > 0 && Character.isLetter (line.charAt (0)))
+ {
+ final int first_space = line.indexOf (' ');
+ if (first_space == -1)
+ {
+ if (genbank_hash.get (line) != null)
+ return GENBANK_MISC;
+ }
+ else
+ {
+ final String first_word = line.substring (0, first_space);
+
+ if (genbank_hash.get (first_word) != null)
+ return GENBANK_MISC;
+ }
+ }
+
+ return UNKNOWN;
+ }
+
+ /**
+ * Returns a String containing the contents of the line with the initial
+ * type string (two letters) and white space (three spaces) removed.
+ */
+ public static String getRestOfLine (String line)
+ {
+ final int END_OF_SPACES = 5;
+
+ if (line.length () > END_OF_SPACES)
+ return line.substring (END_OF_SPACES);
+ else
+ return "";
+ }
+
+ /**
+ * Write the end of entry marker - "//".
+ **/
+ public static void writeEndOfEMBLEntry (Writer writer) throws IOException
+ {
+ writer.write (END_OF_ENTRY_STRING + "\n");
+ }
+
+ public static void writeStartOfGFFEntry (Writer writer) throws IOException
+ {
+ writer.write ("##FASTA\n");
+ }
+
+
+ /**
+ * Write this object to the given stream.
+ * @param writer The stream to write to.
+ **/
+ public abstract void writeToStream (final Writer out_stream)
+ throws IOException;
+
+}
diff --git a/uk/ac/sanger/artemis/io/Location.java b/uk/ac/sanger/artemis/io/Location.java
new file mode 100644
index 0000000..b479348
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/Location.java
@@ -0,0 +1,989 @@
+/* Location.java
+ *
+ * created: Mon Oct 5 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/Location.java,v 1.8 2009-02-02 16:05:32 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.io.LocationLexer.TokenEnumeration;
+
+/**
+ * This class encapsulates the location field of a EMBL entry feature.
+ * As well as allowing access to the location string itself, this class has
+ * functions for parsing and manipulating the location.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Location.java,v 1.8 2009-02-02 16:05:32 tjc Exp $
+ *
+ */
+public class Location
+{
+
+ /**
+ * The canonical parsed version of the location string that was passed to
+ * the constructor.
+ **/
+ private LocationParseNode parse_tree = null;
+
+ /**
+ * A cache of ranges, set and returned by getRanges ().
+ **/
+ private RangeVector ranges = null;
+
+ /**
+ * A cache of the total range, set and returned by getTotalRange ().
+ **/
+ private Range total_range = null;
+
+ /**
+ * Constructs a new Location object with that represents the value
+ * represented by the string.
+ * @exception LocationParseException Thrown if the location String does not
+ * represent a valid Location.
+ **/
+ public Location(final String location_string)
+ throws LocationParseException
+ {
+ parse_tree = getParseTree(location_string).getCanonical();
+
+ if(parse_tree == null)
+ throw new LocationParseException("invalid location", location_string);
+ }
+
+ /**
+ * Constructs a new Location object with that represents the given Range.
+ * @exception OutOfRangeException Thrown if the Range starts below one.
+ **/
+ public Location(final Range location_range)
+ throws OutOfRangeException
+ {
+ if(location_range.getStart() < 1)
+ throw new OutOfRangeException("location out of range: " +
+ location_range.toString ());
+ else
+ parse_tree = new LocationParseNode(location_range);
+
+ ranges = new RangeVector(location_range);
+ }
+
+ /**
+ * Constructs a new Location object with that represents the given Ranges
+ * @param ranges a non-empty vector containing the ranges for the new
+ * Location.
+ * @param complement true if and only if all the ranges should be
+ * complemented in the new location.
+ **/
+ public Location(final RangeVector ranges, final boolean complement)
+ {
+ final LocationParseNodeVector vector = new LocationParseNodeVector();
+
+ if(ranges.size() == 0)
+ throw new Error ("internal error - ranges.size () == 0");
+
+ for(int i = 0 ; i<ranges.size() ; ++i)
+ {
+ final LocationParseNode range_node = new LocationParseNode((Range)ranges.elementAt(i));
+
+ if(complement)
+ {
+ final LocationParseNode complement_node =
+ new LocationParseNode(LocationParseNode.COMPLEMENT, range_node);
+ vector.addElement(complement_node);
+ }
+ else
+ vector.addElement (range_node);
+ }
+
+ if(vector.size() == 1)
+ parse_tree = vector.elementAt (0);
+ else
+ parse_tree = new LocationParseNode(LocationParseNode.JOIN, vector);
+ }
+
+ /**
+ * This method translates the start and end of each Range in this Location
+ * into another coordinate system. The Ranges will be truncated if
+ * necessary.
+ * @param constraint This contains the start and end base of the new
+ * coordinate system. The position given by constraint.getStart () will
+ * be at postion/base 1 in the new coordinate system.
+ * @return the Location translated into the new coordinate system. The
+ * Ranges in the Location will be truncated if thwy overlap either end of
+ * the constraint. null is returned if and only if all the Ranges lie
+ * outside of the new start and end.
+ **/
+ public Location truncate(final Range constraint)
+ {
+ final boolean is_complement = isComplement();
+ final RangeVector ranges = getRanges();
+
+ final RangeVector new_ranges = new RangeVector();
+
+ for(int i = 0 ; i<ranges.size () ; ++i)
+ {
+ final Range truncated_range =
+ ((Range)ranges.elementAt(i)).truncate(constraint);
+
+ if(truncated_range != null)
+ new_ranges.add(truncated_range);
+ }
+
+ if(new_ranges.size() > 0)
+ return new Location (new_ranges, is_complement);
+ else
+ return null;
+ }
+
+ /**
+ * Returns the value of this Location as a string. The value returned will
+ * be the same as string passed to the constructor (but will always look
+ * like join(complement(... if there is a complement and a join).
+ **/
+ public String toString()
+ {
+ return getParsedLocation().toString();
+ }
+
+ /**
+ * Return true if and only if this Location is the same as the given
+ * Location.
+ **/
+ public boolean equals(final Location other_location)
+ {
+ return toString().equals(other_location.toString());
+ }
+
+
+ /**
+ *
+ * Test whether this is a trans-spliced feature location.
+ *
+ */
+ private boolean isTransSpliced()
+ {
+ final LocationParseNode top = getParsedLocation();
+ if(top.getType () != LocationParseNode.JOIN)
+ return false;
+
+ final LocationParseNodeVector children =
+ top.getJoinChildren();
+
+ int num_complement = 0;
+
+ for(int i = children.size() - 1; i >= 0; --i)
+ {
+ final LocationParseNode child_node = children.elementAt(i);
+ if(child_node.getType() == LocationParseNode.COMPLEMENT)
+ num_complement++;
+ }
+
+ if(num_complement == 0 || num_complement == children.size())
+ return false;
+
+ return true;
+ }
+
+ /**
+ * If lookAt5prime is set to true then only return true if the 5' end is
+ * partial otherwise only return true if the 3' end is partial.
+ * @param lookAt5prime
+ * @return
+ */
+ public boolean isPartial(final boolean lookAt5prime)
+ {
+ try
+ {
+ LocationParseNode top = getParsedLocation();
+ if (top.getType() == LocationParseNode.COMPLEMENT)
+ top = top.getComplementChild();
+
+ if (top.getType() == LocationParseNode.JOIN ||
+ top.getType() == LocationParseNode.ORDER)
+ {
+ LocationParseNodeVector nodes = top.getChildren();
+ LocationParseNode node = nodes.elementAt(
+ (lookAt5prime ? 0 : nodes.size()-1));
+ if (node.getType() == LocationParseNode.COMPLEMENT)
+ {
+ node = node.getComplementChild();
+ if (node.getType() == LocationParseNode.RANGE
+ && node.getRange() instanceof FuzzyRange)
+ top = node;
+ }
+ else
+ top = nodes.elementAt(0);
+ }
+
+ if (top.getType() == LocationParseNode.RANGE &&
+ top.getRange() instanceof FuzzyRange)
+ {
+ final FuzzyRange fuzz = (FuzzyRange) top.getRange();
+ if (fuzz.getStartObject() instanceof LowerInteger && (isComplement() ^ lookAt5prime))
+ return true;
+ if (fuzz.getEndObject() instanceof UpperInteger && !(isComplement() ^ lookAt5prime))
+ return true;
+ }
+ }
+ catch (Exception e){}
+ return false;
+ }
+
+
+ /**
+ * Returns the value of this Location as a string with the complement (if
+ * any) as the top node and the join or order (if any) below it.
+ * eg. "complement(join(..." not "join(complement(..."
+ **/
+ public String toStringShort()
+ {
+ final LocationParseNode top = getParsedLocation();
+
+ // cope with trans-spliced locations
+ if(isTransSpliced())
+ return toString();
+
+ if((top.getType () == LocationParseNode.JOIN &&
+ top.getJoinChildren ().elementAt (0).getType () ==
+ LocationParseNode.COMPLEMENT) ||
+ (top.getType () == LocationParseNode.ORDER &&
+ top.getOrderChildren ().elementAt (0).getType () ==
+ LocationParseNode.COMPLEMENT))
+ {
+ final StringBuffer return_buffer;
+ final LocationParseNodeVector children;
+
+ if (top.getType () == LocationParseNode.JOIN)
+ {
+ return_buffer = new StringBuffer("complement(join(");
+ children = top.getJoinChildren ();
+ }
+ else
+ {
+ return_buffer = new StringBuffer("complement(order(");
+ children = top.getOrderChildren();
+ }
+
+
+ // reverse the nodes because in a join(complement( location the
+ // exons a in reverse order
+ for(int i = children.size() - 1; i >= 0; --i)
+ {
+ final LocationParseNode child_node = children.elementAt(i);
+
+ if(child_node.getType() == LocationParseNode.COMPLEMENT)
+ {
+ final LocationParseNode complement_child =
+ child_node.getComplementChild ();
+
+ return_buffer.append (complement_child.toString ());
+ }
+ else // probably an ENTRY_RANGE
+ return_buffer.append (child_node.toString ());
+
+ if(i != 0)
+ return_buffer.append(',');
+ }
+
+ return_buffer.append("))");
+
+ return return_buffer.toString();
+ }
+ else
+ {
+ // does not contain a join and a complement, so there is no need to swap
+ // them
+ return toString ();
+ }
+ }
+
+ /**
+ * This method returns true if and only if this Location is a complement.
+ **/
+ public boolean isComplement()
+ {
+ if(getParsedLocation ().getType () == LocationParseNode.COMPLEMENT)
+ {
+ // COMPLEMENT(RANGE)
+ return true;
+ }
+ else
+ {
+ if(getParsedLocation().getType() == LocationParseNode.JOIN ||
+ getParsedLocation().getType() == LocationParseNode.ORDER)
+ {
+ // a join/order must have at least one child node
+ if(getParsedLocation().getChildren().elementAt(0).getType() ==
+ LocationParseNode.COMPLEMENT)
+ {
+ // JOIN(COMPLEMENT(RANGE)...) or ORDER(COMPLEMENT(RANGE)...)
+ return true;
+ }
+ else
+ {
+ // JOIN(RANGE,...) or ORDER(RANGE,...)
+ return false;
+ }
+ }
+ else
+ {
+ // the head node is not a JOIN, COMPLEMENT or ORDER so it must be a
+ // RANGE
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Return the reference of a new copy of this Location.
+ **/
+ public Location copy()
+ {
+ return new Location(getParsedLocation().copyClean());
+ }
+
+ /**
+ * Change the given range in this Location. If there are two or more
+ * identical ranges in the location the first one will be changed.
+ * @param old_range The Range to change
+ * @param new_range The Range to replace old_range with.
+ * @return a new Location that will be a copy of this Location with the
+ * given Range changed.
+ **/
+ public Location changeRange(final Range old_range, final Range new_range)
+ {
+ final Location new_location = new Location(getParsedLocation().copy());
+ new_location.getParsedLocation().changeRange(old_range, new_range);
+
+ return new_location;
+ }
+
+ /**
+ * Add the given Range in this Location.
+ * @return a new Location that will be a copy of this Location with the
+ * given Range added.
+ **/
+ public Location addRange(final Range new_range)
+ {
+ final Location new_location = new Location(getParsedLocation().copy());
+ final LocationParseNode new_node = new LocationParseNode(new_range);
+
+ new_location.parse_tree =
+ new_location.getParsedLocation().addRangeNode(new_node);
+
+ return new_location;
+ }
+
+ /**
+ * Remove the given Range in this Location. If there are two or more
+ * Ranges in this Location that are identical to remove_range the first
+ * will be removed.
+ * @return a new Location that will be a copy of this Location with the
+ * given LocationParseNode removed.
+ **/
+ public Location removeRange(final Range remove_range)
+ {
+ final Location new_location = new Location(getParsedLocation().copy());
+
+ new_location.parse_tree =
+ new_location.getParsedLocation().removeRange(remove_range);
+
+ return new_location;
+ }
+
+ /**
+ * Return a reversed and complemented copy of this Location.
+ * @param sequence_length The length of the sequence that this Location is
+ * associated with.
+ * @return a new Location that will be a reversed and complemented copy of
+ * this Location. The user_data fields in the returned Location will be
+ * the same as the original.
+ **/
+ public Location reverseComplement(final int sequence_length)
+ {
+ return reverseComplement(sequence_length, 1);
+ }
+
+ /**
+ * Return a reversed and complemented copy of this Location.
+ * @param sequence_length The length of the sequence that this Location is
+ * associated with.
+ * @return a new Location that will be a reversed and complemented copy of
+ * this Location. The user_data fields in the returned Location will be
+ * the same as the original.
+ **/
+ public Location reverseComplement(final int sequence_length,
+ final int offset)
+ {
+ final Location new_location = new Location(getParsedLocation().copy());
+
+ new_location.parse_tree =
+ new_location.getParsedLocation().reverseComplement(sequence_length, offset);
+
+ new_location.parse_tree = new_location.parse_tree.getCanonical();
+ return new_location;
+ }
+
+
+ /**
+ * Return a Range that spans the whole Location.
+ **/
+ public Range getTotalRange()
+ {
+ try
+ {
+ if(total_range == null)
+ {
+ final RangeVector ranges = getRanges();
+
+ int lowest_so_far = -1;
+ int highest_so_far = -1;
+
+ for(int i = 0 ; i<ranges.size() ; ++i)
+ {
+ final int this_start = ((Range)ranges.elementAt(i)).getStart();
+ final int this_end = ((Range)ranges.elementAt(i)).getEnd();
+
+ if(lowest_so_far == -1 || this_start < lowest_so_far)
+ lowest_so_far = this_start;
+
+ if(this_end > highest_so_far)
+ highest_so_far = this_end;
+ }
+
+ total_range = new Range(lowest_so_far, highest_so_far);
+ }
+
+ return total_range;
+ }
+ catch (OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return a Vector containing all the Ranges of this Location (which could
+ * be none).
+ **/
+ public RangeVector getRanges()
+ {
+ if(ranges != null)
+ return ranges;
+
+ ranges = new RangeVector();
+
+ final LocationParseNode parsed_location;
+
+ parsed_location = getParsedLocation();
+
+ // this will contain nodes that will either be COMPLEMENT(RANGE) or
+ // RANGE
+ LocationParseNodeVector range_nodes;
+
+ // strip out the JOIN or ORDER node if any - must be the top node because
+ // we called getCanonical ()
+ if(parsed_location.getType() == LocationParseNode.JOIN)
+ range_nodes = parsed_location.getJoinChildren ();
+ else
+ {
+ if(parsed_location.getType() == LocationParseNode.ORDER)
+ range_nodes = parsed_location.getOrderChildren();
+ else
+ {
+ range_nodes = new LocationParseNodeVector();
+ range_nodes.addElement(parsed_location);
+ }
+ }
+
+ // loop over complement_nodes extracting the Range object the nodes.
+ for(int i = 0 ; i<range_nodes.size() ; ++i)
+ {
+ final LocationParseNode current_node;
+ current_node = range_nodes.elementAt(i);
+
+ final Range current_node_range;
+
+ if(current_node.getType() == LocationParseNode.COMPLEMENT)
+ {
+ final LocationParseNode child = current_node.getComplementChild();
+ if(child.getType() == LocationParseNode.RANGE)
+ current_node_range = child.getRange ();
+ else
+ {
+ // probably an ENTRY_RANGE node
+ continue;
+ }
+ }
+ else
+ {
+ if(current_node.getType() == LocationParseNode.RANGE)
+ current_node_range = current_node.getRange();
+ else
+ {
+ // probably an ENTRY_RANGE node
+ continue;
+ }
+ }
+
+ ranges.add(current_node_range);
+ }
+
+ return ranges;
+ }
+
+ /**
+ * Return the first base that this Location covers or -1 if the location
+ * does not refer to any bases in this sequence.
+ **/
+ public int getFirstBase()
+ {
+ return getTotalRange().getStart();
+ }
+
+ /**
+ * Return the last base that this Location covers or -1 if the location
+ * does not refer to any bases in this sequence.
+ **/
+ public int getLastBase()
+ {
+ return getTotalRange().getEnd();
+ }
+
+ /**
+ * Return a complemented copy of this Location. If this Location is
+ * already a complement then a non complemented copy is returned.
+ **/
+ public Location getComplement()
+ {
+ return new Location(getParsedLocation().getNodeComplement());
+ }
+
+ /**
+ * Returns a parse tree for this Location. Changes to the parse tree will
+ * change the original Location.
+ * @exception LocationParseException Thrown if a parse error occurs.
+ **/
+ public LocationParseNode getParsedLocation()
+ {
+ return parse_tree;
+ }
+
+ public void setParsedLocation(LocationParseNode parse_tree)
+ {
+ this.parse_tree = parse_tree;
+ }
+
+
+ /**
+ * Create a new Location object from the given LocationParseNode tree.
+ **/
+ private Location(LocationParseNode parse_tree)
+ {
+ this.parse_tree = parse_tree;
+ }
+
+ /**
+ * Returns a parse tree for this Location. Changes to the parse tree will
+ * not effect the original Location.
+ * @exception LocationParseException Thrown if a parse error occurs.
+ */
+ private LocationParseNode getParseTree(String location_string)
+ throws LocationParseException
+ {
+ final LocationLexer lexer = new LocationLexer(location_string);
+ final TokenEnumeration enumTk = lexer.getTokens();
+ final LocationParseNode join = parseJoinContents(enumTk);
+
+ if(enumTk.peekElement() != null)
+ throw new LocationParseException("garbage at the end of the " +
+ "location string", enumTk);
+ return join;
+ }
+
+ /**
+ * Parse and return a location in the form of a LocationParseNode. A
+ * location is of the form: complement(...), join(...), order(...) or
+ * <range> (where <range> is described in parseRange()). If the next
+ * tokens in the TokenEnumeration are not a location then an exception is
+ * thrown. */
+ private static LocationParseNode parseLocation(TokenEnumeration enumTk)
+ throws LocationParseException
+ {
+ // failed to parse a range - try parsing a functional(join, etc.)
+
+ final LocationParseNode parsed_functional = parseFunctional(enumTk);
+
+ if(parsed_functional != null)
+ return parsed_functional;
+
+ final LocationParseNode entry_range = parseEntryRange(enumTk);
+
+ if(entry_range != null)
+ return entry_range;
+
+ final LocationParseNode parsed_range = parseRange(enumTk);
+
+ if(parsed_range != null)
+ return parsed_range;
+ else
+ throw new LocationParseException("expected a range or a functional",
+ enumTk);
+ }
+
+ /**
+ * Attempt to parse and return a Range with an entry label in front (like
+ * J00194:(100..202) or J00193:hladr). If the next tokens do not look like
+ * an entry range then it immediately returns null, otherwise an attempt is
+ * made to parse the next tokens as an entry range. If the parse fails
+ * part way through then a LocationParseException is thrown.
+ **/
+ private static LocationParseNode parseEntryRange(TokenEnumeration enumTk)
+ throws LocationParseException
+ {
+ if(!(enumTk.peekElement() instanceof String))
+ return null;
+
+ final String entry_string = (String)enumTk.nextElement();
+
+ if(!enumTk.eatToken(':'))
+ throw new LocationParseException("parse error after reading \"" +
+ entry_string + "\"", enumTk);
+
+ final LocationParseNode entry_location = parseLocation(enumTk);
+
+ return new LocationParseNode(entry_string, entry_location);
+ }
+
+ /**
+ * Attempt to parse and return the contents of a JOIN node (or the top
+ * level node).
+ **/
+ private static LocationParseNode parseJoinContents(TokenEnumeration enumTk)
+ throws LocationParseException
+ {
+ final LocationParseNodeVector return_ranges =
+ new LocationParseNodeVector();
+
+ final LocationParseNode parsed_range = parseLocation(enumTk);
+
+ if(parsed_range == null)
+ return null;
+
+ return_ranges.addElement(parsed_range);
+
+ final Object peek_element = enumTk.peekElement();
+
+ while(enumTk.eatToken(','))
+ {
+ LocationParseNode new_child = parseLocation(enumTk);
+ return_ranges.addElement(new_child);
+ }
+
+ if(return_ranges.size() > 1)
+ return new LocationParseNode(LocationParseNode.JOIN, return_ranges);
+ else
+ return return_ranges.elementAt(0);
+ }
+
+ /**
+ * Attempt to parse and return a Range. If the next tokens do not look
+ * like a range then it immediately returns null, otherwise an attempt is
+ * made to read a range. If the parse fails part way through what looks
+ * like a range then a LocationParseException is thrown.
+ **/
+ private static LocationParseNode parseRange(TokenEnumeration enumTk)
+ throws LocationParseException
+ {
+ boolean is_lower_unbounded_range = false;
+ boolean is_upper_unbounded_range = false;
+
+ // returns Integer, UpperInteger, LowerInteger, Range or null
+ Object start_object = parseRangeBound(enumTk);
+
+ if(start_object == null)
+ return null;
+
+ final Object middle_token = enumTk.peekElement();
+
+ if(!enumTk.eatToken("..") && !enumTk.eatToken('^'))
+ return new LocationParseNode(FuzzyRange.makeRange(start_object));
+
+ if(enumTk.peekElement() == null)
+ throw new LocationParseException("location ends in the middle of " +
+ "a range", enumTk);
+
+ if(start_object instanceof UpperInteger)
+ throw new LocationParseException("range cannot start with: " +
+ start_object, enumTk);
+
+ Object end_object = parseRangeBound(enumTk);
+
+ if(end_object == null)
+ throw new LocationParseException("unexpected characters in location",
+ enumTk);
+
+ if(end_object instanceof LowerInteger)
+ throw new LocationParseException("a range cannot end with: " +
+ end_object, enumTk);
+
+ if(middle_token instanceof Character &&
+ ((Character)middle_token).charValue() == '^')
+ {
+ if(start_object instanceof Integer && end_object instanceof Integer)
+ {
+ try
+ {
+ final Range new_range =
+ new BetweenRange(((Integer) start_object).intValue(),
+ ((Integer) end_object).intValue());
+ return new LocationParseNode(new_range);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new LocationParseException("a range must start before it " +
+ "ends", enumTk);
+ }
+ }
+ else
+ throw new LocationParseException("a range that contains a " +
+ "'^' must start and end with a " +
+ "plain number", enumTk);
+ }
+
+ try
+ {
+ return new LocationParseNode(FuzzyRange.makeRange(start_object,
+ end_object));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new LocationParseException("a range must start before it ends",
+ enumTk);
+ }
+ }
+
+ /**
+ * Parse the start or end of a range.
+ **/
+ private static Object parseRangeBound(TokenEnumeration enumTk) throws
+ LocationParseException
+ {
+ final Object peek_element = enumTk.peekElement();
+
+ if(peek_element instanceof Integer)
+ {
+ if(((Integer) peek_element).intValue() < 1)
+ throw new LocationParseException("range bounds must be " +
+ "greater than 0", enumTk);
+ return enumTk.nextElement();
+ }
+
+ if(peek_element instanceof LowerInteger)
+ {
+ if(((LowerInteger) peek_element).getPosition() < 1)
+ throw new LocationParseException("range bounds must be " +
+ "greater than 0", enumTk);
+
+ return enumTk.nextElement();
+ }
+
+ if(peek_element instanceof UpperInteger)
+ {
+ if(((UpperInteger)peek_element).getPosition() < 1)
+ throw new LocationParseException("range bounds must be " +
+ "greater than 0", enumTk);
+ return enumTk.nextElement();
+ }
+
+ // try to parse a range of the form (100.200)
+ if(enumTk.eatToken ('('))
+ {
+ if(enumTk.peekElement() instanceof Integer)
+ {
+ final Integer start = (Integer)enumTk.nextElement();
+
+ if(enumTk.eatToken('.'))
+ {
+ if(enumTk.peekElement() instanceof Integer)
+ {
+ final Integer end = (Integer)enumTk.nextElement();
+
+ int start_value = start.intValue();
+ int end_value = end.intValue();
+
+ // make sure the ranges are the correct way around
+ if(start_value > end_value)
+ {
+ final int tmp = start_value;
+ start_value = end_value;
+ end_value = tmp;
+ }
+
+ final Object return_object;
+
+ if(start_value < 1)
+ throw new LocationParseException("range bounds must be " +
+ "greater than 0", enumTk);
+
+ if(start_value == end_value)
+ return_object = new Integer(start_value);
+ else
+ {
+ try
+ {
+ return_object = new Range(start_value, end_value);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " +e);
+ }
+ }
+
+ if(enumTk.eatToken(')'))
+ return return_object;
+ else
+ {
+ final String message =
+ "expected a closing parenthesis after reading: (" +
+ start.intValue() + "." + end.intValue();
+ throw new LocationParseException(message, enumTk);
+ }
+ }
+ else
+ throw new LocationParseException("expected an integer", enumTk);
+ }
+ else
+ throw new LocationParseException("expected a '.'", enumTk);
+ }
+ else
+ throw new LocationParseException("expected an integer", enumTk);
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempt to parse and return a functional. If the next tokens do not
+ * look like a functional then it immediately returns null, otherwise an
+ * attempt is made to read a functional. If the parse fails part way
+ * through what looks like a functional then a LocationParseException is
+ * thrown.
+ */
+ private static LocationParseNode parseFunctional(TokenEnumeration enumTk)
+ throws LocationParseException
+ {
+ Object peeked_element = enumTk.peekElement();
+
+ if(peeked_element instanceof String &&
+ ("complement".equals((String) peeked_element) ||
+ "join".equals((String) peeked_element) ||
+ "order".equals((String) peeked_element)))
+ {
+ // remove the first token from the enumTk
+ String functional_name = (String)enumTk.nextElement();
+
+ if(!enumTk.eatToken('('))
+ throw new LocationParseException("expected(", enumTk);
+
+ if("complement".equals(functional_name))
+ {
+ // special case for complement - it should have only one argument
+ LocationParseNode child = parseJoinContents(enumTk);
+
+ if(!enumTk.eatToken(')')) {
+// throw new LocationParseException ("expected )", enumTk);
+ }
+
+ // create a COMPLEMENT type node
+ return new LocationParseNode(LocationParseNode.COMPLEMENT, child);
+ }
+ else
+ {
+ // parseLocation() will parse range,range,range as a JOIN
+ LocationParseNode join = parseJoinContents(enumTk);
+
+ if(!enumTk.eatToken(')')) {
+// throw new LocationParseException("expected )", enumTk);
+ }
+
+ if(join.getType() == LocationParseNode.JOIN)
+ {
+ // create a JOIN or ORDER type node
+ if("join".equals(functional_name))
+ return new LocationParseNode(LocationParseNode.JOIN,
+ join.getChildren());
+ else
+ return new LocationParseNode(LocationParseNode.ORDER,
+ join.getChildren());
+ }
+ else
+ return join;
+ }
+ }
+ else // not a functional
+ return null;
+ }
+
+ /**
+ *
+ * Given a range check which strand it is on.
+ *
+ */
+ public boolean isComplement(Range range)
+ {
+ final LocationParseNode parse_tree = getParsedLocation();
+
+ if(parse_tree.getType() != LocationParseNode.JOIN)
+ return isComplement();
+
+ final LocationParseNodeVector children =
+ parse_tree.getJoinChildren();
+
+ for(int i = children.size() - 1; i >= 0; --i)
+ {
+ LocationParseNode child_node = children.elementAt(i);
+
+ Range child_range = null;
+ if(child_node.getType() == LocationParseNode.COMPLEMENT)
+ {
+ if(child_node.getComplementChild().getType() == LocationParseNode.RANGE)
+ child_range = child_node.getComplementChild().getRange();
+ }
+ else if(child_node.getType() == LocationParseNode.RANGE)
+ child_range = child_node.getRange();
+
+ if((child_node.getType() == LocationParseNode.RANGE ||
+ child_node.getType() == LocationParseNode.COMPLEMENT) &&
+ (child_range != null && child_range.equals(range)))
+ {
+ if(child_node.getType() == LocationParseNode.COMPLEMENT)
+ return true;
+ else
+ return false;
+ }
+ }
+
+ return isComplement();
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/LocationLexer.java b/uk/ac/sanger/artemis/io/LocationLexer.java
new file mode 100644
index 0000000..9f0a7f1
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/LocationLexer.java
@@ -0,0 +1,316 @@
+/* LocationLexer.java
+ *
+ * created: Tue Oct 6 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/LocationLexer.java,v 1.1 2004-06-09 09:49:50 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * The LocationLexer class provides methods for breaking a EMBL feature
+ * location string into tokens. The complete list of possible tokens is
+ * given below.
+ *
+ *
+ * @author Kim Rutherford
+ * @version $Id: LocationLexer.java,v 1.1 2004-06-09 09:49:50 tjc Exp $
+ *
+ */
+
+public class LocationLexer {
+ /**
+ * Create a new LocationLexer object that can be used to tokenise
+ * location_string.
+ */
+ public LocationLexer (String location_string) {
+ this.location_string = location_string;
+ }
+
+ /**
+ * Return a TokenEnumeration containing all the tokens in this string.
+ */
+ public TokenEnumeration getTokens () {
+ return new TokenEnumeration (location_string);
+ }
+
+ /**
+ * This is a helper class for LocationLexer - see LocationLexer.getTokens ()
+ *
+ */
+ public class TokenEnumeration {
+ public TokenEnumeration (String location_string) {
+ this.location_string = location_string;
+ }
+
+ /**
+ * Return the next token but don't remove it from the enumeration.
+ * @return The next token or null if there are no more.
+ */
+ public Object peekElement () {
+ if (peeked_object == null) {
+ peeked_object = removeNextToken ();
+ }
+
+ return peeked_object;
+ }
+
+ /**
+ * Return the next token and remove it from the enumeration
+ */
+ public Object nextElement () {
+ Object o = removeNextToken ();
+ return o;
+ }
+
+ /**
+ * Try to "eat" the first token in the enumeration. If the next token is
+ * the same as the argument String then the next token will be removed
+ * from the enum and it will return true, otherwise the enum will not
+ * change and it wil return false.
+ */
+ public boolean eatToken (String token) {
+ if (peekElement () instanceof String &&
+ ((String)peekElement ()).equals (token)) {
+ nextElement ();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Try to "eat" the first token in the enumeration. If the next token is
+ * the same as the argument Character then the next token will be removed
+ * from the enum and it will return true, otherwise the enum will not
+ * change and it wil return false.
+ */
+ public boolean eatToken (final char token) {
+ if (peekElement () instanceof Character &&
+ ((Character)peekElement ()).charValue () == token) {
+ nextElement ();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return a String contains all the remaining tokens concatenated
+ * together.
+ **/
+ public String toString () {
+ // the number we pick for the initial StringBuffer size is not critical,
+ // but should cover most possibilities
+ final StringBuffer spare_tokens_string = new StringBuffer (100);
+
+ Object next_token = nextElement ();
+
+ while (next_token != null) {
+ spare_tokens_string.append (next_token.toString ());
+ next_token = nextElement ();
+ }
+
+ return spare_tokens_string.toString ();
+ }
+
+ /**
+ * Return the next token and logically remove it from the start of the
+ * enumeration. Returns null when there are no more tokens.
+ * removeNextToken () will return peeked_object (and set it to null)
+ * rather than removing a token iff peeked_object is not null.
+ */
+ private Object removeNextToken () {
+ if (peeked_object == null) {
+
+ // loop until we get to the end of location_string or until we return
+ // a token.
+ while (true) {
+ if (next_char_index == location_string.length ()) {
+ // all tokens have been read
+ return null;
+ }
+
+ final char current_char = location_string.charAt (next_char_index);
+
+ switch (current_char) {
+
+ case ' ': case '\t':
+ // go around the loop again
+ next_char_index++;
+ continue;
+
+ case '(': case ')': case ',': case '^': case ':':
+ // handle single character tokens (except ".")
+ next_char_index++;
+ return new Character (current_char);
+
+ case '>':
+ next_char_index++;
+ if (next_char_index < location_string.length () &&
+ Character.isDigit (location_string.charAt (next_char_index))) {
+ return new UpperInteger (removeInteger ());
+ } else {
+ return new Character ('>');
+ }
+
+ case '<':
+ next_char_index++;
+ if (next_char_index < location_string.length () &&
+ Character.isDigit (location_string.charAt (next_char_index))) {
+ return new LowerInteger (removeInteger ());
+ } else {
+ return new Character ('<');
+ }
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return removeInteger ();
+
+ case '.':
+ if (next_char_index + 1 == location_string.length ()) {
+ // special case we have a "." at the end of the location string -
+ // the parser will catch this are report the error
+ next_char_index++;
+ return new Character ('.');
+ } else {
+ if (location_string.charAt (next_char_index + 1) == '.') {
+ next_char_index += 2;
+ return "..";
+ }
+ else {
+ next_char_index ++;
+ return new Character ('.');
+ }
+ }
+
+ default:
+ {
+ // everything else is a label (eg AF009694), functional name
+ // (ie complement, join or order) or garbage
+ final String label = removeLabel ();
+
+ if (label.equals ("")) {
+ // couldn't read a label so just return the current character
+ // and let the parser sort it out
+
+ next_char_index++;
+ return new String ("" + current_char);
+ } else {
+ return label;
+ }
+ }
+ }
+ }
+ } else {
+ final Object tmp_object = peeked_object;
+ peeked_object = null;
+ return tmp_object;
+ }
+ }
+
+
+ /**
+ * Reads an integer from the current position (next_char_index) in
+ * location_string and increments next_char_index to point to the next
+ * non digit in location_string.
+ */
+ private Integer removeInteger () {
+ String integer_string = "";
+
+ char current_char = location_string.charAt (next_char_index);
+
+ while (Character.isDigit (current_char)) {
+ integer_string += current_char;
+
+ next_char_index++;
+
+ if (next_char_index >= location_string.length ()) {
+ break;
+ }
+
+ current_char = location_string.charAt (next_char_index);
+ }
+
+ return new Integer (integer_string);
+ }
+
+
+ /**
+ * Remove a string or letters, digits and colons from location_string and
+ * adjust next_char_index appropriately. Returns an empty String if the
+ * next character is not alphanumeric.
+ */
+ private String removeLabel () {
+ String return_string = "";
+
+ char current_char = location_string.charAt (next_char_index);
+
+ if (!Character.isLetter (current_char)) {
+ // first character must be a letter
+ return "";
+ }
+
+ while (Character.isLetterOrDigit (current_char) ||
+ current_char == '.' || current_char == '_' ||
+ current_char == '*' || current_char == '\'' ||
+ current_char == '-') {
+ return_string += current_char;
+
+ next_char_index++;
+
+ if (next_char_index >= location_string.length ()) {
+ break;
+ }
+
+ current_char = location_string.charAt (next_char_index);
+ }
+
+ return return_string;
+ }
+
+ /**
+ * Contains string passed to the constructor.
+ */
+ private String location_string;
+
+
+ /**
+ * A pointer into location_string indicating the next character we should
+ * read.
+ */
+ private int next_char_index = 0;
+
+ /**
+ * If peekElement () has been called then the token we read from the
+ * remaining_string is stored here. (see removeNextToken ()).
+ */
+ private Object peeked_object = null;
+ }
+
+
+ /**
+ * This contains the String that was passed to the constructor
+ */
+ private String location_string;
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/LocationParseException.java b/uk/ac/sanger/artemis/io/LocationParseException.java
new file mode 100644
index 0000000..efbcfc9
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/LocationParseException.java
@@ -0,0 +1,68 @@
+/* LocationParseException.java
+ *
+ * created: Wed Oct 7 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/LocationParseException.java,v 1.2 2004-10-29 09:36:24 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.io.LocationLexer.*;
+
+/**
+ * LocationParseException objects are thrown if a parse error occurs during
+ * the parsing of a location string.
+ *
+ * @author Kim Rutherford
+ * @version $Id: LocationParseException.java,v 1.2 2004-10-29 09:36:24 tjc Exp $
+ *
+ */
+
+public class LocationParseException extends ReadFormatException
+{
+ /**
+ * Create a new LocationParseException with the given String as the
+ * message.
+ * @param message The detail message.
+ * @param enumTk An enumeration containing the next tokens to be read. This
+ * is used to give the user an indication of where in the location string
+ * the parsed error happens.
+ **/
+ public LocationParseException(String message, TokenEnumeration enumTk)
+ {
+ super("Parse error at this point: " + enumTk.toString() +
+ ": " + message);
+ }
+
+ /**
+ * Create a new LocationParseException with the given String as the
+ * message.
+ * @param message The detail message.
+ * @param location_string The String in which the error occured.
+ **/
+ public LocationParseException(String message, String location_string)
+ {
+ super("Parse error in this location: " + location_string +
+ ": " + message);
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/LocationParseNode.java b/uk/ac/sanger/artemis/io/LocationParseNode.java
new file mode 100644
index 0000000..b974839
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/LocationParseNode.java
@@ -0,0 +1,960 @@
+/* LocationParseNode.java
+ *
+ * created: Mon Oct 5 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/LocationParseNode.java,v 1.7 2005-11-15 14:02:37 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import javax.swing.JOptionPane;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+
+/**
+ * LocationParseNode class represents one node in the parse tree tree of a
+ * Location. It is a utility class for EMBL.Location.
+ *
+ * @author Kim Rutherford
+ * @version $Id: LocationParseNode.java,v 1.7 2005-11-15 14:02:37 tjc Exp $
+ *
+ **/
+
+class LocationParseNode extends EMBLObject
+{
+
+ /** Node type for an unknown or error node. */
+ final static public int UNKNOWN = 0;
+
+ /** Node type for complement (...) */
+ final static public int COMPLEMENT = 1;
+
+ /** Node type for join (...) */
+ final static public int JOIN = 2;
+
+ /** Node type for order (...) */
+ final static public int ORDER = 3;
+
+ /** Node type for number..number */
+ final static public int RANGE = 4;
+
+ /** Node type for a location reference to another entry */
+ final static public int ENTRY_RANGE = 5;
+
+ /**
+ * The node type can be COMPLEMENT, JOIN, ORDER, RANGE or ENTRY_RANGE
+ **/
+ private int node_type;
+
+ /**
+ * If this LocationParseNode has a node type of JOIN or ORDER this member
+ * will point to an LocationParseNode object containing the children of
+ * this node. If the node type is COMPLEMENT this member will point to an
+ * LocationParseNode object containing the child node of the complement.
+ * If the node type is RANGE this member will point to a Range object.
+ * If the node type is ENTRY_RANGE this will point to a LocationParseNode
+ * of type RANGE and the entry_name member this object will be set.
+ **/
+ private Object child;
+
+ /**
+ * If this LocationParseNode has a node type of ENTRY_RANGE this member
+ * will give the name of the entry, otherwise it will be null.
+ **/
+ private String entry_name;
+
+ /**
+ * Create a new LocationParseNode object with a type given by the first
+ * argument and sub nodes given by the second. The type should be JOIN or
+ * ORDER.
+ **/
+ LocationParseNode(final int node_type,
+ final LocationParseNodeVector children)
+ {
+ if(!(node_type == JOIN || node_type == ORDER))
+ throw new Error("LocationParseNode constructor was called with the wrong type");
+
+ this.node_type = node_type;
+ this.child = children;
+
+ if(children.size() < 1)
+ throw new Error("A functional must have at least one argument");
+ }
+
+ /**
+ * Create a new LocationParseNode object of type COMPLEMENT with child node
+ * given by argument.
+ * @param type must be COMPLEMENT
+ **/
+ LocationParseNode(final int type,
+ final LocationParseNode child)
+ {
+ if(type != COMPLEMENT)
+ throw new Error("LocationParseNode constructor was called with the wrong type");
+
+ this.node_type = COMPLEMENT;
+ this.child = child;
+ }
+
+ /**
+ * Create a new LocationParseNode object of type ENTRY_RANGE with the given
+ * node as the child.
+ **/
+ LocationParseNode(final String entry_name, final LocationParseNode node)
+ {
+ this.node_type = ENTRY_RANGE;
+ this.child = node;
+ this.entry_name = entry_name;
+ }
+
+ /**
+ * Create a new LocationParseNode object of type RANGE with range
+ * given by argument.
+ **/
+ public LocationParseNode(final Range range)
+ {
+ this.node_type = RANGE;
+ this.child = range;
+ }
+
+ /**
+ * Return the type of this node.
+ **/
+ public int getType()
+ {
+ return node_type;
+ }
+
+ /**
+ * Returns the value of this LocationParseNode and it's children as a
+ * string. The method will call toString() on the children of this node
+ * and return those strings as part of the result.
+ **/
+ public String toString()
+ {
+ final String return_string;
+ switch(getType())
+ {
+ case COMPLEMENT:
+ return_string = "complement(" + toStringChild() + ")";
+ break;
+ case JOIN:
+ return_string = "join(" + toStringChildren() + ")";
+ break;
+ case ORDER:
+ return_string = "order(" + toStringChildren() + ")";
+ break;
+ case RANGE:
+ return_string = child.toString();
+ break;
+ case ENTRY_RANGE:
+ return_string = entry_name + ":" + toStringChild();
+ break;
+
+ default:
+ return_string = null;
+ }
+
+ return return_string;
+ }
+
+ /**
+ * Find the node containing old_range and replace the Range with new_range.
+ * @return true if the node was successfully replaced.
+ **/
+ boolean changeRange(final Range old_range,
+ final Range new_range)
+ {
+ switch(getType())
+ {
+ case ENTRY_RANGE:
+ case COMPLEMENT:
+ return ((LocationParseNode) child).changeRange(old_range, new_range);
+ case JOIN:
+ case ORDER:
+ final LocationParseNodeVector children = getChildren();
+
+ for(int i = 0; i < children.size(); ++i)
+ {
+ final LocationParseNode this_child = children.elementAt(i);
+
+ final boolean return_value =
+ this_child.changeRange(old_range, new_range);
+
+ if(return_value)
+ return true;
+ }
+ return false;
+ case RANGE:
+ if(getRange().equals(old_range))
+ {
+ setRange(new_range);
+ return true;
+ }
+ else
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Make a JOIN node from the given RANGE node.
+ **/
+ private LocationParseNode makeJoinFromRange(final LocationParseNode node)
+ {
+ final LocationParseNodeVector children = new LocationParseNodeVector();
+ children.addElement(node);
+
+ return new LocationParseNode(JOIN, children);
+ }
+
+ /**
+ * Add a RANGE node to the current JOIN or ORDER node.
+ **/
+ private void addRangeToJoin(final LocationParseNode new_node)
+ {
+ final LocationParseNodeVector children = getChildren();
+ final int new_range_start = new_node.getRange().getStart();
+
+ if(children.elementAt(0).getType() == COMPLEMENT)
+ {
+ final LocationParseNode complement_node =
+ new LocationParseNode(COMPLEMENT, new_node);
+
+ for(int i = children.size() - 1 ; i >= 0 ; --i)
+ {
+ final LocationParseNode this_child = children.elementAt(i);
+
+ if(this_child.getType() != COMPLEMENT)
+ continue;
+
+ final LocationParseNode complement_child =
+ this_child.getComplementChild();
+
+ if(complement_child.getType() == RANGE)
+ {
+ final Range child_range = complement_child.getRange();
+
+ if(child_range.getStart() > new_range_start)
+ {
+ children.insertElementAt(complement_node, i + 1);
+ return;
+ }
+ }
+ }
+ children.insertElementAt(complement_node, 0);
+ }
+ else
+ {
+ for(int i = 0 ; i < children.size() ; ++i)
+ {
+ final LocationParseNode this_child = children.elementAt(i);
+
+ if(this_child.getType() == RANGE &&
+ this_child.getRange().getStart() > new_range_start)
+ {
+ children.insertElementAt(new_node, i);
+ return;
+ }
+
+ }
+ children.addElementAtEnd(new_node);
+ }
+ }
+
+ /**
+ * Add new_node to this LocationParseNode (or its children). new_node must
+ * be a RANGE node.
+ * @return a changed tree if the node was successfully added or the old
+ * tree otherwise.
+ **/
+ LocationParseNode addRangeNode(final LocationParseNode new_node)
+ {
+ switch(getType())
+ {
+ case ENTRY_RANGE:
+ return this;
+ case RANGE:
+ case COMPLEMENT:
+ return makeJoinFromRange(this).addRangeNode(new_node);
+ case JOIN:
+ case ORDER:
+ addRangeToJoin(new_node);
+ return this;
+ default:
+ throw new Error("internal error - unknown location node type");
+ }
+ }
+
+ /**
+ * Remove a RANGE node to the current JOIN or ORDER node.
+ * @return a new COMPLEMENT or RANGE node if this node currently has two
+ * children. returns this node otherwise.
+ **/
+ private LocationParseNode removeRangeFromJoin(final Range remove_range)
+ {
+ final LocationParseNodeVector children = getChildren();
+
+ if(children.size() > 2)
+ {
+ for(int i = 0 ; i < children.size() ; ++i)
+ {
+ final LocationParseNode this_child = children.elementAt(i);
+
+ if(this_child.getType() == COMPLEMENT)
+ {
+ final LocationParseNode complement_child =
+ this_child.getComplementChild();
+ if(complement_child.getType() == RANGE &&
+ complement_child.getRange().equals(remove_range))
+ {
+ children.removeElement(this_child);
+ return this;
+ }
+ }
+ else
+ {
+ if(this_child.getType() == RANGE &&
+ this_child.getRange().equals(remove_range))
+ {
+ children.removeElement(this_child);
+ return this;
+ }
+ }
+ }
+ }
+ else
+ {
+ for(int i = 0 ; i < children.size() ; ++i)
+ {
+ final LocationParseNode this_child = children.elementAt(i);
+
+ if(this_child.getType() == COMPLEMENT)
+ {
+ final LocationParseNode complement_child =
+ this_child.getComplementChild();
+ if(complement_child.getType() == RANGE &&
+ complement_child.getRange().equals(remove_range))
+ {
+ children.removeElement(this_child);
+ return children.elementAt(0);
+ }
+ }
+ else
+ {
+ if(this_child.getType() == RANGE &&
+ this_child.getRange().equals(remove_range))
+ {
+ children.removeElement(this_child);
+ return children.elementAt(0);
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Remove the node containing remove_range from this LocationParseNode (or
+ * its children).
+ * @return a changed tree if the node was successfully removed or the old
+ * tree otherwise.
+ **/
+ LocationParseNode removeRange(final Range remove_range)
+ {
+ switch(getType())
+ {
+ case ENTRY_RANGE:
+ return this;
+ case RANGE:
+ case COMPLEMENT:
+ throw new Error("internal error - inconsistent location");
+ case JOIN:
+ case ORDER:
+ return removeRangeFromJoin(remove_range);
+ default:
+ throw new Error("internal error - unknown location node type");
+ }
+ }
+
+ /**
+ * Return a reversed and complemented copy of this Location.
+ * @param sequence_length The length of the sequence that this Location is
+ * associated with.
+ * @param offset this is set to zero if the whole sequence is being
+ * operated on or to the start of the region being reverse complemented.
+ * @return a reversed and complemented tree.
+ **/
+ LocationParseNode reverseComplement(final int sequence_length,
+ final int offset)
+ {
+ try
+ {
+ switch(getType())
+ {
+ case ENTRY_RANGE:
+ return this;
+ case RANGE:
+ {
+ final Range range = getRange();
+ final int start = sequence_length - (range.getEnd() - offset + 1) + offset;
+ final int end = sequence_length - (range.getStart() - offset + 1) + offset;
+
+// System.out.println("LocationParseNode.reverseComplement() HERE "+
+// start+ ".."+ end +" sequence_length="+sequence_length+
+// " range.getEnd()="+range.getEnd()+ " range.getStart()="+range.getStart()+
+// " offset="+offset+ " "+
+// start_old+ ".."+end_old);
+
+ final Range new_range = new Range(start, end);
+ final LocationParseNode new_range_node =
+ new LocationParseNode(new_range);
+ return new LocationParseNode(COMPLEMENT, new_range_node);
+ }
+ case COMPLEMENT:
+ {
+ final LocationParseNode child = getComplementChild();
+
+ if(child.getType() == RANGE)
+ {
+ final Range range = child.getRange();
+ final int start = sequence_length - (range.getEnd() - offset + 1) + offset;
+ final int end = sequence_length - (range.getStart() - offset + 1) + offset;
+
+ final Range new_range = new Range(start, end);
+// new Range(sequence_length - (range.getEnd() - offset + 1) + offset,
+// sequence_length - (range.getStart() - offset + 1) + offset);
+ return new LocationParseNode(new_range);
+ }
+ else
+ return child;
+ }
+ case JOIN:
+ case ORDER:
+ {
+ final LocationParseNodeVector children = getChildren();
+
+ final LocationParseNodeVector new_children =
+ new LocationParseNodeVector();
+
+ for(int i = 0 ; i < children.size() ; ++i)
+ {
+ final LocationParseNode this_child = children.elementAt(i);
+
+ final LocationParseNode new_child =
+ this_child.reverseComplement(sequence_length, offset);
+ new_children.addElementAtEnd(new_child);
+ }
+
+ child = new_children;
+
+ return this;
+ }
+ default:
+ throw new Error("internal error - unknown location node type: " +
+ getType());
+ }
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Returns a "canonical" version of the parse tree starting at the current
+ * node. In this context a canonical location is one where any join
+ * or order functional is first in the location string. In a canonical
+ * parse tree only the head node can be a JOIN or ORDER node. Also in a
+ * "join(complement(..." location the exons must be listed in high to low
+ * order ie. (30..40,10..20) not (10..20,30..40). Here is an example: <p>
+ * The canonical version of this string: <p>
+ * "complement(join(100..200,400..500))" is: <p>
+ * "join(complement(400..500),complement(100..200))" <p>
+ * The canonical version is less readable but is more standard.
+ * @return A canonical tree or null if the location is nonsense, for
+ * example "join(complement(join(..."
+ **/
+ LocationParseNode getCanonical()
+ {
+ if(!isValid())
+ return null;
+
+ // this is a new parse tree (a copy of tree starting at this node) with
+ // the JOIN or ORDER node (if any) at the top.
+ final LocationParseNode new_tree = getCanonicalComplement();
+
+ if(new_tree.getType() != JOIN && new_tree.getType() != ORDER)
+ return new_tree;
+
+ // now put the children of the JOIN or ORDER node in the correct order
+ final LocationParseNodeVector new_children =
+ (LocationParseNodeVector)new_tree.child;
+
+ if(new_children.size() >= 2)
+ {
+ // first move all ENTRY_RANGE nodes to the end
+
+ final LocationParseNodeVector entry_range_nodes =
+ new LocationParseNodeVector();
+
+ for(int i = new_children.size() - 1 ; i >= 0 ; --i)
+ {
+ final LocationParseNode node = new_children.elementAt(i);
+ if(node.getType() == ENTRY_RANGE)
+ {
+ new_children.removeElementAt(i);
+ entry_range_nodes.addElementAtEnd(node);
+ }
+ else
+ {
+ if(node.getType() == COMPLEMENT &&
+ node.getComplementChild().getType() == ENTRY_RANGE)
+ {
+ new_children.removeElementAt(i);
+ entry_range_nodes.addElementAtEnd(node);
+ }
+ }
+ }
+
+ if(new_children.size() > 0)
+ {
+
+ // the new head node is a JOIN or ORDER and the children are RANGE
+ // nodes so put the children in order - ascending left to right
+ // or
+ // the children are COMPLEMENT nodes so put the children in order
+ // - ascending right to left
+
+ // bubble sort in place putting the RANGE with the smallest start
+ // base first
+
+ boolean change_happened = true;
+ while(change_happened)
+ {
+ change_happened = false;
+ int this_start;
+ int next_start;
+
+ for(int i = 0 ; i < new_children.size() - 1 ; ++i)
+ {
+ final LocationParseNode this_node = new_children.elementAt(i);
+ final LocationParseNode next_node = new_children.elementAt(i+1);
+
+ if(this_node.getType() == RANGE)
+ this_start = this_node.getRange().getStart();
+ else
+ this_start = this_node.getComplementChild().getRange().getEnd();
+
+ if(next_node.getType() == RANGE)
+ next_start = next_node.getRange().getStart();
+ else
+ next_start = next_node.getComplementChild().getRange().getEnd();
+
+ if(this_node.getType() != RANGE && next_node.getType() != RANGE)
+ {
+ if(this_start < next_start) // swap the two nodes
+ {
+ new_children.setElementAt(next_node, i);
+ new_children.setElementAt(this_node, i + 1);
+ change_happened = true;
+ }
+ }
+ else if(this_start > next_start) // swap the two nodes
+ {
+ new_children.setElementAt(next_node, i);
+ new_children.setElementAt(this_node, i + 1);
+ change_happened = true;
+ }
+ }
+ }
+ }
+
+ for(int i = 0 ; i < entry_range_nodes.size() ; ++i)
+ new_children.addElementAtEnd(entry_range_nodes.elementAt(i));
+ }
+ else
+ throw new Error("internal error - a JOIN should have > 1 child");
+
+ return new_tree;
+ }
+
+ /**
+ * Return true if and only if the tree starting at this node is valid.
+ * "valid" means it has at most two levels of functionals and the lowest
+ * level consists only of RANGE nodes. Also the children of a JOIN or
+ * ORDER node must be all RANGE nodes or all COMPLEMENT nodes - there can't
+ * be a mixture.
+ **/
+ public boolean isValid()
+ {
+ // 2 means check that we have at most two levels.
+ // UNKNOWN means the parent isn't a JOIN, ORDER or COMPLEMENT node (there
+ // is no parent at all.)
+ return isValid(2, UNKNOWN);
+ }
+
+ /**
+ * Return the child node of this node. This node must be a COMPLEMENT type
+ * or an Error will be thrown.
+ **/
+ public LocationParseNode getComplementChild()
+ {
+ if(getType() != COMPLEMENT)
+ throw new Error("in LocationParseNode.getComplementChild() - node " +
+ "is not a COMPLEMENT");
+
+ return (LocationParseNode)child;
+ }
+
+ /**
+ * Return the children of this node. If this node is not a JOIN or ORDER
+ * type then an error will be thrown.
+ **/
+ public LocationParseNodeVector getChildren()
+ {
+ if(!(getType() == JOIN || getType() == ORDER))
+ throw new Error("in LocationParseNode.getChildren() - node " +
+ "is not a JOIN or ORDER");
+
+ return (LocationParseNodeVector)child;
+ }
+
+ /**
+ * Return the children of this node. This node must be a JOIN type node or
+ * an Error will be thrown.
+ **/
+ public LocationParseNodeVector getJoinChildren()
+ {
+ if(getType() != JOIN)
+ throw new Error("in LocationParseNode.getJoinChildren() - node " +
+ "is not a JOIN");
+
+ return (LocationParseNodeVector) child;
+ }
+
+ /**
+ * Return the children of this node. This node must be a JOIN type node or
+ * an Error will be thrown.
+ **/
+ public LocationParseNodeVector getOrderChildren()
+ {
+ if(getType() != ORDER)
+ throw new Error("in LocationParseNode.getOrderChildren() - node " +
+ "is not an ORDER");
+
+ return(LocationParseNodeVector) child;
+ }
+
+ /**
+ * Return the child of this ENTRY_RANGE node. This node must be an
+ * ENTRY_RANGE node or an Error will be thrown. The returned node will be
+ * of type RANGE.
+ **/
+ public LocationParseNode getEntryRangeChild()
+ {
+ if(getType() != ENTRY_RANGE)
+ throw new Error("in LocationParseNode.getEntryRangeChild() - node " +
+ "is not an ENTRY_RANGE");
+
+ return (LocationParseNode)child;
+ }
+
+ /**
+ * Return the Range object for this node. This node must be a RANGE type
+ * node or an Error will be thrown.
+ **/
+ public Range getRange()
+ {
+ if(getType() != RANGE)
+ throw new Error("in LocationParseNode.getRange() - node " +
+ "is not a RANGE " + getType());
+
+ return (Range)child;
+ }
+
+
+ /**
+ * Internal method that implements copy() and copyClean().
+ * @param copy_user_data user_data (from EMBLObject) will be copied if and
+ * only if this parameter is true.
+ **/
+ private LocationParseNode copyInternal(final boolean copy_user_data)
+ {
+ final LocationParseNode new_node;
+
+ switch(getType())
+ {
+ case COMPLEMENT:
+ {
+ final LocationParseNode new_child =
+ getComplementChild().copyInternal(copy_user_data);
+
+ new_node = new LocationParseNode(getType(), new_child);
+ }
+ break;
+ case JOIN:
+ case ORDER:
+ // copy the children
+ final LocationParseNodeVector children_copy =
+ new LocationParseNodeVector();
+ final LocationParseNodeVector children = (LocationParseNodeVector)child;
+ for(int i = 0; i < children.size(); ++i)
+ {
+ final LocationParseNode new_child =
+ children.elementAt(i).copyInternal(copy_user_data);
+ children_copy.addElement(new_child);
+ }
+ new_node = new LocationParseNode(getType(), children_copy);
+ break;
+ case RANGE:
+ new_node = new LocationParseNode(getRange().copy());
+ break;
+ case ENTRY_RANGE:
+ {
+ final LocationParseNode new_child =
+ ((LocationParseNode)child).copyInternal(copy_user_data);
+ new_node = new LocationParseNode(entry_name, new_child);
+ }
+ break;
+ default:
+ throw new Error("internal error - unknown location node type");
+ }
+
+ if(copy_user_data)
+ new_node.setUserData(getUserData());
+ else
+ new_node.setUserData(null);
+
+ return new_node;
+ }
+
+ /**
+ * Return a copy of this node. All children of this node will be copied as
+ * well. No user_data will be copied.
+ **/
+ LocationParseNode copyClean()
+ {
+ return copyInternal(false);
+ }
+
+ /**
+ * Return a copy of this node. All children of this node and all user_data
+ * fields will be copied as well.
+ **/
+ LocationParseNode copy()
+ {
+ return copyInternal(true);
+ }
+
+ /**
+ * Set the value of a RANGE node. If this node is not a RANGE node an
+ * Error will be thrown.
+ **/
+ void setRange(final Range range)
+ {
+ if(getType() != RANGE)
+ throw new Error("in LocationParseNode.getRange() - node " +
+ "is not a RANGE " + getType());
+ this.child = range;
+ }
+
+ /**
+ * Returns the result of calling toString() on each of the children of
+ * this node separated by ",".
+ **/
+ private String toStringChildren()
+ {
+ String return_string = "";
+
+ final LocationParseNodeVector children = (LocationParseNodeVector) child;
+
+ if(getType() == JOIN || getType() == ORDER)
+ {
+ for(int i = 0; i<children.size(); ++i)
+ {
+ if(0 != i)
+ return_string = return_string + ",";
+
+ // System.out.println("adding:" + children.elementAt(i).toString());
+ return_string = return_string + children.elementAt(i).toString();
+ }
+ }
+ else
+ throw new Error ("LocationParseNode.toStringChildren(): Illegal type");
+
+ return return_string;
+ }
+
+ /**
+ * Turn an Object into a String. The Object must be an instance of String,
+ * LocationParseNode or Range.
+ **/
+ private String toStringChild()
+ {
+ if(child instanceof LocationParseNode)
+ return ((LocationParseNode)child).toString();
+ else
+ throw new Error("LocationParseNode.toStringChild() was called on " +
+ "the wrong type of Object");
+ }
+
+ /**
+ * Return a complemented copy of a node. ie. COMPLEMENT(THING) will become
+ * THING and THING will become COMPLEMENT(THING).
+ **/
+ LocationParseNode getNodeComplement()
+ {
+ if(getType() == COMPLEMENT)
+ return getComplementChild().copy();
+ else
+ return new LocationParseNode(COMPLEMENT, copy());
+ }
+
+ /**
+ * Return a copy of the tree starting at this node with any
+ * "COMPLEMENT(JOIN(..." nodes turned to "JOIN(COMPLEMENT(..."
+ **/
+ private LocationParseNode getCanonicalComplement()
+ {
+ if(getType() == COMPLEMENT &&
+ (getComplementChild().getType() == JOIN ||
+ getComplementChild().getType() == ORDER))
+ {
+ final LocationParseNode complement_child = getComplementChild();
+
+ // complement each child of the JOIN or ORDER
+ final LocationParseNodeVector complemented_children =
+ new LocationParseNodeVector();
+
+ final LocationParseNodeVector complement_children =
+ (LocationParseNodeVector)complement_child.child;
+
+ for(int i = 0 ; i < complement_children.size() ; ++i)
+ {
+ final LocationParseNode complemented_child =
+ complement_children.elementAt(i).getNodeComplement();
+ complemented_children.addElement(complemented_child);
+ }
+
+ return new LocationParseNode(complement_child.getType(),
+ complemented_children);
+ }
+ else // no changes needed so just make a copy
+ return this.copy();
+ }
+
+ /**
+ * Return true if and only if the tree starting at this node is valid.
+ * @see #isValid()
+ * @param max_levels The maximum number of levels below this node - more
+ * than this number of levels means that the tree is invalid.
+ **/
+ private boolean isValid(final int max_levels, final int parent_type)
+ {
+ if(getType() == RANGE)
+ {
+ // a RANGE a valid possibility at any level
+ return true;
+ }
+
+ if(getType() == ENTRY_RANGE)
+ {
+ return getEntryRangeChild().isValid();
+ }
+
+ if(max_levels == 0)
+ {
+ // we are at the lowest level and this isn't a RANGE so this tree is
+ // nonsense
+ return false;
+ }
+ else
+ {
+ switch(getType())
+ {
+ case JOIN:
+ case ORDER:
+ if(parent_type == UNKNOWN || parent_type == COMPLEMENT)
+ {
+ // parent has the correct type
+
+ // this is set to true if any of the children are RANGE nodes
+ boolean found_range_child = false;
+
+ // this is set to true if any of the children are COMPLEMENT nodes
+ boolean found_complement_child = false;
+
+ final LocationParseNodeVector children =
+ (LocationParseNodeVector) child;
+
+ for(int i = 0; i < children.size(); ++i)
+ {
+ final LocationParseNode child_node = children.elementAt(i);
+ final boolean return_val =
+ child_node.isValid(max_levels - 1, getType());
+
+ // one of the children is invalid so this node is invalid
+ if(!return_val)
+ return false;
+
+ if(child_node.getType() == RANGE)
+ found_range_child = true;
+
+ if(child_node.getType() == COMPLEMENT)
+ found_complement_child = true;
+ }
+
+ // all the children were OK
+
+ if(found_range_child && found_complement_child)
+ {
+ // a mixture of RANGE and COMPLEMENT nodes is not valid
+ //return false;
+ JOptionPane.showMessageDialog(null, "Contains trans-spliced feature!\n"+toString(),
+ "Trans-spliced site found",
+ JOptionPane.INFORMATION_MESSAGE);
+ return true;
+ }
+ else
+ return true;
+ }
+ else // parent has the wrong type
+ return false;
+
+ case COMPLEMENT:
+ if(parent_type == UNKNOWN ||
+ parent_type == JOIN ||
+ parent_type == ORDER)
+ {
+ return getComplementChild().isValid(max_levels - 1, getType());
+ }
+ else // parent has the wrong type (ie. it is a COMPLEMENT)
+ return false;
+
+ default:
+ // anything else is invalid
+ return false;
+ }
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/io/LocationParseNodeVector.java b/uk/ac/sanger/artemis/io/LocationParseNodeVector.java
new file mode 100644
index 0000000..1990212
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/LocationParseNodeVector.java
@@ -0,0 +1,47 @@
+/* LocationParseNodeVector.java
+ *
+ * created: Wed Oct 7 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.Vector;
+
+/**
+ * LocationParseNodeVector class is a Vector of objects of type
+ * LocationParseNode.
+ * @author Kim Rutherford
+ * */
+
+public class LocationParseNodeVector extends Vector<LocationParseNode>
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Add the given node to the end of the vector.
+ **/
+ protected void addElementAtEnd (LocationParseNode node)
+ {
+ insertElementAt (node, size ());
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/LowerInteger.java b/uk/ac/sanger/artemis/io/LowerInteger.java
new file mode 100644
index 0000000..3b7e7d9
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/LowerInteger.java
@@ -0,0 +1,71 @@
+/* LowerInteger.java
+ *
+ * created: Mon May 3 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/LowerInteger.java,v 1.1 2004-06-09 09:49:55 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This class is used to represent a lower end of a range like this <100..200
+ *
+ * @author Kim Rutherford
+ * @version $Id: LowerInteger.java,v 1.1 2004-06-09 09:49:55 tjc Exp $
+ **/
+
+public class LowerInteger {
+ /**
+ * Create a new LowerInteger from the given Integer.
+ **/
+ public LowerInteger (final Integer position) {
+ this.position = position.intValue ();
+ }
+
+ /**
+ * Create a new LowerInteger from the given int.
+ **/
+ public LowerInteger (final int position) {
+ this.position = position;
+ }
+
+ /**
+ * Return the position that was passed to the constructor.
+ **/
+ public int getPosition () {
+ return position;
+ }
+
+ /**
+ * Return a String representing this object. It will look something like
+ * this: "<100"
+ **/
+ public String toString () {
+ return "<" + getPosition ();
+ }
+
+ /**
+ * The position that was passed to the constructor
+ **/
+ private int position;
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/MSPcrunchDocumentEntry.java b/uk/ac/sanger/artemis/io/MSPcrunchDocumentEntry.java
new file mode 100644
index 0000000..b3ad951
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/MSPcrunchDocumentEntry.java
@@ -0,0 +1,115 @@
+/* MSPcrunchDocumentEntry.java
+ *
+ * created: Sat Apr 15 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/MSPcrunchDocumentEntry.java,v 1.2 2007-09-25 09:59:57 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+
+/**
+ * A DocumentEntry that can read an Entry from a Document containing
+ * MSPcrunch -d or -x output.
+ *
+ * @author Kim Rutherford
+ * @version $Id: MSPcrunchDocumentEntry.java,v 1.2 2007-09-25 09:59:57 tjc Exp $
+ **/
+
+public class MSPcrunchDocumentEntry extends SimpleDocumentEntry
+ implements DocumentEntry {
+ /**
+ * Create a new MSPcrunchDocumentEntry object associated with the given
+ * Document.
+ * @param document This is the file that we will read from. This is also
+ * used for saving the entry back to the file it came from and to give
+ * the new object a name.
+ * @param listener The object that will listen for ReadEvents.
+ * @exception IOException thrown if there is a problem reading the entry -
+ * most likely ReadFormatException.
+ **/
+ MSPcrunchDocumentEntry (final Document document, final ReadListener listener)
+ throws IOException, EntryInformationException {
+ super (new MSPcrunchEntryInformation (), document, listener);
+ }
+
+ /**
+ * Create a new MSPcrunchDocumentEntry that will be a copy of the given
+ * Entry and has no Document associated with it. The new
+ * MSPcrunchDocumentEntry cannot be saved to a file with save () unless
+ * save (Document) has been called first. Some qualifier and location
+ * information will be lost.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys in the new Entry will be quietly thrown away. "Invalid"
+ * means that the key/qualifier is not allowed to occur in an Entry of
+ * this type (probably determined by the EntryInformation object of this
+ * Entry). If false an EntryInformationException will be thrown for
+ * invalid keys or qualifiers.
+ **/
+ public MSPcrunchDocumentEntry (final Entry new_entry, final boolean force)
+ throws EntryInformationException {
+ super (new MSPcrunchEntryInformation (), new_entry, force);
+ }
+
+ /**
+ * Create a new empty MSPcrunchDocumentEntry object that has no Document
+ * associated with it. The new MSPcrunchDocumentEntry cannot be saved to a
+ * file with save () unless save (Document) has been called first. The
+ * save (Document) method will assign a Document.
+ **/
+ public MSPcrunchDocumentEntry (final EntryInformation entry_information) {
+ super (new MSPcrunchEntryInformation ());
+ }
+
+ /**
+ * Returns true if and only if this entry is read only. For now this
+ * always returns true - MSPcrunchDocumentEntry objects can't be changed.
+ **/
+ public boolean isReadOnly () {
+ return true;
+ }
+
+ /**
+ * If the given feature can be added directly to this Entry, then return
+ * it, otherwise create and return a new feature of the appropriate type.
+ * @param copy if true then always new a new copy of the Feature.
+ **/
+ protected Object makeNativeFeature (final Feature feature,
+ final boolean copy) {
+ if (!copy && feature instanceof MSPcrunchStreamFeature) {
+ return (MSPcrunchStreamFeature) feature;
+ } else {
+ return new MSPcrunchStreamFeature (feature);
+ }
+ }
+
+ /**
+ * If the given Sequence can be added directly to this Entry, then return a
+ * copy of it, otherwise create and return a new feature of the appropriate
+ * type for this Entry.
+ **/
+ protected StreamSequence makeNativeSequence (final Sequence sequence) {
+ return new FastaStreamSequence (sequence);
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/MSPcrunchEntryInformation.java b/uk/ac/sanger/artemis/io/MSPcrunchEntryInformation.java
new file mode 100644
index 0000000..2a1b2ea
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/MSPcrunchEntryInformation.java
@@ -0,0 +1,37 @@
+/* MSPcrunchEntryInformation.java
+ *
+ * created: Sun Apr 16 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/MSPcrunchEntryInformation.java,v 1.1 2004-06-09 09:49:58 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * An EntryInformation object for MSPcrunchDocumentEntry objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: MSPcrunchEntryInformation.java,v 1.1 2004-06-09 09:49:58 tjc Exp $
+ **/
+
+public class MSPcrunchEntryInformation extends SimpleEntryInformation {
+
+}
diff --git a/uk/ac/sanger/artemis/io/MSPcrunchStreamFeature.java b/uk/ac/sanger/artemis/io/MSPcrunchStreamFeature.java
new file mode 100644
index 0000000..c283db5
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/MSPcrunchStreamFeature.java
@@ -0,0 +1,445 @@
+/* MSPcrunchStreamFeature.java
+ *
+ * created: Sat Apr 15 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/MSPcrunchStreamFeature.java,v 1.7 2009-04-20 14:41:35 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+
+/**
+ * A StreamFeature that thinks it is a MSPcrunch feature.
+ *
+ * @author Kim Rutherford
+ * @version $Id: MSPcrunchStreamFeature.java,v 1.7 2009-04-20 14:41:35 tjc Exp $
+ **/
+
+public class MSPcrunchStreamFeature
+ extends SimpleDocumentFeature
+ implements DocumentFeature, StreamFeature, ComparableFeature {
+
+ private static Key KEY_X = new Key ("CRUNCH_X");
+ private static Key KEY_D = new Key ("CRUNCH_D");
+
+ /**
+ * Create a new MSPcrunchStreamFeature object. The feature should be added
+ * to an Entry (with Entry.add ()).
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ **/
+ public MSPcrunchStreamFeature (final Key key,
+ final Location location,
+ final QualifierVector qualifiers) {
+ super (null);
+ try {
+ setKey (key);
+ setLocation (location);
+ setQualifiers (qualifiers);
+ } catch (EntryInformationException e) {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (ReadOnlyException e) {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Create a new MSPcrunchStreamFeature with the same key, location and
+ * qualifiers as the given feature. The feature should be added to an
+ * Entry (with Entry.add ()).
+ * @param feature The feature to copy.
+ **/
+ public MSPcrunchStreamFeature (final Feature feature) {
+ super (null);
+
+ if (feature instanceof MSPcrunchStreamFeature) {
+ mspcrunch_line = ((MSPcrunchStreamFeature)feature).mspcrunch_line;
+ }
+
+ try {
+ setKey (feature.getKey ());
+ setLocation (feature.getLocation ());
+ setQualifiers (feature.getQualifiers ());
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return the reference of a new copy of this Feature.
+ **/
+ public Feature copy () {
+ final Feature return_value = new MSPcrunchStreamFeature (this);
+
+ return return_value;
+ }
+
+ /**
+ * Create a new MSPcrunchStreamFeature from the given line. The String
+ * should be in gene finder format.
+ **/
+ private MSPcrunchStreamFeature (final String line)
+ throws ReadFormatException {
+ super (null);
+
+// final StringVector line_bits = StringVector.getStrings (line, " ");
+
+ int index = 0;
+ int lastIndex = 0;
+ int count = 1;
+
+ while( (index = line.indexOf(" ", index)) > -1)
+ {
+ count++;
+ index++;
+ }
+
+ if(count > 8)
+ count = 9;
+ else if(count < 7)
+ throw new ReadFormatException ("invalid MSPcrunch line (not enough " +
+ "fields): " + line);
+
+ final String line_bits[] = new String[count];
+ count = 0;
+ index = 0;
+
+ while( (index = line.indexOf(" ", index)) > -1 &&
+ count < line_bits.length-1)
+ {
+ line_bits[count] = line.substring(lastIndex, index).trim();
+ if(line_bits[count].equals(""))
+ count--;
+
+ count++;
+ index++;
+ lastIndex = index;
+ }
+ if(lastIndex < line.length())
+ {
+ line_bits[count] = line.substring(lastIndex);
+ count++;
+ }
+
+ try
+ {
+ int query_start = Integer.valueOf (line_bits[2]).intValue ();
+ int query_end = Integer.valueOf (line_bits[3]).intValue ();
+
+ final boolean crunch_x;
+ final boolean complement_flag;
+
+ if (line_bits[1].startsWith("(+"))
+ {
+ crunch_x = true;
+ complement_flag = false;
+ }
+ else
+ {
+ if (line_bits[1].startsWith("(-"))
+ {
+ crunch_x = true;
+ complement_flag = true;
+ }
+ else
+ {
+ if (line_bits[1].charAt (0) == '.' ||
+ Character.isDigit (line_bits[1].charAt (0)))
+ {
+ crunch_x = false;
+ if (query_start > query_end)
+ complement_flag = true;
+ else
+ complement_flag = false;
+
+ }
+
+ else
+ {
+ final String message =
+ "invalid MSPcrunch line - column 2 should be a " +
+ "number, (-1) or (+1): " + line;
+ throw new ReadFormatException (message);
+ }
+ }
+ }
+
+
+ if(query_start > query_end)
+ {
+ final int tmp = query_end;
+ query_end = query_start;
+ query_start = tmp;
+ }
+
+ if(crunch_x)
+ line_bits[1] = null;
+
+ final Qualifier query_id_qualifier;
+
+ final String subject_start;
+ final String subject_end;
+ final String subject_id;
+ final String description;
+
+ if(crunch_x)
+ {
+ query_id_qualifier = new Qualifier ("query_id", "unknown");
+ subject_start = line_bits[4];
+ subject_end = line_bits[5];
+ subject_id = line_bits[6];
+ final StringBuffer desc_buffer = new StringBuffer ();
+
+ for (int i = 7 ; i < line_bits.length ; ++i)
+ {
+ desc_buffer.append (line_bits[i]);
+ if(i < line_bits.length - 1)
+ desc_buffer.append (" ");
+ }
+ description = desc_buffer.toString ();
+
+ setKey(KEY_X);
+ }
+ else
+ {
+ query_id_qualifier = new Qualifier ("query_id", line_bits[4]);
+ subject_start = line_bits[5];
+ subject_end = line_bits[6];
+ subject_id = line_bits[7];
+ description = line_bits[8];
+
+ setKey(KEY_D);
+ }
+
+ final Qualifier blast_score_qualifier =
+ new Qualifier ("blast_score", line_bits[0]);
+
+ final Qualifier subject_start_qualifier =
+ new Qualifier ("subject_start", subject_start);
+
+ final Qualifier subject_end_qualifier =
+ new Qualifier ("subject_end", subject_end);
+
+ final Qualifier subject_id_qualifier =
+ new Qualifier ("subject_id", subject_id);
+
+ setQualifier (blast_score_qualifier);
+
+ if(line_bits[1] != null)
+ {
+ // score qualifier must be 1-100
+ final Qualifier score_qualifier =
+ new Qualifier ("score", line_bits[1]);
+
+ final Qualifier percent_id_qualifier =
+ new Qualifier ("percent_id", line_bits[1]);
+
+ setQualifier (score_qualifier);
+ setQualifier (percent_id_qualifier);
+ }
+
+ setQualifier (query_id_qualifier);
+ setQualifier (subject_start_qualifier);
+ setQualifier (subject_end_qualifier);
+ setQualifier (subject_id_qualifier);
+
+// final StringVector note_values = new StringVector ();
+// note_values.add (description);
+//
+// This is more memory intensive than just using the description:
+// note_values.add ("hit to " + subject_id + " " + subject_start +
+// ".." + subject_end + " score: " + line_bits[0] +
+// (line_bits[1] == null ?
+// "" :
+// " percent id: " + line_bits[1]) +
+// " " + description);
+
+ final Qualifier note_qualifier = new Qualifier ("note", description);
+ setQualifier (note_qualifier);
+
+ final RangeVector ranges =
+ new RangeVector (new Range (query_start, query_end));
+
+ setLocation (new Location (ranges, complement_flag));
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (LocationParseException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ this.mspcrunch_line = line;
+ }
+
+ /**
+ * Read and return a MSPcrunchStreamFeature from a stream. A feature must
+ * be the next thing in the stream.
+ * @param stream the Feature is read from this stream
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ * @return null if in_stream is at the end of file when the method is
+ * called
+ **/
+ protected static MSPcrunchStreamFeature
+ readFromStream (LinePushBackReader stream)
+ throws IOException, InvalidRelationException {
+
+ String line = stream.readLine ();
+
+ if (line == null) {
+ return null;
+ }
+
+ try {
+ final MSPcrunchStreamFeature new_feature =
+ new MSPcrunchStreamFeature (line);
+
+ return new_feature;
+ } catch (ReadFormatException exception) {
+ // re-throw the exception with the line number added
+
+ final String new_error_string = exception.getMessage ();
+
+ throw new ReadFormatException (new_error_string,
+ stream.getLineNumber ());
+ }
+ }
+
+ /**
+ * Read the details of a feature from an EMBL stream into the current
+ * object.
+ * @param entry_information The EntryInformation object of the Entry that
+ * will contain the Feature.
+ * @param in_stream the Feature is read from this stream
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException if the stream does not contain MSPcrunch
+ * feature.
+ **/
+ public void setFromStream (final EntryInformation entry_information,
+ final LinePushBackReader in_stream)
+ throws IOException, InvalidRelationException, ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Write this Feature to the given stream.
+ * @param writer The stream to write to.
+ * @exception IOException thrown if there is an io problem while writing
+ * the Feature.
+ **/
+ public void writeToStream (final Writer writer)
+ throws IOException {
+
+ // for now MSPcrunch features are read-only so just write what we read
+ writer.write (mspcrunch_line + "\n");
+
+ /*
+
+ final RangeVector ranges = getLocation ().getRanges ();
+
+ for (int i = 0 ; i < ranges.size () ; ++i) {
+ final Range this_range = ranges.elementAt (i);
+ Qualifier source = getQualifierByName ("mspcrunch_source");
+ Qualifier MSPcrunch_feature = getQualifierByName ("mspcrunch_feature");
+ Qualifier score = getQualifierByName ("score");
+ Qualifier MSPcrunch_group = getQualifierByName ("mspcrunch_group");
+ Qualifier note = getQualifierByName ("note");
+
+ if (source == null) {
+ source = new Qualifier ("source", "");
+ }
+
+ if (MSPcrunch_feature == null) {
+ MSPcrunch_feature = new Qualifier ("MSPcrunch_feature", "");
+ }
+
+ if (score == null) {
+ score = new Qualifier ("score", "");
+ }
+
+ if (MSPcrunch_group == null || MSPcrunch_group.getValues () == null ||
+ MSPcrunch_group.getValues ().elementAt (0).equals ("")) {
+ final Qualifier gene = getQualifierByName ("gene");
+
+ if (gene == null) {
+ MSPcrunch_group = new Qualifier ("MSPcrunch_group", "");
+ } else {
+ MSPcrunch_group = gene;
+ }
+ }
+
+ if (note == null) {
+ note = new Qualifier ("note", "");
+ }
+
+ String frame = ".";
+
+ final Qualifier codon_start = getQualifierByName ("codon_start");
+
+ if (codon_start != null && i == 0) {
+ frame = codon_start.getValues ().elementAt (0);
+ }
+
+ writer.write (getKey () + "\t" +
+ source.getValues ().elementAt (0) + "\t" +
+ MSPcrunch_feature.getValues ().elementAt (0) + "\t" +
+ this_range.getStart () + "\t" +
+ this_range.getEnd () + "\t" +
+ score.getValues () .elementAt (0)+ "\t" +
+ (getLocation ().isComplement () ? "\t-\t" : "\t+\t") +
+ frame + "\t" +
+ MSPcrunch_group.getValues ().elementAt (0) + "\t" +
+ note.getValues ().elementAt (0) + "\n");
+ }
+ */
+
+ }
+
+ /**
+ * The DocumentEntry object that contains this Feature as passed to the
+ * constructor.
+ **/
+ private DocumentEntry entry;
+
+ /**
+ * This is the line of MSPcrunch -d input that was read to get this
+ * MSPcrunchStreamFeature.
+ **/
+ private String mspcrunch_line = null;
+}
diff --git a/uk/ac/sanger/artemis/io/MiscLineGroup.java b/uk/ac/sanger/artemis/io/MiscLineGroup.java
new file mode 100644
index 0000000..cb1a942
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/MiscLineGroup.java
@@ -0,0 +1,94 @@
+/* MiscLineGroup.java
+ *
+ * created: Sun Sep 26 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/MiscLineGroup.java,v 1.1 2004-06-09 09:50:01 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Class for objects that contain lines that aren't handle by a more specific
+ * class.
+ *
+ * @author Kim Rutherford
+ * @version $Id: MiscLineGroup.java,v 1.1 2004-06-09 09:50:01 tjc Exp $
+ **/
+
+abstract public class MiscLineGroup extends LineGroup {
+ /**
+ * Create a new MiscLineGroup object. One line is read from the in_stream
+ * and stored.
+ **/
+ public MiscLineGroup (LinePushBackReader in_stream)
+ throws IOException {
+ line = in_stream.readLine ();
+ }
+
+ /**
+ * Create a new MiscLineGroup object that consists of the given String
+ * (which should not be terminated with a newline).
+ **/
+ public MiscLineGroup (String line) {
+ this.line = line;
+ }
+
+ /**
+ * Return the line that was read from the input stream.
+ **/
+ public String getLine () {
+ return line;
+ }
+
+ /**
+ * Write the line stored in this EmblMisc object to the given stream.
+ * @param writer The stream to write to.
+ **/
+ public void writeToStream (final Writer writer)
+ throws IOException {
+
+ writer.write (toString ());
+ }
+
+ /**
+ * Return this LineGroup object as a String.
+ **/
+ public String toString () {
+ return getLine () + "\n";
+ }
+
+ /**
+ * Set the text of this MiscLineGroup.
+ **/
+ protected void setLine (final String line) {
+ this.line = line;
+ }
+
+ /**
+ * The line that was read or passed to the constructor.
+ **/
+ private String line;
+}
diff --git a/uk/ac/sanger/artemis/io/OutOfDateException.java b/uk/ac/sanger/artemis/io/OutOfDateException.java
new file mode 100644
index 0000000..c78bc27
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/OutOfDateException.java
@@ -0,0 +1,44 @@
+/* OutOfDateException.java
+ *
+ * created: Mon May 24 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/OutOfDateException.java,v 1.1 2004-06-09 09:50:02 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This exception is thrown when an attempt is made to change an entry or
+ * feature that has changed since the last call to Entry.getDateStamp ().
+ *
+ * @author Kim Rutherford
+ * @version $Id: OutOfDateException.java,v 1.1 2004-06-09 09:50:02 tjc Exp $
+ **/
+
+public class OutOfDateException extends Exception {
+ /**
+ * This constructor creates a OutOfDateException with no detail message.
+ **/
+ public OutOfDateException () {
+ super ();
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/io/Packing.java b/uk/ac/sanger/artemis/io/Packing.java
new file mode 100644
index 0000000..0cb8721
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/Packing.java
@@ -0,0 +1,83 @@
+/* Packing.java
+ *
+ * created: Jun 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+class Packing
+{
+ static char bases[] = { 'u',
+ 'a',
+ 'g',
+ 'm',
+ 'c',
+ 'r',
+ 'w',
+ 's',
+ 't',
+ 'y',
+ 'k',
+ 'b',
+ 'd',
+ 'h',
+ 'v',
+ 'n' };
+
+ public Packing(char dna[])
+ {
+ }
+
+ protected static byte pack(char c)
+ {
+ if(c == 'a')
+ return 1;
+ else if(c == 'g')
+ return 2;
+ else if(c == 'c')
+ return 4;
+ else if(c == 't')
+ return 8;
+ else if(c == 'n')
+ return 15;
+
+ for(int i = 0; i < bases.length; i++)
+ {
+ if(c == bases[i])
+ return (byte)i;
+ }
+
+ return 15;
+ }
+
+ protected static char unpack(int i)
+ {
+ return bases[i];
+ }
+
+ public byte wordSize()
+ {
+ return 4;
+ }
+
+
+}
diff --git a/uk/ac/sanger/artemis/io/PartialSequence.java b/uk/ac/sanger/artemis/io/PartialSequence.java
new file mode 100644
index 0000000..4a1f596
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/PartialSequence.java
@@ -0,0 +1,219 @@
+/* EmptySequence.java
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import org.biojava.bio.symbol.IllegalSymbolException;
+
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+
+/**
+ * Used when retrieving just part of a sequence with associated features.
+ **/
+
+public class PartialSequence implements Sequence
+{
+ private char[] sequence;
+ /** Count of the a bases in the sequence. */
+ private int a_count = 0;
+
+ /** Count of the c bases in the sequence. */
+ private int c_count = 0;
+
+ /** Count of the g bases in the sequence. */
+ private int g_count = 0;
+
+ /** Count of the t bases in the sequence. */
+ private int t_count = 0;
+
+ /** full sequence length */
+ private int sequenceLength;
+
+ /** start of this part of the sequence*/
+ private int start;
+
+ /** orientation/directionality of the location. Should be 0,-1 or +1 */
+ private Short strand;
+
+ /** values are 0,1,2 */
+ private Integer phase;
+
+ /**
+ * Holds ony part of the sequence
+ * @param sequence part of the sequence to store
+ * @param sequenceLength full sequence length
+ * @param start start position of sequence
+ * @param strand
+ * @param phase
+ */
+ public PartialSequence(final char[] sequence,
+ final int sequenceLength,
+ final int start,
+ final Short strand,
+ final Integer phase)
+ {
+ try
+ {
+ setFromChar(sequence);
+ this.sequenceLength = sequenceLength;
+ this.start = start;
+ this.strand = strand;
+ this.phase = phase;
+ }
+ catch(IllegalSymbolException e)
+ {
+ e.printStackTrace();
+ }
+ catch(ReadOnlyException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public char charAt(int i)
+ {
+ return sequence[i];
+ }
+
+ public int getACount()
+ {
+ return a_count;
+ }
+
+ public int getCCount()
+ {
+ return c_count;
+ }
+
+ public char[] getCharSubSequence(int this_start, int this_end)
+ {
+ int subSeqLength = this_end-this_start+1;
+ char subSequence[] = new char[subSeqLength];
+ int count = 0;
+
+ this_start -= start;
+ this_end -= start;
+
+ for(int i=this_start;i<=this_end;i++)
+ {
+ subSequence[count] = sequence[i];
+ count++;
+ }
+
+ if(isComplement())
+ return Bases.reverseComplement(subSequence);
+
+ return subSequence;
+ }
+
+
+ public int getGCount()
+ {
+ return g_count;
+ }
+
+ public int getOtherCount()
+ {
+ return
+ sequence.length - (getCCount() + getACount() + getTCount() + getGCount());
+ }
+
+ public String getSubSequence(int start, int end)
+ {
+ return new String(getCharSubSequence(start, end));
+ }
+
+ public char[] getSequence()
+ {
+ return sequence;
+ }
+
+ public int getTCount()
+ {
+ return t_count;
+ }
+
+ public int length()
+ {
+ return sequenceLength;
+ }
+
+ public void setFromChar(char[] sequence) throws ReadOnlyException, IllegalSymbolException
+ {
+ this.sequence = sequence;
+ setCounts();
+ }
+
+ /**
+ * Set the a_count, c_count, t_count and g_count variables.
+ **/
+ private void setCounts()
+ {
+ a_count = c_count = t_count = g_count = 0;
+
+ for(int i = 0 ; i < length(); ++i)
+ counter(sequence[i]);
+ }
+
+ private void counter(char c)
+ {
+ switch(c)
+ {
+ case 'a':
+ ++a_count;
+ break;
+ case 'c':
+ ++c_count;
+ break;
+ case 'g':
+ ++g_count;
+ break;
+ case 't':
+ ++t_count;
+ break;
+ default:
+ break;
+ }
+ }
+
+ public Short getStrand()
+ {
+ return strand;
+ }
+
+ public boolean isComplement()
+ {
+ if(strand.intValue() == -1)
+ return true;
+ return false;
+ }
+
+ public Integer getPhase()
+ {
+ return phase;
+ }
+
+ public void clear()
+ {
+ sequence = null;
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java b/uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java
new file mode 100644
index 0000000..11284b4
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java
@@ -0,0 +1,862 @@
+/* PublicDBDocumentEntry.java
+ *
+ * created: Sat Sep 11 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/PublicDBDocumentEntry.java,v 1.22 2008-12-15 14:07:57 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.FeatureSegmentVector;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.genebuilder.cv.GoBox;
+import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class extends the Entry class with the data for the entry coming from
+ * a Document object. The Document must contain an EMBL entry or a GENBANK
+ * entry.
+ *
+ * @author Kim Rutherford
+ * @version $Id: PublicDBDocumentEntry.java,v 1.22 2008-12-15 14:07:57 tjc Exp $
+ **/
+
+public class PublicDBDocumentEntry extends SimpleDocumentEntry
+ implements DocumentEntry
+{
+ // database keys mapping to EMBL keys with any extra
+ // qualifiers to add in.
+ private static Object[][] DATABASE_MAP_KEYS;
+
+ // database qualifiers mapping to EMBL qualifiers
+ private static String[][] DATABASE_QUALIFIERS_TO_MAP;
+
+ // database qualifiers to ignore when converting to embl
+ private static Object[] DATABASE_QUALIFIERS_TO_REMOVE;
+
+ public static boolean IGNORE_OBSOLETE_FEATURES = true;
+
+ /**
+ * Create a new PublicDBDocumentEntry object associated with the given
+ * Document.
+ * @param entry_information The EntryInformation object of the new Entry.
+ * @param document This is the file that we will read from. This is also
+ * used for saving the entry back to the file it came from and to give
+ * the new object a name.
+ * @param listener The object that will listen for ReadEvents.
+ * @exception IOException thrown if there is a problem reading the entry -
+ * most likely ReadFormatException.
+ * @exception EntryInformationException Thrown if force is false and if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ PublicDBDocumentEntry(final EntryInformation entry_information,
+ final Document document, final ReadListener listener)
+ throws IOException, EntryInformationException
+ {
+ super(entry_information, document, listener);
+ }
+
+ /**
+ * Create a new PublicDBDocumentEntry that will be a copy of the given
+ * Entry and has no Document associated with it. The new
+ * PublicDBDocumentEntry cannot be saved to a file with save () unless save
+ * (Document) has been called first.
+ * @param entry_information The EntryInformation object of the new Entry.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys in the new Entry will be quietly thrown away. "Invalid"
+ * means that the key/qualifier is not allowed to occur in an Entry of
+ * this type (probably determined by the EntryInformation object of this
+ * Entry). If false an EntryInformationException will be thrown for
+ * invalid keys or qualifiers.
+ * @exception EntryInformationException Thrown if force is false and if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ public PublicDBDocumentEntry(final EntryInformation entry_information,
+ final Entry new_entry, final boolean force)
+ throws EntryInformationException
+ {
+ super(entry_information, new_entry, force);
+ }
+
+ /**
+ * Create a new empty PublicDBDocumentEntry object that has no Document
+ * associated with it. The new PublicDBDocumentEntry cannot be saved to a
+ * file with save () unless save (Document) has been called first. The
+ * save (Document) method will assign a Document.
+ * @param entry_information The EntryInformation object of the Entry that
+ * will contain this Feature.
+ **/
+ public PublicDBDocumentEntry(final EntryInformation entry_information)
+ {
+ super(entry_information);
+ }
+
+ /**
+ * If the given feature can be added directly to this Entry, then return
+ * it, otherwise create and return a new feature of the appropriate type.
+ * @param copy if true then always new a new copy of the Feature.
+ **/
+ protected Object makeNativeFeature(final Feature feature,
+ final boolean copy)
+ {
+ if (!copy && (feature instanceof EmblStreamFeature &&
+ this instanceof EmblDocumentEntry ||
+ feature instanceof GenbankStreamFeature &&
+ this instanceof GenbankDocumentEntry))
+ {
+ return (PublicDBStreamFeature) feature;
+ }
+ else
+ {
+ try
+ {
+ if(feature instanceof GFFStreamFeature)
+ return mapGffToNativeFeature(feature);
+ else if (this instanceof EmblDocumentEntry)
+ return new EmblStreamFeature(feature);
+ else
+ return new GenbankStreamFeature(feature);
+ }
+ catch(NullPointerException npe)
+ {
+ System.err.println(
+ ((uk.ac.sanger.artemis.Feature)feature.getUserData()).getIDString() );
+ throw npe;
+ }
+ }
+ }
+
+
+ /**
+ * Map GFF features to EMBL/Genbank
+ * @param feature
+ * @return
+ */
+ private Object mapGffToNativeFeature(final Feature feature)
+ {
+ if(DATABASE_MAP_KEYS == null)
+ initDatabaseMappings();
+
+ Key key = feature.getKey();
+ QualifierVector qualifiers = feature.getQualifiers().copy();
+
+ // ignore if obsolete
+ if(IGNORE_OBSOLETE_FEATURES)
+ {
+ Qualifier isObsoleteQualifier = qualifiers.getQualifierByName("isObsolete");
+ if(isObsoleteQualifier != null)
+ {
+ String value = (String)isObsoleteQualifier.getValues().get(0);
+ if(Boolean.parseBoolean(value))
+ return null;
+ }
+ }
+
+ key = map(key, qualifiers);
+ if(getEntryInformation().isValidQualifier((String) DATABASE_QUALIFIERS_TO_REMOVE[0]))
+ {
+ try
+ {
+ if(this instanceof EmblDocumentEntry)
+ return new EmblStreamFeature (
+ key,
+ feature.getLocation(),
+ qualifiers);
+ else
+ return new GenbankStreamFeature (
+ key,
+ feature.getLocation(),
+ qualifiers);
+ }
+ catch(InvalidRelationException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ Location location = joinUtrs(feature, key, qualifiers);
+ if(location == null)
+ return null;
+ // flatten gene model - combining qualifiers
+ if(key.getKeyString().equals(DatabaseDocument.EXONMODEL) &&
+ ((GFFStreamFeature)feature).getChadoGene() != null)
+ {
+ ChadoCanonicalGene chadoGene = ((GFFStreamFeature)feature).getChadoGene();
+
+ final String name = GeneUtils.getUniqueName(feature);
+ final String transcriptName = chadoGene.getTranscriptFromName(name);
+
+ StringVector sv = new StringVector();
+ sv.add(transcriptName);
+ final Feature transcript = chadoGene.containsTranscript(sv);
+
+ if(transcript != null && GeneUtils.isNonCodingTranscripts(transcript.getKey()))
+ return null;
+
+ qualifiers.removeQualifierByName("ID");
+ int ntranscripts = 0;
+ // add transcript & protein qualifiers to CDS
+ try
+ {
+ final Feature protein = chadoGene.getProteinOfTranscript(transcriptName);
+ if(protein != null)
+ combineQualifiers(qualifiers, protein.getQualifiers().copy(), false);
+
+ if(transcript != null)
+ ntranscripts = handleTranscripts(qualifiers, transcript, ntranscripts, chadoGene);
+ }
+ catch(NullPointerException npe){}
+
+ // add gene qualifiers to CDS
+ QualifierVector geneQualifiers = chadoGene.getGene().getQualifiers().copy();
+
+ // multiple transcripts
+ if(ntranscripts > 1 && geneQualifiers.getQualifierByName("ID") != null)
+ {
+ Qualifier newIDQualifier =
+ new Qualifier("shared_id", (String)geneQualifiers.getQualifierByName("ID").getValues().get(0));
+ addNewQualifier(qualifiers, newIDQualifier);
+ geneQualifiers.removeQualifierByName("ID");
+ }
+ else if(ntranscripts == 1)
+ {
+ if(qualifiers.getQualifierByName("Start_range") != null)
+ addNewQualifier(qualifiers, qualifiers.getQualifierByName("Start_range"));
+ if(qualifiers.getQualifierByName("End_range") != null)
+ addNewQualifier(qualifiers, qualifiers.getQualifierByName("End_range"));
+ }
+ combineQualifiers(qualifiers, geneQualifiers, true);
+ }
+ else if(GeneUtils.isNonCodingTranscripts(key))
+ {
+ // use gene id for non-coding transcripts
+ ChadoCanonicalGene chadoGene = ((GFFStreamFeature)feature).getChadoGene();
+ if(chadoGene != null)
+ {
+ qualifiers.removeQualifierByName("ID");
+ QualifierVector geneQualifiers = chadoGene.getGene().getQualifiers().copy();
+ combineQualifiers(qualifiers, geneQualifiers, true);
+
+ if(qualifiers.getQualifierByName("product") != null)
+ {
+ Qualifier newQualifier = new Qualifier("product", processProductValues(qualifiers.getQualifierByName("product")));
+ qualifiers.setQualifier(newQualifier);
+ }
+ }
+ }
+
+ try
+ {
+ location = handlePartials(qualifiers, location);
+ for(int i=0; i<DATABASE_QUALIFIERS_TO_MAP.length; i++)
+ {
+ if(!getEntryInformation().isValidQualifier(DATABASE_QUALIFIERS_TO_MAP[i][0]))
+ {
+ changeQualifierName(qualifiers, DATABASE_QUALIFIERS_TO_MAP[i][0],
+ DATABASE_QUALIFIERS_TO_MAP[i][1]);
+ }
+ }
+
+ if(qualifiers.getQualifierByName("stop_codon_redefined_as_selenocysteine") != null)
+ {
+ handleSelenocysteine(qualifiers, feature);
+ }
+
+ for(int i=0; i<DATABASE_QUALIFIERS_TO_REMOVE.length; i++)
+ {
+ if(!getEntryInformation().isValidQualifier((String) DATABASE_QUALIFIERS_TO_REMOVE[i]))
+ qualifiers.removeQualifierByName((String) DATABASE_QUALIFIERS_TO_REMOVE[i]);
+ }
+
+ if(key.getKeyString().equals("polypeptide"))
+ return null;
+ else if(key.getKeyString().equals("gene"))
+ return null;
+ else if(key.getKeyString().equals("centromere"))
+ return null;
+ else if(key.getKeyString().equals("transcript") ||
+ key.getKeyString().equals("mRNA"))
+ return null;
+
+
+ if(this instanceof EmblDocumentEntry)
+ return new EmblStreamFeature (
+ key, location,
+ qualifiers);
+ else
+ return new GenbankStreamFeature (
+ key, location,
+ qualifiers);
+ }
+ catch(InvalidRelationException e)
+ {
+ e.printStackTrace();
+ if(feature instanceof DatabaseStreamFeature)
+ return new EmblStreamFeature ();
+ else
+ return new GenbankStreamFeature ();
+ }
+ }
+
+ /**
+ * Handle UTR joins
+ * @param feature
+ * @param key
+ * @param qualifiers
+ * @return
+ */
+ private Location joinUtrs(Feature feature, Key key, QualifierVector qualifiers)
+ {
+ Location location = feature.getLocation();
+ if(key.getKeyString().equals("5'UTR") ||
+ key.getKeyString().equals("3'UTR"))
+ {
+ ChadoCanonicalGene gene = ((GFFStreamFeature)feature).getChadoGene();
+ String utrName = GeneUtils.getUniqueName(feature);
+ String transcriptName = gene.getTranscriptFromName(utrName);
+ List<Feature> utrs;
+
+ if(key.getKeyString().equals("5'UTR"))
+ utrs = gene.get5UtrOfTranscript(transcriptName);
+ else
+ utrs = gene.get3UtrOfTranscript(transcriptName);
+
+ if(utrs.size() > 1)
+ {
+ int start = Integer.MAX_VALUE;
+ RangeVector ranges = new RangeVector();
+ for(int i=0; i<utrs.size(); i++)
+ {
+ Feature utr = utrs.get(i);
+ Range range = utr.getLocation().getTotalRange();
+ if(start > range.getStart())
+ start = range.getStart();
+ ranges.add(range);
+ }
+
+ if(start != feature.getLocation().getTotalRange().getStart())
+ return null;
+
+ if(feature.getLocation().isComplement())
+ ranges.reverse();
+ location =
+ new Location(ranges, feature.getLocation().isComplement());
+ }
+
+ int ntranscripts = gene.getTranscripts().size();
+ if(ntranscripts == 1)
+ transcriptName = gene.getGeneUniqueName();
+ qualifiers.setQualifier(new Qualifier("locus_tag", transcriptName));
+ qualifiers.removeQualifierByName("ID");
+ }
+ return location;
+ }
+
+ /**
+ * Merge qualifiers
+ * @param qualifiers
+ * @param newQualifiers
+ */
+ private void combineQualifiers(final QualifierVector qualifiers,
+ final QualifierVector newQualifiers,
+ final boolean isGene)
+ {
+ for(int i=0; i<newQualifiers.size(); i++)
+ {
+ Qualifier newQualifier = (Qualifier) newQualifiers.get(i);
+
+ if( newQualifier.getName().equals("ID") && !isGene )
+ {
+ continue;
+ }
+
+ // convert GO evidence to codes (e.g. ND=No biological Data available)
+ if(newQualifier.getName().equals("GO"))
+ {
+ final StringVector newValues = newQualifier.getValues();
+ final StringVector tmpNewValues = new StringVector();
+ for(int j=0; j<newValues.size(); j++)
+ {
+ String val = GoBox.getEvidenceCodeGoTextFromText((String)newValues.get(j));
+ tmpNewValues.add(val);
+ }
+
+ newQualifier = new Qualifier("GO", tmpNewValues);
+ }
+
+ if(newQualifier.getName().equals("product"))
+ {
+ newQualifier = new Qualifier("product", processProductValues(newQualifier));
+ }
+
+ if(newQualifier.getName().equals("orthologous_to") ||
+ newQualifier.getName().equals("paralogous_to"))
+ {
+ final StringVector newValues = newQualifier.getValues();
+ final StringVector tmpNewValues = new StringVector();
+ for(int j=0; j<newValues.size(); j++)
+ {
+ if(!newValues.get(j).equals(""))
+ tmpNewValues.add(newValues.get(j));
+ }
+ if(tmpNewValues.size() == 0)
+ continue;
+
+ Pattern p = Pattern.compile("\\w+[ :]link=\\w+");
+ for(int j=0; j<tmpNewValues.size(); j++)
+ {
+ String valueStr = (String)tmpNewValues.get(j);
+ String newValueStr;
+ int indexEnd = valueStr.indexOf(';');
+ String endStr = "";
+ if(indexEnd > -1)
+ endStr = valueStr.substring(indexEnd);
+ Matcher m = p.matcher(valueStr);
+ while(m.find())
+ {
+ int index = valueStr.indexOf("link=", m.start());
+ newValueStr = valueStr.substring(m.start(), index)+
+ valueStr.substring(index+5, m.end())+endStr;
+ if(newQualifier.getName().equals("orthologous_to"))
+ newQualifier = new Qualifier("orthologous_to", newValueStr);
+ else
+ newQualifier = new Qualifier("paralogous_to", newValueStr);
+ qualifiers.addElement(newQualifier);
+ }
+ }
+ continue;
+ }
+
+ addNewQualifier(qualifiers, newQualifier);
+ }
+ }
+
+ /**
+ * Process product qualifier
+ * @param productQualifier
+ * @return
+ */
+ private StringVector processProductValues(Qualifier productQualifier)
+ {
+ final StringVector values = productQualifier.getValues();
+ final StringVector tmpNewValues = new StringVector();
+ for(int j=0; j<values.size(); j++)
+ {
+ String val = (String)values.get(j);
+
+ int ind = 0;
+
+ if((ind=val.indexOf(";db_xref="))>-1 && this instanceof EmblDocumentEntry)
+ val = val.substring(0,ind);
+
+ if((ind=val.indexOf(";evidence="))>-1 && this instanceof EmblDocumentEntry)
+ val = val.substring(0,ind);
+
+ if((ind=val.indexOf(";with="))>-1 && this instanceof EmblDocumentEntry)
+ val = val.substring(0,ind);
+
+ if(val.startsWith("term="))
+ val = val.substring(5, val.length());
+
+ if(val.endsWith(";"))
+ val = val.substring(0, val.length()-1);
+
+ tmpNewValues.add(val);
+ }
+ return tmpNewValues;
+ }
+
+ /**
+ * Add a new qualifier to a list of qualifiers
+ * @param qualifiers
+ * @param newQualifier
+ */
+ private void addNewQualifier(QualifierVector qualifiers, Qualifier newQualifier)
+ {
+ Qualifier qualifier;
+ if( (qualifier = qualifiers.getQualifierByName(newQualifier.getName())) != null)
+ {
+ final StringVector newValues = newQualifier.getValues();
+ final StringVector values = qualifier.getValues();
+
+ if(newValues == null)
+ return;
+ for(int j=0; j<newValues.size(); j++)
+ {
+ String newValue = (String) newValues.get(j);
+ if(!values.contains(newValue))
+ qualifier.addValue(newValue);
+ }
+ }
+ else
+ qualifiers.addElement(newQualifier);
+ }
+
+ /**
+ * Routine to combine transcript qualifiers and for multiple transcripts
+ * create links to the other transcripts (other_transcript) and
+ * to use the transcript ID.
+ * @param qualifiers
+ * @param transcript
+ * @param ntranscripts
+ * @param chadoGene
+ */
+ private int handleTranscripts(QualifierVector qualifiers,
+ Feature transcript,
+ int ntranscripts,
+ ChadoCanonicalGene chadoGene)
+ {
+ QualifierVector transcriptQualifiers = transcript.getQualifiers().copy();
+ combineQualifiers(qualifiers, transcriptQualifiers, false);
+ ntranscripts = chadoGene.getTranscripts().size();
+ if(ntranscripts > 1)
+ {
+ addNewQualifier(qualifiers, transcriptQualifiers.getQualifierByName("ID"));
+ List<Feature> transcripts = chadoGene.getTranscripts();
+ for(int i=0;i<ntranscripts;i++)
+ {
+ Feature thisTranscript = (Feature)transcripts.get(i);
+ String thisTranscriptName = GeneUtils.getUniqueName(thisTranscript);
+ if(!thisTranscriptName.equals( GeneUtils.getUniqueName(transcript )))
+ {
+ Qualifier qualifier = new Qualifier("other_transcript", thisTranscriptName);
+ addNewQualifier(qualifiers, qualifier);
+ }
+ }
+ }
+ return ntranscripts;
+ }
+
+ /**
+ * Maps database (SO) keys to EMBl keys.
+ * @param key
+ * @return
+ */
+ protected static Key mapKeys(final Key key)
+ {
+ if(DATABASE_MAP_KEYS == null)
+ initDatabaseMappings();
+ for(int i=0; i<DATABASE_MAP_KEYS.length; i++)
+ {
+ if(key.getKeyString().equals(DATABASE_MAP_KEYS[i][0]))
+ return new Key((String)DATABASE_MAP_KEYS[i][1]);
+ }
+ return key;
+ }
+
+
+ /**
+ * Maps database (SO) keys to EMBl keys. It will add any extra qualifiers
+ * found in the 3rd column of DATABASE_MAP_KEYS.
+ * @param key
+ * @return
+ */
+ private Key map(final Key key, final QualifierVector qualifiers)
+ {
+ if(DATABASE_MAP_KEYS == null)
+ initDatabaseMappings();
+ for(int i=0; i<DATABASE_MAP_KEYS.length; i++)
+ {
+ if(key.getKeyString().equals(DATABASE_MAP_KEYS[i][0]))
+ {
+ Key mappedKey = new Key((String)DATABASE_MAP_KEYS[i][1]);
+ if(DATABASE_MAP_KEYS[i][2] != null)
+ {
+
+ Qualifier newQualifier = (Qualifier) DATABASE_MAP_KEYS[i][2];
+ if(!getEntryInformation().isValidQualifier(mappedKey, newQualifier.getName()))
+ {
+ try
+ {
+ final int nvalues;
+ if(newQualifier.getValues() == null)
+ nvalues = 0;
+ else
+ nvalues = newQualifier.getValues().size();
+
+ final int type;
+ if(nvalues == 0)
+ type = QualifierInfo.NO_VALUE;
+ else
+ type = QualifierInfo.QUOTED_TEXT;
+ getEntryInformation().addQualifierInfo(
+ new QualifierInfo(newQualifier.getName(),
+ type, null, null,
+ false));
+ }
+ catch(QualifierInfoException e){}
+ }
+
+ qualifiers.addQualifierValues(newQualifier);
+ }
+
+ return mappedKey;
+ }
+ }
+ return key;
+ }
+
+ /**
+ * Change the name of a qualifier
+ * @param qualifiers
+ * @param oldName
+ * @param newName
+ */
+ private void changeQualifierName(QualifierVector qualifiers,
+ final String oldName,
+ final String newName)
+ {
+ QualifierVector tmpQualifiers = new QualifierVector();
+ for(int i=0; i<qualifiers.size(); i++)
+ {
+ Qualifier qualifier = (Qualifier) qualifiers.elementAt(i);
+ if(!qualifier.getName().equals(oldName))
+ {
+ tmpQualifiers.addElement(qualifier);
+ continue;
+ }
+
+ Qualifier newQualifier = new Qualifier(newName, qualifier.getValues());
+ tmpQualifiers.addQualifierValues(newQualifier);
+ }
+ qualifiers.removeAllElements();
+
+ for(int i=0; i<tmpQualifiers.size(); i++)
+ qualifiers.addElement(tmpQualifiers.elementAt(i));
+ }
+
+ /**
+ * If the given Sequence can be added directly to this Entry, then return a
+ * copy of it, otherwise create and return a new feature of the appropriate
+ * type for this Entry.
+ **/
+ protected StreamSequence makeNativeSequence (final Sequence sequence)
+ {
+ if(this instanceof EmblDocumentEntry)
+ return new EmblStreamSequence (sequence);
+ else
+ return new GenbankStreamSequence (sequence);
+ }
+
+ public static Object[] getDatabaseQualifiersToRemove()
+ {
+ initDatabaseMappings();
+ return DATABASE_QUALIFIERS_TO_REMOVE;
+ }
+
+ /**
+ * Read key and qualifier mappings for CHADO to EMBL
+ **/
+ private static void initDatabaseMappings()
+ {
+ InputStream keyStream =
+ Options.class.getResourceAsStream("/key_mapping");
+ if (keyStream == null)
+ keyStream =
+ Options.class.getResourceAsStream("/etc/key_mapping");
+
+ InputStream qualifierStream =
+ Options.class.getResourceAsStream("/qualifier_mapping");
+ if (qualifierStream == null)
+ qualifierStream =
+ Options.class.getResourceAsStream("/etc/qualifier_mapping");
+
+ final Properties keyMapProperties = new Properties();
+ final Properties qualifierMapProperties = new Properties();
+ try
+ {
+ keyMapProperties.load(keyStream);
+ qualifierMapProperties.load(qualifierStream);
+
+ if(System.getProperty("nohistory") != null)
+ qualifierMapProperties.setProperty("history", "");
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+
+ // parse the keyMapProperties
+ DATABASE_MAP_KEYS = new Object[keyMapProperties.size()][3];
+ final Enumeration keysenum = keyMapProperties.propertyNames();
+ int n = 0;
+ while(keysenum.hasMoreElements())
+ {
+ String current_map_name = (String) keysenum.nextElement();
+
+ final StringVector property_values =
+ Options.getPropertyValues(keyMapProperties, current_map_name);
+
+ DATABASE_MAP_KEYS[n][0] = current_map_name;
+ DATABASE_MAP_KEYS[n][1] = property_values.get(0);
+ if(property_values.size() == 2)
+ {
+ String qualifierString[] = ((String)property_values.get(1)).split("=");
+ final uk.ac.sanger.artemis.io.Qualifier qualifier;
+ if(qualifierString.length == 2)
+ qualifier = new uk.ac.sanger.artemis.io.Qualifier(qualifierString[0], qualifierString[1]);
+ else
+ qualifier = new uk.ac.sanger.artemis.io.Qualifier(qualifierString[0]);
+ DATABASE_MAP_KEYS[n][2] = qualifier;
+ }
+ else
+ DATABASE_MAP_KEYS[n][2] = null;
+ n++;
+ }
+
+ // parse the qualifier mappings
+ Enumeration qualifiersenum = qualifierMapProperties.propertyNames();
+ n = 0;
+
+ Vector qualifiersToRemove = new Vector();
+ while(qualifiersenum.hasMoreElements())
+ {
+ String current_map_name = (String) qualifiersenum.nextElement();
+ final StringVector property_values =
+ Options.getPropertyValues(qualifierMapProperties, current_map_name);
+ if(property_values == null || property_values.size() == 0)
+ qualifiersToRemove.add(current_map_name);
+ else
+ n++;
+ }
+
+ DATABASE_QUALIFIERS_TO_MAP = new String[n][2];
+ DATABASE_QUALIFIERS_TO_REMOVE = qualifiersToRemove.toArray();
+
+ qualifiersenum = qualifierMapProperties.propertyNames();
+ n = 0;
+
+ while(qualifiersenum.hasMoreElements())
+ {
+ String current_map_name = (String) qualifiersenum.nextElement();
+ final StringVector property_values =
+ Options.getPropertyValues(qualifierMapProperties, current_map_name);
+ if(property_values != null && property_values.size() > 0)
+ {
+ DATABASE_QUALIFIERS_TO_MAP[n][0] = current_map_name;
+ DATABASE_QUALIFIERS_TO_MAP[n][1] = (String) property_values.get(0);
+ n++;
+ }
+ }
+ }
+
+ /**
+ * Use '<' and '>' signs in the location descriptors to
+ * indicate that the sequence is partial.
+ * @param qualifiers
+ * @param location
+ * @return
+ */
+ private Location handlePartials(QualifierVector qualifiers, Location location)
+ {
+ if(qualifiers.getQualifierByName("Start_range") != null)
+ {
+ try
+ {
+ location = new Location(location.toStringShort().replaceFirst("(\\d)", "<$1"));
+ }
+ catch (LocationParseException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ if(qualifiers.getQualifierByName("End_range") != null)
+ {
+ try
+ {
+ location = new Location(location.toStringShort().replaceAll("^(.*)(\\.)(.*)$","$1$2>$3"));
+ }
+ catch (LocationParseException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return location;
+ }
+
+ /**
+ * Change the stop_codon_redefined_as_selenocysteine SO qualifier
+ * to the transl_except EMBL qualifier.
+ * @param qualifiers
+ * @param feature
+ */
+ private void handleSelenocysteine(QualifierVector qualifiers, Feature feature)
+ {
+ if(!feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL))
+ return;
+ qualifiers.removeQualifierByName("stop_codon_redefined_as_selenocysteine");
+
+ uk.ac.sanger.artemis.Feature f = ((uk.ac.sanger.artemis.Feature)feature.getUserData());
+
+ int translatedBasePosion = 0;
+ String aa = f.getTranslation().toString();
+ for(int i=0; i<aa.length(); i++)
+ {
+ if(AminoAcidSequence.isStopCodon(aa.charAt(i)))
+ {
+ translatedBasePosion = i*3;
+ break;
+ }
+ }
+
+ FeatureSegmentVector segments = f.getSegments();
+ int nbases = 0;
+ int sequenceloc = 0;
+ for(int i=0; i<segments.size(); i++)
+ {
+ int seglen = segments.elementAt(i).getBases().length();
+ if(nbases+seglen > translatedBasePosion && sequenceloc == 0)
+ {
+ Bases bases = f.getStrand().getBases();
+ sequenceloc = segments.elementAt(i).getStart().getPosition() +
+ (translatedBasePosion-nbases);
+
+ if(!f.isForwardFeature())
+ sequenceloc = bases.getComplementPosition(sequenceloc);
+ }
+ nbases += seglen;
+ }
+
+ String pos;
+ if(f.isForwardFeature())
+ pos = sequenceloc+".."+(sequenceloc+2);
+ else
+ pos = sequenceloc+".."+(sequenceloc-2);
+
+ qualifiers.add(new Qualifier("transl_except","(pos:"+pos+",aa:Sec)"));
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/io/PublicDBStreamFeature.java b/uk/ac/sanger/artemis/io/PublicDBStreamFeature.java
new file mode 100644
index 0000000..a25424b
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/PublicDBStreamFeature.java
@@ -0,0 +1,689 @@
+/* PublicDBStreamFeature.java
+ *
+ * created: Tue Sep 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/PublicDBStreamFeature.java,v 1.4 2007-04-12 10:26:40 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+import java.io.*;
+
+/**
+ * This is an implementation of Feature that can read and write itself to a
+ * EMBL or GENBANK stream.
+ *
+ * @author Kim Rutherford
+ * @version $Id: PublicDBStreamFeature.java,v 1.4 2007-04-12 10:26:40 tjc Exp $
+ **/
+
+abstract public class PublicDBStreamFeature
+ extends SimpleDocumentFeature
+ implements DocumentFeature, StreamFeature, ComparableFeature {
+ /**
+ * Create a new PublicDBStreamFeature object. The feature should be added
+ * to an Entry (with Entry.add ()).
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ **/
+ public PublicDBStreamFeature (final Key key,
+ final Location location,
+ final QualifierVector qualifiers) {
+ super (null);
+ try {
+ setKey (key);
+ setLocation (location);
+ setQualifiers (qualifiers);
+ } catch (EntryInformationException e) {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (ReadOnlyException e) {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ // this should never happen because the feature will not be in an Entry
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Create a new PublicDBStreamFeature with the same key, location and
+ * qualifiers as the given feature. The feature should be added to an
+ * Entry (with Entry.add ()).
+ * @param feature The feature to copy.
+ **/
+ public PublicDBStreamFeature (final Feature feature) {
+ super (null);
+ try {
+ setKey (feature.getKey ());
+ setLocation (feature.getLocation ());
+ setQualifiers (feature.getQualifiers ());
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (ReadOnlyException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Write this Feature to the given stream.
+ * @param writer The stream to write to.
+ * @exception IOException thrown if there is an io problem while writing
+ * the Feature.
+ **/
+ public synchronized void writeToStream (final Writer writer)
+ throws IOException {
+ writeKey (writer);
+ writeLocation (writer);
+ writeQualifiers (writer);
+ }
+
+ /**
+ * Read and return a PublicDBStreamFeature from a stream. A feature must
+ * be the next thing in the stream.
+ * @param stream the Feature is read from this stream
+ * @param feature_type this flag indicates whether to read the feature as
+ * an EMBL feature (flag == LineGroup.EMBL_FEATURE_TABLE) or as a GENBANK
+ * feature (flag == LineGroup.GENBANK_FEATURE_TABLE).
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException.
+ * @return null if in_stream is at the end of file when the method is
+ * called
+ **/
+ protected static PublicDBStreamFeature
+ readFromStream (LinePushBackReader stream,
+ int feature_type)
+ throws IOException {
+
+ final PublicDBStreamFeature new_feature;
+
+ if (feature_type == LineGroup.EMBL_FEATURE) {
+ new_feature = new EmblStreamFeature ();
+ } else {
+ new_feature = new GenbankStreamFeature ();
+ }
+
+ try {
+ new_feature.setFromStream (stream);
+ } catch (EOFException e) {
+ return null;
+ }
+
+ return new_feature;
+ }
+
+ /**
+ * This is used by readFromStream () as temporary storage. It is a class
+ * member rather than a local variable so that we don't need to allocate a
+ * object for each call. The number we pick for the initial StringBuffer
+ * size is not critical, but should cover most possibilities
+ **/
+ final static private StringBuffer qualifier_string_buffer =
+ new StringBuffer (1500);
+
+ /**
+ * Read the details of a feature from an EMBL stream into the current
+ * object. (Called only by readFromStream ()).
+ * @param in_stream the Feature is read from this stream
+ * @exception IOException thrown if there is a problem reading the Feature -
+ * most likely ReadFormatException.
+ **/
+ private void setFromStream (final LinePushBackReader in_stream)
+ throws IOException {
+
+ final String first_line = in_stream.readLine ();
+
+ if (first_line == null) {
+ // we tried to read a Feature starting at the end of file
+ throw new EOFException ("while reading a feature");
+ }
+
+ final int line_type = getLineType (first_line);
+
+ if (this instanceof EmblStreamFeature &&
+ line_type != LineGroup.EMBL_FEATURE ||
+ this instanceof GenbankStreamFeature &&
+ line_type != LineGroup.GENBANK_FEATURE) {
+ // this line is not the first line of a feature
+
+ in_stream.pushBack (first_line);
+
+ throw new EOFException ("end of feature table");
+ }
+
+ if (first_line.length () < 15) {
+ throw new ReadFormatException ("line too short",
+ in_stream.getLineNumber ());
+ }
+
+ final String key_string =
+ getKeyStringFromLine (first_line, in_stream.getLineNumber ());
+
+ if (key_string == null) {
+ throw new ReadFormatException ("expected the first line of a " +
+ "feature",
+ in_stream.getLineNumber ());
+ }
+
+ // set to true when we see the first qualifier.
+ // we need this so we know when to stop adding to location_string and
+ // start adding to qualifier_string.
+ boolean location_string_finished = false;
+
+ String location_string = getRestOfFeatureLine (first_line);
+
+ qualifier_string_buffer.setLength (0);
+
+ String line;
+
+ // the line of the input where the qualifiers start - used for error
+ // reporting
+ //int qualifier_start_line = -1;
+
+ // the line of the input where this feature starts - used for error
+ // reporting
+ final int feature_start_line = in_stream.getLineNumber ();
+
+ // loop until there are no more lines in the file or we hit the start of
+ // the next feature or the start of the next line group
+ while (true) {
+ line = in_stream.readLine ();
+
+ if (line == null) {
+ // we have reached the end of file - break out of loop
+ break;
+ }
+
+ final int current_line_type = getLineType (line);
+
+ if (this instanceof EmblStreamFeature &&
+ current_line_type == LineGroup.EMBL_FEATURE ||
+ this instanceof GenbankStreamFeature &&
+ current_line_type == LineGroup.GENBANK_FEATURE) {
+
+ if (getKeyStringFromLine (line, in_stream.getLineNumber ()) == null) {
+
+ // read the text in this line after the key
+ final String rest_of_line = getRestOfFeatureLine (line);
+
+ if (rest_of_line == null) {
+ throw new ReadFormatException ("line too short while reading " +
+ "feature",
+ in_stream.getLineNumber ());
+ }
+
+ if (location_string_finished) {
+
+ final int qualifier_string_length =
+ qualifier_string_buffer.length ();
+
+ final char last_char =
+ qualifier_string_buffer.charAt (qualifier_string_length - 1);
+
+ if (last_char != '"') {
+ // we put space in the string to handle those cases when a
+ // qualifier wraps over the end of a line, but "" needs to be
+ // kept as a single token
+ qualifier_string_buffer.append (" ");
+ }
+
+ qualifier_string_buffer.append (rest_of_line);
+ } else {
+ // the last line(s) we read was part of location string
+
+ if (rest_of_line.startsWith ("/")) {
+
+ // we have now seen the first qualifier line so we can use the
+ // location_string to create a Location object
+
+ location_string_finished = true;
+
+ qualifier_string_buffer.append (rest_of_line);
+
+ //qualifier_start_line = in_stream.getLineNumber ();
+
+ } else {
+ location_string = location_string + rest_of_line;
+ }
+ }
+ // continue with loop
+ } else {
+
+ // line has a feature key then it must be the start of a new
+ // feature
+ in_stream.pushBack (line);
+ break;
+
+ }
+ } else {
+ // this line is not a feature line so return now
+ in_stream.pushBack (line);
+ break;
+ }
+ }
+
+
+ final Key key = new Key (key_string);
+
+ final Location location;
+
+ try {
+ location = new Location (location_string);
+ } catch (LocationParseException exception) {
+ // re-throw the exception with the line number added
+
+ final String new_error_string = exception.getMessage ();
+
+ // subtract 1 because the error was on the previous line
+ throw new ReadFormatException (new_error_string,
+ feature_start_line);
+ }
+
+
+ final QualifierVector qualifiers;
+
+ final String qualifier_string = qualifier_string_buffer.toString ();
+
+ try {
+ qualifiers = getQualifiersFromString (qualifier_string,
+ getEntryInformation ());
+ } catch (QualifierParseException exception) {
+ // re-throw the exception with the line number added
+ final String new_error_string = exception.getMessage ();
+
+ // subtract 1 because the error was on the previous line
+ throw new ReadFormatException (new_error_string,
+ feature_start_line);
+
+ }
+
+ try {
+ set (key, location, qualifiers);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ setDirtyFlag ();
+ }
+
+
+ /**
+ * Get the text in a line from a feature minus the FT tag and the key
+ * @param line the entry line to process
+ */
+ private static String getRestOfFeatureLine (String line) {
+
+ if (line.length () < LOCATION_START_COLUMN) {
+ return null;
+ } else {
+ return line.substring (LOCATION_START_COLUMN).trim ();
+ }
+ }
+
+ private final static int KEY_FIELD_WIDTH = 16;
+
+ /**
+ * Return the key from a embl entry line.
+ * @param line_string the text of the entry line to process
+ * @param line_number the line number of line_string in the input stream
+ * @return null if this isn't the first line of a feature, otherwise the
+ * key of this feature
+ */
+ private static String getKeyStringFromLine (final String line_string,
+ final int line_number)
+ throws ReadFormatException {
+ // the first line of a feature starts with "FT " - remove that first
+ final String rest_of_line = getRestOfLine (line_string);
+
+ if (rest_of_line == null ||
+ rest_of_line.startsWith (" ")) {
+ // this isn't the first line of a feature
+ return null;
+ } else {
+ if (rest_of_line.length () < KEY_FIELD_WIDTH) {
+ return null;
+ } else {
+ if (rest_of_line.charAt (KEY_FIELD_WIDTH - 1) != ' ') {
+ throw new ReadFormatException ("column " +
+ LOCATION_START_COLUMN + " must "
+ + "be empty", line_number);
+ }
+
+ final String key_field = rest_of_line.substring (0,15);
+
+ return key_field.trim ();
+ }
+ }
+ }
+
+ /**
+ * Read some embl feature qualifiers from a stream into a QualifierVector
+ * object. The stream should contain qualifiers in this form:
+ * <PRE> /name1=value1/name2="value2"/name3=[value3] </PRE>
+ * @param in_stream the qualifiers are read from this stream
+ * @exception IOException thrown if there is a problem reading the
+ * qualifiers, such as end of file.
+ * @exception QualifierParseException Thrown if the format of the value
+ * String is not appropriate for a Qualifier with the given name. Each
+ * qualifier has a specific format for the value part which depends on
+ * the name, for example the value part of /codon_start qualifier must be
+ * a number: 1, 2 or 3.
+ * @return A Vector containing one Qualifier object for each name/value
+ * pair read from the stream.
+ **/
+ public static QualifierVector
+ readQualifiers (final Reader in_stream,
+ final EntryInformation entry_information)
+ throws QualifierParseException, IOException {
+
+ QualifierVector return_vector = new QualifierVector ();
+
+ BufferedReader buffered_reader = new BufferedReader (in_stream);
+
+ String name;
+ String value;
+
+ // loop until end of file
+ while (true) {
+
+ name = StreamQualifier.readName (buffered_reader);
+
+ if (name == null) {
+ // end of file/stream
+ break;
+ }
+
+ // save one character in case the next char is not a '='
+ buffered_reader.mark (1);
+
+ final int next_char = buffered_reader.read ();
+
+ if (next_char == -1) {
+ value = null;
+ } else {
+ if (next_char == '=') {
+ value = StreamQualifier.readValue (buffered_reader);
+ } else {
+ // this qualifier doesn't have a value
+ value = null;
+ buffered_reader.reset ();
+ }
+ }
+
+ final Qualifier new_qualifier;
+
+ if (value == null) {
+ new_qualifier = new Qualifier (name);
+ } else {
+ new_qualifier =
+ StreamQualifier.makeStreamQualifier (name, value,
+ entry_information);
+ }
+
+ return_vector.addQualifierValues (new_qualifier);
+ }
+
+ return return_vector;
+ }
+
+ /**
+ * Return a QualifierVector containing the qualifiers from a String.
+ * @param qual_string contains the qualifiers to parse
+ */
+ private static QualifierVector
+ getQualifiersFromString (final String qual_string,
+ final EntryInformation entry_information)
+ throws QualifierParseException {
+
+ final StringReader string_reader = new StringReader (qual_string);
+
+ final QualifierVector qualifiers;
+
+ try {
+ qualifiers = readQualifiers (string_reader, entry_information);
+ } catch (IOException exception) {
+ throw (new QualifierParseException (exception.getMessage ()));
+ }
+
+ string_reader.close ();
+
+ return qualifiers;
+ }
+
+ /**
+ * The column of the output where we should start writting the key.
+ **/
+ private final static int KEY_START_COLUMN = 5;
+
+ /**
+ * The column of the output where we should start writting the location.
+ **/
+ private final static int LOCATION_START_COLUMN = 21;
+
+ /**
+ * We should wrap at this column - no characters will be put in or after
+ * this column
+ **/
+ private final static int LAST_COLUMN = 81;
+
+ /**
+ * Write the key of this Feature to the given stream.
+ **/
+ private void writeKey (Writer writer)
+ throws IOException {
+ if (this instanceof EmblStreamFeature) {
+ writer.write ("FT " + getKey ());
+ } else {
+ writer.write (" " + getKey ());
+ }
+
+ final StringBuffer spaces = new StringBuffer (20);
+
+ // add spaces so that the location starts at coloumn 21
+ for (int i = 0 ;
+ i < LOCATION_START_COLUMN - KEY_START_COLUMN - getKey ().length () ;
+ ++i) {
+ spaces.append (' ');
+ }
+
+ writer.write (spaces.toString ());
+ }
+
+
+ /**
+ * Write the location of this feature to a stream. It is written in the
+ * usual EMBL format. Line that are more than 79 characters wide are
+ * wrapped. The wrapped lines start with "FT ", the
+ * first line doesn't.
+ * @param writer The stream to write to.
+ * @exception IOException thrown if there is an io problem while writing
+ * the Feature.
+ **/
+ private void writeLocation (final Writer writer)
+ throws IOException {
+
+ if (getEntryInformation ().useEMBLFormat ()) {
+ String location_string = getLocation ().toString ();
+
+ // write the first line without the "FT ..." bit as this will already
+ // have been written by writeKey ()
+
+ while (true) {
+ final int wrap_position =
+ getWrapPosition (location_string,
+ ',',
+ LAST_COLUMN - LOCATION_START_COLUMN - 1);
+ if (wrap_position == -1) {
+ // write out the remainer of the location_string
+ writer.write (location_string);
+ writer.write ("\n");
+ break;
+ } else {
+ // write the start of the string (the bit before wrap_position) and
+ // save the rest for the next time around the loop
+
+ writer.write (location_string.substring (0, wrap_position + 1));
+ if (this instanceof EmblStreamFeature) {
+ writer.write ("\nFT ");
+ } else {
+ writer.write ("\n ");
+ }
+
+ location_string = location_string.substring (wrap_position + 1);
+ }
+ }
+ } else {
+ String location_string = getLocation ().toStringShort ();
+
+ // write it out on one line
+ writer.write (location_string);
+ writer.write ("\n");
+ }
+ }
+
+ /**
+ * The value to pass as the line_length argument of getWrapPosition ().
+ **/
+ final static private int QUALIFIER_WRAP_LENGTH =
+ LAST_COLUMN - LOCATION_START_COLUMN - 1;
+
+ /**
+ * Write the qualifiers of this feature to a stream. The qualifiers are
+ * written in EMBL format: ie. <p>
+ * FT /codon_start=1
+ * <p> etc.
+ * @param writer The stream to write to.
+ * @exception IOException thrown if there is an io problem while writing
+ * the Feature.
+ **/
+ private void writeQualifiers (final Writer writer)
+ throws IOException {
+ for (int i = 0 ; i < getQualifiers ().size () ; ++i) {
+ final Qualifier current_qualifier = (Qualifier)getQualifiers ().elementAt (i);
+
+ //final String qualifier_name = current_qualifier.getName ();
+
+ final EntryInformation entry_information = getEntryInformation ();
+
+ final QualifierInfo qualifier_info =
+ entry_information.getQualifierInfo (current_qualifier.getName ());
+
+ // this will contain one String for each /name=value pair
+ final StringVector qualifier_strings =
+ StreamQualifier.toStringVector (qualifier_info, current_qualifier);
+
+ for (int value_index = 0 ;
+ value_index < qualifier_strings.size () ;
+ ++value_index) {
+
+ String qualifier_string = (String)qualifier_strings.elementAt (value_index);
+
+ while (true) {
+ int wrap_position = getWrapPosition (qualifier_string, ' ',
+ QUALIFIER_WRAP_LENGTH);
+
+ if (entry_information.useEMBLFormat () &&
+ (wrap_position == -1 || wrap_position > QUALIFIER_WRAP_LENGTH)) {
+ // EMBL entries cannot go over 80 columns
+ if (qualifier_string.length () > QUALIFIER_WRAP_LENGTH) {
+ wrap_position = QUALIFIER_WRAP_LENGTH;
+ }
+ }
+
+ final String this_string;
+
+ if (wrap_position == -1) {
+ this_string = qualifier_string;
+ } else {
+ // write the start of the string (the bit before wrap_position -
+ // ignoring the space that we wrapped on)
+ this_string = qualifier_string.substring (0, wrap_position);
+ }
+
+ if (this instanceof EmblStreamFeature) {
+ writer.write ("FT ");
+ writer.write (this_string);
+ writer.write ("\n");
+ } else {
+ writer.write (" ");
+ writer.write (this_string);
+ writer.write ("\n");
+ }
+
+ if (wrap_position == -1) {
+ break;
+ }
+
+ // don't write the spaces that start the next line
+ while (wrap_position < qualifier_string.length () &&
+ qualifier_string.charAt (wrap_position) == ' ') {
+ wrap_position += 1;
+ }
+
+ //save the rest for the next time around the loop
+ qualifier_string = qualifier_string.substring (wrap_position);
+
+ if (qualifier_string.length () == 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Find a suitable character position to wrap at.
+ * (Helper method for writeLocation () and writeQualifiers ().)
+ * @param string_to_wrap This is the string to search for the wrap
+ * character.
+ * @param wrap_character This is the character after which we should wrap
+ * the line.
+ * @param line_length The maximum line length before wrapping.
+ * @return The character position in the string_to_wrap of wrap_character
+ * or -1 if there is no need to wrap.
+ **/
+ private int getWrapPosition (String string_to_wrap,
+ char wrap_character,
+ int line_length) {
+ //final int return_index;
+
+ if (string_to_wrap.length () < line_length) {
+ return -1; // no need to wrap
+ }
+
+ final int wrap_char_index =
+ string_to_wrap.substring (0, line_length).lastIndexOf (wrap_character);
+
+ if (wrap_char_index == -1) {
+ // there is no wrap_character within the line_length characters - so
+ // just return the first occurrence in the string (or -1).
+ return string_to_wrap.indexOf (wrap_character);
+ } else {
+ return wrap_char_index;
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/Qualifier.java b/uk/ac/sanger/artemis/io/Qualifier.java
new file mode 100644
index 0000000..dda6c09
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/Qualifier.java
@@ -0,0 +1,195 @@
+/* Qualifier.java
+ *
+ * created: Tue Oct 13 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/Qualifier.java,v 1.4 2007-02-13 09:40:20 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+/**
+ * Each object of this class represents a group of embl qualifiers
+ * (name/value pairs) with the same name, so in effect the object contains
+ * one name and many values.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Qualifier.java,v 1.4 2007-02-13 09:40:20 tjc Exp $
+ * */
+
+public class Qualifier
+{
+
+ /** The name that was passed to the constructor. */
+ private String name;
+
+ /** The values that were passed to the constructor. */
+ private StringVector values;
+
+ public Qualifier()
+ {
+
+ }
+
+ /**
+ * Create a new Qualifier object. This object consists of a name and
+ * possibly some values. In the raw embl file we have /name=value.
+ * @param name The name of this qualifier (ie. the text immediately after
+ * the / in the qualifier)
+ * @param values The values of this qualifier (ie the text immediately after
+ * the = in the qualifiers). This argument should be null if the
+ * qualifier has no values. The values String objects should not include
+ * the quote characters (),"" or []. For example if the original
+ * qualifier was /citation=[3] then the value String should be: 3. This
+ * argument is copied by the constructor.
+ **/
+ public Qualifier (final String name, final StringVector values)
+ {
+ initialise (name, values);
+ }
+
+ /**
+ * Create a new Qualifier object. This object consists of a name and
+ * possibly some values. In the raw embl file we have /name=value.
+ * @param name The name of this qualifier (ie. the text immediately after
+ * the / in the qualifier)
+ * @param value The value of this qualifier (see the other constructor for
+ * details). Other values may be added later.
+ **/
+ public Qualifier (final String name, final String value)
+ {
+ initialise (name, new StringVector (value));
+ }
+
+ /**
+ * Create a new Qualifier object. This object will consist of just a name.
+ * In the raw embl file we have /name.
+ * @param name The name of this qualifier (ie. the text immediately after
+ * the / in the qualifier)
+ **/
+ public Qualifier (final String name)
+ {
+ initialise (name, (StringVector) null);
+ }
+
+ /**
+ * Called by the constructor to initialise the object.
+ * @param name The name of this qualifier (ie. the text immediately after
+ * the / in the qualifier)
+ * @param values The values of this qualifier (see the other constructor for
+ * details). Other values may be added later.
+ **/
+ private void initialise (final String name, final StringVector values)
+ {
+ this.name = name;
+ if(values == null)
+ this.values = null;
+ else
+ {
+ if(values.size() == 0)
+ throw new Error ("internal error - zero length values vector");
+
+ this.values = values.copy ();
+// this.values = values;
+ }
+ }
+
+ /**
+ * Return the name of this qualifier.
+ **/
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Return the values of this qualifier. The StringVector that is returned
+ * is a copy (use addValues (), removeValue () etc. to change the
+ * Qualifier), but the String objects inside the vector are not copied so
+ * the same String references will occur in all copies.
+ **/
+ public StringVector getValues()
+ {
+ if(values == null)
+ return null;
+ else
+ return values.copy();
+ }
+
+ /**
+ * Add the given values to this object.
+ **/
+ public void addValues(final StringVector new_values)
+ {
+ if(values == null)
+ {
+ values = new StringVector ();
+ values.add ((String)null);
+ }
+ if(new_values == null)
+ {
+ // if new_values is null then we have a qualifier with no value (like
+ // /pseudo). we add one null value for each occurrence of the
+ // qualifier.
+ values.add ((String)null);
+ }
+ else
+ {
+ for(int i = 0 ; i < new_values.size() ; ++i)
+ values.add(new_values.elementAt(i));
+ }
+ }
+
+ /**
+ * Add the given value to this object.
+ **/
+ public void addValue(final String new_value)
+ {
+ if(values == null)
+ values = new StringVector();
+
+ values.add(new_value);
+ }
+
+ /**
+ * Remove the given value from this object. The reference of the argument
+ * String must have previously been passed to addValue () or addValues ()
+ * for a removal to occur.
+ **/
+ protected void removeValue(final String value)
+ {
+ values.remove(value);
+
+ if(values.size() == 0)
+ values = null;
+ }
+
+ /**
+ * Return the reference of a new copy of this Qualifier.
+ **/
+ public Qualifier copy()
+ {
+ return new Qualifier(getName(), getValues());
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/io/QualifierInfo.java b/uk/ac/sanger/artemis/io/QualifierInfo.java
new file mode 100644
index 0000000..ddbbdc6
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/QualifierInfo.java
@@ -0,0 +1,233 @@
+/* QualifierInfo.java
+ *
+ * created: Sun Feb 21 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/QualifierInfo.java,v 1.1 2004-06-09 09:50:07 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * Each object of this class contains the information about one qualifier
+ * type.
+ *
+ * @author Kim Rutherford
+ * @version $Id: QualifierInfo.java,v 1.1 2004-06-09 09:50:07 tjc Exp $
+ **/
+
+public class QualifierInfo {
+ /**
+ * Create a new QualifierInfo object.
+ * @param name The name of the qualifier.
+ * @param type The type of the qualifier (one of QUOTED_TEXT, TEXT,
+ * NO_VALUE or OPTIONAL).
+ * @param keys The possible keys this qualifier can be associated
+ * with. (null means the qualifier is valid for all features).
+ * @param required_keys The keys of the features in which this qualifier is
+ * compulsary. (null means the qualifier is not required by any feature).
+ * @param once_only If true this qualifier may occur only once in a
+ * feature (ie. the qualifier can only have one value).
+ **/
+ public QualifierInfo (final String name, final int type,
+ final KeyVector keys, final KeyVector required_keys,
+ final boolean once_only) {
+ this.name = name;
+ this.type = type;
+ this.once_only = once_only;
+
+ if (keys != null) {
+ this.keys = keys.copy ();
+ }
+
+ if (required_keys != null) {
+ this.required_keys = required_keys.copy ();
+ }
+ }
+
+ /**
+ * Used if the value of the qualifier should a text string quoted with
+ * double quotes: "some text".
+ **/
+ public final static int QUOTED_TEXT = 1;
+
+ /**
+ * Used if the value of the qualifier should be an unquoted text string
+ * (containing no spaces).
+ **/
+ public final static int TEXT = 2;
+
+ /**
+ * Used if this qualifier does not take a value (for example /partial)
+ **/
+ public final static int NO_VALUE = 3;
+
+ /**
+ * Used if the value of this qualifier should be quoted but is optional.
+ **/
+ public final static int OPTIONAL_QUOTED_TEXT = 4;
+
+ /**
+ * Used if the value of this qualifier can have any type.
+ **/
+ public final static int ANY = 5;
+
+ /**
+ * Used for other types.
+ **/
+ public final static int UNKNOWN = 0;
+
+ /**
+ * Returns the type ID of this QualifierInfo object.
+ **/
+ public int getType () {
+ return type;
+ }
+
+ /**
+ * Return the name of the QualifierInfo object ("label", "note", etc.)
+ **/
+ public String getName () {
+ return name;
+ }
+
+ /**
+ * Return if and only if a qualifier of this type is allowed in a feature
+ * with the given key.
+ **/
+ public boolean isValidFor (final Key key) {
+ if (keys == null) {
+ return true;
+ } else {
+ return keys.contains (key);
+ }
+ }
+
+ /**
+ * Return if and only if a qualifier of this type is required in a feature
+ * with the given key.
+ **/
+ public boolean isRequiredFor (final Key key) {
+ if (required_keys == null) {
+ return false;
+ } else {
+ return required_keys.contains (key);
+ }
+ }
+
+ /**
+ * Return a list of the Keys for which a qualifier of this type is
+ * valid. (null means the qualifier is valid for all features)
+ **/
+ public KeyVector getValidKeys () {
+ return keys;
+ }
+
+ /**
+ * Return a list of the Keys for which a qualifier of this type is
+ * required. (null means the qualifier is not required by any feature)
+ **/
+ public KeyVector getRequiredKeys () {
+ return required_keys;
+ }
+
+ /**
+ * Return true if and only if this qualifier can appear only once in a
+ * feature.
+ **/
+ public boolean isOnceOnly () {
+ return once_only;
+ }
+
+ /**
+ * A String array containing the possible type strings. This is used by
+ * getTypeID () to check that a valid type string was passed to the
+ * constructor.
+ **/
+ private static final String [] possible_type_strings = {
+ "\"text\"", "text", "\"list\"", "\"opt\"", "(::)", "EC", "feature", "list",
+ "location", "modbase", "none", "number", "real", "ref", "item"
+ };
+
+ /**
+ * Return the type ID for the given type_string. "text" returns
+ * QualifierInfo.QUOTED_TEXT, text returns TEXT, none returns NO_VALUE and
+ * "opt" returns OPTIONAL_QUOTED_TEXT.
+ **/
+ public static int getQualifierTypeID (final String type_string) {
+ for (int i = 0 ; i < possible_type_strings.length ; ++i) {
+ final String this_type_string = possible_type_strings[i];
+
+ if (this_type_string.equals (type_string)) {
+
+ // found a match so return the correct ID
+
+ if (type_string.equals ("\"text\"") ||
+ type_string.equals ("\"list\"")) {
+ return QualifierInfo.QUOTED_TEXT;
+ } else {
+ if (type_string.equals ("text")) {
+ return QualifierInfo.TEXT;
+ } else {
+ if (type_string.equals ("none")) {
+ return QualifierInfo.NO_VALUE;
+ } else {
+ if (type_string.equals ("\"opt\"")) {
+ return QualifierInfo.OPTIONAL_QUOTED_TEXT;
+ } else {
+ // default is text which covers all the non quoted types
+ return QualifierInfo.TEXT;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ throw new Error ("unknown type string: " + type_string);
+ }
+
+ /**
+ * The name string that was passed to the constructor.
+ **/
+ private String name;
+
+ /**
+ * The type ID of this object (as passed to the constructor).
+ **/
+ private int type;
+
+ /**
+ * The valid Keys for this object (as passed to the constructor).
+ **/
+ private KeyVector keys = null;
+
+ /**
+ * The required Keys for this object (as passed to the constructor).
+ **/
+ private KeyVector required_keys = null;
+
+ /**
+ * (See constructor)
+ **/
+ private boolean once_only;
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/QualifierInfoException.java b/uk/ac/sanger/artemis/io/QualifierInfoException.java
new file mode 100644
index 0000000..23a6d5f
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/QualifierInfoException.java
@@ -0,0 +1,47 @@
+/* QualifierInfoException.java
+ *
+ * created: Sun Feb 21 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/QualifierInfoException.java,v 1.1 2004-06-09 09:50:09 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * Objects of this class are thrown when as incorrect type string is passed
+ * to the QualifierInfo constructor or when a QualifierInfo object is
+ * re-added to an EntryInformation object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: QualifierInfoException.java,v 1.1 2004-06-09 09:50:09 tjc Exp $
+ **/
+
+public class QualifierInfoException extends Exception {
+ /**
+ * Create a new QualifierInfoException object with the given String as the
+ * message.
+ **/
+ public QualifierInfoException (final String message) {
+ super (message);
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/QualifierInfoHash.java b/uk/ac/sanger/artemis/io/QualifierInfoHash.java
new file mode 100644
index 0000000..8ded520
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/QualifierInfoHash.java
@@ -0,0 +1,109 @@
+/* QualifierInfoHash.java
+ *
+ * created: Fri Nov 21 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/QualifierInfoHash.java,v 1.1 2004-06-09 09:50:10 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.StringVector;
+
+import java.util.Hashtable;
+
+/**
+ * A Hashtable of QualifierInfo objects. The qualifier names are the keys.
+ * The QualifierInfo objects are the values.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: QualifierInfoHash.java,v 1.1 2004-06-09 09:50:10 tjc Exp $
+ **/
+
+public class QualifierInfoHash {
+ /**
+ * Create a new (empty) QualifierInfoHash object.
+ **/
+ public QualifierInfoHash () {
+
+ }
+
+ /**
+ * Put the QualifierInfo object to the Hashtable with the qualifier name
+ * as the key and the QualifierInfo as the value.
+ */
+ public void put (final QualifierInfo info) {
+ hash.put (info.getName (), info);
+
+ name_cache = null;
+ }
+
+ /**
+ * Return the QualifierInfo that describes the Qualifier with the given
+ * name.
+ **/
+ public QualifierInfo get (final String name) {
+ return (QualifierInfo) hash.get (name);
+ }
+
+ /**
+ * Return a StringVector containing the names of all the QualifierInfo
+ * objects.
+ **/
+ public StringVector names () {
+ if (name_cache == null) {
+ name_cache = new StringVector ();
+
+ for (java.util.Enumeration e = hash.keys () ; e.hasMoreElements() ;) {
+ name_cache.add ((String) e.nextElement());
+ }
+ }
+
+ return name_cache;
+ }
+
+ /**
+ * Return a new copy of this object.
+ **/
+ public QualifierInfoHash copy () {
+ final QualifierInfoHash new_hash = new QualifierInfoHash ();
+
+ new_hash.hash = (Hashtable) hash.clone ();
+
+ return new_hash;
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ */
+ public int size () {
+ return hash.size ();
+ }
+
+ /**
+ * Storage for QualifierInfo objects.
+ */
+ private Hashtable hash = new Hashtable ();
+
+ /**
+ * A cache used by names() to
+ **/
+ private StringVector name_cache = null;
+}
diff --git a/uk/ac/sanger/artemis/io/QualifierInfoVector.java b/uk/ac/sanger/artemis/io/QualifierInfoVector.java
new file mode 100644
index 0000000..6143680
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/QualifierInfoVector.java
@@ -0,0 +1,98 @@
+/* QualifierInfoVector.java
+ *
+ * created: Sun Feb 21 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/QualifierInfoVector.java,v 1.1 2004-06-09 09:50:11 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.Vector;
+
+/**
+ * A Vector of QualifierInfo objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: QualifierInfoVector.java,v 1.1 2004-06-09 09:50:11 tjc Exp $
+ **/
+
+public class QualifierInfoVector {
+ /**
+ * Create a new (empty) QualifierInfoVector object.
+ **/
+ public QualifierInfoVector () {
+
+ }
+
+ /**
+ * Performs the same function as Vector.addElement ()
+ */
+ public void addElement (final QualifierInfo info) {
+ vector.addElement (info);
+ }
+
+ /**
+ * Performs the same function as addElement ()
+ */
+ public void add (final QualifierInfo info) {
+ vector.addElement (info);
+ }
+
+ /**
+ * Performs the same function as Vector.setElementAt ()
+ **/
+ public void setElementAt (final QualifierInfo qualifier_info,
+ final int index) {
+ vector.setElementAt (qualifier_info, index);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ */
+ public QualifierInfo elementAt (final int index) {
+ return (QualifierInfo) vector.elementAt (index);
+ }
+
+ /**
+ * Return a new copy of this object.
+ **/
+ public QualifierInfoVector copy () {
+ final QualifierInfoVector new_vector = new QualifierInfoVector ();
+
+ new_vector.vector = (Vector) vector.clone ();
+
+ return new_vector;
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ */
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Storage for QualifierInfo objects.
+ */
+ private Vector vector = new Vector ();
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/QualifierLazyLoading.java b/uk/ac/sanger/artemis/io/QualifierLazyLoading.java
new file mode 100644
index 0000000..f1d7ec2
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/QualifierLazyLoading.java
@@ -0,0 +1,224 @@
+/* Qualifier.java
+ *
+ * created: Feb 2007
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2007 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import uk.ac.sanger.artemis.util.*;
+
+/**
+ * Each object of this class represents a group of embl qualifiers
+ * (name/value pairs) with the same name, so in effect the object contains
+ * one name and many values.
+ *
+ * @author Kim Rutherford
+ * @version $Id: QualifierLazyLoading.java,v 1.5 2007-07-19 09:49:32 tjc Exp $
+ * */
+
+public class QualifierLazyLoading extends Qualifier
+{
+
+ /** The name that was passed to the constructor. */
+ private String name;
+
+ /** The values that were passed to the constructor. */
+ private List values;
+ private boolean forceLoad = false;
+
+ /**
+ * Create a new Qualifier object. This object consists of a name and
+ * possibly some values. In the raw embl file we have /name=value.
+ * @param name The name of this qualifier (ie. the text immediately after
+ * the / in the qualifier)
+ * @param values The values of this qualifier (ie the text immediately after
+ * the = in the qualifiers). This argument should be null if the
+ * qualifier has no values. The values String objects should not include
+ * the quote characters (),"" or []. For example if the original
+ * qualifier was /citation=[3] then the value String should be: 3. This
+ * argument is copied by the constructor.
+ **/
+ public QualifierLazyLoading (final String name, final List set)
+ {
+ initialiseLazy (name, set);
+ }
+
+ /**
+ * Create a new Qualifier object. This object consists of a name and
+ * possibly some values. In the raw embl file we have /name=value.
+ * @param name The name of this qualifier (ie. the text immediately after
+ * the / in the qualifier)
+ * @param value The value of this qualifier (see the other constructor for
+ * details). Other values may be added later.
+ **/
+ public QualifierLazyLoading (final String name, final LazyQualifierValue value)
+ {
+ List set = new Vector();
+ set.add(value);
+ initialiseLazy (name, set);
+ }
+
+ /**
+ * Called by the constructor to initialise the object.
+ * @param name The name of this qualifier (ie. the text immediately after
+ * the / in the qualifier)
+ * @param values The values of this qualifier (see the other constructor for
+ * details). Other values may be added later.
+ **/
+ private void initialiseLazy(final String name, final List values)
+ {
+ this.name = name;
+ if(values == null)
+ this.values = null;
+ else
+ {
+ if(values.size() == 0)
+ throw new Error ("internal error - zero length values vector");
+
+ this.values = values;
+ }
+ }
+
+ /**
+ * Return the values of this qualifier. The StringVector that is returned
+ * is a copy (use addValues (), removeValue () etc. to change the
+ * Qualifier), but the String objects inside the vector are not copied so
+ * the same String references will occur in all copies.
+ **/
+ public StringVector getValues()
+ {
+ if(values == null)
+ return null;
+ else
+ {
+ StringVector v = new StringVector();
+
+ for(int i=0; i<values.size(); i++)
+ {
+ LazyQualifierValue lazy = (LazyQualifierValue)values.get(i);
+ if(forceLoad)
+ lazy.setForceLoad(true);
+ v.add(i, lazy.getString());
+ }
+
+ return v;
+ }
+ }
+
+ /**
+ *
+ * @param index
+ * @return
+ */
+ public LazyQualifierValue getValue(int index)
+ {
+ return (LazyQualifierValue)values.get(index);
+ }
+
+ /**
+ * Test if all lazy values are loaded
+ * @return
+ */
+ public boolean isAllLazyValuesLoaded()
+ {
+ for(int i=0; i<values.size(); i++)
+ if(!((LazyQualifierValue)values.get(i)).isLazyLoaded())
+ return false;
+ return true;
+ }
+
+ /**
+ *
+ * @param index
+ * @return
+ */
+ public List getLazyValues()
+ {
+ return values;
+ }
+
+ /**
+ * Add the given values to this object.
+ **/
+ public void addValues(final List new_values)
+ {
+ if(values == null)
+ {
+ values = new Vector();
+ values.add ((String)null);
+ }
+ if(new_values == null)
+ {
+ // if new_values is null then we have a qualifier with no value (like
+ // /pseudo). we add one null value for each occurrence of the
+ // qualifier.
+ values.add ((String)null);
+ }
+ else
+ {
+ Iterator it = new_values.iterator();
+ while(it.hasNext())
+ values.add(it.next());
+ }
+ }
+
+ /**
+ * Add the given value to this object.
+ **/
+ public void addValue(final Object new_value)
+ {
+ if(values == null)
+ values = new Vector();
+
+ values.add(new_value);
+ }
+
+ public void removeValue(final Object value)
+ {
+ values.remove(value);
+
+ if(values.size() == 0)
+ values = null;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Return the reference of a new copy of this Qualifier.
+ **/
+ public Qualifier copy()
+ {
+ return new QualifierLazyLoading(getName(), values);
+ }
+
+ public void setForceLoad(boolean forceLoad)
+ {
+ this.forceLoad = forceLoad;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/QualifierParseException.java b/uk/ac/sanger/artemis/io/QualifierParseException.java
new file mode 100644
index 0000000..69b3310
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/QualifierParseException.java
@@ -0,0 +1,47 @@
+/* QualifierParseException.java
+ *
+ * created: Thu Nov 26 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/QualifierParseException.java,v 1.1 2004-06-09 09:50:13 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * QualifierParseException objects are thrown if a parse error occurs during
+ * the parsing of a qualifier string.
+ *
+ * @author Kim Rutherford
+ * @version $Id: QualifierParseException.java,v 1.1 2004-06-09 09:50:13 tjc Exp $
+ **/
+
+public class QualifierParseException extends ReadFormatException {
+ /**
+ * This constructor creates a QualifierParseException with the given String
+ * as the message.
+ * @param message the detail message
+ **/
+ public QualifierParseException (String message) {
+ super (message);
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/QualifierVector.java b/uk/ac/sanger/artemis/io/QualifierVector.java
new file mode 100644
index 0000000..c8b4a66
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/QualifierVector.java
@@ -0,0 +1,169 @@
+/* QualifierVector.java
+ *
+ * created: Tue Oct 13 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/QualifierVector.java,v 1.7 2006-12-06 17:27:12 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.Vector;
+
+/**
+ * This class implements a vector of Qualifier objects. It behaves
+ * differently to the Vector class (see addElement() and replaceElement()).
+ *
+ * @author Kim Rutherford
+ * @version $Id: QualifierVector.java,v 1.7 2006-12-06 17:27:12 tjc Exp $
+ *
+ */
+
+public class QualifierVector extends Vector<Qualifier>
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new (empty) vector of Qualifier objects.
+ */
+ public QualifierVector()
+ {
+ super(7);
+ }
+
+
+ /**
+ * Add the values from the given qualifier to the Qualifier object with the
+ * same name in this QualifierVector or otherwise add a copy of the
+ * argument.
+ * @param qualifier This object contians name and values to add.
+ * @return The Qualifier that was changed or created.
+ **/
+ public Qualifier addQualifierValues(final Qualifier qualifier)
+ {
+ if(qualifier.getName() == null)
+ throw new Error("");
+
+ final int index_of_qualifier =
+ indexOfQualifierWithName(qualifier.getName());
+
+ if(index_of_qualifier == -1)
+ {
+ addElement(qualifier.copy());
+ return null;
+ }
+ else
+ {
+ final Qualifier current_qualifier = elementAt(index_of_qualifier);
+ current_qualifier.addValues(qualifier.getValues());
+ return current_qualifier;
+ }
+ }
+
+ /**
+ * Add the given Qualifier to this QualifierVector, replacing any exisiting
+ * Qualifier with the same name.
+ * @param qualifier The Qualifier to add.
+ **/
+ public void setQualifier(final Qualifier qualifier)
+ {
+ final int index = indexOfQualifierWithName(qualifier.getName());
+
+ if(index == -1)
+ addElement(qualifier);
+ else
+ {
+ removeQualifierByName(qualifier.getName());
+ addElement(qualifier.copy());
+ }
+ }
+
+ /**
+ * Remove the Qualifier with the given name. If there is no Qualifier with
+ * that name return immediately.
+ * @param name The Qualifier name to look for.
+ **/
+ public void removeQualifierByName(final String name)
+ {
+ final int index = indexOfQualifierWithName(name);
+
+ if(index != -1)
+ removeElementAt(index);
+ }
+
+
+ /**
+ * Returns true if and only if this QualifierVector contains a qualifier
+ * with given name.
+ **/
+ public boolean contains(final String name)
+ {
+ if(indexOfQualifierWithName (name) == -1)
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Returns the index of the Qualifier in this QualifierVector with the
+ * given name of -1 if no such Qualifier exists.
+ * @param name The Qualifier name to look for.
+ **/
+ public int indexOfQualifierWithName(String name)
+ {
+ final int vsize = size();
+ for(int i = 0; i < vsize; ++i)
+ {
+ if(elementAt(i).getName().equals(name))
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the Qualifier in this QualifierVector with the given name or
+ * null if no such Qualifier exists.
+ * @param name The Qualifier name to look for.
+ **/
+ public Qualifier getQualifierByName(String name)
+ {
+ final int index_of_named_qualifier = indexOfQualifierWithName(name);
+ if(index_of_named_qualifier == -1)
+ return null;
+ else
+ return elementAt(index_of_named_qualifier);
+ }
+
+ /**
+ * Return the reference of a new copy of this QualifierVector. All of the
+ * Qualifier objects in the vector will be copied too.
+ **/
+ public QualifierVector copy()
+ {
+ return (QualifierVector)super.clone();
+// final QualifierVector return_vector = new QualifierVector();
+// final int vsize = size();
+// for(int i = 0 ; i < vsize; ++i)
+// return_vector.addElement(((Qualifier)elementAt(i)).copy());
+
+// return return_vector;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/io/RWCorbaEntry.java b/uk/ac/sanger/artemis/io/RWCorbaEntry.java
new file mode 100644
index 0000000..282fbf7
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/RWCorbaEntry.java
@@ -0,0 +1,539 @@
+/* CorbaEntry.java
+ *
+ * created: Wed Dec 30 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/RWCorbaEntry.java,v 1.3 2008-06-11 15:12:20 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import nsdb.EmblSeqWriter;
+import nsdb.NucFeatureWriter;
+import nsdb.NucFeature;
+import type.NoResult;
+
+import java.util.Vector;
+import java.io.IOException;
+import java.util.NoSuchElementException;
+
+/**
+ * This class implements the Entry interface with the data for the entry
+ * coming from a Corba server.
+ *
+ * @author Kim Rutherford
+ * @version $Id: RWCorbaEntry.java,v 1.3 2008-06-11 15:12:20 tjc Exp $
+ **/
+
+public class RWCorbaEntry extends EMBLObject
+ implements Entry {
+ /**
+ * Create a new CorbaEntry object from the given handle.
+ * @param entry_information The EntryInformation object of the new Entry.
+ * @param data This is the corba object that we will read from.
+ * @exception EntryInformationException Thrown if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ public RWCorbaEntry (final EntryInformation entry_information,
+ final EmblSeqWriter corba_handle)
+ throws LocationParseException, InvalidKeyException, NoResult,
+ EntryInformationException {
+ this.corba_handle = corba_handle;
+ this.sequence = new CorbaSequence (corba_handle);
+ this.entry_information = entry_information;
+
+ if (this.sequence.length () == 0) {
+ this.sequence = null;
+ }
+ }
+
+ /**
+ * Returns true if and only if there have been some changes to this Entry
+ * since the last save.
+ **/
+ public boolean hasUnsavedChanges () {
+ // XXX implement me (if necessary)
+
+ return false;
+ }
+
+ /**
+ * Returns true if and only if this entry is read only.
+ **/
+ public boolean isReadOnly () {
+ return false;
+ }
+
+ /**
+ * Return the text of the EMBL header of this Entry or null if there is no
+ * header.
+ **/
+ public String getHeaderText () {
+ return null;
+ }
+
+ /**
+ * Set the header of this Entry to be the given text.
+ * @return true if and only if the header was successfully set. Not all
+ * Entry objects can change their header, so it is up to the calling
+ * function to check the return value.
+ * @exception IOException thrown if there is a problem reading the header
+ * from the String - most likely ReadFormatException.
+ **/
+ public boolean setHeaderText (final String new_header) {
+ return false;
+ }
+
+ /**
+ * Write this entry to the file/database/whatever it came from. If this
+ * object is read only a ReadOnlyException will be thrown.
+ **/
+ public void save () throws IOException {
+ try {
+ corba_handle.commit ();
+ } catch (nsdb.CommitFailed e) {
+ throw new IOException ("save failed: " + e.reason);
+ }
+ }
+
+ /**
+ * Return the name of this Entry.
+ **/
+ public String getName () {
+ return "CORBA-" + corba_handle.getBioSeqId ();
+ }
+
+ /**
+ * Set the name of this Entry - if possible (the return value will let the
+ * caller know).
+ * @return true if and only if the name was successfully set. Not all
+ * Entry objects can change there name, so it is up to the calling
+ * function to check the return value.
+ **/
+ public boolean setName (final String name) {
+ return false;
+ }
+
+ /**
+ * Create a new Feature object of an appropriate type in this Entry.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ **/
+ public Feature createFeature (Key key,
+ Location location,
+ QualifierVector qualifiers)
+ throws InvalidRelationException, ReadOnlyException,
+ EntryInformationException {
+ final EmblSeqWriter embl_writer =
+ nsdb.EmblSeqWriterHelper.narrow (corba_handle);
+ if (embl_writer == null) {
+ throw new ReadOnlyException ();
+ } else {
+ try {
+ final nsdb.NucFeatureWriter nuc_feature_writer =
+ embl_writer.createNucFeature (key.toString (), location.toString ());
+
+ final RWCorbaFeature corba_feature =
+ new RWCorbaFeature (getEntryInformation (), nuc_feature_writer);
+
+ corba_feature.setRWCorbaEntry (this);
+
+ corba_feature.setQualifiers (qualifiers);
+
+ final NucFeatureHasher hasher =
+ new NucFeatureHasher (nuc_feature_writer);
+
+ feature_dictionary.put (hasher, corba_feature);
+
+ return corba_feature;
+ } catch (nsdb.LocationParse e) {
+ throw new Error ("internal error - server doesn't understand " +
+ "this location: " + location.toString ());
+ } catch (type.IndexOutOfRange e) {
+ throw new Error ("internal error - location out of range: " + e);
+ } catch (nsdb.InvalidKey e) {
+ throw new Error ("internal error - server won't use " +
+ "this feature key: " + key.toString ());
+ } catch (type.NoResult e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (nsdb.ReadOnlyException e) {
+ throw new ReadOnlyException ();
+ }
+ }
+ }
+
+ /**
+ * Return a count of the number of Feature objects in this Entry.
+ **/
+ public int getFeatureCount () {
+ return corba_handle.getNucFeatureCount ();
+ }
+
+ /**
+ * Add the given Feature to this Entry. If the Feature is already in an
+ * Entry then Entry.remove () should be called on that Entry before calling
+ * Entry.add (). An Error will be thrown otherwise.
+ * @exception ReadOnlyException If this entry is read only.
+ * @exception EntryInformationException Thrown if this Entry
+ * cannot contain the Key, Qualifier or Key/Qualifier combination of the
+ * given Feature.
+ * @return A reference that was passed to add (), if that Feature can be
+ * stored directly in this Entry, otherwise returns a reference to a new
+ * Feature, that is a copy of the argument. The argument reference
+ * should not be used after the call to add (), unless the return
+ * reference happens to be the same as the argument.
+ **/
+ public Feature add (final Feature feature)
+ throws EntryInformationException, ReadOnlyException {
+ if (feature.getEntry () != null) {
+ throw new Error ("internal error - a feature must have one owner");
+ }
+
+ try {
+ return createFeature (feature.getKey (),
+ feature.getLocation (),
+ feature.getQualifiers ());
+ } catch (InvalidRelationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Add the given Feature to this Entry. If the Feature is already in an
+ * Entry then Entry.remove() should be called on that Entry before calling
+ * Entry.forcedAdd() (An Error will be thrown otherwise). Invalid
+ * qualifiers will be quietly thrown away. Features with invalid keys will
+ * not be added (and null will be returned). "Invalid" means that the
+ * key/qualifier is non allowed to occur in an Entry of this type (probably
+ * determined by the EntryInformation object of this Entry).
+ * @exception ReadOnlyException If this entry is read only.
+ * @return A reference that was passed to add (), if that Feature can be
+ * stored directly in this Entry, otherwise returns a reference to a new
+ * Feature, that is a copy of the argument. The argument reference
+ * should not be used after the call to add (), unless the return
+ * reference happens to be the same as the argument. Returns null if and
+ * only if the new Feature has a key that is invalid for this Entry.
+ **/
+ public Feature forcedAdd (final Feature feature)
+ throws ReadOnlyException {
+ if (feature.getEntry () != null) {
+ throw new Error ("internal error - a feature must have one owner");
+ }
+
+ try {
+ return createFeature (feature.getKey (),
+ feature.getLocation (),
+ feature.getQualifiers ());
+ } catch (InvalidRelationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Remove the given Feature from this Entry.
+ * @return true if and only if the Feature was in this Entry.
+ * @exception ReadOnlyException If this entry is read only.
+ **/
+ public boolean remove (Feature feature)
+ throws ReadOnlyException {
+ if (feature instanceof RWCorbaFeature) {
+ final RWCorbaFeature corba_feature = (RWCorbaFeature) feature;
+ final NucFeatureWriter nuc_feature =
+ corba_feature.getNucFeatureWriter ();
+ if (contains (nuc_feature)) {
+ try {
+ corba_handle.remove (nuc_feature);
+ corba_feature.setRWCorbaEntry (null);
+ return true;
+ } catch (nsdb.ReadOnlyException e) {
+ throw new ReadOnlyException ();
+ }
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return the ith Feature from this Entry. This Features are returned in a
+ * consistent order, sorted by the first base of each Feature.
+ **/
+ public Feature getFeatureAtIndex (int index) {
+ try {
+ final NucFeature nuc_feature =
+ corba_handle.getFeatureAtIndex (index);
+
+ final NucFeatureWriter nuc_feature_writer =
+ nsdb.NucFeatureWriterHelper.narrow (nuc_feature);
+
+ return getFeatureOfNucFeature (nuc_feature_writer);
+ } catch (type.IndexOutOfRange e) {
+ throw new IndexOutOfBoundsException ();
+ }
+ }
+
+ /**
+ * Return the index of the given Feature. This does the reverse of
+ * getFeatureAtIndex ().
+ **/
+ public int indexOf (Feature feature) {
+ if (!(feature instanceof RWCorbaFeature)) {
+ return -1;
+ }
+
+ final NucFeature nuc_feature =
+ ((RWCorbaFeature)feature).getNucFeatureWriter ();
+
+ return corba_handle.indexOf (nuc_feature);
+ }
+
+ /**
+ * Returns true if and only if this Entry contains the given feature.
+ **/
+ public boolean contains (Feature feature) {
+ if (feature instanceof RWCorbaFeature) {
+ final NucFeatureWriter nuc_feature =
+ ((RWCorbaFeature) feature).getNucFeatureWriter ();
+ if (contains (nuc_feature)) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns an enumeration of the Feature objects in this Entry. The
+ * returned Enumeration object will generate all features in this object in
+ * turn. The first item generated is the item at index 0, then the item at
+ * index 1, and so on.
+ **/
+ public FeatureEnumeration features () {
+ return new FeatureEnumeration () {
+ public boolean hasMoreFeatures () {
+ return i < features.size ();
+ }
+
+ public Feature nextFeature () {
+ return features.featureAt (i++);
+ }
+
+ private int i = 0;
+
+ private FeatureVector features = getAllFeatures ();
+ };
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ public FeatureVector getFeaturesInRange (Range range)
+ throws OutOfRangeException {
+ final FeatureVector return_features = new FeatureVector ();
+
+ try {
+ final NucFeature [] feature_handles =
+ corba_handle.getNucFeaturesInRange (range.getStart (),
+ range.getEnd ());
+
+ for (int i = 0 ; i < feature_handles.length ; ++i) {
+ final NucFeatureWriter this_nuc_feature =
+ nsdb.NucFeatureWriterHelper.narrow (feature_handles [i]);
+ final RWCorbaFeature rw_corba_feature =
+ getFeatureOfNucFeature (this_nuc_feature);
+ return_features.add (rw_corba_feature);
+ }
+ } catch (NoResult e) {
+
+ } catch (type.IndexOutOfRange e) {
+ throw new OutOfRangeException (range.toString ());
+ }
+
+ return return_features;
+ }
+
+ /**
+ * Return a vector containing the references of all the Feature objects in
+ * this Entry.
+ * @return The features of this Entry. The returned object
+ * is a copy - changes will not effect the Entry object itself.
+ **/
+ public FeatureVector getAllFeatures () {
+ final FeatureVector return_features = new FeatureVector ();
+
+ try {
+
+ final NucFeature [] feature_handles =
+ corba_handle.getNucFeatures ();
+
+ for (int i = 0 ; i < feature_handles.length ; ++i) {
+ final NucFeatureWriter this_nuc_feature =
+ nsdb.NucFeatureWriterHelper.narrow (feature_handles [i]);
+
+ final Feature this_feature = getFeatureOfNucFeature (this_nuc_feature);
+
+ return_features.add (this_feature);
+ }
+ } catch (NoResult e) {
+
+ }
+
+ return return_features;
+ }
+
+ /**
+ * Return the Sequence object from this entry or null if it does not
+ * contain one.
+ * @return a Sequence object for this Entry. the returned object is
+ * not a copy - changes to it will change the Entry object itself
+ **/
+ public Sequence getSequence () {
+ return sequence;
+ }
+
+ /**
+ * Return the EntryInformation object that was passed to the constructor.
+ **/
+ public EntryInformation getEntryInformation () {
+ return entry_information;
+ }
+
+ /**
+ * Returns the RWCorbaFeature reference that corresponds to the given
+ * NucFeatureWriter reference. If no RWCorbaFeature has yet been created
+ * for the given NucFeatureWriter, one will be created and returned. This
+ * method uses and sets feature_dictionary. This method will always return
+ * the same RWCorbaFeature reference for a given NucFeatureWriter.
+ **/
+ private RWCorbaFeature getFeatureOfNucFeature (final NucFeatureWriter
+ nuc_feature) {
+ final NucFeatureHasher hasher = new NucFeatureHasher (nuc_feature);
+
+ final RWCorbaFeature feature =
+ (RWCorbaFeature) feature_dictionary.get (hasher);
+
+ if (feature == null || feature.getEntry () == null) {
+ // make a feature
+ try {
+ final RWCorbaFeature new_feature =
+ new RWCorbaFeature (getEntryInformation (), nuc_feature);
+
+// System.out.println ("created: " + new_feature);
+
+ feature_dictionary.put (hasher, new_feature);
+
+ new_feature.setRWCorbaEntry (this);
+
+ return new_feature;
+ } catch (NoResult e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (EntryInformationException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ } else {
+ return feature;
+ }
+ }
+
+ /**
+ * Returns true if and only if the given NucFeatureWriter object is a
+ * feature in this entry.
+ **/
+ private boolean contains (final NucFeatureWriter nuc_feature) {
+ final NucFeatureHasher hasher = new NucFeatureHasher (nuc_feature);
+
+ final Object feature = feature_dictionary.get (hasher);
+
+ if (feature == null) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * The EmblSeqWriter object that was passed to the constructor.
+ **/
+ private EmblSeqWriter corba_handle;
+
+ /**
+ * This is created in the constructor
+ **/
+ private CorbaSequence sequence = null;
+
+ /**
+ * The Dictionary is used to find the the embl.Feature reference that
+ * corresponds any given NucFeatureWriter object. (see
+ * getFeatureOfNucFeature ()).
+ **/
+ private java.util.Hashtable feature_dictionary =
+ new java.util.Hashtable ();
+
+ /**
+ * The EntryInformation object that was passed to the constructor.
+ **/
+ final private EntryInformation entry_information;
+
+ public void dispose()
+ {
+ // TODO Auto-generated method stub
+
+ }
+}
+
+
+class NucFeatureHasher {
+ NucFeatureHasher (final NucFeatureWriter nuc_feature) {
+ this.nuc_feature = nuc_feature;
+ }
+
+ public int hashCode () {
+ return nuc_feature._hash (Integer.MAX_VALUE);
+ }
+
+ public boolean equals (final Object object) {
+ final NucFeatureWriter other_nuc_feature =
+ ((NucFeatureHasher)object).nuc_feature;
+
+// return nuc_feature._is_equivalent (other_nuc_feature);
+
+ // this works for JacORB, but may for work for other orbs too
+ return nuc_feature.toString ().equals (other_nuc_feature.toString ());
+ }
+
+ final public NucFeatureWriter nuc_feature;
+}
diff --git a/uk/ac/sanger/artemis/io/RWCorbaFeature.java b/uk/ac/sanger/artemis/io/RWCorbaFeature.java
new file mode 100644
index 0000000..a522da2
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/RWCorbaFeature.java
@@ -0,0 +1,733 @@
+/* CorbaFeature.java
+ *
+ * created: Sun May 30 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/RWCorbaFeature.java,v 1.4 2005-11-28 16:46:38 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import nsdb.NucFeatureWriter;
+import nsdb.NucFeature;
+import nsdb.Datestamp;
+import type.NoResult;
+import nsdb.NucFeaturePackage.QualifierValue_u;
+
+import java.util.Date;
+
+/**
+ * This class implements the Feature interface by reading and writing to
+ * CORBA using a nsdb.NucFeatureWriter object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: RWCorbaFeature.java,v 1.4 2005-11-28 16:46:38 tjc Exp $
+ **/
+
+public class RWCorbaFeature extends EMBLObject implements DateStampFeature {
+ /**
+ * Create a new CorbaFeature object.
+ * @param feature_handle The corba handle of the feature
+ * @param entry_information The EntryInformation object of the new Feature.
+ * @exception EntryInformationException Thrown if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ public RWCorbaFeature (final EntryInformation entry_information,
+ final NucFeatureWriter feature_handle)
+ throws NoResult, EntryInformationException {
+ this.feature_handle = feature_handle;
+ this.entry_information = entry_information;
+ }
+
+ /**
+ * Set the value of this object.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * any of the given Qualifier objects.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfRangeException Thrown if any part of the location is out
+ * of range for this sequence.
+ **/
+ public void set (Key key,
+ Location location,
+ QualifierVector qualifiers)
+ throws InvalidRelationException, OutOfRangeException, ReadOnlyException {
+ setKey (key);
+ setLocation (location);
+ setQualifiers (qualifiers);
+ }
+
+ /**
+ * Set the value of this object.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * any of the given Qualifier objects.
+ * @exception OutOfDate If the key has changed in the server since the time
+ * given by datestamp.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfRangeException Thrown if any part of the location is out
+ * of range for the sequence.
+ **/
+ public void set (final Date datestamp,
+ final Key key,
+ final Location location,
+ final QualifierVector qualifiers)
+ throws InvalidRelationException, OutOfRangeException, ReadOnlyException,
+ OutOfDateException {
+ setKey (datestamp, key);
+
+ // pass null because if the setKey() works the server datestamp will
+ // change
+ setLocation (null, location);
+ setQualifiers (null, qualifiers);
+ }
+
+
+ /**
+ * Set the owning Entry of this Feature. Other objects should call
+ * setCorbaEntry () to change the owner.
+ * @param entry The Entry that now owns this Feature.
+ **/
+ private void setEntry (RWCorbaEntry entry) {
+ this.entry = entry;
+ }
+
+ /**
+ * Set the owning CorbaEntry of this Feature.
+ * @param entry The CorbaEntry that now owns this Feature.
+ **/
+ void setRWCorbaEntry (final RWCorbaEntry entry) {
+ setEntry (entry);
+ }
+
+ /**
+ * Set the key field of this object.
+ * @param key The new feature key
+ * @exception InvalidRelationException Throw if this Feature cannot have
+ * the given Key and one of it's current qualifiers.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ **/
+ public void setKey (Key key)
+ throws InvalidRelationException, ReadOnlyException {
+ try {
+ setKey (null, key);
+ } catch (OutOfDateException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Set the key field of this object.
+ * @param key The new feature key
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfDate If the key has changed in the server since the time
+ * given by datestamp.
+ **/
+ public void setKey (final Date datestamp, final Key key)
+ throws InvalidRelationException, ReadOnlyException,
+ OutOfDateException {
+ if (key == null) {
+ return;
+ }
+
+ try {
+ feature_handle.setKey (makeServerStamp (datestamp), key.toString ());
+ this.key = key;
+ } catch (nsdb.ReadOnlyException e) {
+ throw new ReadOnlyException ();
+ } catch (nsdb.OutOfDate e) {
+ throw new OutOfDateException ();
+ } catch (nsdb.InvalidKey e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (type.InvalidRelation e) {
+ throw new InvalidRelationException (e.reason, key);
+ }
+ }
+
+ public void setLocation (Location location, Entry entry)
+ throws OutOfRangeException, ReadOnlyException {
+ setLocation(location);
+ }
+
+ /**
+ * Set the location of this object.
+ * @param location The Location object for the new feature
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfRangeException Thrown if any part of the location is out
+ * of range for this sequence.
+ **/
+ public void setLocation (Location location)
+ throws OutOfRangeException, ReadOnlyException {
+ try {
+ setLocation (null, location);
+ } catch (OutOfDateException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Set the location of this object.
+ * @param location The Location object for the new feature
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfDate If the key has changed in the server since the time
+ * given by datestamp.
+ * @exception OutOfRangeException Thrown if any part of the location is out
+ * of range for this sequence.
+ **/
+ public void setLocation (final Date datestamp,
+ final Location location)
+ throws ReadOnlyException, OutOfDateException, OutOfRangeException {
+ if (location == null) {
+ return;
+ }
+
+ try {
+ feature_handle.setLocation (makeServerStamp (datestamp),
+ location.toString ());
+
+ this.location = location;
+ old_location = location;
+ } catch (nsdb.ReadOnlyException e) {
+ throw new ReadOnlyException ();
+ } catch (nsdb.OutOfDate e) {
+ throw new OutOfDateException ();
+ } catch (nsdb.LocationParse e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (type.IndexOutOfRange e) {
+ throw new OutOfRangeException (location.toString ());
+ }
+ }
+
+ /**
+ * Set the qualifiers of this object discarding all the current qualifiers.
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Throw if this Feature cannot contain
+ * any of the given Qualifier objects.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ **/
+ public void setQualifiers (QualifierVector qualifiers)
+ throws InvalidRelationException, ReadOnlyException {
+ try {
+ setQualifiers (null, qualifiers);
+ } catch (OutOfDateException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Set the qualifiers of this object discarding all the current qualifiers.
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Throw if this Feature cannot contain
+ * any of the given Qualifier objects.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfDateException If the key has changed in the server since
+ * the time given by datestamp.
+ **/
+ public void setQualifiers (final Date datestamp,
+ final QualifierVector qualifiers)
+ throws InvalidRelationException, ReadOnlyException, OutOfDateException {
+ if (qualifiers == null) {
+ return;
+ }
+
+ try {
+ final nsdb.NucFeaturePackage.Qualifier [] qualifier_list =
+ new nsdb.NucFeaturePackage.Qualifier [qualifiers.size ()];
+
+ for (int i = 0 ; i < qualifier_list.length ; ++i) {
+ qualifier_list [i] = getStructFromQualifier((Qualifier)qualifiers.elementAt (i));
+ }
+
+ feature_handle.setQualifiers (makeServerStamp (datestamp),
+ qualifier_list);
+
+ this.qualifiers = qualifiers;
+ } catch (nsdb.ReadOnlyException e) {
+ throw new ReadOnlyException ();
+ } catch (nsdb.OutOfDate e) {
+ throw new OutOfDateException ();
+ } catch (type.InvalidRelation e) {
+ throw new InvalidRelationException (e.reason, getKey ());
+ } catch (nsdb.InvalidQualifier e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (nsdb.QualifierParse e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Add the given Qualifier to this Feature. If this Feature contains a
+ * Qualifier with the same name as the new Qualifier it will be replaced.
+ * @param qualifier The new qualifier to add.
+ * @exception InvalidRelationException Throw if this Feature cannot contain
+ * the given Qualifier.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ * @exception OutOfDateException If the key has changed in the server since
+ * the time given by datestamp.
+ **/
+ public void setQualifier (Qualifier qualifier)
+ throws InvalidRelationException, ReadOnlyException {
+ try {
+ final nsdb.NucFeaturePackage.Qualifier new_qualifier=
+ getStructFromQualifier (qualifier);
+
+ feature_handle.setQualifier (new Datestamp (0), new_qualifier);
+ qualifiers = null;
+ } catch (nsdb.ReadOnlyException e) {
+ throw new ReadOnlyException ();
+ } catch (nsdb.OutOfDate e) {
+ System.err.println ("setQualifier (): OutOfDate");
+ } catch (type.InvalidRelation e) {
+ throw new InvalidRelationException (e.reason, getKey(), qualifier);
+ } catch (nsdb.InvalidQualifier e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (nsdb.QualifierParse e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Remove the Qualifier with the given name. If there is no Qualifier with
+ * that name then return immediately.
+ * @param name The qualifier name to look for.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ **/
+ public void removeQualifierByName (final String name)
+ throws ReadOnlyException {
+
+ }
+
+ /**
+ * Add the values from the given qualifier to the Qualifier object with the
+ * same name in this Feature or otherwise add a copy of the argument.
+ * @param qualifier This object contians name and values to add.
+ * @param values The values to add to the Qualifier.
+ * @return The Qualifier that was changed or created.
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ * @exception ReadOnlyException Thrown if this Feature cannot be changed.
+ **/
+ public Qualifier addQualifierValues (Qualifier qualifier)
+ throws InvalidRelationException, ReadOnlyException {
+ // throw new ReadOnlyException ();
+ return null;
+ }
+
+ /**
+ * Return the unique identifier of this StreamFeature.
+ **/
+ long getNumericID () {
+ return id;
+ }
+
+ /**
+ * Return the key of this feature, as passed to the constructor.
+ **/
+ public Key getKey () {
+ checkStamp ();
+
+ if (key == null) {
+ key = new Key (feature_handle.getKey ());
+ }
+
+ return key;
+ }
+
+ /**
+ * Return the Location of this Feature, as passed to the constructor.
+ **/
+ public Location getLocation () {
+ checkStamp ();
+ if (location == null) {
+ return grabLocation ();
+ } else {
+ return location;
+ }
+ }
+
+ /**
+ * Return a QualifierVector object containing the qualifiers for this
+ * feature. This method does not return a copy of the qualifier vector so
+ * changing the vector will change the feature.
+ **/
+ public QualifierVector getQualifiers () {
+ checkStamp ();
+
+ if (qualifiers == null) {
+ qualifiers = new QualifierVector ();
+
+ try {
+ final nsdb.NucFeaturePackage.Qualifier [] corba_qualifier_handles =
+ feature_handle.getQualifiers ();
+
+ for (int i = 0 ; i < corba_qualifier_handles.length ; ++i) {
+ final nsdb.NucFeaturePackage.Qualifier this_corba_qualifier =
+ corba_qualifier_handles[i];
+
+ final Qualifier new_qualifier =
+ getQualifierFromStruct (this_corba_qualifier);
+
+ qualifiers.setQualifier (new_qualifier);
+ }
+ } catch (NoResult e) {
+
+ }
+ }
+
+ return qualifiers;
+ }
+
+ /**
+ * Return the Qualifier in this Feature with the given name or null if
+ * there no such Qualifier.
+ **/
+ public Qualifier getQualifierByName (final String name)
+ throws InvalidRelationException {
+ checkStamp ();
+
+ return getQualifiers ().getQualifierByName (name);
+ }
+
+ /**
+ * Given a Qualifier reference return a nsdb.NucFeaturePackage.Qualifier
+ * object.
+ **/
+ private nsdb.NucFeaturePackage.Qualifier
+ getStructFromQualifier (final Qualifier qualifier) {
+
+ final StringVector qualifier_values = qualifier.getValues ();
+
+ if (qualifier_values == null) {
+ return new nsdb.NucFeaturePackage.Qualifier (qualifier.getName (),
+ new QualifierValue_u [0]);
+ } else {
+ final QualifierValue_u [] return_values =
+ new QualifierValue_u [qualifier_values.size ()];
+
+ for (int i = 0 ; i < return_values.length ; ++i) {
+ return_values[i] = new QualifierValue_u ();
+ return_values[i].text ((String)qualifier_values.elementAt (i));
+ }
+
+ return new nsdb.NucFeaturePackage.Qualifier (qualifier.getName (),
+ return_values);
+ }
+ }
+
+ /**
+ * Given a nsdb.NucFeaturePackage.Qualifier reference return a
+ * embl.Qualifier object.
+ **/
+ private Qualifier
+ getQualifierFromStruct (final nsdb.NucFeaturePackage.Qualifier handle) {
+ try {
+ final StringVector values = getQualifierStringValues (handle);
+
+ EntryInformation entry_info = getEntryInformation ();
+
+ if (!entry_info.isValidQualifier (getKey (), handle.name)) {
+ final String message =
+ getKey () + " cannot have " + handle.name +
+ " as a qualifier";
+ throw new InvalidRelationException (message, getKey (),
+ new Qualifier (handle.name));
+ }
+
+ if (values.size () == 0) {
+ return new Qualifier (handle.name);
+ } else {
+ return new Qualifier (handle.name, values);
+ }
+ } catch (InvalidRelationException e) {
+ System.out.println ("exception reading from corba: " + e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Grab the location from the server and set the location field.
+ **/
+ Location grabLocation () {
+ try {
+ final String location_string =
+ feature_handle.getLocation ().getLocationString ();
+
+ if (location == null ||
+ ! location.toString ().equals (location_string)) {
+ if (old_location != null &&
+ location_string.equals (old_location.toString ())) {
+ location = old_location;
+ } else {
+ location = new Location (location_string);
+ }
+ } else {
+ // location hasn't changed
+ }
+ } catch (LocationParseException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ } catch (NoResult e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ return location;
+ }
+
+ /**
+ * Return the first base of this feature.
+ **/
+ public int getFirstBase () {
+ return getLocation ().getFirstBase ();
+ }
+
+ /**
+ * Return the last base of this feature.
+ **/
+ public int getLastBase () {
+ return getLocation ().getLastBase ();
+ }
+
+ /**
+ * Return the Entry object that contains this Feature.
+ **/
+ public Entry getEntry () {
+ return entry;
+ }
+
+ /**
+ * Return the reference of a new copy of this Feature. The new Feature
+ * will not be in any Entry, so it should be explicitly added with a call
+ * to Entry.add ().
+ **/
+ public Feature copy () {
+ final Feature new_feature = new EmblStreamFeature (this);
+
+ return new_feature;
+ }
+
+ /**
+ * Return the datestamp of this feature - that is, the time it was last
+ * changed.
+ **/
+ public Date getDatestamp () {
+ checkServerStamp ();
+ return datestamp;
+ }
+
+ /**
+ * Return the values of the given qualifier as String objects.
+ **/
+ private StringVector
+ getQualifierStringValues (nsdb.NucFeaturePackage.Qualifier qualifier) {
+
+ final QualifierValue_u [] corba_values =
+ qualifier.values;
+
+ final StringVector return_array = new StringVector ();
+
+ for (int value_index = 0 ;
+ value_index < corba_values.length ;
+ ++value_index) {
+
+ final QualifierValue_u corba_value =
+ corba_values[value_index];
+
+ /*final*/ String this_value_string;
+
+ switch (corba_value.discriminator ()) {
+ case NucFeature.string_qtc:
+ return_array.add (corba_value.text ());
+ break;
+ case NucFeature.integer_qtc:
+ return_array.add (String.valueOf (corba_value.integer ()));
+ break;
+ case NucFeature.real_qtc:
+ return_array.add (String.valueOf (corba_value.real ()));
+ break;
+ case NucFeature.TranslationException_qtc:
+ return_array.add (String.valueOf (corba_value.translation_exception ()));
+ break;
+ case NucFeature.CodonTranslation_qtc:
+ return_array.add (String.valueOf (corba_value.codon_translation ()));
+ break;
+ case NucFeature.Anticodon_qtc:
+ return_array.add (String.valueOf (corba_value.anti_codon ()));
+ break;
+ case NucFeature.SpliceConsensus_qtc:
+ return_array.add (String.valueOf (corba_value.splice_consensus ()));
+ break;
+ case NucFeature.RepeatUnit_qtc:
+ return_array.add (String.valueOf (corba_value.repeat_unit ()));
+ break;
+ case NucFeature.DbXref_qtc:
+ return_array.add (String.valueOf (corba_value.db_xref ()));
+ break;
+ }
+ }
+
+ return return_array;
+ }
+
+ /**
+ * Return the NucFeatureWriter reference that was passed to the constructor.
+ **/
+ NucFeatureWriter getNucFeatureWriter () {
+ return feature_handle;
+ }
+
+ /**
+ * Used by checkStamp () - the server won't be queried if the last call to
+ * checkStamp () (and server query) happened less then 10 seconds ago.
+ **/
+ private Date last_query_time = null;
+
+ /**
+ * Get the Datestamp from the corba object of this feature, if the
+ * datestamp hasn't been retrieved in the last n seconds. If it is newer
+ * than the current datestamp (stored in the datestamp field of this
+ * Feature), then all cached information will be forgotten
+ **/
+ private void checkStamp () {
+ final java.util.Calendar calendar = java.util.Calendar.getInstance ();
+
+ final java.util.Date current_time = calendar.getTime ();
+
+ if (last_query_time == null ||
+ current_time.getTime () - last_query_time.getTime () > 10000) {
+
+ checkServerStamp ();
+
+ last_query_time = current_time;
+ }
+ }
+
+ /**
+ * Read the datestamp from the feature in the server. If it is newer
+ * than the current datestamp (stored in the datestamp field of this
+ * Feature), then all cached information will be forgotten
+ **/
+ private void checkServerStamp () {
+ final Datestamp server_datestamp = feature_handle.getDatestamp ();
+
+ if (datestamp == null ||
+ server_datestamp.value > (long)datestamp.getTime () / 1000) {
+
+ datestamp = new Date ((long)server_datestamp.value * 1000);
+
+ key = null;
+ if (location != null) {
+ old_location = location;
+ }
+ location = null;
+ qualifiers = null;
+ }
+ }
+
+ /**
+ * Make a Datestamp object from the given Date object.
+ **/
+ private static Datestamp makeServerStamp (final Date datestamp) {
+ final Datestamp server_datestamp;
+
+ if (datestamp == null) {
+ server_datestamp = new Datestamp (0);
+ } else {
+ server_datestamp = new Datestamp ((int) (datestamp.getTime () / 1000));
+ }
+
+ return server_datestamp;
+ }
+
+ /**
+ * Return the EntryInformation object that was passed to the constructor.
+ **/
+ public EntryInformation getEntryInformation () {
+ return entry_information;
+ }
+
+ /**
+ * Returns true if and only if this Feature can't be changed and can't be
+ * removed from it's entry.
+ **/
+ public boolean isReadOnly () {
+ return false;
+ }
+
+ /**
+ * The EntryInformation object that was passed to the constructor.
+ **/
+ final private EntryInformation entry_information;
+
+ /**
+ * The NucFeature object that was passed to the constructor.
+ **/
+ private NucFeatureWriter feature_handle;
+
+ /**
+ * The Key of this Feature (set by getKey () and checkStamp ())
+ **/
+ private Key key;
+
+ /**
+ * The Location of this Feature (set by getLocation () and checkStamp ())
+ **/
+ private Location location;
+
+ /**
+ * The Location of this Feature (set by getLocation () and checkStamp ())
+ **/
+ private QualifierVector qualifiers;
+
+ /**
+ * The CorbaEntry object that contains this Feature as passed to the
+ * constructor.
+ **/
+ private RWCorbaEntry entry;
+
+ /**
+ * A unique identifier for this feature.
+ **/
+ private final long id = id_counter++;
+
+ /**
+ * This is incremented each time a constructor is called.
+ **/
+ private static long id_counter = 0;
+
+ /**
+ * This is the "date stamp" of the feature. ie. the time it changed (as far
+ * as we know). It will be checked (by calling
+ * feature_handle.getDatestamp()) in the get methods and will be passed to
+ * the set methods.
+ **/
+ private Date datestamp = null;
+
+ /**
+ * The Location of this Feature before the last call to checkServerStamp ().
+ **/
+ private Location old_location = null;
+}
diff --git a/uk/ac/sanger/artemis/io/Range.java b/uk/ac/sanger/artemis/io/Range.java
new file mode 100644
index 0000000..88f636b
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/Range.java
@@ -0,0 +1,246 @@
+/* Range.java
+ *
+ * created: Tue Oct 6 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/Range.java,v 1.1 2004-06-09 09:50:18 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.lang.Error;
+
+/**
+ * This class represents an inclusive range of integers.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Range.java,v 1.1 2004-06-09 09:50:18 tjc Exp $
+ **/
+
+public class Range {
+ /**
+ * Create a new Range object. The start must be less than or equal to the
+ * end.
+ * @param start the start of the range
+ * @param end the end of the range
+ * @throws OutOfRangeException if the end value is less than the start
+ * value.
+ **/
+ public Range (int start, int end)
+ throws OutOfRangeException {
+ if (end < start) {
+ throw new OutOfRangeException ("start: " + start + " > " + "end: " +
+ end);
+ }
+
+ this.start = start;
+ this.end = end;
+ }
+
+ /**
+ * Create a new Range object representing a single integer.
+ * @param number The single integer in the range.
+ **/
+ public Range (int number) {
+ this.start = number;
+ this.end = number;
+ }
+
+ /**
+ * Return the number of integers in the range, inclusive of the end points.
+ **/
+ public int getCount () {
+ return end - start + 1;
+ }
+
+ /**
+ * Return a string representation of this object in the form "start..end",
+ * where start and end are the integers that were passed to the
+ * constructor.
+ **/
+ public String toString () {
+ if (end > start) {
+ return start + ".." + end;
+ } else {
+ return Integer.toString (start);
+ }
+ }
+
+ /**
+ * Return the start position of this range, as passed to the constructor.
+ **/
+ public int getStart () {
+ return start;
+ }
+
+ /**
+ * Return the start position of this range, as passed to the constructor.
+ **/
+ public int getEnd () {
+ return end;
+ }
+
+ /**
+ * Return a copy of this object.
+ **/
+ public Range copy () {
+ try {
+ return new Range (getStart (), getEnd ());
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Returns true if and only if the test_range is completely contained within
+ * this Range.
+ **/
+ public boolean contains (final Range test_range) {
+ if (this.overlaps (test_range)) {
+ if (getStart () <= test_range.getStart () &&
+ getEnd () >= test_range.getEnd ()) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return a copy of this object with the start and end changed.
+ **/
+ public Range change (final int start, final int end)
+ throws OutOfRangeException {
+ return new Range (start, end);
+ }
+
+ /**
+ * This method translates the start and end of this Range into another
+ * coordinate system. The Range will be truncated if necessary.
+ * @param constraint This contains the start and end base of the new
+ * coordinate system. The position given by constraint.getStart () will
+ * be at postion/base 1 in the new coordinate system.
+ * @return the Range translated into the new coordinate system. The Range
+ * will be truncated if it overlaps either end of the constraint. null
+ * is returned if and only if the new Range lies outside of the new
+ * start and end.
+ **/
+ public Range truncate (final Range constraint) {
+ if (constraint.getEnd () < getStart ()) {
+ return null;
+ } else {
+ if (constraint.getStart () > getEnd ()) {
+ return null;
+ } else {
+ final int new_start = getStart () - constraint.getStart () + 1;
+ final int new_end = getEnd () - constraint.getStart () + 1;
+
+ final Object start_object;
+
+ if (new_start < 1) {
+ start_object = new LowerInteger (1);
+ } else {
+ start_object = new Integer (new_start);
+ }
+
+ final Object end_object;
+
+ if (new_end > constraint.getCount ()) {
+ end_object = new UpperInteger (constraint.getCount ());
+ } else {
+ end_object = new Integer (new_end);
+ }
+
+ try {
+ return FuzzyRange.makeRange (start_object, end_object);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return true if and only if the argument equals this Range.
+ **/
+ public boolean equals (final Range test_range) {
+ if (test_range instanceof Range) {
+ if (test_range.getStart () == getStart () &&
+ test_range.getEnd () == getEnd ()) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return true if and only if there is an overlap between this Range and the
+ * given Range.
+ **/
+ public boolean overlaps (final Range arg_range) {
+ if (getStart () < arg_range.getStart () &&
+ getEnd () < arg_range.getStart ()) {
+ return false;
+ }
+ if (getStart () > arg_range.getEnd () &&
+ getEnd () > arg_range.getEnd ()) {
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Return true if and only if there is an overlap between this Range and the
+ * given Range.
+ **/
+ public boolean fuzzyOverlaps (final Range r2, int nbases) {
+
+ if (getStart () < r2.getStart () - nbases &&
+ getEnd () < r2.getStart () - nbases) {
+ return false;
+ }
+ if (getStart () > r2.getEnd () + nbases &&
+ getEnd () > r2.getEnd () + nbases) {
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * The is the start value that was passed to the constructor
+ */
+ private int start;
+
+ /**
+ * The is the end value that was passed to the constructor
+ */
+ private int end;
+}
+
+
diff --git a/uk/ac/sanger/artemis/io/RangeVector.java b/uk/ac/sanger/artemis/io/RangeVector.java
new file mode 100644
index 0000000..b3eb2a8
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/RangeVector.java
@@ -0,0 +1,83 @@
+/* RangeVector.java
+ *
+ * created: Thu Oct 29 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/RangeVector.java,v 1.3 2007-10-25 19:25:00 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.util.Vector;
+
+/**
+ * This class implements a Vector of Range objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: RangeVector.java,v 1.3 2007-10-25 19:25:00 tjc Exp $
+ *
+ **/
+
+public class RangeVector extends Vector<Range>
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new vector of Range objects.
+ **/
+ public RangeVector()
+ {
+ }
+
+ /**
+ * Create a new vector of Range objects containing just the given Range.
+ **/
+ public RangeVector(final Range range)
+ {
+ add (range);
+ }
+
+
+ /**
+ * Reverse this RangeVector in place.
+ **/
+ public void reverse()
+ {
+ for(int i = 0 ; i < size () / 2 ; ++i)
+ {
+ final int swap_position = size () - i - 1;
+ final Range tmp = elementAt(i);
+ setElementAt(elementAt(swap_position), i);
+ setElementAt(tmp, swap_position);
+ }
+ }
+
+ public boolean containsRange(final Range r)
+ {
+ for(int i=0; i<size(); i++)
+ {
+ Range thisRange = elementAt(i);
+ if(r.equals(thisRange))
+ return true;
+ }
+ return false;
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/io/RawStreamSequence.java b/uk/ac/sanger/artemis/io/RawStreamSequence.java
new file mode 100644
index 0000000..1b6079c
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/RawStreamSequence.java
@@ -0,0 +1,390 @@
+/* RawStreamSequence.java
+ *
+ * created: Mon Jun 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/RawStreamSequence.java,v 1.10 2008-12-09 11:48:14 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.sequence.BasePattern;
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Vector;
+
+/**
+ * This is a subclass of StreamSequence containing raw sequence.
+ *
+ * @author Kim Rutherford
+ * @version $Id: RawStreamSequence.java,v 1.10 2008-12-09 11:48:14 tjc Exp $
+ **/
+
+public class RawStreamSequence extends StreamSequence
+{
+
+ /**
+ * The _character_ positions of the header lines relative to the sequence.
+ * This is in the same order as fasta_header_strings.
+ **/
+ private Vector<Integer> fasta_header_positions = null;
+
+ /**
+ * This Vector holds the fasta headers (if any) that where seen while
+ * reading the sequence. This is in the same order as
+ * fasta_header_positions.
+ **/
+ private Vector<String> fasta_header_strings = null;
+
+ /**
+ * Create a new RawStreamSequence object from a stream containing raw
+ * bases.
+ * @param in_stream The stream to read from. When the constructor returns
+ * the stream will at the next line after the sequence.
+ **/
+ public RawStreamSequence(final LinePushBackReader in_stream)
+ throws IOException
+ {
+ readSequence(in_stream);
+ }
+
+ /**
+ * Make a new RawStreamSequence containing the same sequence as the given
+ * Sequence.
+ **/
+ public RawStreamSequence(final Sequence sequence)
+ {
+ this(new String(sequence.getCharSubSequence(1, sequence.length())));
+
+ if(sequence instanceof RawStreamSequence)
+ {
+ final RawStreamSequence raw_stream_sequence =
+ (RawStreamSequence) sequence;
+ this.fasta_header_positions =
+ (Vector)raw_stream_sequence.fasta_header_positions.clone();
+ this.fasta_header_strings =
+ (Vector)raw_stream_sequence.fasta_header_strings.clone();
+ }
+ }
+
+ /**
+ * Make a new RawStreamSequence containing the same sequence as the given
+ * String
+ **/
+ public RawStreamSequence(final String sequence_string)
+ {
+ setFromChar(sequence_string.toCharArray());
+ }
+
+ /**
+ * Return a new StreamSequence object that is a copy of this one.
+ **/
+ public StreamSequence copy()
+ {
+ return new RawStreamSequence(this);
+ }
+
+ /**
+ * Return the sequence type (RAW_FORMAT for this class).
+ **/
+ public int getFormatType()
+ {
+ return StreamSequenceFactory.RAW_FORMAT;
+ }
+
+ /**
+ * Read the header for this sequence(if any).
+ **/
+ protected void readHeader(final LinePushBackReader in_stream)
+ throws IOException
+ {
+ // no header for raw sequence
+ }
+
+ /**
+ * This method will read raw sequence from the stream removing whitespace
+ * as it goes. No checks are made on the format of the sequence, apart
+ * from checking that the stream contains only letters.
+ **/
+ private void readSequence(final LinePushBackReader in_stream)
+ throws IOException
+ {
+
+ // initialise these here because this method is called before class
+ // variables are initialised
+ fasta_header_positions = new Vector<Integer>();
+ fasta_header_strings = new Vector<String>();
+
+ final int buffer_capacity = 5000;
+
+ // we buffer up the sequence bases then assign them to sequence once all
+ // bases are read
+ setSequencePackingCapacity(buffer_capacity);
+
+ String line;
+ int nbase = 0;
+ boolean validSequenceCheck = true;
+ String hdrLine = null;
+ while((line = in_stream.readLine()) !=null)
+ {
+ if(line.startsWith(">"))
+ {
+ storeHeader(line, nbase);
+ validSequenceCheck = true;
+ hdrLine = line;
+ continue;
+ }
+
+ line = line.trim().toLowerCase();
+
+ if(validSequenceCheck)
+ {
+ if(BasePattern.patternType(line) == BasePattern.ILLEGAL_PATTERN)
+ {
+ // ignore non-nucleotide sequence
+ if(hdrLine != null)
+ {
+ System.err.println(
+ "Warning:: ignore non-nucleotide sequence found in "+hdrLine.substring(1));
+ fasta_header_strings.remove(hdrLine.substring(1));
+ fasta_header_positions.remove(new Integer(nbase));
+ }
+
+ while((line = in_stream.readLine()) !=null)
+ {
+ if(line.startsWith(">"))
+ {
+ storeHeader(line, nbase);
+ hdrLine = line;
+ break;
+ }
+ }
+ continue;
+ }
+ validSequenceCheck = false;
+ }
+
+ if(line.length() > 0)
+ appendChar(line.toCharArray());
+ nbase += line.length();
+ hdrLine = null;
+ }
+
+ setCounts();
+ }
+
+ private void storeHeader(String line, int nbase)
+ {
+ fasta_header_strings.addElement(line.substring(1));
+ fasta_header_positions.addElement(new Integer(nbase));
+ }
+
+ /**
+ * Write this Sequence to the given stream.
+ * @param writer The stream to write to.
+ **/
+ public void writeToStream(final Writer writer)
+ throws IOException
+ {
+ final StringBuffer line_buffer = new StringBuffer(90);
+ final String sequence = new String(getCharSequence());
+
+ final int SEQUENCE_LINE_BASE_COUNT = 60;
+
+ int header_counter = 0;
+ final int [] header_positions = getFastaHeaderPositions();
+
+ int [] header_positions_sorted = new int[header_positions.length];
+ System.arraycopy(header_positions, 0,
+ header_positions_sorted, 0,
+ header_positions.length);
+ java.util.Arrays.sort(header_positions_sorted);
+
+ final String [] header_strings = getFastaHeaderStrings();
+
+ int i = 0;
+
+ while(i < sequence.length())
+ {
+ if(header_counter < header_positions.length)
+ {
+ if(i == header_positions_sorted[header_counter])
+ {
+ // dump a header
+ String header = "";
+ for(int j = 0; j<header_positions.length; j++)
+ {
+ if(header_positions_sorted[header_counter] ==
+ header_positions[j])
+ {
+ header = header_strings[j];
+ continue;
+ }
+ }
+
+ writer.write(">" + header + "\n");
+ ++header_counter;
+ }
+ }
+
+ // get the bases in chunks of at most 60
+
+ int this_line_length;
+
+ if(sequence.length() - i < SEQUENCE_LINE_BASE_COUNT)
+ this_line_length = sequence.length() - i;
+ else
+ this_line_length = SEQUENCE_LINE_BASE_COUNT;
+
+ if(header_counter < header_positions.length)
+ {
+ if(i + this_line_length > header_positions_sorted[header_counter])
+ this_line_length = header_positions_sorted[header_counter] - i;
+ }
+
+ line_buffer.setLength(0);
+ line_buffer.append(sequence.substring(i, i + this_line_length));
+ line_buffer.append("\n");
+
+ writer.write(line_buffer.toString());
+
+ if((i / SEQUENCE_LINE_BASE_COUNT) % 100 == 0)
+ Thread.yield();
+
+ if(header_counter < header_positions.length)
+ {
+ if(header_positions_sorted[header_counter] < i + SEQUENCE_LINE_BASE_COUNT)
+ {
+ i = header_positions_sorted[header_counter];
+ continue;
+ }
+ }
+
+ i += SEQUENCE_LINE_BASE_COUNT;
+ }
+ }
+
+ /**
+ *
+ * Reset the header positions. This is used when contigs a reordered
+ * by a SequenceChangeEvent.CONTIG_REORDER event.
+ *
+ */
+ public void setFastaHeaderPosition(final int old_position[],
+ final int new_position[])
+ {
+ if(old_position == new_position)
+ return;
+
+ if(fasta_header_positions != null && fasta_header_positions.size() > 0)
+ {
+ Vector<Integer> seen = new Vector<Integer>(fasta_header_positions.size());
+ for(int i = 0 ; i < fasta_header_positions.size(); ++i)
+ {
+ int current_position = ((Integer)fasta_header_positions.elementAt(i)).intValue();
+
+ for(int j = 0; j < old_position.length; ++j)
+ {
+ if(current_position == old_position[j] &&
+ !seen.contains(new Integer(j)))
+ {
+ fasta_header_positions.set(i, new Integer(new_position[j]));
+// System.out.println("CHANGED current_position="+current_position+
+// " old_position[i]="+old_position[i]+
+// " new_position[i]="+new_position[i]);
+ seen.add(new Integer(j));
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the fasta header positions when the complete sequence is reverse complemented.
+ * @param contigRanges
+ */
+ public void setFastaHeaderPositionsOnReverseComplement(final RangeVector contigRanges)
+ {
+ if(fasta_header_positions != null && fasta_header_positions.size() > 0)
+ {
+ int length = length();
+
+ for(int i = 0 ; i < fasta_header_positions.size(); ++i)
+ {
+ int current_position = ((Integer)fasta_header_positions.elementAt(i)).intValue();
+ for(int j=0; j<contigRanges.size(); j++)
+ {
+ Range range = (Range) contigRanges.get(j);
+ int start = range.getStart()-1;
+ if(start == current_position)
+ fasta_header_positions.set(i, new Integer(length-range.getEnd()));
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Return an array containing the character positions of the fasta header
+ * lines. The positions are returned in the same order as the strings from
+ * getFastaHeaderStrings().
+ **/
+ int [] getFastaHeaderPositions()
+ {
+ if(fasta_header_positions != null && fasta_header_positions.size() > 0)
+ {
+ final int [] return_value = new int [fasta_header_positions.size()];
+
+ for(int i = 0 ; i < return_value.length ; ++i)
+ {
+ return_value[i] =
+ ((Integer)fasta_header_positions.elementAt(i)).intValue();
+ }
+
+ return return_value;
+ }
+ else
+ return new int [0];
+ }
+
+ /**
+ * Return an array containing the fasta headers for the input sequence.
+ * The positions are returned in the same order as the strings from
+ * getFastaHeaderPositions().
+ **/
+ String [] getFastaHeaderStrings()
+ {
+ if(fasta_header_strings != null && fasta_header_strings.size() > 0)
+ {
+ final String [] return_value = new String [fasta_header_strings.size()];
+
+ for(int i = 0 ; i < return_value.length ; ++i)
+ return_value[i] =(String)fasta_header_strings.elementAt(i);
+
+ return return_value;
+ }
+ else
+ return new String [0];
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/io/ReadAndWriteEntry.java b/uk/ac/sanger/artemis/io/ReadAndWriteEntry.java
new file mode 100644
index 0000000..278bcaa
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/ReadAndWriteEntry.java
@@ -0,0 +1,494 @@
+/* ReadAndWriteEntry.java
+
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2008 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.JFrame;
+
+import uk.ac.sanger.artemis.components.database.DatabaseEntrySource;
+import uk.ac.sanger.artemis.components.filetree.LocalAndRemoteFileManager;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.sequence.NoSequenceException;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.InputStreamProgressEvent;
+import uk.ac.sanger.artemis.util.InputStreamProgressListener;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.FeatureVector;
+import uk.ac.sanger.artemis.Options;
+
+public class ReadAndWriteEntry
+{
+
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(ReadAndWriteEntry.class);
+
+ private static DatabaseEntrySource ENTRY_SOURCE;
+
+ /**
+ * Read from the database, given a srcFeature uniquename
+ * @param uniqueName
+ * @return
+ * @throws OutOfRangeException
+ * @throws NoSequenceException
+ * @throws IOException
+ */
+ public static Entry readEntryFromDatabase(final String uniqueName,
+ DatabaseEntrySource entry_source)
+ throws OutOfRangeException, NoSequenceException, IOException
+ {
+ if(entry_source == null)
+ {
+ ReadAndWriteEntry.ENTRY_SOURCE = new DatabaseEntrySource();
+ entry_source = ENTRY_SOURCE;
+ boolean promptUser = true;
+ //if(System.getProperty("read_only") != null)
+ if (UI.mode == UI.UIMode.SCRIPT)
+ promptUser = false;
+
+ if(!entry_source.setLocation(promptUser))
+ return null;
+ }
+
+ String url = (String)entry_source.getLocation();
+ int index = url.indexOf("?");
+
+ String userName = url.substring(index+1).trim();
+ if(userName.startsWith("user="))
+ userName = userName.substring(5);
+
+ final String srcFeatureId = getFeatureId(entry_source, uniqueName);
+
+ final InputStreamProgressListener stream_progress_listener =
+ new InputStreamProgressListener()
+ {
+ public void progressMade(final InputStreamProgressEvent event)
+ {
+ final int char_count = event.getCharCount();
+ if(char_count != -1)
+ logger4j.debug("chars read so far: " + char_count);
+ }
+ public void progressMade(String progress)
+ {
+ logger4j.debug(progress);
+ }
+ };
+ return entry_source.getEntry(srcFeatureId, userName,
+ stream_progress_listener);
+ }
+
+ /**
+ * Read from the database, given a srcFeature uniquename
+ * @param uniqueName
+ * @return
+ * @throws OutOfRangeException
+ * @throws NoSequenceException
+ * @throws IOException
+ */
+ public static Entry readEntryFromDatabase(final String uniqueName)
+ throws OutOfRangeException, NoSequenceException, IOException
+ {
+ return readEntryFromDatabase(uniqueName, null);
+ }
+
+ /**
+ * Write entry to a file
+ * @param entry
+ * @param file
+ * @param flatten Flatten the gene model and combine the qualifiers if true.
+ * If false it will write all features and qualifiers out.
+ * @param ignore obsolete features if true
+ * @param force invalid qualifiers and any features with invalid keys will
+ * be quietly thrown away when saving.
+ * @param include_diana_extensions false if writing EMBL submission format.
+ * @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT,
+ * GFF_FORMAT or ANY_FORMAT. If ANY_FORMAT then the Entry will
+ * be saved in the same format it was created, otherwise it will be saved
+ * in the given format.
+ * @param parent
+ * @throws IOException
+ * @throws EntryInformationException
+ */
+ public static void writeDatabaseEntryToFile(final Entry entry, final File file,
+ final boolean flatten,
+ final boolean ignoreObsolete,
+ final boolean force,
+ final boolean include_diana_extensions,
+ final int destination_type,
+ final JFrame parent)
+ throws IOException, EntryInformationException
+ {
+ GeneUtils.lazyLoadAll(entry, parent);
+
+ EntryInformation artemis_entry_information = Options.getArtemisEntryInformation();
+ if(!flatten)
+ {
+ final FeatureVector features = entry.getAllFeatures();
+ for(int i=0; i<features.size(); i++)
+ addAllKeysQualifiers(artemis_entry_information, features.elementAt(i).getEmblFeature());
+
+ if(entry.getEMBLEntry() instanceof GFFDocumentEntry)
+ addQualifierToEntryInfo(artemis_entry_information,
+ (String)PublicDBDocumentEntry.getDatabaseQualifiersToRemove()[0]);
+ }
+ PublicDBDocumentEntry.IGNORE_OBSOLETE_FEATURES = ignoreObsolete;
+
+ if(destination_type == DocumentEntryFactory.EMBL_FORMAT &&
+ (entry.getHeaderText() == null ||
+ entry.getHeaderText().equals("") ||
+ entry.getHeaderText().startsWith("#")))
+ {
+ String name = file.getName();
+ int ind = name.lastIndexOf(".embl.gz");
+ if(ind > -1)
+ name = name.substring(0, ind);
+ else
+ {
+ ind = name.lastIndexOf(".embl");
+ if(ind > -1)
+ name = name.substring(0, ind);
+
+ }
+
+ int length = entry.getBases().getLength();
+ String header = "ID "+name+"; SV ; ; ; ; ; "+length+" BP.";
+ if(entry.getFeatureCount() > 0)
+ header = header.concat("\nFH Key "+
+ "Location/Qualifiers\nFH\n");
+ entry.setHeaderText(header);
+ }
+
+ if(include_diana_extensions)
+ entry.save(file, destination_type, force, artemis_entry_information);
+ else
+ entry.saveStandardOnly(file, destination_type, force);
+ }
+
+
+ /**
+ * Add all keys and qualifiers for a given feature to the EntryInformation
+ * @param entry_information
+ * @param feature
+ */
+ protected static void addAllKeysQualifiers(final EntryInformation entry_information,
+ final Feature feature)
+ {
+ Key new_key = feature.getKey();
+
+ new_key = PublicDBDocumentEntry.mapKeys(new_key);
+
+ boolean keyAdded = false;
+ if(!entry_information.isValidKey(new_key))
+ {
+ entry_information.addKey(new_key);
+ keyAdded = true;
+ }
+
+ final QualifierVector feature_qualifiers = feature.getQualifiers();
+
+ // check the qualifiers
+ for(int i = 0 ; i < feature_qualifiers.size() ; ++i)
+ {
+ final Qualifier this_qualifier = (Qualifier)feature_qualifiers.elementAt(i);
+ final String this_qualifier_name = this_qualifier.getName();
+
+ if(!entry_information.isValidQualifier(this_qualifier_name) ||
+ !entry_information.isValidQualifier(new_key, this_qualifier_name) ||
+ keyAdded)
+ {
+ QualifierInfo qualifierInfo = entry_information.getQualifierInfo(this_qualifier_name);
+
+ if(qualifierInfo == null)
+ {
+ KeyVector keys = new KeyVector();
+ qualifierInfo = new QualifierInfo(this_qualifier_name, QualifierInfo.QUOTED_TEXT,
+ keys, null, false);
+ try
+ {
+ entry_information.addQualifierInfo(qualifierInfo);
+ }
+ catch(QualifierInfoException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ if(qualifierInfo.getValidKeys() != null)
+ qualifierInfo.getValidKeys().add(new_key);
+ }
+ }
+
+ }
+
+ protected static void addQualifierToEntryInfo(final EntryInformation entry_information,
+ final String qualifier_name)
+ {
+ KeyVector keys = new KeyVector();
+ QualifierInfo qualifierInfo = new QualifierInfo(qualifier_name, QualifierInfo.QUOTED_TEXT,
+ keys, null, false);
+ try
+ {
+ entry_information.addQualifierInfo(qualifierInfo);
+ }
+ catch(QualifierInfoException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Get feature id
+ * @param entry_source
+ * @param srcUniqueName
+ * @return
+ */
+ public static String getFeatureId(
+ final DatabaseEntrySource entry_source, final String srcUniqueName)
+ {
+ final DatabaseDocument doc = entry_source.getDatabaseDocument();
+ org.gmod.schema.sequence.Feature feature = doc.getFeatureByUniquename(srcUniqueName);
+ return Integer.toString(feature.getFeatureId());
+ }
+
+ /**
+ * return the ENTRY_SOURCE
+ */
+ public static DatabaseEntrySource getEntrySource()
+ {
+ return ENTRY_SOURCE;
+ }
+
+ public static void main(final String args[])
+ {
+ try
+ {
+ String names[];
+ boolean flatten = true;
+ boolean ignoreObsolete = true;
+
+ if( (args != null && args.length == 1 && args[0].startsWith("-h")) ||
+ (args == null || args.length < 1))
+ {
+ System.out.println("-h\tshow help");
+
+ System.out.println("-f\t[y|n] flatten the gene model, default is y");
+ System.out.println("-flt\tspace separated list of qualifiers to ignore (GFF only)");
+ System.out.println("-i\t[y|n] ignore obsolete features, default is y");
+ System.out.println("-s\tspace separated list of sequences to read and write out");
+ System.out.println("-o\t[EMBL|GFF] output format, default is EMBL");
+
+ // note that read_only and noprompt -D parameters redundant now
+ System.out.println("Advanced parameters:");
+ System.out.println("-l\tlocation of EMBL mapping files (qualifier_mapping and key_mapping)");
+ System.out.println("-z\t[y|n] gzip output, default is y");
+ System.out.println("-a\t[y|n] for EMBL submission format change to n, default is y");
+ System.out.println("-pp\t[y|n] read polypeptide domain features, default is n");
+ System.out.println("-r\t[y|n] remove product qualifiers from pseudogene (only for EMBL submission format), default is n");
+ System.out.println("-c\tthe URL for your Chado database e.g. db.genedb.org:5432/snapshot?genedb_ro (if not using default)");
+ System.out.println("-u\t[swing|console|script] the UI mode : run in swing (with popup dialog boxes) mode, run in console mode (choices entered in the console window), or in script mode (all choices default to continue, all parameters passed on command line) ");
+ System.out.println("-p\tthe password for connecting to the Chado database");
+ System.out.println("-fp\t the file path (the folder you want to save the files in)");
+ System.out.println("-np\t[y|n] do not write out private qualifiers, default is y");
+
+ System.exit(0);
+ }
+
+
+ names = args;
+ int format = DocumentEntryFactory.EMBL_FORMAT;
+ boolean include_diana_extensions = true;
+ String suffix = ".embl";
+ boolean gzip = true;
+ boolean noprivates = true;
+ boolean removeProductForPseudo = false;
+
+ String filePath = "";
+
+ for(int i = 0; i < args.length; i++)
+ {
+ String key = args[i].toLowerCase();
+ if (key.equals("-f"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("n"))
+ flatten = false;
+ }
+ else if (key.equals("-i"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("n"))
+ ignoreObsolete = false;
+ }
+ else if (key.equals("-a"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("n"))
+ include_diana_extensions = false;
+ }
+ else if (key.equals("-z"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("n"))
+ gzip = false;
+ }
+ else if (key.equals("-np"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("n"))
+ noprivates = false;
+ }
+ else if (key.equals("-o"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("gff"))
+ {
+ format = DocumentEntryFactory.GFF_FORMAT;
+ suffix = ".gff";
+ }
+ }
+ else if (key.equals("-pp"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("y"))
+ LocalAndRemoteFileManager.domainLoad.setSelected(true);
+ }
+
+ // GSV :: added these command-line parameters
+ // note that read_only and noprompt -D parameters redundant now
+ else if (key.equals("-u"))
+ {
+ System.setProperty("uimode", args[i + 1]);
+ }
+ else if (key.equals("-c"))
+ {
+ System.setProperty("chado", args[i + 1]);
+ }
+ else if (key.equals("-r"))
+ {
+ if(i + 1 < args.length && args[i + 1].toLowerCase().equals("y"))
+ removeProductForPseudo = true;
+ }
+ else if (key.equals("-p"))
+ {
+ System.setProperty("chadoPassword", args[i + 1]);
+ }
+ else if (key.equals("-fp"))
+ {
+ filePath = args[i + 1];
+ }
+ }
+
+ // run this after all the system properties have been set
+ UI.initalise();
+
+ java.util.Vector<String> files = null;
+ for(int i = 0; i < args.length; i++)
+ {
+ if(args[i].toLowerCase().equals("-s"))
+ {
+ if(files == null)
+ files = new java.util.Vector<String>();
+ for(int j = i + 1; j < args.length; j++)
+ {
+ if(args[j].startsWith("-"))
+ break;
+ files.add(args[j]);
+ i++;
+ }
+ }
+ else if(args[i].toLowerCase().equals("-flt"))
+ {
+ for(int j = i + 1; j < args.length; j++)
+ {
+ if(args[j].startsWith("-")) {
+ break;
+ }
+ GFFStreamFeature.removeAttribute(args[j]);
+ i++;
+ }
+ }
+ else if(args[i].startsWith("-"))
+ {
+ i++;
+ }
+ else
+ {
+ if(files == null)
+ files = new java.util.Vector<String>();
+ if(!files.contains(args[i]))
+ files.add(args[i]);
+ }
+ }
+ if(files != null && files.size() > 0)
+ {
+ names = new String[files.size()];
+ files.toArray(names);
+ }
+
+ if(filePath.length() != 0)
+ {
+ filePath += "/";
+ }
+
+ if(gzip)
+ suffix = suffix + ".gz";
+
+ if(noprivates)
+ System.setProperty("noprivate", "true");
+
+ for(int i=0;i < names.length; i++)
+ {
+
+ System.out.println("read :: "+names[i]+" write :: "+names[i]+suffix);
+ logger4j.info("read :: "+names[i]+" write :: "+names[i]+suffix);
+
+ Entry entry = ReadAndWriteEntry.readEntryFromDatabase(names[i], ENTRY_SOURCE);
+ DocumentEntryFactory.REMOVE_PRODUCT_FROM_PSEUDOGENE = removeProductForPseudo;
+ try
+ {
+ ReadAndWriteEntry.writeDatabaseEntryToFile(
+ entry, new File(filePath + names[i]+suffix), flatten, ignoreObsolete,
+ false, include_diana_extensions, format, null);
+ System.out.println("done");
+ logger4j.info("done");
+ }
+ catch(EntryInformationException eie)
+ {
+ String label = "Destination format can't handle all keys/qualifiers - continue?";
+ boolean canContinue = UI.booleanUserInput(label, eie.getMessage());
+
+ if (canContinue)
+ {
+ ReadAndWriteEntry.writeDatabaseEntryToFile(entry, new File(filePath + names[i] + suffix),
+ flatten, ignoreObsolete, true,
+ include_diana_extensions, format, null);
+ System.out.println("done");
+ logger4j.info("done");
+ }
+ }
+ entry.dispose();
+ }
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+ System.exit(0);
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/ReadEvent.java b/uk/ac/sanger/artemis/io/ReadEvent.java
new file mode 100644
index 0000000..a323283
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/ReadEvent.java
@@ -0,0 +1,56 @@
+/* EntryStreamEvent.java
+ *
+ * created: Fri Aug 15 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/ReadEvent.java,v 1.1 2004-06-09 09:50:21 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * An Event that holds a message, a warning or an error generated while
+ * reading an Entry.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ReadEvent.java,v 1.1 2004-06-09 09:50:21 tjc Exp $
+ **/
+
+public class ReadEvent extends java.util.EventObject {
+ /**
+ * Create a new ReadEvent with the given source and message.
+ **/
+ public ReadEvent (final Object source, final String message) {
+ super (source);
+ this.message = message;
+ }
+
+ /**
+ * Return the message that was passed to the constructor.
+ **/
+ public String getMessage () {
+ return message;
+ }
+
+ /**
+ * The message that was passed to the constructor.
+ **/
+ final String message;
+}
diff --git a/uk/ac/sanger/artemis/io/ReadFormatException.java b/uk/ac/sanger/artemis/io/ReadFormatException.java
new file mode 100644
index 0000000..05a668a
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/ReadFormatException.java
@@ -0,0 +1,76 @@
+/* ReadFormatException.java
+ *
+ * created: Tue Oct 13 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/ReadFormatException.java,v 1.1 2004-06-09 09:50:22 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+import java.io.IOException;
+
+/**
+ * An object of this type is thrown while reading if the format of the input
+ * stream is incorrect.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ReadFormatException.java,v 1.1 2004-06-09 09:50:22 tjc Exp $
+ *
+ **/
+public class ReadFormatException extends IOException {
+ /**
+ * This constructor creates a ReadFormatException with no detail message.
+ **/
+ private ReadFormatException () {
+ super ();
+ }
+
+ /**
+ * This constructor creates a ReadFormatException with the given String
+ * as the message.
+ * @param message the detail message
+ **/
+ public ReadFormatException (String message) {
+ this (message, 0);
+ }
+
+ /**
+ * This constructor creates a ReadFormatException with the given String
+ * as the message and a line number.
+ * @param message The detail message.
+ * @param line_number The line number at which the error occured.
+ **/
+ public ReadFormatException (String message, int line_number) {
+ super (message);
+ this.line_number = line_number;
+ }
+
+ /**
+ * Return the line number of the exception.
+ **/
+ public int getLineNumber () {
+ return line_number;
+ }
+
+ /**
+ * The line number of the error.
+ **/
+ private int line_number = 0;
+}
diff --git a/uk/ac/sanger/artemis/io/ReadListener.java b/uk/ac/sanger/artemis/io/ReadListener.java
new file mode 100644
index 0000000..46f6d68
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/ReadListener.java
@@ -0,0 +1,37 @@
+/* ReadListener.java
+ *
+ * created: Fri Aug 15 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/ReadListener.java,v 1.1 2004-06-09 09:50:23 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * The ReadListener interface is used to listen for ReadEvents.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ReadListener.java,v 1.1 2004-06-09 09:50:23 tjc Exp $
+ **/
+
+public interface ReadListener {
+ void notify (final ReadEvent event);
+}
diff --git a/uk/ac/sanger/artemis/io/ReadOnlyEmblStreamFeature.java b/uk/ac/sanger/artemis/io/ReadOnlyEmblStreamFeature.java
new file mode 100644
index 0000000..dddb2f9
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/ReadOnlyEmblStreamFeature.java
@@ -0,0 +1,183 @@
+/* ReadOnlyEmblStreamFeature.java
+ *
+ * created: Mon Aug 13 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/ReadOnlyEmblStreamFeature.java,v 1.1 2004-06-09 09:50:24 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+/**
+ * A read-only version of EmblStreamFeature - all set() methods throw
+ * ReadOnlyException.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ReadOnlyEmblStreamFeature.java,v 1.1 2004-06-09 09:50:24 tjc Exp $
+ **/
+
+public class ReadOnlyEmblStreamFeature extends EmblStreamFeature {
+ /**
+ * Create a new EmblStreamFeature object.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception InvalidRelationException Thrown if this Feature cannot contain
+ * the given Qualifier.
+ **/
+ public ReadOnlyEmblStreamFeature (Key key,
+ Location location,
+ QualifierVector qualifiers)
+ throws InvalidRelationException {
+ super (key, location, qualifiers);
+
+ finished_constructor = true;
+ }
+
+ /**
+ * Set the owning DocumentEntry of this Feature.
+ * @param entry The Entry that now owns this Feature.
+ **/
+ public void setDocumentEntry (final DocumentEntry entry)
+ throws ReadOnlyException {
+ if (finished_constructor) {
+ throw new ReadOnlyException ();
+ } else {
+ super.setDocumentEntry (entry);
+ }
+ }
+
+ /**
+ * Set the value of this object.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public void set (final Key key,
+ final Location location,
+ final QualifierVector qualifiers)
+ throws OutOfRangeException, EntryInformationException,
+ ReadOnlyException {
+ if (finished_constructor) {
+ throw new ReadOnlyException ();
+ } else {
+ super.set (key, location, qualifiers);
+ }
+ }
+
+ /**
+ * Set the key field of this object.
+ * @param key The new feature key
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public void setKey (final Key key)
+ throws EntryInformationException, ReadOnlyException {
+ if (finished_constructor) {
+ throw new ReadOnlyException ();
+ } else {
+ super.setKey (key);
+ }
+ }
+
+ /**
+ * Set the location of this object.
+ * @param location The Location object for the new feature
+ **/
+ public void setLocation (final Location location)
+ throws ReadOnlyException, OutOfRangeException {
+ if (finished_constructor) {
+ throw new ReadOnlyException ();
+ } else {
+ super.setLocation (location);
+ }
+ }
+
+ /**
+ * Set the qualifiers of this object.
+ * @param qualifiers The new qualifiers for the feature. null means remove
+ * all qualifiers.
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public void setQualifiers (final QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException {
+ if (finished_constructor) {
+ throw new ReadOnlyException ();
+ } else {
+ super.setQualifiers (qualifiers);
+ }
+ }
+
+ /**
+ * Add the given Qualifier to this Feature. If this Feature contains a
+ * Qualifier with the same name as the new Qualifier it will be replaced.
+ * @param qualifier The new qualifier to add.
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public void setQualifier (final Qualifier qualifier)
+ throws EntryInformationException, ReadOnlyException {
+ if (finished_constructor) {
+ throw new ReadOnlyException ();
+ } else {
+ super.setQualifier (qualifier);
+ }
+ }
+
+ /**
+ * Remove the Qualifier with the given name. If there is no Qualifier with
+ * that name then return immediately.
+ * @exception EntryInformationException Thrown if a required qualifier is
+ * removed.
+ * @param name The qualifier name to look for.
+ **/
+ public void removeQualiferByName (final String name)
+ throws EntryInformationException, ReadOnlyException {
+ if (finished_constructor) {
+ throw new ReadOnlyException ();
+ } else {
+ super.removeQualifierByName (name);
+ }
+ }
+
+ /**
+ * Add the values from the given qualifier to the Qualifier object with the
+ * same name in this Feature or otherwise add a copy of the argument.
+ * @param qualifier This object contains name and values to add.
+ * @return The Qualifier that was changed or created.
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public Qualifier addQualifierValues (final Qualifier qualifier)
+ throws EntryInformationException, ReadOnlyException {
+ if (finished_constructor) {
+ throw new ReadOnlyException ();
+ } else {
+ return super.addQualifierValues (qualifier);
+ }
+ }
+
+ private boolean finished_constructor = false;
+}
diff --git a/uk/ac/sanger/artemis/io/ReadOnlyEntry.java b/uk/ac/sanger/artemis/io/ReadOnlyEntry.java
new file mode 100644
index 0000000..418e31a
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/ReadOnlyEntry.java
@@ -0,0 +1,109 @@
+/* ReadOnlyEntry.java
+ *
+ * created: Tue Feb 15 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/ReadOnlyEntry.java,v 1.1 2004-06-09 09:50:26 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+
+/**
+ * Base class for those Entry classes that have only read-only methods.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: ReadOnlyEntry.java,v 1.1 2004-06-09 09:50:26 tjc Exp $
+ **/
+
+abstract public class ReadOnlyEntry {
+ /**
+ * Always returns true for objects of this class.
+ **/
+ public boolean isReadOnly () {
+ return true;
+ }
+
+ /**
+ * This method always returns false for objects of this class.
+ **/
+ public boolean hasUnsavedChanges () {
+ return false;
+ }
+
+ /**
+ * Attempt to set the header of this Entry to be the given text - always
+ * fails (returns false).
+ **/
+ public boolean setHeaderText (final String new_header) {
+ return false;
+ }
+
+ /**
+ * Always throws a ReadOnlyException exception for objects of this class.
+ **/
+ public void save () throws IOException {
+ throw new ReadOnlyException ("Save is not implemented for this entry");
+ }
+
+ /**
+ * Set the name of this Entry - always fails (returns false).
+ **/
+ public boolean setName (final String name) {
+ return false;
+ }
+
+ /**
+ * Always throws a ReadOnlyException exception for objects of this class.
+ **/
+ public Feature createFeature (Key key,
+ Location location,
+ QualifierVector qualifiers)
+ throws ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Always throws ReadOnlyException for this type of Entry.
+ **/
+ public Feature add (final Feature feature)
+ throws ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Always throws ReadOnlyException for this type of Entry.
+ **/
+ public Feature forcedAdd (final Feature feature)
+ throws ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+
+ /**
+ * Always throws a ReadOnlyException exception for objects of this class.
+ **/
+ public boolean remove (Feature feature)
+ throws ReadOnlyException {
+ throw new ReadOnlyException ();
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/Sequence.java b/uk/ac/sanger/artemis/io/Sequence.java
new file mode 100644
index 0000000..3c7eaeb
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/Sequence.java
@@ -0,0 +1,89 @@
+/* Sequence.java
+ *
+ * created: Mon Oct 12 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/Sequence.java,v 1.5 2008-12-11 16:54:23 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.ReadOnlyException;
+import org.biojava.bio.symbol.IllegalSymbolException;
+
+/**
+ * Sequence interface
+ *
+ * @author Kim Rutherford
+ * @version $Id: Sequence.java,v 1.5 2008-12-11 16:54:23 tjc Exp $
+ *
+ */
+
+public interface Sequence
+{
+ /**
+ * Return a the given range of bases as a String.
+ * @param start The start base of the range.
+ * @param end The end base of the range.
+ **/
+ String getSubSequence(int start, int end);
+ char[] getCharSubSequence(int start, int end);
+
+ char charAt(int i);
+
+ /**
+ * Set this sequence to hold the bases in the given String.
+ * @exception ReadOnlyException If this Sequence cannot be changed.
+ **/
+ void setFromChar(final char sequence[])
+ throws ReadOnlyException, IllegalSymbolException;
+
+ /**
+ * Returns the length of the sequence in bases.
+ **/
+ int length();
+
+ /**
+ * Return the count of c bases in the whole sequence.
+ **/
+ int getCCount();
+
+ /**
+ * Return the count of g bases in the whole sequence.
+ **/
+ int getGCount();
+
+ /**
+ * Return the count of a bases in the whole sequence.
+ **/
+ int getACount();
+
+ /**
+ * Return the count of t bases in the whole sequence.
+ **/
+ int getTCount();
+
+ /**
+ * Return the count of non-g,c,t,a bases in the whole sequence.
+ **/
+ int getOtherCount();
+
+ //void clear();
+}
diff --git a/uk/ac/sanger/artemis/io/SimpleDocumentEntry.java b/uk/ac/sanger/artemis/io/SimpleDocumentEntry.java
new file mode 100644
index 0000000..0e2f07c
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/SimpleDocumentEntry.java
@@ -0,0 +1,1265 @@
+/* SimpleDocumentEntry.java
+ *
+ * created: Tue Feb 15 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/SimpleDocumentEntry.java,v 1.30 2009-09-03 13:33:18 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Vector;
+import java.util.Hashtable;
+
+/**
+ * This class contains the methods common to all DocumentEntry objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: SimpleDocumentEntry.java,v 1.30 2009-09-03 13:33:18 tjc Exp $
+ **/
+
+abstract public class SimpleDocumentEntry
+ implements DocumentEntry
+{
+
+ /**
+ * A vector of ReadOnlyEmblStreamFeature objects - one for each fasta
+ * record in the DocumentEntry. These won't be written when the
+ * DocumentEntry is written.
+ **/
+ private FeatureVector fake_fasta_features = new FeatureVector();
+
+ /** EntryInformation object that was passed to constructor */
+ final private EntryInformation entry_information;
+
+ /** collection to send ReadEvents to */
+ private Vector<ReadListener> listeners = new Vector<ReadListener>();
+
+ /**
+ * The Document object that was passed to the constructor. This should be
+ * the document that this SimpleDocumentEntry was read from.
+ **/
+ private Document document = null;
+
+ /**
+ * This contains all the lines(stored as LineGroup objects) from the entry
+ * stream that was passed to the constructor.
+ **/
+ protected Vector<LineGroup> line_groups = new Vector<LineGroup>();
+
+ /**
+ * The DocumentEntryAutosaveThread that is started when the first call is
+ * made to setDirtyFlag().
+ **/
+ private Thread autosave_thread = null;
+
+ /**
+ * The Date when this Entry last changed or null if this Entry
+ * hasn't changed since the last save. Set to null by save().
+ **/
+ private java.util.Date last_change_time = null;
+
+ /**
+ * Set to true in the constructor while features are added. setDirtyFlag()
+ * will do nothing while this is true.
+ **/
+ protected boolean in_constructor = false;
+
+ protected Hashtable<String, Range> contig_ranges;
+
+ /**
+ * Create a new SimpleDocumentEntry from the given Document.
+ * @param entry_information The EntryInformation object of the new Entry.
+ * @param document This is the file that we will read from. This is also
+ * used for saving the entry back to the file it came from and to give
+ * the new object a name.
+ * @exception IOException thrown if there is a problem reading the entry -
+ * most likely ReadFormatException.
+ * @exception EntryInformationException Thrown if force is false and if this
+ * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
+ * one of the features in the given Entry.
+ **/
+ public SimpleDocumentEntry(final EntryInformation entry_information,
+ final Document document,
+ final ReadListener read_listener)
+ throws IOException, EntryInformationException
+ {
+ this.document = document;
+ this.entry_information = new SimpleEntryInformation(entry_information);
+ this.in_constructor = true; // flag used by setDirtyFlag()
+
+ if(read_listener != null)
+ addReadListener(read_listener);
+
+ final LinePushBackReader pushback_reader =
+ getDocument().getLinePushBackReader();
+
+ LineGroup new_line_group;
+
+ final int MAX_LOOP = 9999;
+
+ while((new_line_group =
+ LineGroup.readNextLineGroup(pushback_reader, this)) != null)
+ {
+ if(new_line_group instanceof SimpleDocumentFeature)
+ {
+ final SimpleDocumentFeature new_feature =
+ (SimpleDocumentFeature)new_line_group;
+
+ // try several times because adding the Feature may cause more than
+ // one exception
+ int i;
+ EntryInformationException saved_error = null;
+
+ for(i = 0; i<MAX_LOOP; ++i)
+ {
+ try
+ {
+ addInternal(new_feature, true);
+ break;
+ }
+ catch(EntryInformationException e)
+ {
+ getEntryInformation().fixException(e);
+ saved_error = e;
+ }
+ }
+
+ if(i == MAX_LOOP)
+ throw new Error("internal error - too many exceptions: " +
+ saved_error.getMessage());
+ }
+ else
+ addLineGroup(new_line_group);
+
+ if(new_line_group instanceof IndexFastaStream)
+ break;
+ }
+
+ pushback_reader.close();
+
+ // we added some features above hence:
+ last_change_time = null;
+
+ final Sequence sequence = getSequence();
+
+ if(sequence != null && sequence instanceof FastaStreamSequence)
+ {
+ // add a feature for each FASTA record if there are more
+ // than one record in the FASTA sequence
+ final FastaStreamSequence fasta_sequence =
+ (FastaStreamSequence)sequence;
+
+ final String[] header_strings = fasta_sequence.getFastaHeaderStrings();
+
+ if(header_strings.length > 1)
+ {
+ final int[] header_positions =
+ fasta_sequence.getFastaHeaderPositions();
+
+ //final FeatureTable feature_table =
+ getFeatureTable();
+
+ for(int i = 0 ; i < header_strings.length ; ++i)
+ {
+ try
+ {
+ final Range new_range;
+
+ if(i == header_strings.length - 1)
+ {
+ if(header_positions[i] == fasta_sequence.length())
+ throw new ReadFormatException("empty FASTA record: >" +
+ header_strings[i]);
+
+ new_range = new Range(header_positions[i] + 1,
+ fasta_sequence.length());
+ }
+ else
+ {
+ if(header_positions[i] == header_positions[i+1])
+ throw new ReadFormatException("empty FASTA record: >" +
+ header_strings[i]);
+
+ new_range = new Range(header_positions[i] + 1,
+ header_positions[i+1]);
+ }
+
+ String thisHeader[] = header_strings[i].split("\\s");
+ final QualifierVector qualifiers = new QualifierVector();
+
+ qualifiers.setQualifier(new Qualifier("note",
+ header_strings[i]));
+ qualifiers.setQualifier(new Qualifier("label", thisHeader[0]));
+ if(i % 2 == 0)
+ qualifiers.setQualifier(new Qualifier("colour", "10"));
+ else
+ qualifiers.setQualifier(new Qualifier("colour", "11"));
+
+ //ReadOnlyEmblStreamFeature
+ final EmblStreamFeature new_feature =
+ new EmblStreamFeature(new Key("fasta_record"),
+ new Location(new_range),
+ qualifiers);
+
+ fake_fasta_features.add(new_feature);
+
+ // record coordinates to adjust feature coordinates
+ if(contig_ranges == null)
+ contig_ranges = new Hashtable<String, Range>();
+
+ // find the sequence id from the header
+ contig_ranges.put(thisHeader[0], new_range);
+ }
+ catch(InvalidRelationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+ addFakeFeatures();
+ }
+ }
+
+ this.in_constructor = false;
+ }
+
+ /**
+ * Create a new SimpleDocumentEntry with no Document associated with it.
+ * @param entry_information The EntryInformation object of the new Entry.
+ **/
+ public SimpleDocumentEntry(final EntryInformation entry_information)
+ {
+ this.entry_information = new SimpleEntryInformation(entry_information);
+ }
+
+ /**
+ * Create a new SimpleDocumentEntry that will be a copy of the given Entry
+ * and has no Document associated with it. The new SimpleDocumentEntry
+ * cannot be saved to a file with save() unless save(Document) has been
+ * called first. Some qualifier and location information will be lost if
+ * the argument Entry is a different type to this class.
+ * @param entry_information The EntryInformation object of the Entry that
+ * will contain this Feature.
+ * @param force If true then invalid qualifiers and any features with
+ * invalid keys in the new Entry will be quietly thrown away. "Invalid"
+ * means that the key/qualifier is not allowed to occur in an Entry of
+ * this type(probably determined by the EntryInformation object of this
+ * Entry). If false an EntryInformationException will be thrown for
+ * invalid keys or qualifiers.
+ **/
+ public SimpleDocumentEntry(final EntryInformation entry_information,
+ final Entry new_entry, final boolean force)
+ throws EntryInformationException
+ {
+ this.entry_information = new SimpleEntryInformation(entry_information);
+
+ if(new_entry.getClass().equals(this.getClass()) ||
+ (this instanceof GFFDocumentEntry && new_entry instanceof DatabaseDocumentEntry))
+ {
+ try
+ {
+ setHeaderText(new_entry.getHeaderText());
+ }
+ catch(IOException e)
+ {
+ System.err.println(e);
+ // if it doesn't work just ignore it
+ }
+ }
+
+ final FeatureEnumeration feature_enum = new_entry.features();
+
+ Set<String> failed = null;
+ while(feature_enum.hasMoreFeatures())
+ {
+ final Feature new_feature = feature_enum.nextFeature();
+
+ try
+ {
+ if(force)
+ {
+ Feature f = (SimpleDocumentFeature)makeNativeFeature(new_feature, true);
+
+ if(f != null)
+ {
+ if(forcedAdd(f) == null)
+ {
+ if(failed == null)
+ failed = new HashSet<String>();
+ failed.add(new_feature.getKey().getKeyString());
+ }
+ }
+ }
+ else
+ {
+ final Object docFeature = makeNativeFeature(new_feature, true);
+ if(docFeature instanceof SimpleDocumentFeature[])
+ {
+ SimpleDocumentFeature[] docFeatures = (SimpleDocumentFeature[])docFeature;
+ for(int i=0; i<docFeatures.length; i++)
+ add((SimpleDocumentFeature)docFeatures[i]);
+ }
+ else if(docFeature != null)
+ add((SimpleDocumentFeature)docFeature);
+ }
+ }
+ catch(ReadOnlyException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ if(failed != null)
+ UI.warn("Failed to use the following keys\n"+failed.toString(), "Warning - unknown keys");
+
+ final Sequence new_sequence = new_entry.getSequence();
+
+ if(new_sequence != null)
+ setSequence(makeNativeSequence(new_sequence));
+ }
+
+ /**
+ * Return the text of the header of this Entry or null if there is no
+ * header.
+ **/
+ public String getHeaderText()
+ {
+ final StringBuffer buffer = new StringBuffer();
+
+ for(int i = 0 ; i < line_groups.size() ; ++i)
+ {
+ final LineGroup current_line_group = line_groups.elementAt(i);
+
+ if(!(current_line_group instanceof FeatureTable) &&
+ !(current_line_group instanceof Sequence))
+ buffer.append(current_line_group.toString());
+ }
+
+ if(buffer.length() > 0)
+ return buffer.toString();
+ else
+ return null;
+ }
+
+ /**
+ * Set the header of this Entry to be the given text.
+ * @return true if and only if the header was successfully set. Not all
+ * Entry objects can change their header, so it is up to the calling
+ * function to check the return value. If null the current header will be
+ * removed.
+ * @exception IOException thrown if there is a problem reading the header
+ * from the String - most likely ReadFormatException.
+ **/
+ public boolean setHeaderText(final String new_header)
+ throws IOException
+ {
+
+ final Vector<LineGroup> new_line_groups = new Vector<LineGroup>();
+
+ if(new_header != null)
+ {
+ final Reader reader = new StringReader(new_header);
+
+ final LinePushBackReader pushback_reader =
+ new LinePushBackReader(reader);
+
+ LineGroup new_line_group;
+
+ try
+ {
+ while((new_line_group =
+ LineGroup.readNextLineGroup(pushback_reader, this)) != null)
+ {
+ if(new_line_group instanceof MiscLineGroup)
+ new_line_groups.addElement(new_line_group);
+ else
+ throw new ReadFormatException("the header must contain only " +
+ "header lines");
+ }
+ }
+ catch(InvalidRelationException e)
+ {
+ throw new ReadFormatException("the header must contain only " +
+ "header lines");
+ }
+ }
+
+ // now remove all the EmblMisc and GenbankMisc LineGroup objects from
+ // this Entry
+ for(int i = line_groups.size() - 1 ; i >= 0 ; --i)
+ {
+ final LineGroup current_line_group = line_groups.elementAt(i);
+
+ if(current_line_group instanceof MiscLineGroup)
+ line_groups.removeElementAt(i);
+ }
+
+ if(new_header != null)
+ {
+ // then add the new LineGroup objects
+ for(int i = 0 ; i < new_line_groups.size() ; ++i)
+ line_groups.insertElementAt(new_line_groups.elementAt(i), i);
+ }
+
+ setDirtyFlag();
+
+ return true;
+ }
+
+ /**
+ * Write this Entry to the given stream.
+ * @param writer The stream to write to.
+ * @exception IOException thrown if there is a problem writing the entry.
+ **/
+ public void writeToStream(final Writer writer)
+ throws IOException
+ {
+ removeFakeFeatures();
+
+ try
+ {
+ for(int i = 0 ; i < line_groups.size() ; ++i)
+ {
+ final LineGroup current_line_group = line_groups.elementAt(i);
+ if(this instanceof GFFDocumentEntry &&
+ current_line_group instanceof FastaStreamSequence)
+ LineGroup.writeStartOfGFFEntry(writer);
+ current_line_group.writeToStream(writer);
+ }
+
+ if(line_groups.size() == 1)
+ {
+ // don't write out the "//" end of entry marker if we have only one
+ // LineGroup - this makes life easier for external programs that need
+ // to read the feature table or sequence
+ return;
+ }
+ else
+ {
+ if(line_groups.size() == 2)
+ {
+ final LineGroup second_line_group = line_groups.elementAt(1);
+
+ // don't write out the "//" end of entry marker if this is raw or
+ // FASTA sequence
+ if(second_line_group instanceof RawStreamSequence ||
+ second_line_group instanceof FastaStreamSequence)
+ return;
+ }
+ }
+
+ if(this instanceof PublicDBDocumentEntry)
+ LineGroup.writeEndOfEMBLEntry(writer);
+
+ }
+ finally
+ {
+ addFakeFeatures();
+ }
+ }
+
+ /**
+ * Return a count of the number of Feature objects in this Entry.
+ **/
+ public int getFeatureCount()
+ {
+ final FeatureTable feature_table = findFeatureTable();
+
+ if(feature_table == null)
+ return 0;
+ else
+ return feature_table.getFeatureCount();
+ }
+
+ /**
+ * The method is identical to add() except that it doesn't check the
+ * read_only flag before adding and it doesn't throw ReadOnlyExceptions.
+ * This is used by the constructor to avoid exceptions.
+ *
+ * Add the given Feature to this Entry. If the Feature is already in an
+ * Entry then Entry.remove() should be called on that Entry before calling
+ * Entry.add(). An Error will be thrown otherwise.
+ * @param throw_entry_info_exceptions if true throw
+ * EntryInformationExceptions, otherwise just send a ReadEvent.
+ * @exception EntryInformationException Thrown if this Entry
+ * cannot contain the Key, Qualifier or Key/Qualifier combination of the
+ * given Feature or if a required qualifier is missing.
+ * @return A reference that was passed to add(), if that Feature can be
+ * stored directly in this Entry, otherwise returns a reference to a new
+ * Feature, that is a copy of the argument. The argument reference
+ * should not be used after the call to add(), unless the return
+ * reference happens to be the same as the argument.
+ **/
+ private Feature addInternal(final Feature feature,
+ final boolean throw_entry_info_exceptions)
+ throws EntryInformationException
+ {
+ if(feature.getEntry() != null)
+ throw new Error("internal error - a feature must have one owner");
+
+ final EntryInformation entry_information = getEntryInformation();
+
+ if(!entry_information.isValidKey(feature.getKey()))
+ {
+ final String message = feature.getKey() + " is not a valid key";
+
+ fireEvent(new ReadEvent(this, message));
+
+ throw new InvalidKeyException(feature.getKey() + " is not a valid " +
+ "key for this entry", feature.getKey());
+ }
+
+ final QualifierVector new_qualifiers = feature.getQualifiers();
+
+ final Key new_key = feature.getKey();
+
+ // check the qualifiers
+ for(int i = 0 ; i < new_qualifiers.size() ; ++i)
+ {
+ final Qualifier this_qualifier = (Qualifier)new_qualifiers.elementAt(i);
+ final String this_qualifier_name = this_qualifier.getName();
+
+ if(!entry_information.isValidQualifier(new_key, this_qualifier_name))
+ {
+ final String message = new_key + " can't have " + this_qualifier_name
+ + " as a qualifier";
+
+ fireEvent(new ReadEvent(this, message));
+
+ if(throw_entry_info_exceptions)
+ throw new InvalidRelationException(message, new_key,
+ this_qualifier);
+ }
+ }
+
+ final SimpleDocumentFeature native_feature =
+ (SimpleDocumentFeature)makeNativeFeature(feature, false);
+
+ final FeatureTable feature_table = getFeatureTable();
+ feature_table.add(native_feature);
+
+ try
+ {
+ native_feature.setDocumentEntry(this);
+ }
+ catch(ReadOnlyException e)
+ {
+ // makeNativeFeature() should never return a read only feature
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ setDirtyFlag();
+
+ return native_feature;
+ }
+
+ /**
+ * Add the given Feature to this Entry. If the Feature is already in an
+ * Entry then Entry.remove() should be called on that Entry before calling
+ * Entry.add(). An Error will be thrown otherwise.
+ * @exception ReadOnlyException If this entry is read only.
+ * @exception EntryInformationException Thrown if this Entry
+ * cannot contain the Key, Qualifier or Key/Qualifier combination of the
+ * given Feature or if a required qualifier is missing.
+ * @return A reference that was passed to add(), if that Feature can be
+ * stored directly in this Entry, otherwise returns a reference to a new
+ * Feature, that is a copy of the argument. The argument reference
+ * should not be used after the call to add(), unless the return
+ * reference happens to be the same as the argument.
+ **/
+ public Feature add(final Feature feature)
+ throws EntryInformationException, ReadOnlyException
+ {
+ if(isReadOnly())
+ throw new ReadOnlyException();
+
+ return addInternal(feature, true);
+ }
+
+ /**
+ * Add the given Feature to this Entry. If the Feature is already in an
+ * Entry then Entry.remove() should be called on that Entry before calling
+ * Entry.forcedAdd()(An Error will be thrown otherwise). Invalid
+ * qualifiers will be quietly thrown away. Features with invalid keys will
+ * not be added(and null will be returned). "Invalid" means that the
+ * key/qualifier is non allowed to occur in an Entry of this type(probably
+ * determined by the EntryInformation object of this Entry). Any
+ * qualifiers that are required for this Entry will be quietly added(with
+ * a zero-length string as the value).
+ * @exception ReadOnlyException If this entry is read only.
+ * @return A reference that was passed to add(), if that Feature can be
+ * stored directly in this Entry, otherwise returns a reference to a new
+ * Feature, that is a copy of the argument. The argument reference
+ * should not be used after the call to add(), unless the return
+ * reference happens to be the same as the argument. Returns null if and
+ * only if the new Feature has a key that is invalid for this Entry.
+ **/
+ public Feature forcedAdd(final Feature feature)
+ throws ReadOnlyException
+ {
+ if(isReadOnly())
+ throw new ReadOnlyException();
+
+ if(feature.getEntry() != null)
+ throw new Error("internal error - a feature must have one owner");
+
+ final EntryInformation entry_information = getEntryInformation();
+
+ if(!entry_information.isValidKey(feature.getKey()))
+ return null;
+
+ final QualifierVector feature_qualifiers = feature.getQualifiers();
+ final QualifierVector fixed_qualifiers = new QualifierVector();
+ final Key new_key = feature.getKey();
+
+ // set to true if there is an invalid qualifier
+ boolean qualifiers_fixed = false;
+
+ // check the qualifiers
+ for(int i = 0 ; i < feature_qualifiers.size() ; ++i)
+ {
+ final Qualifier this_qualifier = (Qualifier)feature_qualifiers.elementAt(i);
+
+ final String this_qualifier_name = this_qualifier.getName();
+
+ if(entry_information.isValidQualifier(new_key, this_qualifier_name))
+ fixed_qualifiers.setQualifier(this_qualifier);
+ else
+ qualifiers_fixed = true;
+ }
+
+ final SimpleDocumentFeature native_feature =
+ (SimpleDocumentFeature)makeNativeFeature(feature, false);
+
+ if(qualifiers_fixed)
+ {
+ try
+ {
+ native_feature.setQualifiers(fixed_qualifiers);
+ }
+ catch(EntryInformationException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+ else
+ {
+ // otherwise use the feature as is
+ }
+
+ final FeatureTable feature_table = getFeatureTable();
+ feature_table.add(native_feature);
+ native_feature.setDocumentEntry(this);
+ setDirtyFlag();
+
+ return native_feature;
+ }
+
+ /**
+ * The method is identical to remove() except that it doesn't check the
+ * read_only flag before removing and it doesn't throw ReadOnlyExceptions.
+ *
+ * Remove the given Feature from this Entry.
+ * @return true if and only if the Feature was in this Entry.
+ **/
+ protected boolean removeInternal(Feature feature)
+ {
+ final FeatureTable feature_table = findFeatureTable();
+
+ if(feature_table == null)
+ return false;
+ else
+ {
+ final SimpleDocumentFeature feature_from_table =
+ (SimpleDocumentFeature) feature_table.remove(feature);
+
+ if(feature_from_table == null)
+ return false;
+ else
+ {
+ try
+ {
+ feature_from_table.setDocumentEntry(null);
+ }
+ catch(ReadOnlyException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ // get rid of the feature table
+ if(feature_table.getFeatureCount() == 0)
+ removeLineGroup(feature_table);
+
+ setDirtyFlag();
+
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Remove the given Feature from this Entry.
+ * @return true if and only if the Feature was in this Entry.
+ **/
+ public boolean remove(Feature feature)
+ throws ReadOnlyException
+ {
+ if(isReadOnly() || feature.isReadOnly())
+ throw new ReadOnlyException();
+
+ return removeInternal(feature);
+ }
+
+ /**
+ * Return the ith Feature from this Entry. This Features are returned in a
+ * consistent order, sorted by the first base of each Feature.
+ **/
+ public Feature getFeatureAtIndex(int i)
+ {
+ final FeatureTable feature_table = findFeatureTable();
+
+ if(feature_table == null)
+ return null;
+ else
+ return feature_table.getFeatureAtIndex(i);
+ }
+
+ /**
+ * Return the index of the given Feature. This does the reverse of
+ * getFeatureAtIndex().
+ **/
+ public int indexOf(final Feature feature)
+ {
+ final FeatureTable feature_table = findFeatureTable();
+
+ if(feature_table == null)
+ return -1;
+ else
+ return feature_table.indexOf(feature);
+ }
+
+ /**
+ * Returns true if and only if this Entry contains the given feature.
+ **/
+ public boolean contains(final Feature feature)
+ {
+ final FeatureTable feature_table = findFeatureTable();
+
+ if(feature_table == null)
+ return false;
+ else
+ return feature_table.contains(feature);
+ }
+
+ /**
+ * Create a new Feature object of an appropriate type in this Entry.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature(can be null if
+ * there are no qualifiers).
+ **/
+ public Feature createFeature(final Key key,
+ final Location location,
+ final QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException,
+ OutOfRangeException
+ {
+ if(isReadOnly())
+ throw new ReadOnlyException();
+
+ final Feature new_feature;
+
+ if(this instanceof EmblDocumentEntry)
+ new_feature = new EmblStreamFeature(key, location, qualifiers);
+ else if(this instanceof DatabaseDocumentEntry)
+ new_feature = new DatabaseStreamFeature(key, location, qualifiers);
+ else if(this instanceof GFFDocumentEntry)
+ new_feature = new GFFStreamFeature(key, location, qualifiers);
+ else
+ new_feature = new GenbankStreamFeature(key, location, qualifiers);
+
+ add(new_feature);
+ setDirtyFlag();
+
+ return new_feature;
+ }
+
+ /**
+ * Return a vector containing the references of the Feature objects within
+ * the given range.
+ * @param range Return features that overlap this range - ie the start of
+ * the feature is less than or equal to the end of the range and the end
+ * of the feature is greater than or equal to the start of the range.
+ * @return The features of this feature table the are within
+ * the given range. The returned object is a copy - changes will not
+ * effect the FeatureTable object itself.
+ **/
+ public FeatureVector getFeaturesInRange(Range range)
+ {
+ final FeatureTable feature_table = findFeatureTable();
+
+ if(feature_table == null)
+ return new FeatureVector();
+ else
+ return feature_table.getFeaturesInRange(range);
+ }
+
+ /**
+ * Return a vector containing the references of all the Feature objects in
+ * this Entry.
+ * @return The features of this Entry. The returned object
+ * is a copy - changes will not effect the Entry object itself.
+ **/
+ public FeatureVector getAllFeatures()
+ {
+ final FeatureTable feature_table = findFeatureTable();
+
+ if(feature_table == null)
+ return new FeatureVector();
+ else
+ return feature_table.getAllFeatures();
+ }
+
+ /**
+ * Returns an enumeration of the Feature objects in this
+ * SimpleDocumentEntry. The returned Enumeration object will generate
+ * all features in this object in turn. The first item generated is the
+ * item at index 0, then the item at index 1, and so on.
+ **/
+ public FeatureEnumeration features()
+ {
+ final FeatureTable feature_table = findFeatureTable();
+
+ if(feature_table == null)
+ {
+ return new FeatureEnumeration() {
+ public boolean hasMoreFeatures() {
+ return false;
+ }
+
+ public Feature nextFeature() {
+ return null;
+ }
+ };
+ }
+ else
+ return feature_table.features();
+ }
+
+ /**
+ * Return the Sequence object from this entry or null if it does not
+ * contain one.
+ * @return a Sequence object for this Entry. the returned object is
+ * not a copy - changes to it will change the Entry object itself
+ **/
+ public Sequence getSequence()
+ {
+ for(int i = 0 ; i < line_groups.size() ; ++i)
+ {
+ final LineGroup current_line_group = line_groups.elementAt(i);
+
+ if(current_line_group instanceof Sequence)
+ return(Sequence) current_line_group;
+ }
+
+ return null;
+ }
+
+ /**
+ * Add a new LineGroup object to this Entry.
+ * @param new_line_group A new LineGroup to add.
+ **/
+ private void addLineGroup(final LineGroup new_line_group)
+ {
+ if(new_line_group instanceof FeatureHeader)
+ {
+ // insert FH lines before the Sequence and Features(if any)
+ for(int i = 0 ; i < line_groups.size() ; ++i)
+ {
+ final LineGroup this_line_group = line_groups.elementAt(i);
+
+ if(this_line_group instanceof Feature ||
+ this_line_group instanceof FeatureTable ||
+ this_line_group instanceof Sequence)
+ {
+ line_groups.insertElementAt(new_line_group, i);
+ return;
+ }
+ }
+ }
+ else if(new_line_group instanceof Feature)
+ {
+ // Features before the Sequence and FeatureTable(if any)
+ for(int i = 0 ; i < line_groups.size() ; ++i)
+ {
+ final LineGroup this_line_group = line_groups.elementAt(i);
+
+ if(this_line_group instanceof FeatureTable ||
+ this_line_group instanceof Sequence)
+ {
+ line_groups.insertElementAt(new_line_group, i);
+ return;
+ }
+ }
+ }
+ else if(!(new_line_group instanceof Sequence) &&
+ !(new_line_group instanceof FeatureTable))
+ {
+ if(new_line_group instanceof GFFMisc)
+ {
+ String line = ((GFFMisc)new_line_group).toString();
+ if(line.indexOf("FASTA") > -1) // ignore
+ return;
+ }
+
+ // insert before features and sequence
+ for(int i = 0 ; i < line_groups.size() ; ++i)
+ {
+ final LineGroup this_line_group = line_groups.elementAt(i);
+
+ if(this_line_group instanceof Feature ||
+ this_line_group instanceof FeatureTable ||
+ this_line_group instanceof FeatureHeader ||
+ this_line_group instanceof Sequence)
+ {
+ line_groups.insertElementAt(new_line_group, i);
+ return;
+ }
+ }
+ }
+
+ // otherwise add at end
+ line_groups.addElement(new_line_group);
+ }
+
+ /**
+ * Return the name of this Entry or null if it has no name.
+ **/
+ public String getName()
+ {
+ if(getDocument() == null || getDocument().getName() == null)
+ return null;
+ else
+ return getDocument().getName();
+ }
+
+ /**
+ * Set the name of this Entry - if possible(the return value will let the
+ * caller know).
+ * @return true if and only if the name was successfully set. Not all
+ * Entry objects can change there name, so it is up to the calling
+ * function to check the return value.
+ **/
+ public boolean setName(final String name)
+ {
+ if(name == null || name.length() == 0)
+ return false;
+
+ setDocument(new FileDocument(new File(name)));
+
+ return true;
+ }
+
+ /**
+ * Return the EntryInformation object for this Entry.
+ **/
+ public EntryInformation getEntryInformation()
+ {
+ return entry_information;
+ }
+
+ /**
+ * Create and return a new FeatureTable(). The new FeatureTable will be
+ * added to line_groups in the appropriate place.
+ **/
+ private FeatureTable createFeatureTable()
+ {
+ final FeatureTable new_feature_table;
+
+ new_feature_table = new StreamFeatureTable();
+
+ // put feature table just before SEQUENCE LineGroup
+ if(line_groups.size() > 0 &&
+ line_groups.lastElement() instanceof Sequence)
+ line_groups.insertElementAt(new_feature_table,
+ line_groups.size() - 1);
+ else // no SEQUENCE so add the feature table at the end
+ line_groups.insertElementAt(new_feature_table, line_groups.size());
+
+ return new_feature_table;
+ }
+
+ /**
+ * Return the FeatureTable of this DocumentEntry or null if there isn't one
+ * yet.
+ **/
+ private FeatureTable findFeatureTable()
+ {
+ final int line_groups_size = line_groups.size();
+
+ for(int i = 0; i < line_groups_size; ++i)
+ {
+ final LineGroup current_line_group = line_groups.elementAt(i);
+
+ if(current_line_group instanceof FeatureTable)
+ return (FeatureTable)current_line_group;
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Return the FeatureTable object from this entry(which will be created if
+ * this object does not already contain one). This method has package
+ * scope because other package should add, remove and query Features in the
+ * FeatureTable indirectly(through the Entry class).
+ * @return a FeatureTable object for this Entry. the returned object is
+ * not a copy - changes to it will change the Entry object itself
+ **/
+ private FeatureTable getFeatureTable()
+ {
+ final FeatureTable found_feature_table = findFeatureTable();
+
+ if(found_feature_table == null)
+ return createFeatureTable();
+ else
+ return found_feature_table;
+ }
+
+ /**
+ * Remove the given LineGroup from this SimpleDocumentEntry.
+ **/
+ private void removeLineGroup(final LineGroup line_group)
+ {
+ for(int i = 0 ; i < line_groups.size() ; ++i)
+ {
+ final LineGroup current_line_group = line_groups.elementAt(i);
+
+ if(current_line_group == line_group)
+ {
+ line_groups.removeElementAt(i);
+ setDirtyFlag();
+ return;
+ }
+ }
+ }
+
+ /**
+ * Return the File reference that was passed to the constructor or null if
+ * none was passed.
+ **/
+ public Document getDocument()
+ {
+ return document;
+ }
+
+
+ /**
+ * Set the document to use when saving this DocumentEntry.
+ **/
+ public void setDocument(final Document document)
+ {
+ this.document = document;
+ }
+
+
+ /**
+ * Write this Entry to the Document it came from. This method uses the
+ * current Document reference(as given by getDocument()) and will throw a
+ * NullPointerException if the is no current Document ie. if getDocument()
+ * returns null. Use save(Document) to save the current Entry
+ * to a different Document.
+ * @exception IOException thrown if there is an IO problem saving the entry.
+ **/
+ public void save()
+ throws IOException
+ {
+ save(getDocument());
+ }
+
+
+ /**
+ * Write this Entry to the given Document.
+ * @param document This is the file that we will write to.
+ * @exception IOException thrown if there is an IO problem saving the entry.
+ **/
+ public void save(final Document document)
+ throws IOException
+ {
+ final Writer out_file;
+ try
+ {
+ out_file = document.getWriter();
+ }
+ catch(NullPointerException npe)
+ {
+ return;
+ }
+
+ writeToStream(out_file);
+ out_file.close();
+
+ last_change_time = null;
+ }
+
+
+ /**
+ * Returns true if and only if this entry is read only.
+ **/
+ public boolean isReadOnly()
+ {
+ return false;
+ }
+
+
+ /**
+ * Returns true if and only if there have been some changes to this Entry
+ * since the last save.
+ **/
+ public boolean hasUnsavedChanges()
+ {
+ return last_change_time != null;
+ }
+
+
+ /**
+ * Set last_change_time so that hasUnsavedChanges() will return true.
+ * last_change_time can be read with getLastChangeTime().
+ **/
+ public void setDirtyFlag()
+ {
+ if(in_constructor)
+ {
+ // features are being added, but the entry hasn't really changed
+ }
+ else
+ {
+ if(autosave_thread == null && getName() != null)
+ {
+ // this is the first change so start autosaving
+ autosave_thread = new DocumentEntryAutosaveThread(this);
+ autosave_thread.start();
+ }
+
+ final java.util.Calendar calendar = java.util.Calendar.getInstance();
+ last_change_time = calendar.getTime();
+ }
+ }
+
+
+ /**
+ * Return the Date when this Entry last changed.
+ **/
+ public Date getLastChangeTime()
+ {
+ return last_change_time;
+ }
+
+
+ /**
+ * Add the given Sequence object to this Entry.
+ **/
+ public void setSequence(final StreamSequence sequence)
+ {
+ final Sequence old_sequence = getSequence();
+
+ if(old_sequence != null)
+ removeLineGroup((StreamSequence) old_sequence);
+
+ addLineGroup(sequence);
+ }
+
+
+ /**
+ * If the given feature can be added directly to this Entry, then return
+ * it, otherwise create and return a new feature of the appropriate type
+ * (eg. return an EmblStreamFeature if this Entry is a EmblDocumentEntry).
+ * The feature should not be in an Entry when this method is called.
+ * @param copy if true then always make a new copy of the Feature.
+ **/
+ protected abstract Object
+ makeNativeFeature(final Feature feature,
+ final boolean copy);
+
+ /**
+ * If the given Sequence can be added directly to this Entry, then return a
+ * copy of it, otherwise create and return a new feature of the appropriate
+ * type for this Entry.
+ **/
+ protected abstract StreamSequence
+ makeNativeSequence(final Sequence sequence);
+
+ /**
+ * Add the elements of fake_fasta_features to the feature table.
+ **/
+ private void addFakeFeatures()
+ {
+ final FeatureTable feature_table = getFeatureTable();
+
+ for(int i = 0 ; i < fake_fasta_features.size() ; ++i)
+ feature_table.add(fake_fasta_features.featureAt(i));
+ }
+
+ /**
+ * Remove the elements of fake_fasta_features from the feature table.
+ **/
+ private void removeFakeFeatures()
+ {
+ final FeatureTable feature_table = getFeatureTable();
+
+ for(int i = 0 ; i < fake_fasta_features.size() ; ++i)
+ feature_table.remove(fake_fasta_features.featureAt(i));
+ }
+
+ /**
+ * Add an object that will receive ReadEvents.
+ **/
+ private void addReadListener(final ReadListener listener)
+ {
+ listeners.addElement(listener);
+ }
+
+ /**
+ * Send the given event to all the listeners.
+ **/
+ private void fireEvent(final ReadEvent event)
+ {
+ for(int i = 0 ; i < listeners.size() ; ++i)
+ ((ReadListener)listeners.elementAt(i)).notify(event);
+ }
+
+ public void dispose()
+ {
+ for(int i=0; i<line_groups.size(); i++)
+ line_groups.removeElementAt(i);
+ line_groups = null;
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/SimpleDocumentFeature.java b/uk/ac/sanger/artemis/io/SimpleDocumentFeature.java
new file mode 100644
index 0000000..3c8f514
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/SimpleDocumentFeature.java
@@ -0,0 +1,534 @@
+/* SimpleDocumentFeature.java
+ *
+ * created: Thu Feb 17 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/SimpleDocumentFeature.java,v 1.4 2009-06-10 09:47:40 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+/**
+ * SimpleDocumentFeature class
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: SimpleDocumentFeature.java,v 1.4 2009-06-10 09:47:40 tjc Exp $
+ **/
+
+abstract class SimpleDocumentFeature extends LineGroup
+ implements Feature {
+ /**
+ * Create a new DocumentFeature with the given DocumentEntry as it's
+ * owner. The Entry can be null if this Feature doesn't have an owner
+ * (yet).
+ **/
+ public SimpleDocumentFeature (final DocumentEntry entry) {
+ this.entry = entry;
+ }
+
+ /**
+ * Return the Entry object that contains this Feature.
+ **/
+ public Entry getEntry () {
+ return entry;
+ }
+
+ /**
+ * If the entry is not null set its dirty flag.
+ **/
+ protected void setDirtyFlag () {
+ if (getEntry () != null) {
+ getDocumentEntry ().setDirtyFlag ();
+ }
+ }
+
+
+ /**
+ * Return the DocumentEntry that contains this DocumentFeature. A
+ * DocumentFeature will always be owned by a DocumentEntry.
+ **/
+ public DocumentEntry getDocumentEntry () {
+ return entry;
+ }
+
+ /**
+ * Returns true if and only if this Feature can't be changed or can't be
+ * removed from it's entry.
+ **/
+ public boolean isReadOnly () {
+ if (getEntry () != null && getEntry ().isReadOnly ()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Set the owning Entry of this Feature. Other objects should call
+ * setDocumentEntry () to change the owner.
+ * @param entry The Entry that now owns this Feature.
+ **/
+ private void setEntry (final DocumentEntry entry) {
+ this.entry = entry;
+ }
+
+ /**
+ * Set the owning DocumentEntry of this Feature.
+ * @param entry The Entry that now owns this Feature.
+ **/
+ protected void setDocumentEntry (final DocumentEntry entry)
+ throws ReadOnlyException {
+ setEntry (entry);
+ }
+
+ /**
+ * This is incremented each time a constructor is called.
+ **/
+ private static long id_counter = 0;
+
+ /**
+ * Set the value of this object.
+ * @param key The new feature key - can be null if location or qualifiers
+ * is not null.
+ * @param location The Location object for the new feature - can be null if
+ * key or qualifiers is not null.
+ * @param qualifiers The qualifiers for the new feature - can be null if
+ * key or location is not null.
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public void set (final Key key,
+ final Location location,
+ final QualifierVector qualifiers)
+ throws EntryInformationException, OutOfRangeException,
+ ReadOnlyException {
+
+ if (getEntry () != null && getEntry ().isReadOnly ()) {
+ throw new ReadOnlyException ();
+ }
+
+ final Key key_to_test;
+
+ if (key == null) {
+ key_to_test = this.key;
+ } else {
+ key_to_test = key;
+ }
+
+ final QualifierVector qualifiers_to_test;
+
+ if (qualifiers == null) {
+ qualifiers_to_test = this.qualifiers;
+ } else {
+ qualifiers_to_test = qualifiers;
+ }
+
+ // save the Entry because the call to remove () will set it to null
+ final Entry saved_entry = getEntry ();
+
+ try {
+ if (saved_entry != null) {
+ // remove and then add the Feature because changing the Key may
+ // change the position of the Feature in the FeatureTable eg. changing
+ // CDS to CDS_motif can move the Feature because CDS is always sorted
+ // before CDS_motif if they have the same start base
+ saved_entry.remove (this);
+ }
+
+ if (key != null) {
+ this.key = key;
+ }
+ if (qualifiers != null) {
+ this.qualifiers = qualifiers;
+ }
+ if (location != null) {
+ setLocation (location);
+ }
+
+ setDirtyFlag ();
+
+ } finally {
+ if (saved_entry != null) {
+ try {
+ saved_entry.add (this);
+ } catch (EntryInformationException e) {
+ saved_entry.forcedAdd (this);
+
+ throw new Error ("internal error (feature key change may have " +
+ "been lost) - unexpected exception: " + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the value of this object.
+ * @param entry The Entry that now owns this Feature.
+ * @param key The new feature key
+ * @param location The Location object for the new feature
+ * @param qualifiers The qualifiers for the new feature
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ private void set (final DocumentEntry entry,
+ final Key key,
+ final Location location,
+ final QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException {
+ setDocumentEntry (entry);
+ setKey (key);
+
+ // throw away the cache
+ first_base = -1;
+ last_base = -1;
+ this.location = location;
+
+ setQualifiers (qualifiers);
+
+ setDirtyFlag ();
+ }
+
+ /**
+ * Set the key field of this object.
+ * @param key The new feature key
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public void setKey (final Key key)
+ throws EntryInformationException, ReadOnlyException {
+ if (getEntry () != null && getEntry ().isReadOnly ()) {
+ throw new ReadOnlyException ();
+ }
+
+ for (int i = 0 ; i < qualifiers.size () ; ++i) {
+ final String this_name = ((Qualifier)qualifiers.elementAt(i)).getName();
+
+ if (!getEntryInformation ().isValidQualifier (key, this_name)) {
+ final String message =
+ key + " cannot have /" + this_name +
+ " as a qualifier";
+ throw new InvalidRelationException(message, key,
+ (Qualifier)qualifiers.elementAt(i));
+ }
+ }
+
+ // save the Entry because the call to remove () will set it to null
+ final Entry saved_entry = getEntry ();
+
+ try {
+ if (saved_entry != null) {
+ // remove and then add the Feature because changing the Key may
+ // change the position of the Feature in the FeatureTable eg. changing
+ // CDS to CDS_motif can move the Feature because CDS is always sorted
+ // before CDS_motif if they have the same start base
+ saved_entry.remove (this);
+ }
+
+ this.key = key;
+
+ setDirtyFlag ();
+
+ } finally {
+ if (saved_entry != null) {
+ try {
+ saved_entry.add (this);
+ } catch (EntryInformationException e) {
+ saved_entry.forcedAdd (this);
+
+ throw new Error ("internal error (feature key change may have " +
+ "been lost) - unexpected exception: " + e);
+ }
+ }
+ }
+ }
+
+ public void setLocation (final Location location)
+ throws ReadOnlyException, OutOfRangeException {
+ setLocation(location, null);
+ }
+
+ /**
+ * Set the location of this object.
+ * @param location The Location object for the new feature
+ **/
+ public void setLocation (final Location location, Entry saved_entry)
+ throws ReadOnlyException, OutOfRangeException {
+
+ if (getEntry () != null && getEntry ().isReadOnly ()) {
+ throw new ReadOnlyException ();
+ }
+
+ // save the Entry because the call to remove () will set it to null
+ if(saved_entry == null)
+ saved_entry = getEntry ();
+
+ try {
+ if (saved_entry != null) {
+ // remove and then add the Feature because changing the Location may
+ // change the position of the Feature in the FeatureTable
+ saved_entry.remove (this);
+ }
+
+ this.location = location;
+
+ setDirtyFlag ();
+
+ // throw away the cache
+ first_base = -1;
+ last_base = -1;
+
+ } finally {
+
+ if (saved_entry != null) {
+ try {
+ saved_entry.add (this);
+ } catch (EntryInformationException e) {
+ saved_entry.forcedAdd (this);
+
+ first_base = -1;
+ last_base = -1;
+
+ throw new Error ("internal error (some feature qualifiers may " +
+ "have been lost) - unexpected exception: " + e);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Set the qualifiers of this object.
+ * @param qualifiers The new qualifiers for the feature. null means remove
+ * all qualifiers.
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public void setQualifiers (final QualifierVector qualifiers)
+ throws EntryInformationException, ReadOnlyException {
+
+ if (getEntry () != null && getEntry ().isReadOnly ()) {
+ throw new ReadOnlyException ();
+ }
+
+ if (qualifiers == null) {
+ this.qualifiers = new QualifierVector ();
+ } else {
+ for (int i = 0 ; i < qualifiers.size () ; ++i) {
+
+ final String this_name = ((Qualifier)qualifiers.elementAt(i)).getName();
+
+ if (!getEntryInformation ().isValidQualifier (getKey (), this_name)) {
+ final String message =
+ getKey () + " cannot have /" + this_name + " as a qualifier";
+ throw new InvalidRelationException(message, getKey(),
+ (Qualifier)qualifiers.elementAt(i));
+ }
+ }
+ this.qualifiers = qualifiers.copy ();
+ }
+
+ setDirtyFlag ();
+ }
+
+ /**
+ * Add the given Qualifier to this Feature. If this Feature contains a
+ * Qualifier with the same name as the new Qualifier it will be replaced.
+ * @param qualifier The new qualifier to add.
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public void setQualifier (final Qualifier qualifier)
+ throws EntryInformationException, ReadOnlyException {
+ if (getEntry () != null && getEntry ().isReadOnly ()) {
+ throw new ReadOnlyException ();
+ }
+
+ getQualifiers ().setQualifier (qualifier);
+
+ setDirtyFlag ();
+ }
+
+ /**
+ * Remove the Qualifier with the given name. If there is no Qualifier with
+ * that name then return immediately.
+ * @exception EntryInformationException Thrown if a required qualifier is
+ * removed.
+ * @param name The qualifier name to look for.
+ **/
+ public void removeQualifierByName (final String name)
+ throws EntryInformationException, ReadOnlyException {
+ if (getEntry () != null && getEntry ().isReadOnly ()) {
+ throw new ReadOnlyException ();
+ }
+
+ getQualifiers ().removeQualifierByName (name);
+
+ setDirtyFlag ();
+ }
+
+ /**
+ * Add the values from the given qualifier to the Qualifier object with the
+ * same name in this Feature or otherwise add a copy of the argument.
+ * @param qualifier This object contains name and values to add.
+ * @return The Qualifier that was changed or created.
+ * @exception EntryInformationException Thrown if this Feature cannot
+ * contain the given Key, Qualifier or Key/Qualifier combination.
+ **/
+ public Qualifier addQualifierValues (final Qualifier qualifier)
+ throws EntryInformationException, ReadOnlyException {
+ if (getEntry () != null && getEntry ().isReadOnly ()) {
+ throw new ReadOnlyException ();
+ }
+
+ setDirtyFlag ();
+
+ return getQualifiers ().addQualifierValues (qualifier);
+ }
+
+ /**
+ * Return the unique identifier of this PublicDBStreamFeature.
+ **/
+ public long getNumericID () {
+ return id;
+ }
+
+ /**
+ * Return the Key of this Feature, as passed to the constructor.
+ **/
+ public Key getKey () {
+ return key;
+ }
+
+ /**
+ * Return the Location of this Feature, as passed to the constructor.
+ **/
+ public Location getLocation () {
+ return location;
+ }
+
+ /**
+ * Return the first base of this feature.
+ **/
+ public int getFirstBase () {
+ if (first_base == -1) {
+ first_base = getLocation ().getFirstBase ();
+ }
+ return first_base;
+ }
+
+ /**
+ * Return the last base of this feature.
+ **/
+ public int getLastBase () {
+ if (last_base == -1) {
+ last_base = getLocation ().getLastBase ();
+ }
+ return last_base;
+ }
+
+ /**
+ * Return a vector containing the qualifiers for this feature. This method
+ * does not return a copy of the qualifier vector so changing the vector
+ * will change the feature.
+ **/
+ public QualifierVector getQualifiers () {
+ return qualifiers;
+ }
+
+ /**
+ * Return the Qualifier in this Feature with the given name or null if
+ * there no such Qualifier.
+ **/
+ public Qualifier getQualifierByName (String name) {
+ return getQualifiers ().getQualifierByName (name);
+ }
+
+
+ /**
+ * Return the EntryInformation object from the Entry of this feature (if
+ * there is an Entry) or SimpleEntryInformation.default_entry_information
+ * otherwise.
+ **/
+ public EntryInformation getEntryInformation () {
+ if (getEntry () == null) {
+ return SimpleEntryInformation.getDefaultEntryInformation ();
+ } else {
+ return getEntry ().getEntryInformation ();
+ }
+ }
+
+ /**
+ * Return a String containing this Feature written in its native format.
+ **/
+// public String toString () {
+// final StringWriter string_writer = new StringWriter ();
+
+// try {
+// writeToStream (string_writer);
+// } catch (IOException e) {
+// throw new Error ("internal error - unexpected exception: " + e);
+// }
+
+// return string_writer.toString ();
+// }
+
+ /**
+ * Return the reference of a new copy of this Feature.
+ **/
+ public abstract Feature copy ();
+
+ /**
+ * The DocumentEntry object that contains this Feature as passed to the
+ * constructor.
+ **/
+ private DocumentEntry entry;
+
+ /**
+ * The is the key that was passed to the constructor
+ **/
+ private Key key;
+
+ /**
+ * The is the Location object that was passed to the constructor
+ **/
+ private Location location;
+
+ /**
+ * The is the vector of qualifiers that was passed to the constructor
+ **/
+ private QualifierVector qualifiers = new QualifierVector ();
+
+ /**
+ * The first base of the location of this feature. This is a cache.
+ **/
+ private int first_base = -1;
+
+ /**
+ * The last base of the location of this feature. This is a cache.
+ **/
+ private int last_base = -1;
+
+ /**
+ * A unique identifier for this feature.
+ **/
+ private final long id = id_counter++;
+}
diff --git a/uk/ac/sanger/artemis/io/SimpleEntryInformation.java b/uk/ac/sanger/artemis/io/SimpleEntryInformation.java
new file mode 100644
index 0000000..81cdcb0
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/SimpleEntryInformation.java
@@ -0,0 +1,511 @@
+/* SimpleEntryInformation.java
+ *
+ * created: Wed Feb 9 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/SimpleEntryInformation.java,v 1.5 2008-06-26 09:38:54 tjc Exp $
+ */
+
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+
+/**
+ * A SimpleEntryInformation is an EntryInformation object allows any key or
+ * qualifier.
+ *
+ * @author Kim Rutherford
+ * @version $Id: SimpleEntryInformation.java,v 1.5 2008-06-26 09:38:54 tjc Exp $
+ **/
+
+public class SimpleEntryInformation
+ implements EntryInformation {
+
+ /**
+ * Create a new SimpleEntryInformation object.
+ **/
+ public SimpleEntryInformation () {
+
+ }
+
+ /**
+ * Create a new SimpleEntryInformation object which is a copy of the given
+ * object.
+ **/
+ public SimpleEntryInformation (final EntryInformation new_info) {
+ if (new_info.getAllQualifierInfo () != null) {
+ qualifier_info_hash = new_info.getAllQualifierInfo ();
+ }
+ if (new_info.getValidKeys () != null) {
+ valid_keys = new_info.getValidKeys ();
+ }
+ if (new_info.getUserKeys () != null) {
+ user_keys = new_info.getUserKeys ().copy ();
+ }
+
+ use_embl_format = new_info.useEMBLFormat ();
+ }
+
+ /**
+ * Add a new feature key to the list of keys returned by getValidKeys()
+ * and getSortedValidKeys(). Any key added with this method can have any
+ * qualifier.
+ **/
+ public void addKey (final Key key) {
+ if (user_keys != null && user_keys.contains (key)) {
+ return;
+ }
+
+ if (user_keys == null) {
+ user_keys = new KeyVector ();
+ }
+
+ user_keys.add (key);
+ }
+
+ /**
+ * Add a QualifierInfo object to this EntryInformation object. This will
+ * change the return values of getSortedValidKeys() and
+ * getValidKeys() if the new QualifierInfo object refers to a new key.
+ * @exception QualifierInfoException Thrown if a QualifierInfo object with
+ * the same name, but conflicting types has already been added to this
+ * EntryInformation object.
+ **/
+ public void addQualifierInfo (final QualifierInfo qualifier_info)
+ throws QualifierInfoException {
+
+ if (qualifier_info_hash == null) {
+ qualifier_info_hash = new QualifierInfoHash ();
+ }
+
+ final QualifierInfo current_qualifier_info =
+ getQualifierInfo (qualifier_info.getName ());
+
+ if (current_qualifier_info != null) {
+ if (qualifier_info.getType () != qualifier_info.UNKNOWN &&
+ current_qualifier_info.getType () != qualifier_info.getType ()) {
+ final String message =
+ "qualifier " + qualifier_info.getName () + " used with " +
+ "conflicting types";
+ throw new QualifierInfoException (message);
+ }
+
+ if (qualifier_info.getValidKeys () == null ||
+ qualifier_info.getRequiredKeys () == null) {
+
+ qualifier_info_hash.put (qualifier_info);
+ }
+ } else {
+ qualifier_info_hash.put (qualifier_info);
+ }
+
+ final KeyVector qualifier_valid_keys = qualifier_info.getValidKeys ();
+
+ if (qualifier_valid_keys != null) {
+ for (int i = 0 ; i < qualifier_valid_keys.size () ; ++i) {
+ final Key this_key = (Key)qualifier_valid_keys.get(i);
+
+ if (valid_keys != null && valid_keys.contains (this_key)) {
+ continue;
+ }
+
+ if (valid_keys == null) {
+ valid_keys = new KeyVector ();
+ }
+
+ valid_keys.add (this_key);
+ }
+ }
+ }
+
+ /**
+ * Return a vector containing the valid feature keys. The returned
+ * Vector is a copy.
+ * @return null if and only if all keys are valid.
+ **/
+ public KeyVector getValidKeys () {
+ if (valid_keys == null) {
+ if (user_keys == null) {
+ return null;
+ } else {
+ return user_keys.copy ();
+ }
+ } else {
+ final KeyVector return_keys = valid_keys.copy ();
+
+ if (user_keys != null) {
+ for (int i = 0 ; i < user_keys.size () ; ++i) {
+ if (!return_keys.contains ((Key)user_keys.get (i))) {
+ return_keys.add ((Key)user_keys.get (i));
+ }
+ }
+ }
+
+ return return_keys;
+ }
+ }
+
+ /**
+ * Return a alphanumerically sorted vector of the valid keys.
+ * @return null if and only if all keys are valid.
+ **/
+ public KeyVector getSortedValidKeys () {
+ final KeyVector return_vector = getValidKeys ();
+
+ if (return_vector == null) {
+ return null;
+ }
+
+ return_vector.sort ();
+ return return_vector;
+ }
+
+ /**
+ * Return the Key that should be used when a new (empty) feature is created.
+ **/
+ public Key getDefaultKey () {
+ Key misc_feature_key = new Key ("misc_feature");
+
+ if (isValidKey (misc_feature_key))
+ return misc_feature_key;
+
+ misc_feature_key = new Key ("region");
+ if (isValidKey (misc_feature_key))
+ return misc_feature_key;
+
+ return (Key)getValidKeys ().get (0);
+ }
+
+ /**
+ * Return a vector containing all valid feature qualifier names for
+ * features with the given key. The returned StringVector is a copy.
+ * @return null if and only if any Qualifier is legal.
+ **/
+ public StringVector getValidQualifierNames (final Key key) {
+ if (getQualifierInfoHash () == null) {
+ return null;
+ }
+
+ final StringVector all_names = getQualifierInfoHash ().names ();
+
+ final StringVector return_names = new StringVector ();
+
+ for (int i = 0 ; i < all_names.size () ; ++i) {
+ final QualifierInfo this_qualifier_info =
+ getQualifierInfoHash ().get ((String)all_names.elementAt (i));
+
+ if (this_qualifier_info.isValidFor (key)) {
+ return_names.add (this_qualifier_info.getName ());
+ }
+ }
+
+ if (return_names.size () == 0) {
+ return null;
+ } else {
+ return_names.sort ();
+ return return_names;
+ }
+ }
+
+ /**
+ * Return a vector containing the names of the required feature qualifiers
+ * for the given key.
+ * @return null if and only if no Qualifier is necessary.
+ **/
+ public StringVector getRequiredQualifiers (final Key key) {
+ if (getQualifierInfoHash () == null) {
+ return null;
+ }
+
+ final StringVector all_names = getQualifierInfoHash ().names ();
+
+ final StringVector return_names = new StringVector ();
+
+ for (int i = 0 ; i < all_names.size () ; ++i) {
+ final QualifierInfo this_qualifier_info =
+ getQualifierInfoHash ().get ((String)all_names.elementAt (i));
+
+ if (this_qualifier_info.isRequiredFor (key)) {
+ return_names.add (this_qualifier_info.getName ());
+ }
+ }
+
+ if (return_names.size () == 0) {
+ return null;
+ } else {
+ return_names.sort ();
+ return return_names;
+ }
+ }
+
+ /**
+ * Return true if and only if the given String contains a valid
+ * qualifier name.
+ **/
+ public boolean isValidQualifier (final String name) {
+ if (getQualifierInfoHash () == null) {
+ return true;
+ }
+
+ if (getQualifierInfoHash ().get (name) == null) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Return true if and only if given qualifier_name is a legal qualifier
+ * name for the given key.
+ **/
+ public boolean isValidQualifier (final Key key,
+ final String qualifier_name) {
+ if (getUserKeys () != null && getUserKeys ().contains (key)) {
+ // any qualifier is valid for a user key
+ return true;
+ }
+
+ if (getQualifierInfoHash () == null) {
+ return true;
+ }
+
+ final QualifierInfo qualifier_info =
+ getQualifierInfoHash ().get (qualifier_name);
+
+ if (qualifier_info == null) {
+ return false;
+ } else {
+ return qualifier_info.isValidFor (key);
+ }
+ }
+
+ /**
+ * Return true if and only if given qualifier_name is a legal qualifier
+ * name for the given key and must be present in a feature with that key.
+ **/
+ public boolean isRequiredQualifier (final Key key,
+ final String qualifier_name) {
+ if (getUserKeys () != null && getUserKeys ().contains (key)) {
+ // there are no required qualifiers for a user key
+ return false;
+ }
+
+ if (getQualifierInfoHash () == null) {
+ return false;
+ }
+
+ final QualifierInfo qualifier_info =
+ getQualifierInfoHash ().get (qualifier_name);
+
+ if (qualifier_info == null) {
+ return false;
+ } else {
+ return qualifier_info.isRequiredFor (key);
+ }
+ }
+
+ /**
+ * Return true if and only if the given key is a valid in this
+ * EntryInformation.
+ **/
+ public boolean isValidKey (final Key key) {
+ if (valid_keys == null) {
+ return true;
+ } else {
+ if (valid_keys.contains (key)) {
+ return true;
+ } else {
+ if (getUserKeys () != null && getUserKeys ().contains (key)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the QualifierInfo object for the given qualifier name, or null if
+ * there are no qualifiers with the given name associated with this
+ * EntryInformation object.
+ **/
+ public QualifierInfo getQualifierInfo (final String qualifier_name) {
+ if (getQualifierInfoHash () == null) {
+ return null;
+ } else {
+ return getQualifierInfoHash ().get (qualifier_name);
+ }
+ }
+
+ /**
+ * Return a default EntryInformation object. It will allow any key or
+ * qualifier.
+ **/
+ public static EntryInformation getDefaultEntryInformation () {
+ return new SimpleEntryInformation ();
+ }
+
+ /**
+ * Returns true if strict EMBL format will be used when writing. If true
+ * qualifiers will always wrap at 80 columns.
+ * EMBL format locations have the complement (if any) inside the join. eg:
+ * join(complement(1..100),complement(200..300)).
+ * The default format has the complement on the outside, which is much more
+ * readable. eg: complement(join(1..100,200..300))
+ **/
+ public boolean useEMBLFormat () {
+ return use_embl_format;
+ }
+
+ /**
+ * Set the use_embl_format flag (see useEMBLFormat ()). true means the
+ * strict EMBL format should be used when writing.
+ **/
+ public void setEMBLFormat (final boolean use_embl_format) {
+ this.use_embl_format = use_embl_format;
+ }
+
+ /**
+ * Return a Vector contains all the QualifierInfo objects that this
+ * EntryInformation knows about.
+ * @return null if and only if there are no QualifierInfo objects in this
+ * object.
+ **/
+ public QualifierInfoHash getAllQualifierInfo () {
+ if (getQualifierInfoHash () == null) {
+ return null;
+ } else {
+ return getQualifierInfoHash ().copy ();
+ }
+ }
+
+ /**
+ * Fix this EntryInformation so that the given exception won't happen
+ * again.
+ **/
+ public void fixException (final EntryInformationException exception) {
+ if (exception instanceof InvalidKeyException) {
+ final InvalidKeyException key_exception =
+ (InvalidKeyException) exception;
+
+ addKey (key_exception.getKey ());
+ }
+
+ if (exception instanceof InvalidRelationException) {
+ final InvalidRelationException relation_exception =
+ (InvalidRelationException) exception;
+
+ if (relation_exception.getQualifier () == null) {
+ final Key key_to_add = relation_exception.getKey ();
+
+ addKey (key_to_add);
+ } else {
+ final Qualifier qualifier = relation_exception.getQualifier ();
+
+ // change the QualifierInfo object in the EntryInformation so that it
+ // can handle this key/qualifier combination
+
+ final QualifierInfo qualifier_info =
+ getQualifierInfo (qualifier.getName ());
+
+ final QualifierInfo new_qualifier_info;
+
+ if (qualifier_info == null) {
+ new_qualifier_info =
+ new QualifierInfo (qualifier.getName (),
+ QualifierInfo.QUOTED_TEXT, null, null, false);
+ } else {
+ new_qualifier_info =
+ new QualifierInfo (qualifier_info.getName (),
+ qualifier_info.getType (), null, null, false);
+ }
+
+ try {
+ addQualifierInfo (new_qualifier_info);
+ } catch (QualifierInfoException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+
+ if (exception instanceof InvalidQualifierException) {
+ final String exception_qualifier_name =
+ ((InvalidQualifierException) exception).getQualifier ().getName ();
+
+ // add it again, but make it less restrictive
+ final QualifierInfo new_info =
+ new QualifierInfo (exception_qualifier_name, QualifierInfo.UNKNOWN,
+ null, null, false);
+
+ try {
+ addQualifierInfo (new_info);
+ } catch (QualifierInfoException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+ }
+
+ /**
+ * Returns the keys that where added with addKey ().
+ * @return null if and only if there are no user keys yet.
+ **/
+ public KeyVector getUserKeys () {
+ return user_keys;
+ }
+
+ /**
+ * Return qualifier_info_hash.
+ **/
+ private QualifierInfoHash getQualifierInfoHash () {
+ return qualifier_info_hash;
+ }
+
+ /**
+ * Returns valid_keys.
+ **/
+ private KeyVector getKeys () {
+ return valid_keys;
+ }
+
+ /**
+ * This is set by the constructor and addQualifierInfo(). null means
+ * any qualifier is valid
+ **/
+ private QualifierInfoHash qualifier_info_hash = null;
+
+ /**
+ * This is set by the constructor and addQualifierInfo(). null means any
+ * key is valid
+ **/
+ private KeyVector valid_keys = null;
+
+ /**
+ * This is set by the constructor and by addUserKey(). null means any
+ **/
+ private KeyVector user_keys = null;
+
+ /**
+ * true if the EMBL location format should be used when writing. (See
+ * useEMBLLocationFormat () ).
+ **/
+ private boolean use_embl_format = false;
+}
diff --git a/uk/ac/sanger/artemis/io/StreamFeature.java b/uk/ac/sanger/artemis/io/StreamFeature.java
new file mode 100644
index 0000000..98346fa
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/StreamFeature.java
@@ -0,0 +1,47 @@
+/* StreamFeature.java (formally ReaderFeature.java)
+ *
+ * created: Wed Dec 30 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/StreamFeature.java,v 1.1 2004-06-09 09:50:33 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This is an implementation of Feature that can read and write itself to a
+ * stream.
+ * @author Kim Rutherford
+ * @version $Id: StreamFeature.java,v 1.1 2004-06-09 09:50:33 tjc Exp $
+ **/
+
+public interface StreamFeature extends Feature {
+ /**
+ * Write this Feature to the given stream.
+ * @param writer The stream to write to.
+ * @exception IOException thrown if there is an io problem while writing
+ * the Feature.
+ **/
+ void writeToStream (final Writer writer)
+ throws IOException;
+}
diff --git a/uk/ac/sanger/artemis/io/StreamFeatureTable.java b/uk/ac/sanger/artemis/io/StreamFeatureTable.java
new file mode 100644
index 0000000..41ac070
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/StreamFeatureTable.java
@@ -0,0 +1,76 @@
+/* StreamFeatureTable.java
+ *
+ * created: Wed Sep 15 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.io.IOException;
+import java.io.Writer;
+
+
+/**
+ * This object contains all the features in an entry. This object can write
+ * itself to a stream.
+ *
+ * @author Kim Rutherford
+ */
+
+class StreamFeatureTable extends FeatureTable {
+ /**
+ * Create a new empty StreamFeatureTable object.
+ **/
+ StreamFeatureTable () {
+
+ }
+
+ /**
+ * Create a new StreamFeatureTable object from the given FeatureTable.
+ **/
+ StreamFeatureTable (final FeatureTable feature_table) {
+ final FeatureEnumeration feature_enum = feature_table.features ();
+
+ while (feature_enum.hasMoreFeatures ()) {
+ final Feature new_feature = feature_enum.nextFeature ();
+
+ add (new_feature);
+ }
+ }
+
+ /**
+ * Write this StreamFeatureTable to the given stream.
+ * @param writer The stream to write to.
+ **/
+ public void writeToStream (final Writer writer)
+ throws IOException {
+
+ final FeatureEnumeration feature_enum = features ();
+
+ while (feature_enum.hasMoreFeatures ()) {
+ final StreamFeature this_feature =
+ (StreamFeature) feature_enum.nextFeature ();
+
+ this_feature.writeToStream (writer);
+
+ Thread.yield ();
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/StreamQualifier.java b/uk/ac/sanger/artemis/io/StreamQualifier.java
new file mode 100644
index 0000000..5cb78f2
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/StreamQualifier.java
@@ -0,0 +1,474 @@
+/* StreamQualifier.java
+ *
+ * created: Wed Dec 30 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/StreamQualifier.java,v 1.3 2008-11-07 17:54:26 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+/**
+ * This class contains routines for reading and writing Qualifiers.
+ *
+ * @author Kim Rutherford
+ * @version $Id: StreamQualifier.java,v 1.3 2008-11-07 17:54:26 tjc Exp $
+ **/
+
+public // XXX
+
+class StreamQualifier {
+ /**
+ * Create a new Qualifier object by unquoting the value part of the
+ * Qualifier and the calling the Qualifier constructor. This object
+ * consists of a name and a value. In the raw embl file we have
+ * /name=value.
+ * @param name The name of this qualifier (ie. the text immediately after
+ * the / in the qualifier)
+ * @param value The value of this qualifier (ie the text immediately after
+ * the = in the qualifier). This argument may be null if the qualifier
+ * has no value. Unlike the Qualifier constructor the value String
+ * should include the quote characters (),"" or [] if the original
+ * qualifier contains them. For example if the original qualifier was
+ * /citation=[3] then the value String should be: [3].
+ * @exception QualifierParseException Thrown if the value String is
+ * incorrectly quoted.
+ **/
+ public static Qualifier
+ makeStreamQualifier (final String name,
+ final String value,
+ final EntryInformation entry_information)
+ throws QualifierParseException {
+
+ if (!entry_information.isValidQualifier (name)) {
+ // use this qualifier value to decide how qualifiers with this name
+ // should be quoted
+ final QualifierInfo new_qualifier_info;
+ if (value.startsWith ("\"")) {
+ new_qualifier_info =
+ new QualifierInfo (name, QualifierInfo.QUOTED_TEXT,
+ null, null, false);
+ } else {
+ new_qualifier_info =
+ new QualifierInfo (name, QualifierInfo.TEXT, null, null, false);
+ }
+
+ try {
+ entry_information.addQualifierInfo (new_qualifier_info);
+ } catch (QualifierInfoException e) {
+ // this shouldn't happen because we have just checked that there is no
+ // qualifier with this name
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ return new Qualifier (name, unquote (value));
+ }
+
+ /**
+ * Return a String version of the given Qualifier.
+ * @param qualifier_info Used to determine how to quote the qualifiers
+ **/
+ public static String toString (final QualifierInfo qualifier_info,
+ final Qualifier qualifier) {
+ final StringVector values = qualifier.getValues ();
+
+ if (values == null) {
+ return '/' + qualifier.getName ();
+ } else {
+ // the number we pick for the initial StringBuffer size is not critical,
+ // but should cover most possibilities
+ final StringBuffer buffer = new StringBuffer (50);
+
+ for (int i = 0 ; i < values.size () ; ++i) {
+ buffer.append ('/');
+ buffer.append (qualifier.getName ());
+ if (values.elementAt (i) != null) {
+ buffer.append ('=');
+ buffer.append (quotedValue (qualifier_info,
+ qualifier.getName (),
+ (String)values.elementAt (i)));
+ }
+ }
+
+ return buffer.toString ();
+ }
+ }
+
+ /**
+ * Return a StringVector containing one String (of the form /name=value)
+ * for each of the values of the given qualifier.
+ * @param qualifier_info Used to determine how to quote the qualifiers
+ **/
+ public static StringVector
+ toStringVector (final QualifierInfo qualifier_info,
+ final Qualifier qualifier) {
+ final StringVector values = qualifier.getValues ();
+
+ final StringVector return_vector = new StringVector ();
+
+ if (values == null) {
+ return_vector.add ('/' + qualifier.getName ());
+ } else {
+ for (int i = 0 ; i < values.size () ; ++i) {
+ // the number we pick for the initial StringBuffer size is not
+ // critical
+ final StringBuffer buffer = new StringBuffer (50);
+
+ buffer.append ('/');
+ buffer.append (qualifier.getName ());
+ if (values.elementAt (i) != null) {
+ buffer.append ('=');
+ buffer.append (quotedValue (qualifier_info,
+ qualifier.getName (),
+ (String)values.elementAt (i)));
+ }
+ return_vector.add (buffer.toString ());
+ }
+ }
+
+ return return_vector;
+ }
+
+ /**
+ * This is used by readFromStream () as temporary storage. It is a class
+ * member rather than a local variable so that we don't need to allocate a
+ * object for each call. The number we pick for the initial StringBuffer
+ * size is not critical, but should cover most possibilities to prevent
+ * reallocation.
+ **/
+ final private static StringBuffer read_name_string_buffer =
+ new StringBuffer (20);
+
+ /**
+ * Read a qualifier name from a stream.
+ * @param buffered_reader the stream to read from
+ * @return the qualifier name if successful, otherwise null
+ */
+ static String readName (final BufferedReader buffered_reader)
+ throws QualifierParseException, IOException {
+
+ int current_char;
+
+ while ((current_char = buffered_reader.read ()) != -1 &&
+ 0 != current_char // Kaffe 1.00 returns 0 at end of string
+ ) {
+ if (' ' == current_char ||
+ '\n' == current_char ||
+ '\r' == current_char ||
+ '\t' == current_char) {
+ // read a whitespace character so go back to the top of the loop
+ continue;
+ } else {
+ if ('/' == current_char) {
+ // we have found the start of the qualifier name
+ break;
+ } else {
+ // if the character isn't a / or space then something is wrong
+ throw new QualifierParseException ("failed to read a qualifier " +
+ "name from this string: " +
+ (char)current_char +
+ buffered_reader.readLine ());
+ }
+ }
+ }
+
+ if (-1 == current_char ||
+ 0 == current_char // Kaffe 1.00 returns 0 at end of string
+ ) {
+ // end of file
+ return null;
+ }
+
+ buffered_reader.mark (1);
+
+ read_name_string_buffer.setLength (0);
+
+ while ((current_char = buffered_reader.read ()) != -1) {
+ if (Character.isLetter ((char) current_char) ||
+ Character.isDigit ((char) current_char) ||
+ '_' == current_char ||
+ '+' == current_char) {
+
+ read_name_string_buffer.append ((char) current_char);
+
+ // save the new position and go around the loop again
+ buffered_reader.mark (1);
+ continue;
+ } else {
+ // we have read one character too many
+ buffered_reader.reset ();
+ break;
+ }
+ }
+
+ final String return_string = read_name_string_buffer.toString ();
+
+ if (return_string.length () == 0) {
+ throw new QualifierParseException ("zero length qualifier name read " +
+ "from this string: " +
+ buffered_reader.readLine ());
+ } else {
+ return return_string;
+ }
+ }
+
+ /**
+ * This is used by readFromStream () as temporary storage. It is a class
+ * member rather than a local variable so that we don't need to allocate a
+ * object for each call. The number we pick for the initial array
+ * size is not critical, but should cover most possibilities to prevent
+ * reallocation.
+ **/
+ private static char [] read_value_buffer = new char [5000];
+
+ /**
+ * The index into read_value_buffer - used by readValue () to keep track of
+ * where to put the next character.
+ **/
+ private static int buffer_index = 0;
+
+
+ /**
+ * Append the given char to read_value_buffer (at the position
+ * buffer_index), reallocating the buffer if necessary
+ **/
+ private static void appendToValueBuffer (final char new_char) {
+ if (buffer_index >= read_value_buffer.length) {
+ // reallocate as the buffer is full
+
+ final char [] temp_buffer = new char [read_value_buffer.length*2];
+
+ System.arraycopy (read_value_buffer, 0,
+ temp_buffer, 0,
+ read_value_buffer.length);
+ read_value_buffer = temp_buffer;
+ }
+
+ read_value_buffer [buffer_index++] = (char) new_char;
+ }
+
+ /**
+ * Read a qualifier value from a stream.
+ * @param buffered_reader the stream to read from
+ * @return the qualifier value if successful, otherwise null
+ * @exception QualifierParseException Thrown if the format of the
+ * value String is not appropriate for a Qualifier with the given name or
+ * if the qualifier can't be read.
+ * Each qualifier has a specific format for the value part which depends
+ * on the name, for example the value part of /codon_start qualifier must
+ * be a number: 1, 2 or 3.
+ */
+ static synchronized String readValue (final BufferedReader buffered_reader)
+ throws QualifierParseException, IOException {
+
+ buffer_index = 0;
+
+ buffered_reader.mark (1);
+
+ int current_char = buffered_reader.read ();
+
+ if (-1 == current_char) {
+ return "";
+ }
+
+ // this is the character the marks the end of the value string. the
+ // default value of 0 means a '/' should end the string.
+ char final_char = 0;
+
+ // this will be set to ", [ or ( if the value starts with one of those
+ // characters
+ char start_char = 0;
+
+ // this is is used to balance the round or square brackets. it is
+ // incremented each time an open bracket is seen (after the first one) and
+ // decremented each time a close bracket is seen (except for the last).
+ //
+ int bracket_count = 0;
+
+ if ('"' == current_char) {
+ final_char = '"';
+ }
+ if ('[' == current_char) {
+ final_char = ']';
+ start_char = '[';
+ ++bracket_count;
+ }
+ if ('(' == current_char) {
+ final_char = ')';
+ start_char = '(';
+ ++bracket_count;
+ }
+
+ if (0 == final_char) {
+ // the character we read isn't one of the delimiter characters so put it
+ // back
+ buffered_reader.reset ();
+ } else {
+ // append the char now so that loop doesn't stop immediately in the '"'
+ // case
+ appendToValueBuffer ((char) current_char);
+ }
+
+ buffered_reader.mark (1);
+
+ while ((current_char = buffered_reader.read ()) != -1) {
+
+ // change newlines and other control characters to spaces
+ if (Character.isISOControl ((char)current_char) &&
+ current_char != '\t') {
+ current_char = ' ';
+ }
+
+ if (current_char != '"') {
+ if (current_char == start_char) {
+ ++bracket_count;
+ } else {
+ if (current_char == final_char) {
+ --bracket_count;
+ }
+ }
+ }
+
+ if (current_char == final_char && bracket_count == 0) {
+
+ if (current_char == '"') {
+ // check for two quotes in a row
+
+ // since the current character is a quote we know we can change the
+ // mark
+
+ buffered_reader.mark (1);
+
+ final int next_char = buffered_reader.read ();
+
+ if (next_char == '"') {
+ // we have hit a quoted quote
+ appendToValueBuffer ('"');
+ appendToValueBuffer ('"');
+ continue;
+ } else {
+ // end of line or next qualifier
+
+ if (next_char != -1) {
+ buffered_reader.reset ();
+ }
+
+ appendToValueBuffer ('"');
+ break;
+ }
+ } else {
+ // end of value
+ appendToValueBuffer ((char) current_char);
+ break;
+ }
+ } else {
+
+ if (0 == final_char && '/' == current_char) {
+ // in this case '/' marks the end of the value. we need to push back
+ // the '/' so that reading the next qualifier will work
+ buffered_reader.reset ();
+ break;
+ } else {
+ appendToValueBuffer ((char) current_char);
+
+ // save the new position and go around the loop again
+ buffered_reader.mark (1);
+ continue;
+ }
+ }
+ }
+
+ if (bracket_count > 0) {
+ throw new QualifierParseException ("hit the end of line while looking " +
+ "for a \"" + final_char + "\"");
+
+ }
+
+ // move buffer_index back past any whitespace
+ while (buffer_index > 0 &&
+ Character.isWhitespace (read_value_buffer[buffer_index-1])) {
+ --buffer_index;
+ }
+
+ return new String (read_value_buffer, 0, buffer_index);
+ }
+
+ /**
+ * Return the value part of a Qualifier correctly quoted for insertion into
+ * a embl entry.
+ * @param qualifier_info The type of the qualifier that we will quote.
+ * @param name The name part of qualifier. The quote characters to check
+ * for depend on this name.
+ * @param value Quote this value.
+ **/
+ private static String quotedValue (final QualifierInfo qualifier_info,
+ final String name, final String value) {
+ if (qualifier_info != null &&
+ (qualifier_info.getType () == QualifierInfo.QUOTED_TEXT ||
+ qualifier_info.getType () == QualifierInfo.OPTIONAL_QUOTED_TEXT)) {
+ return '"' + value + '"';
+ } else {
+ if (value.indexOf ('/') != -1) {
+ // quote it anyway
+ return '"' + value + '"';
+ } else {
+ return value;
+ }
+ }
+ }
+
+ /**
+ * Return the value part of a qualifier with any quote characters removed.
+ * @param name The name part of qualifier. The quote characters to check
+ * for depend on this name.
+ * @param value This is the value String to strip the quote characters
+ * from. This may be null if this qualifier has no value part (for
+ * example /partial).
+ * @return The unquoted version of the qualifier value or null if the value
+ * passed to unquote() is null.
+ * @exception QualifierParseException Thrown if the value String is
+ * incorrectly quoted for a qualifier with the given name. For example
+ * there is a quote at one end of the value and not the other.
+ **/
+ private static String unquote (final String value)
+ throws QualifierParseException {
+ if (value.length () >= 2) {
+ final char first_char = value.charAt (0);
+ final char last_char = value.charAt (value.length () - 1);
+
+ if (first_char == '"' && last_char == '"') {
+ return value.substring (1, value.length () - 1);
+ }
+ if (first_char != '"' && last_char != '"') {
+ return value;
+ }
+
+ throw new QualifierParseException ("unbalanced quotes: " + value);
+ } else {
+ return value;
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/StreamSequence.java b/uk/ac/sanger/artemis/io/StreamSequence.java
new file mode 100644
index 0000000..0fdeae7
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/StreamSequence.java
@@ -0,0 +1,469 @@
+/* StreamSequence.java (formally ReaderSequence.java)
+ *
+ * created: Wed Dec 30 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998-2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/StreamSequence.java,v 1.15 2008-12-11 15:43:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * This is an implementation of Sequence that can read and write itself to a
+ * stream.
+ *
+ * Sequence stored in 4 bit chunks.
+ *
+ * @author Kim Rutherford
+ * @version $Id: StreamSequence.java,v 1.15 2008-12-11 15:43:31 tjc Exp $
+ **/
+
+public abstract class StreamSequence
+ extends LineGroup implements Sequence
+{
+
+ /**
+ * Return a new StreamSequence object that is a copy of this one.
+ **/
+ abstract public StreamSequence copy();
+
+ /**
+ * Return the sequence type (one of EMBL_FORMAT, RAW_FORMAT, FASTA_FORMAT,
+ * etc.)
+ **/
+ abstract public int getFormatType();
+
+ /**
+ * Contains the sequence data for this object. It will contain the bases
+ * of the sequence with no spaces after the Feature constructor finishes.
+ **/
+ private byte[] sequencePacked;
+
+ /** Count of the a bases in the sequence. */
+ private int a_count = 0;
+
+ /** Count of the c bases in the sequence. */
+ private int c_count = 0;
+
+ /** Count of the g bases in the sequence. */
+ private int g_count = 0;
+
+ /** Count of the t bases in the sequence. */
+ private int t_count = 0;
+
+ /** char array for returning sequence chunks */
+ private char[] dst = null;
+
+ private int sequence_length;
+ private char bases[];
+
+ /**
+ * Return a the given range of bases as a String. Returns an empty
+ * sequence if the end position is less than the start position.
+ * @param start The start base of the range.
+ * @param end The end base of the range.
+ **/
+ public String getSubSequence(int start, int end)
+ {
+ if(end < start) // empty range
+ return "";
+ else
+ {
+ final char[] c = getCharSubSequence(start,end);
+ if(end > length())
+ end = length();
+
+ return new String(c,0,end-start+1);
+ }
+ }
+
+ public void forceReset()
+ {
+ dst = null;
+ }
+
+ public char[] getCharSubSequence(int start, int end)
+ {
+ char[] this_dst = null;
+ if(end-start > 1000)
+ this_dst = dst;
+
+ int dst_length = 0;
+ if(this_dst != null)
+ dst_length = this_dst.length;
+
+ if(this_dst == null || dst_length < end-start+1 || end >= length())
+ {
+ if(end-start > 1000)
+ {
+ dst = new char[end-start+1];
+ this_dst = dst;
+ }
+ else
+ this_dst = new char[end-start+1];
+
+ dst_length = this_dst.length;
+// dst = new char[end-start+3];
+// System.out.println("REALLOCATE "+ this_dst.length);
+ }
+
+// int packStart = Math.round( (float)start/2.f ) - 1;
+ int packStart = (start - 1) >> 1;
+ int packEnd = (int) Math.round( (double)end/2.d ); // end/2;
+
+ int count = 0;
+ byte currStorageUnit;
+ int index1;
+ int index2;
+
+ // skip first four bits
+ if(start % 2 == 0)
+ {
+ currStorageUnit = sequencePacked[packStart];
+ index1 = (int)(currStorageUnit & 0x000F);
+ this_dst[count] = bases[index1];
+
+ packStart++;
+ packEnd++;
+ count++;
+ }
+
+ for(int i=packStart; i <= packEnd && count < dst_length && i < sequencePacked.length; i++)
+ {
+ currStorageUnit = sequencePacked[i];
+ index1 = (int)(currStorageUnit & 0x000F);
+ index2 = (int)( (currStorageUnit >> 4) & 0x000F);
+
+ try
+ {
+ this_dst[count] = bases[index2];
+ }
+ catch(ArrayIndexOutOfBoundsException oob)
+ {
+// System.out.println("start "+start+" end "+end+" packStart "+packStart+" packEnd "+packEnd);
+// System.out.println("count "+count+" this_dst.length "+this_dst.length+" index2 "+index2);
+// oob.printStackTrace();
+ }
+ count++;
+
+ if(count < dst_length)
+ {
+ this_dst[count] = bases[index1];
+ }
+
+ count++;
+ }
+
+ return this_dst;
+ }
+
+ public char[] getCharSequence()
+ {
+ char dst[] = new char[length()];
+ int packEnd = (int)Math.round( (double)length()/2.d );
+ int count = 0;
+ byte currStorageUnit = 0;
+
+ for(int i=0; i < packEnd; i++)
+ {
+ currStorageUnit = sequencePacked[i];
+ int index1 = (int)(currStorageUnit & 0x000F);
+ currStorageUnit = (byte) (currStorageUnit>>4);
+ int index2 = (int)(currStorageUnit & 0x000F);
+
+ dst[count] = bases[index2];
+ count++;
+
+ if(count < dst.length)
+ dst[count] = bases[index1];
+
+// System.out.print(Packing.unpack(index2));
+// if(count < dst.length)
+// System.out.print(Packing.unpack(index1));
+
+ count++;
+ }
+// System.out.println("\n"+currStorageUnit);
+
+ return dst;
+ }
+
+ public char charAt(final int i)
+ {
+ final int packStart = (i-1) >> 1;
+ final byte currStorageUnit = sequencePacked[packStart];
+ final int index;
+
+ if(i % 2 == 0)
+ index = (int)(currStorageUnit & 0x000F);
+ else
+ index = (int)( (currStorageUnit >> 4) & 0x000F);
+
+ return bases[index];
+ }
+
+ public void setFromChar(final char dna[])
+ {
+ sequence_length = dna.length;
+ int numBytes = (int)Math.round( (double)sequence_length/2.d );
+
+ sequencePacked = new byte[numBytes];
+ setFromChar(dna, 0, 0, sequence_length);
+ setCounts(dna);
+ }
+
+ /**
+ *
+ * Set this sequence to hold the bases in the given byte array.
+ *
+ **/
+ private void setFromChar(final char dna[], int offset,
+ final int bit_shift,
+ final int new_sequence_length)
+ {
+ int offsetSize = offset >> 1;
+ int bytePointer = offset >> 1;
+ int symbolPointer = 0;
+ int numBytes = (int)Math.round( (double)dna.length/2.d );
+
+ // filled last unit if = 0
+ int filledLastUnit = dna.length & 0x0001;
+ byte currByte;
+ if(bit_shift != 0)
+ {
+// System.out.println("Bit Shifty..."+offset);
+ currByte = sequencePacked[sequence_length>>1];
+ currByte = (byte)(currByte | Packing.pack(dna[symbolPointer]));
+ sequencePacked[bytePointer] = currByte;
+ bytePointer++;
+ symbolPointer++;
+ if(filledLastUnit == 0)
+ offsetSize += 1;
+ }
+ else
+ currByte = 0;
+
+ for(int i=bytePointer; i<numBytes+offsetSize; i++)
+ {
+ // each byte consists of 4 bit nibbles
+ // process each separately
+ for(int j=0; j < 2; j++)
+ {
+ currByte = (byte)(currByte<<4 | Packing.pack(dna[symbolPointer]));
+
+ symbolPointer++;
+
+ if(j == 0 && symbolPointer == dna.length)
+ {
+ currByte = (byte)(currByte<<4);
+ break;
+ }
+ }
+ sequencePacked[bytePointer] = currByte;
+// System.out.println(" "+symbolPointer+" bytePointer " + bytePointer + " to " +
+// Integer.toString((int) currByte, 2) + " decimal " +
+// Integer.toString((int) currByte, 10) + " hex " +
+// Integer.toString((int) currByte, 16));
+ bytePointer++;
+ currByte = 0;
+ }
+
+ if(bases == null)
+ bases = Packing.bases;
+
+ }
+
+ protected void appendChar(final char dna[])
+ {
+ int newlength = sequence_length + dna.length;
+ int numBytes = (int)Math.round( (double)newlength/2.d );
+ if(numBytes > capacity())
+ expandCapacity(numBytes);
+
+// System.out.println("capacity "+capacity()+" numBytes "+numBytes);
+ // filled last unit if = 0
+ int filledLastUnit = sequence_length & 0x0001;
+
+ setFromChar(dna, sequence_length, filledLastUnit, newlength);
+ sequence_length = newlength;
+ }
+
+ protected void setSequencePackingCapacity(final int n)
+ {
+ int numBytes = Math.round( n/2.f );
+ sequencePacked = new byte[numBytes];
+ }
+
+
+ /**
+ * This implements the expansion semantics of ensureCapacity but is
+ * unsynchronized for use internally by methods which are already
+ * synchronized.
+ *
+ * @see java.lang.StringBuffer#ensureCapacity(int)
+ */
+ private void expandCapacity(int minimumCapacity)
+ {
+ int newCapacity = (sequencePacked.length + 1) * 2;
+ if(newCapacity < 0)
+ newCapacity = Integer.MAX_VALUE;
+ else if(minimumCapacity > newCapacity)
+ newCapacity = minimumCapacity;
+
+// System.out.println("EXPANDING.... "+newCapacity);
+ byte newValue[] = new byte[newCapacity];
+ System.arraycopy(sequencePacked, 0, newValue, 0, sequencePacked.length);
+ sequencePacked = newValue;
+ }
+
+ /**
+ * Returns the current capacity of the String buffer. The capacity
+ * is the amount of storage available for newly inserted
+ * characters; beyond which an allocation will occur.
+ *
+ * @return the current capacity of this string buffer.
+ */
+ private synchronized int capacity()
+ {
+ return sequencePacked.length;
+ }
+
+
+ /**
+ * Write this Sequence to the given stream.
+ * @param writer The stream to write to.
+ **/
+ public abstract void writeToStream(final Writer writer)
+ throws IOException;
+
+ /**
+ * Returns the length of the sequence in bases.
+ **/
+ public int length()
+ {
+ return sequence_length;
+ }
+
+ /**
+ * Return the count of c bases in the whole sequence.
+ **/
+ public int getCCount()
+ {
+ return c_count;
+ }
+
+ /**
+ * Return the count of g bases in the whole sequence.
+ **/
+ public int getGCount()
+ {
+ return g_count;
+ }
+
+ /**
+ * Return the count of a bases in the whole sequence.
+ **/
+ public int getACount()
+ {
+ return a_count;
+ }
+
+ /**
+ * Return the count of t bases in the whole sequence.
+ **/
+ public int getTCount()
+ {
+ return t_count;
+ }
+
+ /**
+ * Return the count of non-g,c,t,a bases in the whole sequence.
+ **/
+ public int getOtherCount()
+ {
+ return
+ length() - (getCCount() + getACount() + getTCount() + getGCount());
+ }
+
+
+ /**
+ * Set the a_count, c_count, t_count and g_count variables.
+ **/
+ protected void setCounts()
+ {
+ final int len = length();
+ final int packEnd = (int)Math.round( (double)len/2.d );
+ int count = 0;
+ byte currStorageUnit;
+ int index1;
+ int index2;
+
+ for(int i=0; i < packEnd; i++)
+ {
+ currStorageUnit = sequencePacked[i];
+ index1 = currStorageUnit & 0x000F;
+ index2 = (currStorageUnit>>4) & 0x000F;
+
+ counter(bases[index2]);
+ count++;
+
+ if(count < len)
+ counter(bases[index1]);
+
+ count++;
+ }
+ }
+
+
+ /**
+ * Set the a_count, c_count, t_count and g_count variables.
+ **/
+ private void setCounts(char[] sequence_chars)
+ {
+ a_count = c_count = t_count = g_count = 0;
+
+ for(int i = 0 ; i < length(); ++i)
+ counter(sequence_chars[i]);
+ }
+
+ private void counter(char c)
+ {
+ switch(c)
+ {
+ case 'a':
+ ++a_count;
+ break;
+ case 'c':
+ ++c_count;
+ break;
+ case 'g':
+ ++g_count;
+ break;
+ case 't':
+ ++t_count;
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/io/StreamSequenceFactory.java b/uk/ac/sanger/artemis/io/StreamSequenceFactory.java
new file mode 100644
index 0000000..21529ca
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/StreamSequenceFactory.java
@@ -0,0 +1,117 @@
+/* StreamSequenceFactory.java
+ *
+ * created: Mon Jun 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/StreamSequenceFactory.java,v 1.1 2004-06-09 09:50:38 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+
+import java.io.IOException;
+
+/**
+ * This class contains the method makeStreamSequence (), which reads a
+ * StreamSequence object from a LinePushBackReader object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: StreamSequenceFactory.java,v 1.1 2004-06-09 09:50:38 tjc Exp $
+ **/
+
+abstract public class StreamSequenceFactory
+{
+
+ /** The tag used for sequence that is in EMBL format. */
+ final public static int EMBL_FORMAT = 1;
+
+ /** The tag used for sequence that is in GENBANK format. */
+ final public static int GENBANK_FORMAT = 2;
+
+ /** The tag use for sequence that is in raw format. */
+ final public static int RAW_FORMAT = 3;
+
+ /** The tag use for sequence that is in FASTA or similar format. */
+ final public static int FASTA_FORMAT = 4;
+
+ final public static int INDEXED_FASTA_FORMAT = 5;
+
+ /**
+ * Read a StreamSequence object from a LinePushBackReader object.
+ **/
+ public static StreamSequence makeStreamSequence(final LinePushBackReader
+ in_stream, Entry entry)
+ throws IOException
+ {
+ final int sequence_type = getSequenceType(in_stream);
+
+ switch (sequence_type)
+ {
+ case EMBL_FORMAT:
+ return new EmblStreamSequence(in_stream);
+ case FASTA_FORMAT:
+ {
+ if(IndexFastaStream.isIndexed(entry))
+ {
+ IndexFastaStream ifs = new IndexFastaStream(entry);
+ if(ifs.useIndex())
+ return ifs;
+ }
+ return new FastaStreamSequence(in_stream);
+ }
+ case GENBANK_FORMAT:
+ return new GenbankStreamSequence(in_stream);
+ case RAW_FORMAT:
+ default:
+ return new RawStreamSequence(in_stream);
+ }
+ }
+
+ /**
+ * Return the sequence type that can be read from the given
+ * LinePushBackReader. ie EMBL_FORMAT, FASTA_FORMAT, etc.
+ **/
+ public static int getSequenceType(LinePushBackReader in_stream)
+ throws IOException
+ {
+ final String seq_header_line = in_stream.readLine ();
+
+ in_stream.pushBack (seq_header_line);
+
+ if(seq_header_line.startsWith ("SQ "))
+ return EMBL_FORMAT;
+ else
+ {
+ if(seq_header_line.startsWith (">"))
+ return FASTA_FORMAT;
+ else
+ {
+ if (seq_header_line.startsWith ("BASE COUNT") ||
+ seq_header_line.startsWith ("ORIGIN"))
+ return GENBANK_FORMAT;
+ else
+ return RAW_FORMAT;
+ }
+ }
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/io/UI.java b/uk/ac/sanger/artemis/io/UI.java
new file mode 100644
index 0000000..0d61783
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/UI.java
@@ -0,0 +1,161 @@
+package uk.ac.sanger.artemis.io;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+import java.io.IOException;
+
+/*
+ * A class mediating between the IO package classes and the user. Determines
+ * how the user should be prompted (if at all) during the execution of file
+ * export tasks. It will pop-up windows in swing mode, and will write to
+ * the console in other modes. It will await user interaction in console and
+ * swing modes, but will press on in script mode.
+ */
+public class UI {
+
+ /*
+ * Describes 3 possible file export run states.
+ */
+ public enum UIMode {
+ SWING, // the traditional GUI application, or running from inside a console with X11 graphics
+ CONSOLE, // running from a text-only console, able to enter responses into the command-line
+ SCRIPT // running in a completely automated manner, not stopping for errors, useful for cron-jobs
+ }
+
+ public static UIMode mode = UIMode.SWING;
+
+ public static void initalise()
+ {
+ String uimodeProperty = System.getProperty("uimode");
+ if (uimodeProperty != null)
+ {
+ uimodeProperty = uimodeProperty.toUpperCase();
+ for (UIMode mode : UIMode.values())
+ {
+ if (mode.toString().equals(uimodeProperty))
+ {
+ UI.mode = mode;
+ }
+ }
+ }
+ }
+
+ private static String input()
+ {
+ InputStreamReader reader = new InputStreamReader (System.in);
+ BufferedReader buf_reader = new BufferedReader (reader);
+ String result = "";
+ try {
+ result = buf_reader.readLine().trim(); // read the number as a string
+ }
+ catch (IOException ioe) {
+ System.out.println ("IO exception = " + ioe);
+ }
+ return result.trim();
+ }
+
+ public static String userInput( final String prompt, final boolean password )
+ {
+ if (password == true)
+ {
+ UIEraserThread et = new UIEraserThread(prompt + ": ");
+ Thread mask = new Thread(et);
+ mask.start();
+ String result = input();
+ et.stopMasking();
+ mask.stop();
+ // System.out.println("Password: '" + result + "'");
+ return result;
+ }
+ System.out.print(prompt + ": " );
+ return input();
+ }
+
+ public static boolean booleanUserInput(final String label, final String message)
+ {
+ if (mode == UIMode.SCRIPT)
+ {
+ System.out.println(label + " : " + message + " (in script mode so continuing...)");
+ return true;
+ }
+
+ else if (mode == UIMode.SWING)
+ {
+ final JPanel msgPanel = new JPanel(new BorderLayout());
+ msgPanel.add(new JLabel(label), BorderLayout.NORTH);
+ JTextArea msgError = new JTextArea(message);
+ msgError.setLineWrap(true);
+ msgError.setEditable(false);
+ JScrollPane scollMsg = new JScrollPane(msgError);
+ msgPanel.add(scollMsg, BorderLayout.CENTER);
+
+ int val = JOptionPane.OK_OPTION;
+ //if(System.getProperty("noprompt") == null)
+ val = JOptionPane.showConfirmDialog(null, msgPanel,
+ "Keys/Qualifier", JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+
+ return (val == JOptionPane.OK_OPTION) ? true : false;
+
+ }
+
+ return boolConsoleInput(label + "\n" + message + "(y/n)");
+ }
+
+ private static boolean boolConsoleInput(String prompt)
+ {
+ System.out.print(prompt + ": " );
+ InputStreamReader reader = new InputStreamReader (System.in);
+ BufferedReader buf_reader = new BufferedReader (reader);
+ String result = "";
+ try {
+ result = buf_reader.readLine().trim(); // read the number as a string
+ }
+ catch (IOException ioe) {
+ System.out.println ("IO exception = " + ioe);
+ }
+ // System.out.println("Entered '" + result + "'");
+ if (result.equals("y"))
+ return true;
+ if (result.equals("n"))
+ return false;
+ System.out.println("Please answer y or n");
+ return boolConsoleInput(prompt);
+ }
+
+ public static void info(String message, String heading)
+ {
+ message("INFO", message, heading);
+ }
+
+ public static void warn(String message, String heading)
+ {
+ message("WARN", message, heading);
+ }
+
+ public static void error(String message, String heading)
+ {
+ message("ERROR", message, heading);
+ }
+
+ public static void message(String messageType, String message, String heading)
+ {
+ if ((mode == UIMode.SCRIPT) || (mode == UIMode.CONSOLE))
+ {
+ System.out.println(messageType + " :: " + heading + " :: " + message);
+ }
+ else if (mode == UIMode.SWING)
+ {
+ JOptionPane.showMessageDialog(null, message,heading, JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/io/UIEraserThread.java b/uk/ac/sanger/artemis/io/UIEraserThread.java
new file mode 100644
index 0000000..2398cb0
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/UIEraserThread.java
@@ -0,0 +1,34 @@
+package uk.ac.sanger.artemis.io;
+
+class UIEraserThread implements Runnable {
+ private boolean stop;
+
+ /**
+ *@param The prompt displayed to the user
+ */
+ public UIEraserThread(String prompt) {
+ System.out.print(prompt);
+ }
+
+ /**
+ * Begin masking...display asterisks (*)
+ */
+ public void run () {
+ stop = true;
+ while (stop) {
+ System.out.print("\010 ");
+ try {
+ Thread.currentThread().sleep(1);
+ } catch(InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Instruct the thread to stop masking
+ */
+ public void stopMasking() {
+ this.stop = false;
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/io/UpperInteger.java b/uk/ac/sanger/artemis/io/UpperInteger.java
new file mode 100644
index 0000000..13b419c
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/UpperInteger.java
@@ -0,0 +1,66 @@
+/* UpperInteger.java
+ *
+ * created: Mon May 3 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/UpperInteger.java,v 1.1 2004-06-09 09:50:39 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.io;
+
+/**
+ * This class is used to represent a upper end of a range like this 100..>200
+ * @author Kim Rutherford
+ * @version $Id: UpperInteger.java,v 1.1 2004-06-09 09:50:39 tjc Exp $
+ **/
+
+public class UpperInteger {
+ /**
+ * Create a new UpperInteger from the given int.
+ **/
+ protected UpperInteger (final Integer position) {
+ this.position = position.intValue ();
+ }
+
+ /**
+ * Create a new UpperInteger from the given Integer.
+ **/
+ protected UpperInteger (final int position) {
+ this.position = position;
+ }
+
+ /**
+ * Return the position that was passed to the constructor.
+ **/
+ protected int getPosition () {
+ return position;
+ }
+
+ /**
+ * Return a String representing this. It will look something like
+ * this: ">100"
+ **/
+ public String toString () {
+ return ">" + getPosition ();
+ }
+
+ /** position that was passed to the constructor */
+ private int position;
+}
diff --git a/uk/ac/sanger/artemis/io/ValidateFeature.java b/uk/ac/sanger/artemis/io/ValidateFeature.java
new file mode 100644
index 0000000..29c84b8
--- /dev/null
+++ b/uk/ac/sanger/artemis/io/ValidateFeature.java
@@ -0,0 +1,1106 @@
+/* ValidateFeature
+ *
+ * created: 2013
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2013 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.io;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.JOptionPane;
+
+import org.apache.log4j.Level;
+
+import uk.ac.sanger.artemis.EntryGroup;
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.FeatureKeyPredicate;
+import uk.ac.sanger.artemis.FeatureKeyQualifierPredicate;
+import uk.ac.sanger.artemis.FeaturePredicate;
+import uk.ac.sanger.artemis.FeaturePredicateConjunction;
+import uk.ac.sanger.artemis.FilteredEntryGroup;
+import uk.ac.sanger.artemis.GotoEventSource;
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.Selection;
+import uk.ac.sanger.artemis.SimpleEntryGroup;
+import uk.ac.sanger.artemis.components.BasePlotGroup;
+import uk.ac.sanger.artemis.components.EntryFileDialog;
+import uk.ac.sanger.artemis.components.FeatureListFrame;
+import uk.ac.sanger.artemis.components.FileViewer;
+import uk.ac.sanger.artemis.components.database.DatabaseEntrySource;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.genebuilder.cv.GoBox;
+import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
+import uk.ac.sanger.artemis.sequence.Marker;
+import uk.ac.sanger.artemis.util.DatabaseDocument;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.DocumentFactory;
+import uk.ac.sanger.artemis.util.StringVector;
+
+public class ValidateFeature
+{
+ private static String[] geneModelParts;
+
+ //##sequence-region seqid start end
+ private static Pattern HEADER_SEQ_REGION = Pattern.compile("##sequence-region \\S+ \\d+ \\d+");
+ private static Pattern CAPITAL_START = Pattern.compile("^[A-Z]+\\S*");
+ private static Pattern ID_PREFIX = Pattern.compile("^[^.:]+");
+
+ private static String[] RESERVED_TAGS =
+ { "ID", "Name", "Alias", "Parent",
+ "Target", "Gap", "Derives_from", "Note",
+ "Dbxref", "Ontology_term", "Is_circular",
+ "Start_range", "End_range"};
+
+ private static String[] OTHER_RESERVED_TAGS =
+ { "GO", "EC_number", "EMBL_qualifier", "SignalP_prediction",
+ "GPI_anchor_cleavage_site", "GPI_anchored", "PlasmoAP_score" };
+
+ private static String[] ALLOWED_TAGS_WITH_NO_VALUE =
+ { "stop_codon_redefined_as_selenocysteine", "Name" };
+
+ private EntryGroup entryGrp;
+ private FeaturePredicate cds_predicate;
+
+ public ValidateFeature(EntryGroup entryGrp)
+ {
+ this.entryGrp = entryGrp;
+ this.cds_predicate = null;
+ }
+
+ public static void testHeader(final String headerTxt)
+ {
+ final BufferedReader reader = new BufferedReader(
+ new StringReader(headerTxt));
+ try
+ {
+ String str;
+ while ((str = reader.readLine()) != null)
+ {
+ if(str.startsWith("##sequence-region "))
+ {
+ if(!HEADER_SEQ_REGION.matcher(str).matches())
+ {
+ System.out.println("ERROR : HEADER "+str);
+ }
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+
+ public void featureListErrors(final EntryGroup grp,
+ final Selection sel,
+ final GotoEventSource gotoSrc,
+ final BasePlotGroup plotGrp)
+ {
+ if(GeneUtils.isGFFEntry( grp ))
+ {
+ showFeatureList(ATTR_PREDICATE, "Attributes", grp, sel, gotoSrc, plotGrp);
+ if(!GeneUtils.isDatabaseEntry(grp))
+ showFeatureList(CDS_PHASE_PREDICATE, "CDS phase", grp, sel, gotoSrc, plotGrp);
+
+ showFeatureList(STRAND_PREDICATE, "Gene Strand Errors", grp, sel, gotoSrc, plotGrp);
+ showFeatureList(BOUNDARY_PREDICATE, "Gene Boundary Errors", grp, sel, gotoSrc, plotGrp);
+ showFeatureList(COMPLETE_GENE_MODEL_PREDICATE, "Incomplete Gene Model", grp, sel, gotoSrc, plotGrp);
+ showFeatureList(PARTIAL_PREDICATE, "Check Partial Settings", grp, sel, gotoSrc, plotGrp);
+ showFeatureList(ID_PREDICATE, "Check ID Settings", grp, sel, gotoSrc, plotGrp);
+ }
+
+ showFeatureList(INTERNAL_STOP, "Internal Stop Codons", grp, sel, gotoSrc, plotGrp);
+ showFeatureList(NO_VALID_STOP, "No Valid Stop Codons", grp, sel, gotoSrc, plotGrp);
+ }
+
+ private void showFeatureList(final FeaturePredicate predicate,
+ final String title,
+ final EntryGroup grp,
+ final Selection sel,
+ final GotoEventSource gotoSrc,
+ final BasePlotGroup plotGrp)
+ {
+ final FilteredEntryGroup fltrGrp = new FilteredEntryGroup (grp, predicate, title);
+ if(fltrGrp.getAllFeaturesCount() < 1)
+ return;
+
+ final FeatureListFrame featureList =
+ new FeatureListFrame (title, sel, gotoSrc, fltrGrp, plotGrp);
+ featureList.setVisible(true);
+ }
+
+ protected static boolean isGFF(final uk.ac.sanger.artemis.io.Feature f, final EntryGroup entryGrp)
+ {
+ final boolean isGFF = entryGrp == null || GeneUtils.isGFFEntry( entryGrp );
+ if(isGFF && f instanceof GFFStreamFeature)
+ return true;
+ return false;
+ }
+
+ /**
+ * Check a single feature
+ * GFF - check complete gene model
+ * - check boundaries are valid
+ * - check all features are on the same strand
+ * - check CDS features have a phase
+ * - check partial attributes consistent
+ * - check attribute column
+ * - qualifiers have a value (not empty)
+ * - only reserved tags start with uppercase
+ * - CDS have no internal stop codon
+ * - CDS have valid stop codon
+ *
+ * @param f
+ * @param showOnlyFailures
+ * @return report
+ */
+ private LinkedHashMap<String, Level> featureValidate(final uk.ac.sanger.artemis.io.Feature f,
+ final boolean showOnlyFailures)
+ {
+ boolean pass = true;
+
+ final LinkedHashMap<String, Level> report = new LinkedHashMap<String, Level>();
+ final String fTxt = featureTxt(f);
+ if(isGFF(f, entryGrp))
+ {
+ final GFFStreamFeature gffFeature = (GFFStreamFeature)f;
+ report.put("\n"+GeneUtils.getUniqueName(gffFeature)+" ("+fTxt+"):", Level.INFO);
+
+ if(isPartOfGene(gffFeature))
+ {
+ int compl = isCompleteGeneModelOK(gffFeature);
+ switch (compl)
+ {
+ case 1: report.put("No gene found", Level.FATAL); pass = false; break;
+ case 2: report.put("Missing transcript", Level.FATAL); pass = false; break;
+ default: report.put("Gene model is complete", Level.INFO); break;
+ }
+
+ final int gb = isBoundaryOK(gffFeature);
+ if(gb == 0)
+ report.put(getGeneBoundaryMsg(gb), Level.INFO);
+ else
+ {
+ pass = false;
+ report.put(getGeneBoundaryMsg(gb), Level.FATAL);
+ }
+
+ if(isStrandOK(gffFeature))
+ report.put("Gene features all on same strand", Level.INFO);
+ else
+ {
+ pass = false;
+ report.put("Gene features found on different strand", Level.FATAL);
+ }
+
+ if(!isPartialConsistent(gffFeature, "Start_range") ||
+ !isPartialConsistent(gffFeature, "End_range"))
+ {
+ pass = false;
+ report.put("Partial settings not consistent", Level.FATAL);
+ }
+
+ if(!isIdPrefixConsistent(gffFeature))
+ {
+ pass = false;
+ report.put("Prefix of ID attribute not consistent within gene model", Level.FATAL);
+ }
+ }
+
+ if( (entryGrp == null || !GeneUtils.isDatabaseEntry(entryGrp)) && !isCDSPhaseOK(gffFeature))
+ {
+ pass = false;
+ report.put("CDS phase (codon_start) not set", Level.FATAL);
+ }
+
+ String attr = isAttributesOK(gffFeature);
+ if(!attr.equals(""))
+ {
+ pass = false;
+ if(attr.endsWith("\n"))
+ attr = attr.substring(0, attr.length()-1);
+ report.put(attr, Level.FATAL);
+ }
+ }
+ else
+ {
+ if(f.getUserData() == null)
+ return report;
+ report.put("\n"+((uk.ac.sanger.artemis.Feature)f.getUserData()).getIDString()+
+ " ("+fTxt+"):", Level.INFO);
+ }
+
+ if(isInternalStops(f))
+ {
+ pass = false;
+ report.put("Internal stop codon found", Level.FATAL);
+ }
+
+ final EntryInformation eInfo;
+ if(f.getEntry() == null)
+ eInfo = ((Feature)f.getUserData()).getEntry().getEntryInformation();
+ else
+ eInfo = f.getEntry().getEntryInformation();
+
+ // test GO qualifier
+ final String goErrMsg;
+ if((goErrMsg = validateGO(f.getQualifiers(), eInfo)).length() > 0)
+ {
+ pass = false;
+ report.put(goErrMsg, Level.FATAL);
+ }
+
+ if(!hasValidStop(f))
+ {
+ pass = false;
+ report.put("No valid stop codon found", Level.FATAL);
+ }
+
+ if(pass)
+ {
+ if(showOnlyFailures)
+ return null;
+ report.put("PASS", Level.INFO);
+ }
+ return report;
+ }
+
+ /**
+ * Check a single feature and append validation report to the
+ * viewer.
+ * @return true if successfully validate
+ */
+ public boolean featureValidate(final uk.ac.sanger.artemis.io.Feature f,
+ final FileViewer fv,
+ final boolean showFailedFeaturesOnly)
+ {
+ final LinkedHashMap<String, Level> report = featureValidate(f, showFailedFeaturesOnly);
+ boolean pass = false;
+ if (report != null)
+ {
+ for (Map.Entry<String, Level> entry : report.entrySet())
+ {
+ if (!showFailedFeaturesOnly && entry.getKey().equals("PASS"))
+ pass = true;
+ fv.appendString(entry.getKey() + "\n", entry.getValue());
+ }
+ }
+ else
+ return true;
+ return pass;
+ }
+
+ /**
+ * Check GO annotation for:
+ * 1) unexpected white space in with/from and dbxref columns
+ * 2) with/from must be empty when using IDA, NAS, ND, TAS or EXP evidence code
+ * 3) GO:0005515 can only have IPI evidence code
+ * 4) IEP is not allowed for molecular_function and cellular_component terms
+ * 5) with field must be filled when using ISS, ISA, ISO and ISM codes.
+ * @param qualifiers
+ * @param eInfo
+ * @return errors found in GO annotation
+ */
+ public static String validateGO(QualifierVector qualifiers, EntryInformation eInfo)
+ {
+ final StringBuilder buff = new StringBuilder();
+ final Qualifier q = qualifiers.getQualifierByName("GO");
+ if (q != null)
+ {
+ try
+ {
+ final QualifierInfo qI = eInfo.getQualifierInfo("GO");
+ final StringVector qualifierStrs = StreamQualifier.toStringVector(qI,q);
+ for (String qualifierStr: qualifierStrs)
+ {
+ final String code = getEvidenceCodeAbbreviation(qualifierStr);
+ final String goid = getField("GOid=", qualifierStr);
+ final String with = getField("with=", qualifierStr);
+ final String dbxref = getField("dbxref=", qualifierStr);
+
+ // System.err.println(GeneUtils.getUniqueName(f)+" "+code+" "+qualifierStr);
+ if (code != null)
+ {
+ if (code.equals("IDA") || code.equals("NAS") || code.equals("ND") ||
+ code.equals("TAS") || code.equals("EXP"))
+ {
+ // with/from must be empty
+ if (with.length() > 0)
+ buff.append("GOid=" + goid
+ + ", the with/from must be empty when using " + code + "\n");
+ }
+ else if (code.equals("ISS") || code.equals("ISA") ||
+ code.equals("ISO") || code.equals("ISM"))
+ {
+ // with field must be filled
+ if (with.length() == 0)
+ buff.append("GOid=" + goid +
+ ", the with/from field must be filled when using " + code + "\n");
+ }
+ }
+
+ // GO:0005515 can only have IPI evidence code
+ if (goid.equals("GO:0005515") && (code == null || !code.equals("IPI")))
+ buff.append("GOid=" + goid + ", can only have IPI evidence code\n");
+
+ // IEP is not allowed for molecular_function and cellular_component terms
+ if (code != null && code.equals("IEP")
+ && !getField("aspect=", qualifierStr).equals("P"))
+ buff.append("GOid=" + goid + ", IEP is restricted to Biological Process terms\n");
+
+ if (with.indexOf(" ") > -1 || dbxref.indexOf(" ") > -1)
+ {
+ buff.append("GOid=" + goid + ", ");
+ if (with.indexOf(" ") > -1)
+ buff.append("with/from field (" + with + ") contains white space ");
+ if (dbxref.indexOf(" ") > -1)
+ buff.append("dbxref field (" + dbxref + ") contains white space");
+ buff.append("\n");
+ }
+ }
+ } catch(Exception e){}
+ }
+
+ return buff.toString();
+ }
+
+ /**
+ * Extract the value of a field from a qualifier string
+ * @param fieldName
+ * @param qualifierString
+ * @return
+ */
+ private static String getField(final String fieldName, final String qualifierStr)
+ {
+ String field = "";
+ int ind1 = qualifierStr.toLowerCase().indexOf(fieldName.toLowerCase());
+ int ind2 = qualifierStr.indexOf(";", ind1);
+
+ int len = fieldName.length();
+ if(ind2 > ind1 && ind1 > -1)
+ field = qualifierStr.substring(ind1+len,ind2);
+ else if(ind1 > -1)
+ field = qualifierStr.substring(ind1+len);
+
+ if(field.endsWith("\""))
+ field = field.substring(0, field.length()-1);
+ if(field.startsWith("\""))
+ field = field.substring(1);
+ return field;
+ }
+
+ private static String getEvidenceCodeAbbreviation(String goText)
+ {
+ final String evidence = getField("evidence=", goText);
+ for(int i=0; i<GoBox.evidenceCodes[2].length; i++)
+ if(GoBox.evidenceCodes[2][i].equalsIgnoreCase(evidence))
+ return GoBox.evidenceCodes[0][i];
+
+ for(int i=0; i<GoBox.evidenceCodes[0].length; i++)
+ if(GoBox.evidenceCodes[0][i].equalsIgnoreCase(evidence))
+ return GoBox.evidenceCodes[0][i];
+ return null;
+ }
+
+ /**
+ * Check if the gene model is complete
+ * @param gffFeature
+ * @return 0 - complete gene model
+ * 1 - no gene found
+ * 2 - missing transcript
+ */
+ protected static int isCompleteGeneModelOK(final GFFStreamFeature gffFeature)
+ {
+ final ChadoCanonicalGene gene = gffFeature.getChadoGene();
+ if(gene == null)
+ return 1;
+
+ final List<uk.ac.sanger.artemis.io.Feature> transcripts = gene.getTranscripts();
+ if(transcripts.size() < 1)
+ return 2;
+
+ return 0;
+ }
+
+ protected static int isBoundaryOK(final GFFStreamFeature gffFeature)
+ {
+ final ChadoCanonicalGene gene = gffFeature.getChadoGene();
+ int gb = 0;
+ if(gene != null && isGene(gffFeature))
+ gb = GeneUtils.isBoundaryOK(gene);
+ return gb;
+ }
+
+ protected static boolean isStrandOK(final GFFStreamFeature gffFeature)
+ {
+ final ChadoCanonicalGene gene = gffFeature.getChadoGene();
+ if(gene != null && isGene(gffFeature) && !GeneUtils.isStrandOK(gene))
+ return false;
+ return true;
+ }
+
+ /**
+ * Test if the partial qualifiers are consistent within a gene model
+ * @param gffFeature
+ * @param partialKeyStr - either Start_range or End_range qualifier keys
+ * @return
+ */
+ protected static boolean isPartialConsistent(final GFFStreamFeature gffFeature,
+ final String partialKeyStr)
+ {
+ final ChadoCanonicalGene gene = gffFeature.getChadoGene();
+ if(gene == null)
+ return true;
+ try
+ {
+ // is the gene marked as partial
+ boolean partial =
+ (gene.getGene().getQualifierByName(partialKeyStr) == null ? false : true);
+
+ if(partial)
+ {
+ if(!isPartialChildren(partial, gene, gene.getGene(), partialKeyStr))
+ return false;
+ }
+ else
+ {
+ final List<uk.ac.sanger.artemis.io.Feature> transcripts = gene.getTranscripts();
+ for(uk.ac.sanger.artemis.io.Feature f: transcripts)
+ {
+ // is the transcript marked as partial
+ partial = (f.getQualifierByName(partialKeyStr) == null ? false : true);
+ if(!isPartialChildren(partial, gene, f, partialKeyStr))
+ return false;
+ }
+ }
+ }
+ catch (Exception e){ e.printStackTrace(); }
+ return true;
+ }
+
+ /**
+ * Return true if the child features agree with the parent
+ * feature for a given key
+ * @param parentIsPartial
+ * @param gene
+ * @param f
+ * @param partialKeyStr
+ * @return
+ * @throws InvalidRelationException
+ */
+ private static boolean isPartialChildren(
+ final boolean parentIsPartial,
+ final ChadoCanonicalGene gene,
+ final uk.ac.sanger.artemis.io.Feature f,
+ final String partialKeyStr) throws InvalidRelationException
+ {
+ final Set<uk.ac.sanger.artemis.io.Feature> children = gene.getChildren(f);
+ for(uk.ac.sanger.artemis.io.Feature child: children)
+ {
+ String keyStr = child.getKey().getKeyString();
+ boolean isComplement = f.getLocation().isComplement();
+ if(keyStr.equals("three_prime_UTR"))
+ {
+ if(isComplement)
+ {
+ if(partialKeyStr.equals("End_range"))
+ continue;
+ }
+ else
+ {
+ if(partialKeyStr.equals("Start_range"))
+ continue;
+ }
+ }
+ else if(keyStr.equals("five_prime_UTR"))
+ {
+ if(isComplement)
+ {
+ if(partialKeyStr.equals("Start_range"))
+ continue;
+ }
+ else
+ {
+ if(partialKeyStr.equals("End_range"))
+ continue;
+ }
+ }
+
+ boolean partial = (child.getQualifierByName(partialKeyStr) == null ? false : true);
+ if( ( partial && !parentIsPartial ) ||
+ ( !partial && parentIsPartial ) )
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Test if the ID GFF3 attribute prefix is consistent within a gene model
+ * @param gffFeature
+ * @return true if the prefix is the same within the gene model features
+ */
+ protected static boolean isIdPrefixConsistent(final GFFStreamFeature gffFeature)
+ {
+ final ChadoCanonicalGene gene = gffFeature.getChadoGene();
+ if(gene == null)
+ return true;
+
+ try
+ {
+ if(gffFeature.getKey().getKeyString().endsWith("gene"))
+ return (gene.getGene().getQualifierByName("ID") != null);
+
+ if(gene.getGene().getQualifierByName("ID") == null)
+ return true;
+ if(gffFeature.getQualifierByName("ID") == null)
+ return false;
+
+ String id = gene.getGene().getQualifierByName("ID").getValues().elementAt(0);
+ final Matcher m = ID_PREFIX.matcher(id);
+ if(m.matches())
+ {
+ id = gffFeature.getQualifierByName("ID").getValues().elementAt(0);
+ return id.startsWith( m.group() );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+ /**
+ * The phase is REQUIRED for all CDS features.
+ * @param gffFeature
+ */
+ public static boolean isCDSPhaseOK(final GFFStreamFeature gffFeature)
+ {
+ if(!gffFeature.getKey().getKeyString().equals("CDS"))
+ return true;
+
+ final Qualifier codonStart = gffFeature.getQualifierByName("codon_start");
+ if(codonStart == null)
+ return false;
+ return true;
+ }
+
+
+ /**
+ * Check attribute column
+ * @param gffFeature
+ */
+ protected static String isAttributesOK(final GFFStreamFeature gffFeature)
+ {
+ final StringBuilder str = new StringBuilder();
+ final QualifierVector qualifiers = gffFeature.getQualifiers();
+ for(Qualifier qualifier: qualifiers)
+ {
+ if(CAPITAL_START.matcher(qualifier.getName()).matches())
+ {
+ boolean found = false;
+ for(String r: RESERVED_TAGS)
+ if(qualifier.getName().equals(r))
+ found = true;
+
+ for(String r: OTHER_RESERVED_TAGS)
+ if(qualifier.getName().equals(r))
+ found = true;
+
+ if(!found)
+ {
+ String msg = qualifier.getName()+" non-reserved attribute name begins with uppercase\n";
+ str.append(msg);
+ }
+ }
+
+ // check format tag=value
+ final StringVector values = qualifier.getValues();
+ if( values == null || values.size() < 1 ||
+ ( values.size() == 1 && values.get(0).equals("")) )
+ {
+ boolean allowed = false;
+
+ if(GeneUtils.isDatabaseEntry(gffFeature))
+ {
+ for(String a: ALLOWED_TAGS_WITH_NO_VALUE)
+ if(qualifier.getName().equals(a))
+ allowed = true;
+ }
+
+ if(!allowed)
+ {
+ String msg = qualifier.getName()+" atribute has no value\n";
+ str.append(msg);
+ }
+ }
+ }
+ return str.toString();
+ }
+
+ protected boolean isInternalStops(final uk.ac.sanger.artemis.io.Feature feature)
+ {
+ if(feature.getUserData() == null)
+ return false;
+ final uk.ac.sanger.artemis.Feature f = (uk.ac.sanger.artemis.Feature) feature.getUserData();
+ final FeaturePredicate cds_predicate = getCodingFeaturePredicate();
+ if(!cds_predicate.testPredicate (f))
+ return false;
+
+ if(feature instanceof GFFStreamFeature && GFFUtils.isSelenocysteine(feature))
+ return false;
+
+ final AminoAcidSequence aa = f.getTranslation ();
+ return aa.containsStopCodon ();
+ }
+
+ public boolean hasValidStop(final uk.ac.sanger.artemis.io.Feature feature)
+ {
+ final FeaturePredicate cds_predicate = getCodingFeaturePredicate();
+ if(feature.getUserData() == null)
+ return true;
+ final uk.ac.sanger.artemis.Feature f = (uk.ac.sanger.artemis.Feature) feature.getUserData();
+ if(!cds_predicate.testPredicate (f))
+ return true;
+
+ return f.hasValidStopCodon (true);
+ }
+
+ private FeaturePredicate getCodingFeaturePredicate()
+ {
+ if(cds_predicate != null)
+ return cds_predicate;
+
+ if(entryGrp != null && GeneUtils.isGFFEntry( entryGrp ))
+ {
+
+ final FeaturePredicate codingPredicate = new FeaturePredicate(){
+ private String nonCodingTranscripts[] = GeneUtils.getNonCodingTranscripts();
+ public boolean testPredicate(Feature feature)
+ {
+ try
+ {
+ final GFFStreamFeature gffF = (GFFStreamFeature)feature.getEmblFeature();
+ final ChadoCanonicalGene chadoGene = gffF.getChadoGene();
+ if(chadoGene != null)
+ {
+ uk.ac.sanger.artemis.io.Feature transcriptF =
+ chadoGene.getTranscriptFeatureFromName(GeneUtils.getUniqueName(gffF));
+ final String transcriptKey = transcriptF.getKey().getKeyString();
+
+ for(int i=0; i<nonCodingTranscripts.length; i++)
+ if(nonCodingTranscripts[i].equals(transcriptKey))
+ return false;
+ }
+ }
+ catch(Exception e){}
+ return true;
+ };
+ };
+ cds_predicate =
+ new FeaturePredicateConjunction(
+ new FeatureKeyPredicate(new Key(DatabaseDocument.EXONMODEL)),
+ codingPredicate,
+ FeaturePredicateConjunction.AND);
+ }
+ else
+ cds_predicate =
+ new FeaturePredicateConjunction(
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
+ new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
+ FeaturePredicateConjunction.AND);
+
+ return cds_predicate;
+ }
+
+ private static String getGeneBoundaryMsg(int gb)
+ {
+ String msg = "Valid gene boundary";
+ switch(gb)
+ {
+ case 1: msg = "Transcript start or end is outside gene range";
+ break;
+ case 2: msg = "Child feature of a transcript is outside the transcript range";
+ break;
+ case 3: msg = "Span of the children features does not match start and end of the transcript";
+ break;
+ case 4: msg = "Protein range does not match CDS";
+ break;
+ case 5: msg = "Gene range does not match the largest transcript range";
+ break;
+ }
+ return msg;
+ }
+
+ protected static boolean isPartOfGene(final GFFStreamFeature gffFeature)
+ {
+ final String keyStr = gffFeature.getKey().getKeyString();
+ for(String part: getGeneModelParts())
+ if( part.equals(keyStr) )
+ return true;
+
+ return false;
+ }
+
+ private static boolean isGene(final GFFStreamFeature gffFeature)
+ {
+ final String keyStr = gffFeature.getKey().getKeyString();
+ return keyStr.equals("gene") || keyStr.equals("pseudogene");
+ }
+
+
+ private String featureTxt(final uk.ac.sanger.artemis.io.Feature f)
+ {
+ final Feature feature = (Feature) (f.getUserData());
+ if(feature == null)
+ return f.getLocation().toStringShort();
+ final Marker low_marker = feature.getFirstBaseMarker();
+ final Marker high_marker = feature.getLastBaseMarker();
+ String low_pos, high_pos;
+ if(low_marker == null || high_marker == null)
+ {
+ low_pos = "unknown";
+ high_pos = "unknown";
+ }
+ else
+ {
+ if(low_marker.getRawPosition() < high_marker.getRawPosition())
+ {
+ low_pos = String.valueOf(low_marker.getRawPosition());
+ high_pos = String.valueOf(high_marker.getRawPosition());
+ }
+ else
+ {
+ low_pos = String.valueOf(high_marker.getRawPosition());
+ high_pos = String.valueOf(low_marker.getRawPosition());
+ }
+ }
+
+ if(GeneUtils.isDatabaseEntry(entryGrp))
+ {
+ try
+ {
+ if(feature.getQualifierByName("Start_range") != null)
+ low_pos = "<"+low_pos;
+ if(feature.getQualifierByName("End_range") != null)
+ high_pos = ">"+high_pos;
+ }
+ catch (InvalidRelationException e){}
+ }
+ else
+ {
+ if(feature.getLocation().isPartial(true)) // 5prime
+ {
+ if(feature.isForwardFeature())
+ low_pos = "<"+low_pos;
+ else
+ high_pos = ">"+high_pos;
+ }
+ if(feature.getLocation().isPartial(false)) // 3prime
+ {
+ if(feature.isForwardFeature())
+ high_pos = ">"+high_pos;
+ else
+ low_pos = "<"+low_pos;
+ }
+ }
+
+ final StringBuilder txt = new StringBuilder();
+ txt.append(feature.getKey().getKeyString()).append(" ");
+ txt.append(low_pos).append("..").append(high_pos);
+
+ if(!feature.isForwardFeature())
+ txt.append(" c");
+ return txt.toString();
+ }
+
+
+ private static String[] getGeneModelParts()
+ {
+ if(geneModelParts != null)
+ return geneModelParts;
+
+ final String[] geneModelKeys = {
+ "gene", "transcript", "mRNA", "CDS", "exon", "polypeptide", "three_prime_UTR", "five_prime_UTR",
+ "pseudogene", "pseudogenic_transcript", "pseudogenic_exon" };
+ final String[] ncTranscripts = GeneUtils.getNonCodingTranscripts();
+ geneModelParts = new String[
+ geneModelKeys.length+ncTranscripts.length];
+
+ for(int i=0; i<geneModelKeys.length; i++)
+ geneModelParts[i] = geneModelKeys[i];
+
+ for(int i=0; i<ncTranscripts.length; i++)
+ geneModelParts[i+geneModelKeys.length] = ncTranscripts[i];
+
+ return geneModelParts;
+ }
+
+ /**
+ * Open a panel with the validation results
+ * @param entry
+ * @param seq
+ */
+ private void showReport(final Entry entry, final String seq)
+ {
+ final FeatureVector features = entry.getAllFeatures();
+ final FileViewer fv = new FileViewer("Validation Report :: "+seq+" "+
+ features.size()+" feature(s)", false, false, true);
+ int nfail = 0;
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ {
+ if(!featureValidate(f, fv, true))
+ nfail++;
+ }
+ fv.setTitle(fv.getTitle()+" Fails:"+nfail);
+ fv.setVisible(true);
+ }
+
+ /**
+ * Write the validation results to a file
+ * @param writer
+ * @param entry
+ * @param seq
+ */
+ private void writeReport(final BufferedWriter writer, final Entry entry, final String seq)
+ {
+ final FeatureVector features = entry.getAllFeatures();
+ try
+ {
+ for(uk.ac.sanger.artemis.io.Feature f: features)
+ {
+ LinkedHashMap<String, Level> report = featureValidate(f, true);
+ if (report != null)
+ {
+ for (Map.Entry<String, Level> ent : report.entrySet())
+ {
+ writer.append(ent.getKey());
+ writer.newLine();
+ }
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+
+ private static FeaturePredicate ATTR_PREDICATE = new FeaturePredicate()
+ {
+ public boolean testPredicate(uk.ac.sanger.artemis.Feature feature)
+ {
+ return isAttributesOK((GFFStreamFeature) feature.getEmblFeature()).length() > 0;
+ }
+ };
+
+ private static FeaturePredicate CDS_PHASE_PREDICATE = new FeaturePredicate()
+ {
+ public boolean testPredicate(uk.ac.sanger.artemis.Feature feature)
+ {
+ return !isCDSPhaseOK((GFFStreamFeature) feature.getEmblFeature());
+ }
+ };
+
+ private static FeaturePredicate STRAND_PREDICATE = new FeaturePredicate()
+ {
+ public boolean testPredicate(uk.ac.sanger.artemis.Feature feature)
+ {
+ return !isStrandOK((GFFStreamFeature) feature.getEmblFeature());
+ }
+ };
+
+ private static FeaturePredicate BOUNDARY_PREDICATE = new FeaturePredicate()
+ {
+ public boolean testPredicate(uk.ac.sanger.artemis.Feature feature)
+ {
+ return isBoundaryOK((GFFStreamFeature) feature.getEmblFeature()) > 0;
+ }
+ };
+
+ private static FeaturePredicate COMPLETE_GENE_MODEL_PREDICATE = new FeaturePredicate()
+ {
+ public boolean testPredicate(uk.ac.sanger.artemis.Feature feature)
+ {
+ if(!isPartOfGene((GFFStreamFeature) feature.getEmblFeature()))
+ return false;
+ return isCompleteGeneModelOK((GFFStreamFeature) feature.getEmblFeature()) > 0;
+ }
+ };
+
+ private static FeaturePredicate PARTIAL_PREDICATE = new FeaturePredicate()
+ {
+ public boolean testPredicate(uk.ac.sanger.artemis.Feature feature)
+ {
+ if( isPartialConsistent((GFFStreamFeature) feature.getEmblFeature(), "Start_range") &&
+ isPartialConsistent((GFFStreamFeature) feature.getEmblFeature(), "End_range") )
+ return false;
+ return true;
+ }
+ };
+
+ private static FeaturePredicate ID_PREDICATE = new FeaturePredicate()
+ {
+ public boolean testPredicate(uk.ac.sanger.artemis.Feature feature)
+ {
+ if( isIdPrefixConsistent((GFFStreamFeature) feature.getEmblFeature() ))
+ return false;
+ return true;
+ }
+ };
+
+ private FeaturePredicate INTERNAL_STOP = new FeaturePredicate()
+ {
+ public boolean testPredicate(uk.ac.sanger.artemis.Feature feature)
+ {
+ return isInternalStops(feature.getEmblFeature());
+ }
+ };
+
+
+ private FeaturePredicate NO_VALID_STOP = new FeaturePredicate()
+ {
+ public boolean testPredicate(uk.ac.sanger.artemis.Feature feature)
+ {
+ return !hasValidStop(feature.getEmblFeature());
+ }
+ };
+
+ public static void main(final String args[])
+ {
+ if( (args != null && args.length == 1 && args[0].startsWith("-h")) ||
+ (args == null || args.length < 1))
+ {
+ System.out.println("Artemis validation options:");
+ System.out.println("-h\tshow help");
+ System.out.println("-s\tspace separated list of sequences to read and write out");
+ System.out.println("-c\tthe URL for your Chado database e.g. db.genedb.org:5432/snapshot?genedb_ro");
+ System.out.println("-o\twrite report to specified file");
+ System.exit(0);
+ }
+
+ BufferedWriter outfile = null;
+ for(int i = 0; i < args.length; i++)
+ {
+ if (args[i].equalsIgnoreCase("-c"))
+ System.setProperty("chado", args[i + 1]);
+ else if (args[i].equalsIgnoreCase("-o"))
+ try
+ {
+ outfile = new BufferedWriter(new FileWriter(args[i + 1]));
+ }
+ catch (IOException e)
+ {
+ JOptionPane.showMessageDialog(null, e.getMessage());
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ final Vector<String> seqs = new Vector<String>();
+ for(int i = 0; i < args.length; i++)
+ {
+ if(args[i].toLowerCase().equals("-s"))
+ {
+ for(int j = i + 1; j < args.length; j++)
+ {
+ if(args[j].startsWith("-"))
+ break;
+ seqs.add(args[j]);
+ }
+ }
+ else if(args[i].startsWith("-"))
+ i++;
+ else
+ {
+ if(!seqs.contains(args[i]))
+ seqs.add(args[i]);
+ }
+ }
+
+ System.setProperty("black_belt_mode","true");
+ Options.getOptions();
+
+ if(System.getProperty("chado") != null)
+ {
+ DatabaseEntrySource entrySrc = null;
+ try
+ {
+ for(String seq: seqs)
+ {
+ System.out.println("VALIDATING... "+seq);
+ uk.ac.sanger.artemis.Entry entry = ReadAndWriteEntry.readEntryFromDatabase(seq, entrySrc);
+ entrySrc = ReadAndWriteEntry.getEntrySource();
+ final EntryGroup egrp = new SimpleEntryGroup();
+ egrp.add(entry);
+
+ ValidateFeature validate = new ValidateFeature(egrp);
+ if(outfile == null)
+ validate.showReport(entry.getEMBLEntry(), seq);
+ else
+ validate.writeReport(outfile, entry.getEMBLEntry(), seq);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ JOptionPane.showMessageDialog(null, e.getMessage());
+ }
+ }
+ else
+ {
+ for(String seq: seqs)
+ {
+ System.out.println("VALIDATING... "+seq);
+ final Document doc = DocumentFactory.makeDocument(seq);
+ final uk.ac.sanger.artemis.io.Entry entry = EntryFileDialog.getEntryFromFile(
+ null, doc, Options.getArtemisEntryInformation(), false);
+
+ ValidateFeature validate = new ValidateFeature(null);
+ if(outfile == null)
+ validate.showReport(entry, doc.getName());
+ else
+ validate.writeReport(outfile, entry, doc.getName());
+ }
+ }
+
+ if(outfile != null)
+ try
+ {
+ outfile.close();
+ }
+ catch (IOException e){}
+ }
+
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/j2ssh/FTProgress.java b/uk/ac/sanger/artemis/j2ssh/FTProgress.java
new file mode 100644
index 0000000..b44c702
--- /dev/null
+++ b/uk/ac/sanger/artemis/j2ssh/FTProgress.java
@@ -0,0 +1,71 @@
+/* FileTransferProgressMonitor.java
+ *
+ * created: Aug 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.j2ssh;
+
+import com.sshtools.j2ssh.FileTransferProgress;
+import javax.swing.JProgressBar;
+
+public class FTProgress implements FileTransferProgress
+{
+ private JProgressBar progress;
+ private long bytesTotal;
+ private String filename;
+
+ public FTProgress(JProgressBar progress, String filename)
+ {
+ this.progress = progress;
+ this.filename = filename;
+ }
+
+ public boolean isCancelled()
+ {
+ return false;
+ }
+
+ public void progressed(long bytesSoFar)
+ {
+ progress.setValue((new Long(bytesSoFar)).intValue());
+ int percent = (int)((bytesSoFar*100)/bytesTotal);
+ progress.setString(filename+" "+percent+" %");
+ }
+
+ public void started(final long bytesTotal, final String remoteFile)
+ {
+ this.bytesTotal = bytesTotal;
+ progress.setMinimum(0);
+ progress.setMaximum((new Long(bytesTotal)).intValue());
+ }
+
+ public void completed()
+ {
+ }
+
+ public long getProgress()
+ {
+ return progress.getValue();
+ }
+
+}
+
diff --git a/uk/ac/sanger/artemis/j2ssh/FileTransferProgressMonitor.java b/uk/ac/sanger/artemis/j2ssh/FileTransferProgressMonitor.java
new file mode 100644
index 0000000..566f966
--- /dev/null
+++ b/uk/ac/sanger/artemis/j2ssh/FileTransferProgressMonitor.java
@@ -0,0 +1,104 @@
+/* FileTransferProgressMonitor
+ *
+ * created: Aug 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.j2ssh;
+
+import javax.swing.JFrame;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.JViewport;
+import java.awt.Component;
+import javax.swing.Box;
+import java.awt.Point;
+import java.awt.Dimension;
+
+import com.sshtools.j2ssh.FileTransferProgress;
+
+import uk.ac.sanger.artemis.components.SwingWorker;
+
+public class FileTransferProgressMonitor
+{
+ private FTProgress ftprogress;
+ private JFrame progressFrame;
+ private Box yBox = Box.createVerticalBox();
+ private JScrollPane scroll;
+ private int count = 0;
+
+ public FileTransferProgressMonitor(Component parent)
+ {
+ progressFrame = new JFrame("Transfer");
+ progressFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ scroll = new JScrollPane(yBox);
+ scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ progressFrame.getContentPane().add(scroll);
+
+ if(parent != null)
+ {
+ Point loc = parent.getLocationOnScreen();
+ loc.x+=100;
+ loc.y+=100;
+ progressFrame.setLocation(loc);
+ }
+
+ yBox.add(Box.createHorizontalStrut(240));
+ progressFrame.pack();
+ progressFrame.setVisible(true);
+ }
+
+ public FTProgress add(String filename)
+ {
+ if(filename.length() > 25)
+ filename = filename.substring(0,25)+"...";
+
+ JProgressBar progress = new JProgressBar();
+ progress.setStringPainted(true);
+ progress.setString(" "+filename+" 0 % ");
+ yBox.add(progress);
+
+ if(count == 16)
+ scroll.setPreferredSize(scroll.getSize());
+
+ progressFrame.pack();
+ if(count > 15)
+ {
+ JViewport vport = scroll.getViewport();
+ vport.setViewPosition(
+ new Point(0,vport.getSize().height));
+ }
+
+ progressFrame.validate();
+ ftprogress = new FTProgress(progress, filename);
+
+ count++;
+ return ftprogress;
+ }
+
+ public void close()
+ {
+ progressFrame.setVisible(false);
+ progressFrame.dispose();
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/j2ssh/SshFileManager.java b/uk/ac/sanger/artemis/j2ssh/SshFileManager.java
new file mode 100644
index 0000000..1e79853
--- /dev/null
+++ b/uk/ac/sanger/artemis/j2ssh/SshFileManager.java
@@ -0,0 +1,447 @@
+/* ExternalProgram.java
+ *
+ * created: Aug 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.j2ssh;
+
+import javax.swing.JOptionPane;
+
+import java.io.File;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import java.util.Hashtable;
+
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.sftp.FileAttributes;
+import com.sshtools.j2ssh.sftp.SftpFile;
+import com.sshtools.j2ssh.SftpClient;
+import com.sshtools.j2ssh.SshException;
+
+/**
+*
+* Client to use ssh connection to server to run blast/fasta
+* remotely.
+*
+*/
+public class SshFileManager
+{
+ private Hashtable dir_list;
+ private Hashtable file_list;
+ private SshClient ssh;
+
+ public SshFileManager()
+ {
+ SshLogin sshLogin = new SshLogin();
+ ssh = sshLogin.getSshClient();
+ }
+
+ public SshFileManager(SshLogin sshLogin)
+ {
+ ssh = sshLogin.getSshClient(true);
+ pwd();
+ }
+
+ private void rescue()
+ {
+ try
+ {
+ ssh.disconnect();
+ SshLogin sshLogin = new SshLogin();
+ ssh = sshLogin.getSshClient();
+ }
+ catch(Exception exp)
+ {
+ System.out.println("SshFileManager.rescue()");
+ exp.printStackTrace();
+ }
+ }
+
+
+ /**
+ *
+ * Return an active SftpClient object
+ *
+ */
+ private SftpClient getSftpClient()
+ throws IOException
+ {
+ SftpClient sftp;
+ try
+ {
+ if(!ssh.hasActiveSftpClient())
+ return ssh.openSftpClient();
+
+ sftp = ssh.getActiveSftpClient();
+ return sftp;
+ }
+ catch(IOException ioe)
+ {}
+ return ssh.openSftpClient();
+ }
+
+ /**
+ *
+ * Remote directory listing
+ *
+ */
+ public boolean remoteList(String remoteRootDir)
+ throws IOException
+ {
+ SftpClient sftp = getSftpClient();
+ Object list[] = null;
+
+ try
+ {
+ list = sftp.ls(remoteRootDir).toArray();
+ }
+ catch(java.io.FileNotFoundException fnf)
+ {
+ return false;
+ }
+
+ dir_list = new Hashtable();
+ file_list = new Hashtable();
+
+ for(int i=0; i < list.length; i++)
+ {
+ SftpFile sfile = (SftpFile)list[i];
+ FileAttributes fat = sfile.getAttributes();
+
+ if(sfile.isDirectory() || sfile.isLink())
+ dir_list.put(sfile.getFilename(), fat);
+
+ file_list.put(sfile.getFilename(), fat);
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * Delete file or directory
+ *
+ */
+ public boolean delete(String filename)
+ {
+ try
+ {
+ SftpClient sftp = getSftpClient();
+ sftp.rm(filename);
+// sftp.quit();
+ }
+ catch(IOException ioe)
+ {
+ rescue();
+ ioe.printStackTrace();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * Make directory
+ *
+ */
+ public boolean mkdir(String dir)
+ {
+ try
+ {
+ SftpClient sftp = getSftpClient();
+ sftp.mkdir(dir);
+// sftp.quit();
+ }
+ catch(IOException ioe)
+ {
+ rescue();
+ ioe.printStackTrace();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * Get working directory
+ *
+ */
+ public String pwd()
+ {
+ String pwd = null;
+ try
+ {
+ SftpClient sftp = getSftpClient();
+ pwd = sftp.pwd();
+// sftp.quit();
+ }
+ catch(SshException exp)
+ {
+ JOptionPane.showMessageDialog(null,
+ "Cannot start SSH session.\n",
+ "SSH Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ catch(IOException ioe)
+ {
+ rescue();
+ ioe.printStackTrace();
+ }
+
+ return pwd;
+ }
+
+ /**
+ *
+ *
+ *
+ */
+ public boolean rename(String old_file, String new_file)
+ {
+ try
+ {
+ SftpClient sftp = getSftpClient();
+ sftp.rename(old_file,new_file);
+// sftp.quit();
+ }
+ catch(IOException ioe)
+ {
+ rescue();
+// ioe.printStackTrace();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * @param name of file to get status for
+ *
+ */
+ public FileAttributes stat(String filename)
+ {
+ SftpClient sftp = null;
+ try
+ {
+ sftp = getSftpClient();
+ FileAttributes fat = sftp.stat(filename);
+ return fat;
+ }
+ catch(SshException sshe)
+ {
+ rescue();
+ return stat(filename);
+ }
+ catch(IOException ioe)
+ {
+ return null;
+ // remote file doesn't exist
+ }
+
+ }
+
+ /**
+ *
+ * @param dir name of directory to write the file to
+ * @param local_file file to copy to the server
+ *
+ */
+ public boolean put(final String dir, final File local_file,
+ final FTProgress monitor, final boolean force)
+ {
+ SftpClient sftp = null;
+
+ try
+ {
+ sftp = getSftpClient();
+ FileAttributes attr = sftp.stat(dir+"/"+local_file.getName());
+
+ if(attr.isDirectory())
+ {
+ JOptionPane.showMessageDialog(null,
+ "Cannot overwrite the directory\n"+
+ dir+"/"+local_file.getName(),"Cannot Overwrite",
+ JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ else if(!force)
+ {
+ int n = JOptionPane.showConfirmDialog(null,
+ "Overwrite\n"+
+ dir+"/"+local_file.getName() + "\n?",
+ "Confirm the sequence entry",
+ JOptionPane.YES_NO_OPTION);
+ if(n == JOptionPane.NO_OPTION)
+ return false;
+ }
+ }
+ catch(SshException sshe)
+ {
+ if(System.getProperty("debug") != null)
+ {
+ System.out.println("put() ");
+ sshe.printStackTrace();
+ }
+
+ rescue();
+ return put(dir, local_file, monitor, force);
+ }
+ catch(IOException ioe)
+ {
+ // remote file doesn't exist
+ }
+
+ if(sftp == null)
+ return false;
+
+
+ try
+ {
+ sftp.put(local_file.getCanonicalPath(),
+ dir+"/"+local_file.getName(), monitor);
+ return true;
+ }
+ catch(SshException sshe)
+ {
+ if(System.getProperty("debug") != null)
+ {
+ System.out.println("put() 2");
+ sshe.printStackTrace();
+ }
+ rescue();
+ return put(dir, local_file, monitor, true);
+ }
+ catch(IOException ioe)
+ {
+ rescue();
+ ioe.printStackTrace();
+ return false;
+ }
+
+ }
+
+
+ /**
+ *
+ * Return the file contents as a byte array
+ *
+ */
+ public byte[] getFileContents(String file, final FTProgress monitor)
+ {
+ // sometimes SftpClient.get() hangs so put in
+ // separate thread
+
+ SshGet get = new SshGet(file, monitor);
+ get.start();
+
+ try
+ {
+ int count = 0;
+ while(get.isAlive())
+ {
+ Thread.currentThread().sleep(50);
+ count++;
+ if(count > 100 && monitor.getProgress() < 1.)
+ {
+ get.destroy();
+ SshLogin sshLogin = new SshLogin();
+ ssh = sshLogin.getSshClient();
+ return getFileContents(file,monitor);
+ }
+ }
+ }
+ catch(InterruptedException iex){}
+
+ return get.getByteArray();
+ }
+
+
+ public Hashtable getFileList()
+ {
+ return file_list;
+ }
+
+ public Hashtable getDirList()
+ {
+ return dir_list;
+ }
+
+ public boolean isConnected()
+ {
+ if(ssh == null || !ssh.isConnected())
+ return false;
+
+ return true;
+ }
+
+ public class SshGet extends Thread
+ {
+ private String file;
+ private FTProgress monitor;
+ private ByteArrayOutputStream os;
+ private boolean done = false;
+
+ public SshGet(final String file, final FTProgress monitor)
+ {
+ this.file = file;
+ this.monitor = monitor;
+ }
+
+ public void run()
+ {
+ while(!done)
+ {
+ try
+ {
+ SftpClient sftp = getSftpClient();
+
+ os = new ByteArrayOutputStream();
+ sftp.get(file, os, monitor);
+ done = true;
+ os.close();
+ }
+ catch(SshException se2)
+ {
+ rescue();
+ }
+ catch(IOException ioe2)
+ {
+ done = true; // file doesn't exist?
+ }
+ }
+ }
+
+ public void destroy()
+ {
+ done = true;
+ }
+
+ public byte[] getByteArray()
+ {
+ return os.toByteArray();
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/j2ssh/SshLogin.java b/uk/ac/sanger/artemis/j2ssh/SshLogin.java
new file mode 100644
index 0000000..6f0b000
--- /dev/null
+++ b/uk/ac/sanger/artemis/j2ssh/SshLogin.java
@@ -0,0 +1,279 @@
+/* SshLogin.java
+ *
+ * created: Aug 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.j2ssh;
+
+
+import javax.swing.JOptionPane;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.JPasswordField;
+import javax.swing.SwingConstants;
+
+import java.awt.GridLayout;
+import java.io.IOException;
+import java.util.Properties;
+
+
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
+import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient;
+import com.sshtools.j2ssh.transport.IgnoreHostKeyVerification;
+
+
+/**
+*
+* Client to use ssh connection to server to run blast/fasta
+* remotely.
+*
+*/
+public class SshLogin
+{
+ // defaults
+ //private String logfile = null;
+
+ // login variables
+ private String hostname = null;
+ private String user = null;
+ private int port = -1;
+ private static JPasswordField pfield = new JPasswordField(16);
+ private static JTextField portfield = new JTextField(16);
+ private static JTextField hostfield = new JTextField(16);
+ private static JTextField ufield = new JTextField(16);
+ private static SshClient ssh;
+ private static Properties settings;
+
+ public SshLogin()
+ {
+ /*try
+ {
+ logfile = System.getProperty("logfile");
+ // Setup a logfile
+ if(logfile != null)
+ {
+ Handler fh = new FileHandler(logfile);
+ fh.setFormatter(new SimpleFormatter());
+ Logger.getLogger("com.sshtools").setUseParentHandlers(false);
+ Logger.getLogger("com.sshtools").addHandler(fh);
+ Logger.getLogger("com.sshtools").setLevel(Level.WARNING);
+ }
+ else
+ Logger.getLogger("com.sshtools").setLevel(Level.OFF);
+ }
+ catch(IOException ioe){}*/
+
+ if(settings == null)
+ settings = setProperties();
+ }
+
+ public SshClient getSshClient()
+ {
+ return getSshClient(false);
+ }
+
+ public SshClient getSshClient(final boolean fail)
+ {
+ if(ssh == null || !ssh.isConnected())
+ {
+ try
+ {
+ ssh = login(fail);
+ }
+ catch(java.net.ConnectException ce)
+ {
+ ce.printStackTrace();
+ }
+ catch(IOException ioe){}
+ }
+ return ssh;
+ }
+
+ public String getUser()
+ {
+ return ufield.getText().trim();
+ }
+
+ public static String getHostname()
+ {
+ return hostfield.getText().trim();
+ }
+
+ public static String getPort()
+ {
+ return portfield.getText().trim();
+ }
+
+ public static Properties getProperties()
+ {
+ return settings;
+ }
+
+ /**
+ *
+ * Log the user in.
+ *
+ */
+ private SshClient login(final boolean fail)
+ throws IOException
+ {
+ SshClient ssh = null;
+ int result = AuthenticationProtocolState.FAILED;
+ int count = 0;
+
+ while(result != AuthenticationProtocolState.COMPLETE)
+ {
+ if( !(count == 0 && pfield.getPassword().length > 0) )
+ {
+ if(!setLogin())
+ return null;
+ }
+
+ // Create a password authentication instance
+ PasswordAuthenticationClient pwd = new PasswordAuthenticationClient();
+ user = ufield.getText().trim();
+ pwd.setUsername(user);
+ pwd.setPassword(new String(pfield.getPassword()));
+
+ // Make a client connection
+ ssh = new SshClient();
+ hostname = hostfield.getText().trim();
+ if(portfield.getText().trim().equals(""))
+ port = -1;
+ else
+ port = Integer.parseInt(portfield.getText().trim());
+
+ if(port < 0)
+ ssh.connect(hostname, new IgnoreHostKeyVerification());
+ else
+ ssh.connect(hostname,port, new IgnoreHostKeyVerification());
+
+ // Try the authentication
+ result = ssh.authenticate(pwd);
+ if(fail && result == AuthenticationProtocolState.FAILED)
+ return null;
+
+ count++;
+ }
+ return ssh;
+ }
+
+ public JPanel getLogin()
+ {
+ JPanel promptPanel = new JPanel(new GridLayout(4,2));
+
+ if(hostname != null && hostfield.getText().equals(""))
+ hostfield.setText(hostname);
+
+ if(port >-1 && portfield.getText().equals(""))
+ portfield.setText(Integer.toString(port));
+
+ if(user != null && ufield.getText().equals(""))
+ ufield.setText(user);
+
+ JLabel hostlab = new JLabel(" Hostname: ", SwingConstants.RIGHT);
+ JLabel portlab = new JLabel(" Port: ", SwingConstants.RIGHT);
+
+ JLabel ulab = new JLabel(" Username: ", SwingConstants.RIGHT);
+ JLabel plab = new JLabel(" Password: ", SwingConstants.RIGHT);
+
+ //add labels etc
+ promptPanel.add(hostlab);
+ promptPanel.add(hostfield);
+
+ promptPanel.add(portlab);
+ promptPanel.add(portfield);
+
+ promptPanel.add(ulab);
+ promptPanel.add(ufield);
+
+ promptPanel.add(plab);
+ promptPanel.add(pfield);
+ return promptPanel;
+ }
+
+ /**
+ *
+ * Get password field
+ *
+ */
+ public JPasswordField getJPasswordField()
+ {
+ return pfield;
+ }
+
+ /**
+ *
+ * Set the login information.
+ *
+ */
+ private boolean setLogin()
+ {
+ JPanel promptPanel = getLogin();
+
+ Object[] options = { "CANCEL", "LOGIN AND RUN"};
+
+ int select = JOptionPane.showOptionDialog(null, promptPanel,
+ "LOGIN",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ options,
+ options[1]);
+
+ if(select == 0)
+ return false;
+
+ return true;
+ }
+
+ /**
+ *
+ * Get the properties from the j2ssh.properties file.
+ *
+ */
+ private Properties setProperties()
+ {
+ Properties settings = new Properties();
+ ClassLoader cl = this.getClass().getClassLoader();
+ // try out of the classpath
+ try
+ {
+ settings.load(cl.getResourceAsStream("j2ssh.properties"));
+ }
+ catch (Exception e)
+ {
+ }
+
+ if(hostname == null && settings.getProperty("host") != null)
+ hostname = settings.getProperty("host");
+ if(port < 0 && settings.getProperty("port") != null)
+ port = Integer.parseInt(settings.getProperty("port"));
+ if(user == null)
+ user = System.getProperty("user.name");
+
+ return settings;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/j2ssh/SshPSUClient.java b/uk/ac/sanger/artemis/j2ssh/SshPSUClient.java
new file mode 100644
index 0000000..880ac27
--- /dev/null
+++ b/uk/ac/sanger/artemis/j2ssh/SshPSUClient.java
@@ -0,0 +1,743 @@
+/* SshPSUClient.java
+ *
+ * created: Aug 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.j2ssh;
+
+import uk.ac.sanger.artemis.components.MessageDialog;
+
+import javax.swing.JFileChooser;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+
+import java.util.Vector;
+import java.util.Properties;
+
+import com.sshtools.j2ssh.SshException;
+import com.sshtools.j2ssh.SshClient;
+import com.sshtools.j2ssh.session.SessionChannelClient;
+import com.sshtools.j2ssh.sftp.SftpFile;
+import com.sshtools.j2ssh.SftpClient;
+import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+
+
+/**
+*
+* Client to use ssh connection to server to run blast/fasta
+* remotely.
+*
+*/
+public class SshPSUClient extends Thread
+{
+ public static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(SshPSUClient.class);
+ // defaults
+ private String listfilepath = null;
+ private String cmd = null;
+ private String bsub = null;
+ private String logfile = null;
+ private String db = null;
+ private String wdir = null;
+ private boolean justProg = false;
+
+ //
+ private SshClient ssh;
+ private String user;
+ private boolean keep = false;
+ private boolean zipResults = false;
+
+ //
+ StdoutStdErrHandler stdouth;
+ StdoutStdErrHandler stderrh;
+
+ public SshPSUClient(String args[])
+ {
+ // process arguments
+ if(args != null && args.length > 0)
+ {
+ for(int i=0; i<args.length; i++)
+ {
+ if(args[i].equals("-f") && i < args.length-1)
+ listfilepath = args[i+1];
+ else if(args[i].equals("-cmd") && i < args.length-1)
+ cmd = args[i+1];
+ else if(args[i].equals("-bsub") && i < args.length-1)
+ bsub = args[i+1];
+ else if(args[i].equals("-l") && i < args.length-1)
+ logfile = args[i+1];
+ else if(args[i].equals("-d") && i < args.length-1)
+ db = args[i+1];
+ else if(args[i].equals("-wdir") && i < args.length-1)
+ wdir = args[i+1];
+ else if(args[i].equals("-keep"))
+ keep = true;
+ }
+ }
+
+ SshLogin sshLogin = new SshLogin();
+ ssh = sshLogin.getSshClient();
+ user = sshLogin.getUser();
+ }
+
+ public SshPSUClient(final String cmd)
+ {
+ this.cmd = cmd;
+ SshLogin sshLogin = new SshLogin();
+ ssh = sshLogin.getSshClient();
+ justProg = true;
+ }
+
+ private SshClient rescue()
+ {
+ try
+ {
+ ssh.disconnect();
+ SshLogin sshLogin = new SshLogin();
+ ssh = sshLogin.getSshClient();
+ }
+ catch(Exception exp)
+ {
+ logger4j.warn("SshPSUClient.rescue()");
+ exp.printStackTrace();
+ }
+ return ssh;
+ }
+
+ public void run()
+ {
+ String program = cmd;
+ boolean completed = false;
+ try
+ {
+ ConfigurationLoader.initialize(false);
+
+ if(ssh == null)
+ return;
+
+ if(justProg)
+ runProgram();
+ else
+ completed = runBlastOrFasta(program);
+
+ // Quit
+ //ssh.disconnect();
+ }
+ catch(IOException ioe){}
+ finally
+ {
+ if(completed)
+ new MessageDialog(null,
+ "Finished \n" + program,
+ "Process Finished",
+ false);
+ }
+ }
+
+
+ /**
+ *
+ * Read the sequence filenames in a list file
+ * @param String file list filename
+ * @return the sequence filename collection
+ *
+ */
+ private Vector readListFile(String file)
+ {
+ Vector seqfiles = new Vector();
+ try
+ {
+ String line;
+ BufferedReader in = new BufferedReader(new FileReader(file));
+ while((line = in.readLine()) != null )
+ {
+ File seq = new File(line);
+ if(seq.exists())
+ {
+ seqfiles.add(seq.getAbsolutePath());
+ }
+ }
+ in.close();
+ }
+ catch (IOException e)
+ {
+ logger4j.warn("Problem reading list file");
+ }
+ return seqfiles;
+ }
+
+ /**
+ *
+ * Wait until a file appears on the server.
+ *
+ */
+ private boolean waitUntilFileAppears(String file)
+ throws InterruptedException, IOException
+ {
+ for(int i=0; i < 500; i++)
+ {
+ logger4j.debug("waitUntilFileAppears() "+file);
+ Thread.sleep(1000);
+ try
+ {
+ if(fileExists(getSftpClient() , wdir, file))
+ return true;
+ }
+ catch(SshException sshe)
+ {
+ if(System.getProperty("debug") != null)
+ {
+ logger4j.warn("waitUntilFileAppears()");
+ sshe.printStackTrace();
+ }
+ try
+ {
+ rescue();
+ continue;
+ } catch(Exception exp) {}
+ }
+ }
+
+ return false;
+ }
+
+ private boolean fileExists(SftpClient sftp, String wdir, String file)
+ throws SshException, IOException
+ {
+ Object list[] = null;
+ try
+ {
+ list = sftp.ls(wdir).toArray();
+ }
+ catch(SshException sshe)
+ {
+ sftp = getSftpClient();
+ list = sftp.ls(wdir).toArray();
+ }
+
+ for(int j=0; j<list.length;j++)
+ {
+ if( ((SftpFile)list[j]).getFilename().equals(file) )
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ *
+ * Get the properties from the j2ssh.properties file.
+ *
+ */
+ private Properties getProperties()
+ {
+ Properties settings = new Properties();
+ ClassLoader cl = this.getClass().getClassLoader();
+ // try out of the classpath
+ try
+ {
+ settings.load(cl.getResourceAsStream("j2ssh.properties"));
+ }
+ catch (Exception e)
+ {
+ }
+
+ if(bsub == null && settings.getProperty("bsub") != null)
+ bsub = settings.getProperty("bsub");
+ if(db == null)
+ {
+ if(settings.getProperty("default_db") != null)
+ db = settings.getProperty("default_db");
+ else
+ db = "%uniprot";
+ }
+
+ if(settings.getProperty("zip") != null)
+ {
+ String zipValue = settings.getProperty("zip");
+ zipResults = Boolean.parseBoolean(zipValue);
+
+ logger4j.debug("zip results :: "+zipResults);
+ }
+
+ if(wdir == null && settings.getProperty("wdir") != null)
+ wdir = settings.getProperty("wdir");
+
+ if(cmd != null)
+ {
+ if(cmd.equals("blastp") && settings.getProperty("blastp") != null)
+ cmd = settings.getProperty("blastp");
+ else if(cmd.equals("blastn") && settings.getProperty("blastn") != null)
+ cmd = settings.getProperty("blastn");
+ else if(cmd.equals("blastx") && settings.getProperty("blastx") != null)
+ cmd = settings.getProperty("blastx");
+ else if(cmd.equals("tblastx") && settings.getProperty("tblastx") != null)
+ cmd = settings.getProperty("tblastx");
+ else if(cmd.equals("fasta") && settings.getProperty("fasta") != null)
+ cmd = settings.getProperty("fasta");
+ else if(cmd.equals("fastx") && settings.getProperty("fastx") != null)
+ cmd = settings.getProperty("fastx");
+ }
+
+ return settings;
+ }
+
+
+ /**
+ *
+ * Run fasta or blast on the server ssh'ed into
+ *
+ */
+ private boolean runBlastOrFasta(String program)
+ throws IOException
+ {
+ Properties settings = getProperties();
+
+ // prompt for local listfile
+ if(listfilepath == null)
+ {
+ JFileChooser chooser = new JFileChooser();
+ int returnVal = chooser.showOpenDialog(null);
+ if(returnVal == JFileChooser.APPROVE_OPTION)
+ listfilepath = chooser.getSelectedFile().getAbsolutePath();
+ else
+ return false;
+ }
+
+ SftpClient sftp = getSftpClient();
+
+ // loop over sequence files in the listfile
+ Vector seqfile = readListFile(listfilepath);
+ for(int i=0; i<seqfile.size();i++)
+ {
+ String filepath = (String)seqfile.get(i);
+ int index = filepath.lastIndexOf(System.getProperty("file.separator"));
+ String filename = filepath;
+ if(index > -1)
+ filename = filename.substring(index+1);
+
+ if(i == 0)
+ {
+ try
+ {
+ if(wdir.endsWith("scratch108") || wdir.endsWith("scratch108/"))
+ {
+ if(fileExists(sftp , wdir+"/bacteria/", user))
+ wdir = wdir+"/bacteria/";
+ else if(fileExists(sftp , wdir+"/parasites/", user))
+ wdir = wdir+"/parasites/";
+ else if(fileExists(sftp , wdir+"/pathogen/", user))
+ wdir = wdir+"/pathogen/";
+ else if(fileExists(sftp , wdir+"/viruses/", user))
+ wdir = wdir+"/viruses/";
+ }
+
+ if(!keep)
+ wdir = wdir + "/" + user;
+ sftp.mkdir(wdir);
+ wdir = wdir + "/" + program + "/";
+
+ sftp.mkdir(wdir);
+ logger4j.debug("mkdir() " + wdir);
+ // sftp.put(filepath, wdir+filename);
+ }
+ catch(SshException sshe)
+ {
+ logger4j.debug("runBlastOrFasta()");
+ if(System.getProperty("debug") != null)
+ {
+ sshe.printStackTrace();
+ }
+ rescue();
+ sftp = getSftpClient();
+ if(!wdir.endsWith(program + "/"))
+ wdir = wdir + "/" + program + "/";
+ }
+ catch(IOException ioe)
+ {
+ // directory already created
+ }
+ }
+
+
+ try
+ {
+ sftp.put(filepath, wdir+filename);
+
+ logger4j.debug("PUT SUCCESS "+wdir+filename);
+ }
+ catch(SshException ioe)
+ {
+ logger4j.debug("runBlastOrFasta() - 2");
+ if(System.getProperty("debug") != null)
+ {
+ ioe.printStackTrace();
+ }
+ rescue();
+ sftp = getSftpClient();
+ sftp.put(filepath, wdir+filename);
+ }
+
+ logger4j.debug("STARTING session");
+
+ SessionChannelClient session = null;
+
+ try
+ {
+ if(!ssh.isConnected())
+ rescue();
+
+ session = ssh.openSessionChannel();
+ }
+ catch(IOException exp)
+ {
+ logger4j.debug("NOT STARTED runBlastOrFasta() ----- 3 "+filename);
+ if(System.getProperty("debug") != null)
+ {
+ exp.printStackTrace();
+ }
+ rescue();
+ }
+
+ String outputfile = wdir+filename+".out";
+ final String actualCMD;
+
+ if(bsub == null)
+ {
+ if( ((cmd.indexOf("fasta3") > -1) || (cmd.indexOf("fastx3") > -1))
+ && settings.getProperty(db) != null)
+ db = settings.getProperty(db);
+ else if(db.startsWith("%"))
+ db = db.substring(1,db.length());
+
+ if( (cmd.indexOf("fasta3") > -1) ||
+ (cmd.indexOf("fastx3") > -1) )
+ actualCMD = cmd+" "+wdir+filename+" "+db+" > "+outputfile;
+ else
+ actualCMD = cmd+" -d "+db+" -i "+wdir+filename+" -o "+outputfile;
+ }
+ else
+ {
+ if( (cmd.indexOf("fasta3") > -1) ||
+ (cmd.indexOf("fastx3") > -1) )
+ {
+ if(settings.getProperty(db) != null)
+ db = settings.getProperty(db);
+ actualCMD = bsub+" -o "+ outputfile +" -e "+ outputfile + ".err " +
+ cmd+" "+wdir+filename+" "+db;
+ }
+ else
+ actualCMD = bsub+" -o "+ outputfile +" -e "+ outputfile + ".err " +
+ cmd+" "+db+" "+wdir+filename;
+ }
+
+ // run the application
+ logger4j.debug(actualCMD);
+
+ try
+ {
+ session.executeCommand(actualCMD);
+ }
+ catch(IOException exp)
+ {
+ logger4j.debug("runBlastOrFasta() - 3");
+ if(System.getProperty("debug") != null)
+ {
+ exp.printStackTrace();
+ }
+ }
+
+ logger4j.debug("STARTED session "+filename);
+
+ // Reading from the session InputStream
+ StdoutStdErrHandler stdouth = new StdoutStdErrHandler(session, true);
+ StdoutStdErrHandler stderrh = new StdoutStdErrHandler(session, false);
+
+ stdouth.start();
+ stderrh.start();
+
+ boolean isFile = false;
+ try
+ {
+ // make sure we hang around for stdout
+ while(stdouth.isAlive() || stderrh.isAlive())
+ Thread.sleep(10);
+
+ isFile = waitUntilFileAppears(filename+".out");
+ }
+ catch(InterruptedException ie)
+ {
+ ie.printStackTrace();
+ }
+
+ // stdout & stderr
+ logger4j.debug("STDOUT "+filename+"\n"+stdouth.getOutput());
+ logger4j.debug("STDERR "+filename+"\n"+stderrh.getOutput());
+
+
+ try
+ {
+ sftp = getSftpClient();
+ sftp.get(outputfile, filepath+".out");
+ }
+ catch(Exception ioe)
+ {
+ logger4j.debug("runBlastOrFasta() - 3");
+ if(System.getProperty("debug") != null)
+ {
+ ioe.printStackTrace();
+ }
+ rescue();
+ sftp = getSftpClient();
+ sftp.get(outputfile, filepath+".out");
+ }
+
+ logger4j.debug("GET SUCCESS "+filepath+".out");
+ sftp.rm(wdir+filename);
+
+ if(!keep)
+ {
+ sftp.rm(outputfile);
+ }
+ else if(zipResults)
+ {
+ cmd = "gzip "+outputfile+"; zip -j "+wdir+program+".zip "+
+ outputfile+".gz; rm -f "+outputfile+".gz";
+ logger4j.debug(cmd);
+ SshPSUClient sshClient = new SshPSUClient(cmd);
+ sshClient.start();
+ }
+ sftp = getSftpClient();
+ sftp.rm(outputfile+".err");
+ session.close();
+ }
+
+ return true;
+ }
+
+
+
+ /**
+ *
+ * Run fasta or blast on the server ssh'ed into
+ *
+ */
+ private boolean runProgram()
+ throws IOException
+ {
+ SessionChannelClient session = null;
+
+ try
+ {
+ if(!ssh.isConnected())
+ rescue();
+
+ session = ssh.openSessionChannel();
+ }
+ catch(IOException exp)
+ {
+ logger4j.warn("NOT STARTED runProgram()");
+ if(System.getProperty("debug") != null)
+ {
+ exp.printStackTrace();
+ }
+ rescue();
+ }
+
+ // run the application
+
+ try
+ {
+ session.executeCommand(cmd);
+ }
+ catch(IOException exp)
+ {
+ logger4j.warn("session : "+cmd);
+ logger4j.warn("runProgram: "+exp.getMessage());
+ if(System.getProperty("debug") != null)
+ {
+ exp.printStackTrace();
+ }
+ }
+
+ logger4j.debug("STARTED session "+cmd);
+
+ // Reading from the session InputStream
+ stdouth = new StdoutStdErrHandler(session, true);
+ stderrh = new StdoutStdErrHandler(session, false);
+
+ stdouth.start();
+ stderrh.start();
+
+ try
+ {
+ // make sure we hang around for stdout
+ while(stdouth.isAlive() || stderrh.isAlive())
+ Thread.sleep(2);
+ }
+ catch(InterruptedException ie)
+ {
+ ie.printStackTrace();
+ }
+
+ // stdout & stderr
+ if(stderrh.getBufferSize() > 0)
+ logger4j.debug("STDERR :"+stderrh.getOutput());
+
+ session.close();
+ return true;
+ }
+
+
+ public StringBuffer getStdOutBuffer()
+ {
+ return stdouth.getStdOutBuffer();
+ }
+
+ public String getStdOut()
+ {
+ return stdouth.getOutput();
+ }
+
+ public String getStdErr()
+ {
+ return stderrh.getOutput();
+ }
+
+ /**
+ *
+ * Return an active SftpClient object
+ *
+ */
+ private synchronized SftpClient getSftpClient()
+ throws IOException
+ {
+ SftpClient sftp;
+
+ try
+ {
+ if(!ssh.hasActiveSftpClient())
+ return ssh.openSftpClient();
+
+ sftp = ssh.getActiveSftpClient();
+ return sftp;
+ }
+ catch(SshException sshe)
+ {
+ try
+ {
+ sftp = ssh.openSftpClient();
+ return sftp;
+ }
+ catch(IOException ioe)
+ {
+ logger4j.debug("getSftpClient() -- 2");
+ if(System.getProperty("debug") != null)
+ {
+ ioe.printStackTrace();
+ }
+ rescue();
+ }
+ }
+ catch(IOException ioe2)
+ {
+ logger4j.debug("getSftpClient()");
+ if(System.getProperty("debug") != null)
+ {
+ ioe2.printStackTrace();
+ }
+ rescue();
+ }
+ return ssh.openSftpClient();
+ }
+
+ /**
+ *
+ * Thread to handle stdout/stderr reading without blocking.
+ *
+ */
+ class StdoutStdErrHandler extends Thread
+ {
+ private SessionChannelClient session;
+ private boolean isStdout;
+ private StringBuffer buff = new StringBuffer();
+
+ protected StdoutStdErrHandler(final SessionChannelClient session,
+ final boolean isStdout)
+ {
+ this.session = session;
+ this.isStdout = isStdout;
+ }
+
+ public void run()
+ {
+ try
+ {
+ final InputStream in;
+ if(isStdout)
+ in = session.getInputStream();
+ else
+ in = session.getStderrInputStream();
+
+ final byte buffer[] = new byte[400];
+ int read;
+ while((read = in.read(buffer)) > 0)
+ buff.append(new String(buffer, 0, read));
+
+ in.close();
+ }
+ catch(Exception ioe)
+ {
+ ioe.printStackTrace();
+ }
+ }
+
+ public synchronized StringBuffer getStdOutBuffer()
+ {
+ return buff;
+ }
+
+ public synchronized String getOutput()
+ {
+ return buff.toString();
+ }
+
+ public int getBufferSize()
+ {
+ return buff.length();
+ }
+ }
+
+
+ /**
+ * The main program for the PasswordConnect class
+ *
+ * @param args The command line arguments
+ */
+ public static void main(String args[])
+ {
+ new SshPSUClient(args);
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/AGWindowAlgorithm.java b/uk/ac/sanger/artemis/plot/AGWindowAlgorithm.java
new file mode 100644
index 0000000..98d6d99
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/AGWindowAlgorithm.java
@@ -0,0 +1,176 @@
+/* AGWindowAlgorithm.java
+ *
+ * created: Wed Apr 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/AGWindowAlgorithm.java,v 1.4 2009-03-17 17:47:42 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns a single floating point number, which is the
+ * percent AG content of the range. The Strand to use is set in the
+ * constructor.
+ *
+ * @author Kim Rutherford
+ * @version $Id: AGWindowAlgorithm.java,v 1.4 2009-03-17 17:47:42 tjc Exp $
+ **/
+
+public class AGWindowAlgorithm extends BaseAlgorithm {
+ /**
+ * Create a new GCWindowAlgorithm object.
+ * @param strand The strand to do the calculation on.
+ **/
+ public AGWindowAlgorithm (final Strand strand) {
+ super (strand, "AG Content (%)", "ag_content");
+
+ setScalingFlag (true);
+ }
+
+ /**
+ * Return the percent GC between a pair of bases.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues (int start, int end, final float [] values) {
+ final String sequence;
+
+ try {
+ sequence = getStrand ().getSubSequence (new Range (start, end));
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ float gc_count = 0;
+
+ for (int i = 0 ; i < sequence.length () ; ++i) {
+ final char this_char = sequence.charAt (i);
+// System.out.println (this_char);
+
+ if (this_char == 'g' || this_char == 'a') {
+ ++gc_count;
+ }
+ }
+
+// System.out.println ("start: " + start + " end: " + end + " returning: " + gc_count/sequence.length ());
+
+ values[0] = gc_count/sequence.length () * 100;
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - one
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize () {
+ final Integer super_window_size = super.getDefaultWindowSize ();
+ if (super_window_size != null) {
+ // the superclass version of getDefaultWindowSize () returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer (120);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize () {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize ();
+ if (super_max_window_size != null) {
+ // the superclass version of getDefaultMaxWindowSize () returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer (500);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize () {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize ();
+ if (super_min_window_size != null) {
+ // the superclass version of getDefaultMinWindowSize () returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer (24);
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (100);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (0);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size) {
+ if (window_size > 10) {
+ return new Integer (window_size / 10);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage () {
+ return new Float (getStrand ().getBases ().getAverageAGPercent ());
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/ATDeviationAlgorithm.java b/uk/ac/sanger/artemis/plot/ATDeviationAlgorithm.java
new file mode 100644
index 0000000..4bd315f
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/ATDeviationAlgorithm.java
@@ -0,0 +1,198 @@
+/* ATDeviationAlgorithm.java
+ *
+ * created: Tue Mar 16 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/ATDeviationAlgorithm.java,v 1.1 2004-06-09 09:51:14 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns a single floating point number, which is the
+ * value of (A-T)/(A+T) in the range. The Strand to use is set in the
+ * constructor.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ATDeviationAlgorithm.java,v 1.1 2004-06-09 09:51:14 tjc Exp $
+ **/
+
+public class ATDeviationAlgorithm extends BaseAlgorithm {
+ /**
+ * Create a new ATDeviationAlgorithm object
+ * @param strand The strand to do the calculation on.
+ **/
+ public ATDeviationAlgorithm (final Strand strand) {
+ super (strand, "AT Deviation (A-T)/(A+T)", "at_deviation");
+
+ setScalingFlag (true);
+ }
+
+ /**
+ * Return the value of (A content - T content)/(A content + T content)
+ * between the given pair of bases.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues (int start, int end, final float [] values) {
+ final String sequence;
+
+ try {
+ sequence = getStrand ().getSubSequence (new Range (start, end));
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ float a_count = 0;
+ float t_count = 0;
+
+ for (int i = 0 ; i < sequence.length () ; ++i) {
+ final char this_char = sequence.charAt (i);
+
+ if (this_char == 'a') {
+ ++a_count;
+ }
+
+ if (this_char == 't') {
+ ++t_count;
+ }
+ }
+
+ if (a_count + t_count > 0) {
+// if (start == 1) {
+// System.out.println ("start: " + start + " end: " + end +
+// " returning: " +
+// (a_count - t_count) / (a_count + t_count));
+
+// }
+ values[0] = (a_count - t_count) / (a_count + t_count);
+ } else {
+ values[0] = 0;
+ }
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - one
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize () {
+ final Integer super_window_size = super.getDefaultWindowSize ();
+ if (super_window_size != null) {
+ // the superclass version of getDefaultWindowSize () returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer (30);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize () {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize ();
+ if (super_max_window_size != null) {
+ // the superclass version of getDefaultMaxWindowSize () returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer (5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize () {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize ();
+ if (super_min_window_size != null) {
+ // the superclass version of getDefaultMinWindowSize () returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer (10);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size) {
+ if (window_size > 10) {
+ return new Integer (window_size / 10);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (1);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (-1);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage () {
+ final float a_count = getStrand ().getACount ();
+ final float t_count = getStrand ().getTCount ();
+
+ final float a_minus_t = a_count - t_count;
+ final float a_plus_t = a_count + t_count;
+
+ if (a_plus_t > 0) {
+ return new Float (a_minus_t/a_plus_t);
+ } else {
+ return new Float (0.0);
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/Algorithm.java b/uk/ac/sanger/artemis/plot/Algorithm.java
new file mode 100644
index 0000000..1ffeb9e
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/Algorithm.java
@@ -0,0 +1,311 @@
+/* Algorithm.java
+ *
+ * created: Tue Dec 15 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/Algorithm.java,v 1.2 2009-04-07 09:11:29 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.Options;
+
+/**
+ * This class represents an algorithm that can be plotted.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Algorithm.java,v 1.2 2009-04-07 09:11:29 tjc Exp $
+ **/
+
+public abstract class Algorithm {
+
+
+ /**
+ * The name of this algorithm, as passed to the constructor.
+ **/
+ private String algorithm_name;
+
+ /**
+ * The short (one word) name of this algorithm, as passed to the
+ * constructor.
+ **/
+ private String algorithm_short_name;
+
+ /**
+ * Set by disableMaxAndMin () and enableMaxAndMin ().
+ **/
+ private boolean max_min_disabled = false;
+
+ /**
+ * Set by the constructor by looking at the options with this name:
+ * getAlgorithmShortName () + "_default_window_size"
+ **/
+ private Integer options_window_size = null;
+
+ /**
+ * Set by the constructor by looking at the options with this name:
+ * getAlgorithmShortName () + "_default_min_window_size"
+ **/
+ private Integer options_max_window_size = null;
+
+ /**
+ * Set by the constructor by looking at the options with this name:
+ * getAlgorithmShortName () + "_default_min_window_size"
+ **/
+ private Integer options_min_window_size = null;
+
+ private boolean userMaxMin = false;
+ private float userMin = Float.MIN_VALUE;
+ private float userMax = Float.MAX_VALUE;
+
+ /**
+ * Create a new Algorithm object.
+ * @param algorithm_name A String used to identify this algorithm to the
+ * user.
+ * @param algorithm_short_name A String used to identify this algorithm
+ * internally. This is used to make the names of the keys for looking up
+ * the window size options. eg. long name: "GC Content (%)", short name:
+ * gc_content, which gives these options names:
+ * gc_content_default_window_size, gc_content_default_max_window_size and
+ * gc_content_default_min_window_size.
+ **/
+ public Algorithm (final String algorithm_name,
+ final String algorithm_short_name)
+ {
+ this.algorithm_name = algorithm_name;
+ this.algorithm_short_name = algorithm_short_name;
+
+ options_min_window_size =
+ Options.getOptions ().getIntegerProperty (getAlgorithmShortName () +
+ "_default_min_window");
+
+ if (options_min_window_size != null &&
+ options_min_window_size.intValue () < 1) {
+ options_min_window_size = null;
+ }
+
+ options_max_window_size =
+ Options.getOptions ().getIntegerProperty (getAlgorithmShortName () +
+ "_default_max_window");
+
+ if (options_max_window_size != null) {
+ if (options_min_window_size == null) {
+ if (options_max_window_size.intValue () < 1) {
+ options_max_window_size = null;
+ }
+ } else {
+ if (options_max_window_size.intValue () <
+ options_min_window_size.intValue ()) {
+ options_max_window_size = options_min_window_size;
+ }
+ }
+ }
+
+ options_window_size =
+ Options.getOptions ().getIntegerProperty (getAlgorithmShortName () +
+ "_default_window_size");
+
+ if (options_window_size != null) {
+ if (options_min_window_size == null) {
+ if (options_window_size.intValue () < 1) {
+ options_window_size = null;
+ }
+ } else {
+ if (options_window_size.intValue () <
+ options_min_window_size.intValue ()) {
+ options_window_size = options_min_window_size;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the name of this algorithm.
+ **/
+ public String getAlgorithmName ()
+ {
+ return algorithm_name;
+ }
+
+ /**
+ * Return the name of this algorithm.
+ **/
+ public void setAlgorithmName (String algorithm_name)
+ {
+ this.algorithm_name = algorithm_name;
+ }
+
+ /**
+ * Return the short (one word) name of this algorithm, as passed tp the
+ * constructor.
+ **/
+ public String getAlgorithmShortName ()
+ {
+ return algorithm_short_name;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize ()
+ {
+ return options_window_size;
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize ()
+ {
+ return options_max_window_size;
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize ()
+ {
+ return options_min_window_size;
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size)
+ {
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm. Subclasses of this class
+ * should override getMaximumInternal () rather than this method.
+ * @return null is returned if this algorithm doesn't have a fixed maximum
+ * or if maxAndMinDisabled () is true.
+ **/
+ final public Float getMaximum ()
+ {
+ if (scalingFlag ())
+ return null;
+ else if(isUserMaxMin())
+ return getUserMax();
+ else
+ return getMaximumInternal ();
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return null is returned if this algorithm doesn't have a fixed maximum.
+ **/
+ protected Float getMaximumInternal ()
+ {
+ return null;
+ }
+
+ /**
+ * Return the minimum value of this algorithm. Subclasses of this class
+ * should override getMinimumInternal () rather than this method.
+ * @return null is returned if this algorithm doesn't have a fixed minimum
+ * or if maxAndMinDisabled () is true.
+ **/
+ final public Float getMinimum ()
+ {
+ if (scalingFlag ())
+ return null;
+ else if(isUserMaxMin())
+ return getUserMin();
+ else
+ return getMinimumInternal ();
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return null is returned if this algorithm doesn't have a fixed minimum.
+ **/
+ protected Float getMinimumInternal ()
+ {
+ return null;
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage ()
+ {
+ return null;
+ }
+
+ /**
+ * Force getMinimum () and getMaximum () to return null if and only if
+ * enable_scaling is true.
+ **/
+ public void setScalingFlag (final boolean enable_scaling)
+ {
+ max_min_disabled = enable_scaling;
+ }
+
+ /**
+ * Return true if and only if getMaximum () and getMinimum () should return
+ * null.
+ **/
+ public boolean scalingFlag ()
+ {
+ return max_min_disabled;
+ }
+
+ public float getUserMin()
+ {
+ return userMin;
+ }
+
+ public void setUserMin(float userMin)
+ {
+ this.userMin = userMin;
+ }
+
+ public float getUserMax()
+ {
+ return userMax;
+ }
+
+ public void setUserMax(float userMax)
+ {
+ this.userMax = userMax;
+ }
+
+ public boolean isUserMaxMin()
+ {
+ return userMaxMin;
+ }
+
+ public void setUserMaxMin(boolean userMaxMin)
+ {
+ this.userMaxMin = userMaxMin;
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/BaseAlgorithm.java b/uk/ac/sanger/artemis/plot/BaseAlgorithm.java
new file mode 100644
index 0000000..f644cc2
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/BaseAlgorithm.java
@@ -0,0 +1,222 @@
+/* BaseAlgorithm.java
+ *
+ * created: Wed Dec 16 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/BaseAlgorithm.java,v 1.9 2009-06-26 15:52:48 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.sequence.*;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.FontMetrics;
+import java.awt.Color;
+import java.awt.BasicStroke;
+
+/**
+ * The BaseAlgorithm class is the base class for algorithms that work
+ * directly on bases. A BaseAlgorithm has a name and is specific to one
+ * Strand of DNA, meaning the algorithm can't change strand part way along.
+ *
+ * @author Kim Rutherford
+ * @version $Id: BaseAlgorithm.java,v 1.9 2009-06-26 15:52:48 tjc Exp $
+ **/
+
+public abstract class BaseAlgorithm extends Algorithm
+{
+ private Strand strand;
+
+ /**
+ * Create a new BaseAlgorithm object.
+ * @param strand The strand to do the calculation on.
+ * @param algorithm_name A String used to identify this algorithm to the
+ * user.
+ * @param algorithm_short_name A String used to identify this algorithm
+ * internally. See the Algorithm constructor for more details.
+ **/
+ public BaseAlgorithm (final Strand strand, final String algorithm_name,
+ final String algorithm_short_name)
+ {
+ super (algorithm_name, algorithm_short_name);
+ this.bases = strand.getBases();
+ this.strand = strand;
+
+ if (strand.isForwardStrand ()) {
+ forward_flag = true;
+ } else {
+ forward_flag = false;
+ }
+ }
+
+ /**
+ * Return the Bases object of the Strand that was passed to the
+ * constructor.
+ **/
+ public Bases getBases () {
+ return bases;
+ }
+
+ /**
+ * Returns the strand we will do the calculation on.
+ **/
+ public Strand getStrand () {
+ if (forward_flag ^ rev_comp_display) {
+ return getBases ().getForwardStrand ();
+ } else {
+ return getBases ().getReverseStrand ();
+ }
+ }
+
+ /**
+ * If rev_comp_display is true all calculations will be performs on the
+ * opposite Strand to the strand that was passed to the constructor.
+ **/
+ public void setRevCompDisplay (final boolean rev_comp_display) {
+ this.rev_comp_display = rev_comp_display;
+ }
+
+ /**
+ * Returns true if the FeatureDisplay is reverse complemented. All
+ * calculations should be performed on the opposite Strand to the strand
+ * that was passed to the constructor.
+ **/
+ public boolean isRevCompDisplay () {
+ return rev_comp_display;
+ }
+
+
+ /**
+ * Draw in a legend
+ */
+ public void drawLegend(Graphics g, int font_height,
+ int font_width, LineAttributes[] lines,
+ int numPlots)
+ {
+ Graphics2D g2d = (Graphics2D)g;
+
+ FontMetrics fm = g2d.getFontMetrics();
+ int lineHgt = 3 * font_height/4;
+
+ if( (strand.isForwardStrand() && !isRevCompDisplay()) ||
+ (!strand.isForwardStrand() && isRevCompDisplay()))
+ {
+ int width = 0;
+ for(int i=0; i<numPlots; i++)
+ {
+ g2d.setColor(Color.black);
+
+ if(lines[i].getLabel() != null) // user defined label
+ {
+ g2d.drawString(lines[i].getLabel(),width,font_height);
+ width += lines[i].getLabelWidth(fm);
+ }
+ else
+ {
+ g2d.drawString(Integer.toString(i+1),width,font_height);
+ width += 5*font_width;
+ }
+
+ BasicStroke stroke = (BasicStroke)g2d.getStroke();
+ g2d.setStroke(new BasicStroke(3.f));
+ g2d.setColor(lines[i].getLineColour());
+ g2d.drawLine(width - (font_width*1), lineHgt, width - (font_width*3), lineHgt);
+ g2d.setStroke(stroke);
+ }
+ }
+ else
+ {
+ g2d.setColor(Color.black);
+ g2d.drawString("4",0,font_height);
+ g2d.drawString("5",font_width*5,font_height);
+ g2d.drawString("6",font_width*10,font_height);
+
+ BasicStroke stroke = (BasicStroke)g2d.getStroke();
+ g2d.setStroke(new BasicStroke(3.f));
+ int frame = strand.getSequenceLength() % 3;
+
+ //System.out.println("FRAME "+frame+" length="+strand.getSequenceLength());
+ Color col4 = null;
+ Color col5 = null;
+ Color col6 = null;
+
+ switch(frame)
+ {
+ case 0:
+ col4 = lines[1].getLineColour();
+ col5 = lines[2].getLineColour();
+ col6 = lines[0].getLineColour();
+ break;
+ case 1:
+ col4 = lines[2].getLineColour();
+ col5 = lines[0].getLineColour();
+ col6 = lines[1].getLineColour();
+ break;
+ case 2:
+ col4 = lines[0].getLineColour();
+ col5 = lines[1].getLineColour();
+ col6 = lines[2].getLineColour();
+ break;
+ }
+
+ g2d.setColor(col4);
+ g2d.drawLine(font_width*2, lineHgt, font_width*4, lineHgt);
+
+ g2d.setColor(col5);
+ g2d.drawLine(font_width*7, lineHgt, font_width*9, lineHgt);
+
+ g2d.setColor(col6);
+ g2d.drawLine(font_width*12, lineHgt, font_width*14, lineHgt);
+ g2d.setStroke(stroke);
+ }
+ }
+
+ /**
+ * Return the value of the function between a pair of bases.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The results are returned in this array, hence it should be
+ * allocated at the size given by getValueCount ().
+ **/
+ public abstract void getValues (int start, int end, final float [] values);
+
+ /**
+ * Return the number of values a call to getValues () will return.
+ **/
+ public abstract int getValueCount ();
+
+ /**
+ * The Bases we will do the calculation on.
+ **/
+ private Bases bases;
+
+ /**
+ * If rev_comp_display is true all calculations will be performed on the
+ * opposite Strand to the strand that was passed to the constructor.
+ **/
+ private boolean rev_comp_display = false;
+
+ /**
+ * true if and only if the calculations should be done on the forward
+ * Strand.
+ **/
+ private boolean forward_flag;
+}
diff --git a/uk/ac/sanger/artemis/plot/CSCSAlgorithm.java b/uk/ac/sanger/artemis/plot/CSCSAlgorithm.java
new file mode 100644
index 0000000..ba36e79
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/CSCSAlgorithm.java
@@ -0,0 +1,432 @@
+/* CSCSAlgorithm.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ *
+ * Corrected Scaled Chi Square (Mathur and Tuli 1991)
+ * Mathur M, Tuli R (1991) Analysis of codon usage in genes for nitrogen
+ * fixation from phylogenetically diverse organisms. J Mol Evol 32:364-373
+ *
+ * @author Derek Gatherer
+ * original version 10-09-03
+ * revised 01-12-04
+ **/
+
+public class CSCSAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new CSCSAlgorithm object.
+ * @param strand The Strand to do the calculation on.
+ **/
+ public CSCSAlgorithm(final Strand strand)
+ {
+ super(strand, makeName(strand), "Corrected Scaled Chi Square");
+ setScalingFlag(true);
+ }
+
+ public void drawLegend(Graphics g, int font_height,
+ int font_width, Color[] frameColour)
+ {
+
+ }
+
+ /**
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range). If the start/end pair
+ * doesn't give a multiple of three bases end is moved down so that it is
+ * a multiple of three.
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ if(getStrand().isForwardStrand()) // rather than isRevCompDisplay()
+ {
+// System.out.println("isRevCompDisplay does not activate here");
+ }
+ else
+ {
+ final int new_end =
+ getStrand().getBases().getComplementPosition(start);
+ final int new_start =
+ getStrand().getBases().getComplementPosition(end);
+
+ end = new_end;
+ start = new_start;
+// System.out.println("Revcomp, so new start:"+start+"new end:"+end);
+ }
+
+ final String sub_sequence;
+
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ if(getStrand().isForwardStrand())
+ end -= (end - start + 1) % 3;
+ else
+ start += (end - start + 1) % 3;
+
+ try
+ {
+ sub_sequence = getStrand().getSubSequence(new Range(start, end));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final float[][][][] exp_value = new float[4][4][4][4]; // 3D for bases, 1 for frame
+
+ char [] sequence_raw;
+ sequence_raw = sub_sequence.toCharArray();
+
+ int [][][][] obs_value = new int [4][4][4][4];
+ float [] chi_square = new float [3];
+
+ for(int x = 0 ; x < 4 ; ++x)
+ {
+ for(int y = 0 ; y < 4 ; ++y)
+ {
+ for(int z = 0 ; z < 4 ; ++z)
+ {
+ for(int a = 0 ; a < 4 ; ++a)
+ {
+ obs_value[x][y][z][a] = 0;
+ exp_value[x][y][z][a] = 0;
+ }
+ }
+ }
+ }
+
+ char this_f_base = 0;
+ char next_f_base = 0;
+ char last_f_base = 0;
+ int this_f_base_index = 0;
+ int next_f_base_index = 0;
+ int last_f_base_index = 0;
+
+ int GC = 0; // GC count
+ float GCfreq = 0; // GC/length
+
+// get GC content
+ for(int c = 0 ; c < sequence_raw.length; c++)
+ {
+ char this_base = sequence_raw[c];
+ if(this_base == 'g' || this_base == 'c')
+ GC++;
+ }
+ GCfreq = (float)GC/sequence_raw.length;
+
+ chi_square = new float [3];
+ for(int i = 0 ; i < sequence_raw.length - 5 ; i+=3)
+ {
+ for(int frame = 0; frame < 3; frame++)
+ {
+ this_f_base = sequence_raw[i + frame];
+ next_f_base = sequence_raw[i + 1 + frame];
+ last_f_base = sequence_raw[i + 2 + frame];
+
+ this_f_base_index = Bases.getIndexOfBase(this_f_base);
+ next_f_base_index = Bases.getIndexOfBase(next_f_base);
+ last_f_base_index = Bases.getIndexOfBase(last_f_base);
+
+ // ignore Ns
+ if(this_f_base_index < 4 && next_f_base_index < 4 && last_f_base_index < 4)
+ ++obs_value[this_f_base_index][next_f_base_index][last_f_base_index][(frame+start)%3];
+ }
+ }
+
+ // having collected the observed values in all 3 frames, formulate the expected
+
+ for(int frame_index = 0 ; frame_index < 3 ; ++frame_index)
+ {
+ int frame = frame_index; //(frame_index+start)%3; // so it corresponds to the above
+ // Phe 001 000
+ // 2-mer AT exp = total aa * (1-GC)
+ exp_value[0][0][0][frame] = (obs_value[0][0][0][frame]+obs_value[0][0][1][frame])*(1-GCfreq);
+ // 2-mer GC exp = total aa * GC
+ exp_value[0][0][1][frame] = (obs_value[0][0][0][frame]+obs_value[0][0][1][frame])*GCfreq;
+ // Leu = 002 003 100 101 102 103
+ // arg leu SNW exp = total aa * GC * (1-GC) /1.5 100 003 102
+ // arg leu SNS exp = total aa * GC * GC /1.5 101 103
+ // arg leu WNW exp = total aa * (1-GC) * (1-GC)/1.5 002
+ exp_value[0][0][2][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* ((1-GCfreq) * (1-GCfreq))/(float)1.5;
+ exp_value[0][0][3][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+ exp_value[1][0][0][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+ exp_value[1][0][1][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* (GCfreq * GCfreq) /(float)1.5;
+ exp_value[1][0][2][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* ((1-GCfreq) * (1-GCfreq))/(float)1.5;
+ exp_value[1][0][3][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* (GCfreq * GCfreq) /(float)1.5;
+// Ile = 200 201 202
+// ile A/T exp = total aa * (1-GC)/1.5
+ exp_value[2][0][0][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])* (1-GCfreq)/(float)1.5;
+ exp_value[2][0][1][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])* GCfreq/(float)1.5;
+ exp_value[2][0][2][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])* (1-GCfreq)/(float)1.5;
+// Val = 300 301 302 303
+// 4-mer AT exp = total aa * (1-GC)/2
+ exp_value[3][0][0][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])*(1-GCfreq)/2;
+// 4-mer GC exp = total aa * GC/2
+ exp_value[3][0][1][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])*GCfreq/2;
+ exp_value[3][0][2][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])*(1-GCfreq)/2;
+ exp_value[3][0][3][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])*GCfreq/2;
+// Ser = 010 011 012 013 230 231
+// ser AT exp = total aa * (1-GC)/3
+ exp_value[0][1][0][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*(1-GCfreq)/3;
+// ser GC exp = total aa * GC/3
+ exp_value[0][1][1][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*GCfreq/3;
+ exp_value[0][1][2][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*(1-GCfreq)/3;
+ exp_value[0][1][3][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*GCfreq/3;
+ exp_value[2][3][0][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*(1-GCfreq)/3;
+ exp_value[2][3][1][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*GCfreq/3;
+// Pro = 110 111 112 113
+ exp_value[1][1][0][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])*(1-GCfreq)/2;
+ exp_value[1][1][1][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])*GCfreq/2;
+ exp_value[1][1][2][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])*(1-GCfreq)/2;
+ exp_value[1][1][3][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])*GCfreq/2;
+// Thr = 210 211 212 213
+ exp_value[2][1][0][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])*(1-GCfreq)/2;
+ exp_value[2][1][1][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])*GCfreq/2;
+ exp_value[2][1][2][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])*(1-GCfreq)/2;
+ exp_value[2][1][3][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])*GCfreq/2;
+// Ala = 310 311 312 313
+ exp_value[3][1][0][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])*(1-GCfreq)/2;
+ exp_value[3][1][1][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])*GCfreq/2;
+ exp_value[3][1][2][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])*(1-GCfreq)/2;
+ exp_value[3][1][3][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])*GCfreq/2;
+// Tyr = 020 021
+ exp_value[0][2][0][frame] = (obs_value[0][2][0][frame]+obs_value[0][2][1][frame])*(1-GCfreq);
+ exp_value[0][2][1][frame] = (obs_value[0][2][0][frame]+obs_value[0][2][1][frame])*GCfreq;
+// His = 120 121
+ exp_value[1][2][0][frame] = (obs_value[1][2][0][frame]+obs_value[1][2][1][frame])*(1-GCfreq);
+ exp_value[1][2][1][frame] = (obs_value[1][2][0][frame]+obs_value[1][2][1][frame])*GCfreq;
+// Gln = 122 123
+ exp_value[1][2][2][frame] = (obs_value[1][2][2][frame]+obs_value[1][2][3][frame])*(1-GCfreq);
+ exp_value[1][2][3][frame] = (obs_value[1][2][2][frame]+obs_value[1][2][3][frame])*GCfreq;
+// Asn = 220 221
+ exp_value[2][2][0][frame] = (obs_value[2][2][0][frame]+obs_value[2][2][1][frame])*(1-GCfreq);
+ exp_value[2][2][1][frame] = (obs_value[2][2][0][frame]+obs_value[2][2][1][frame])*GCfreq;
+// Lys = 222 223
+ exp_value[2][2][2][frame] = (obs_value[2][2][2][frame]+obs_value[2][2][3][frame])*(1-GCfreq);
+ exp_value[2][2][3][frame] = (obs_value[2][2][2][frame]+obs_value[2][2][3][frame])*GCfreq;
+// Asp = 320 321
+ exp_value[3][2][0][frame] = (obs_value[3][2][0][frame]+obs_value[3][2][1][frame])*(1-GCfreq);
+ exp_value[3][2][1][frame] = (obs_value[3][2][0][frame]+obs_value[3][2][1][frame])*GCfreq;
+// Glu = 322 323
+ exp_value[3][2][2][frame] = (obs_value[3][2][2][frame]+obs_value[3][2][3][frame])*(1-GCfreq);
+ exp_value[3][2][3][frame] = (obs_value[3][2][2][frame]+obs_value[3][2][3][frame])*GCfreq;
+//Cys = 030 031
+ exp_value[0][3][0][frame] = (obs_value[0][3][0][frame]+obs_value[0][3][1][frame])*(1-GCfreq);
+ exp_value[0][3][1][frame] = (obs_value[0][3][0][frame]+obs_value[0][3][1][frame])*GCfreq;
+// Arg = 130 131 132 133 232 233
+// arg leu SNW exp = total aa * GC * (1-GC) /1.5 130 233 132
+// arg leu SNS exp = total aa * GC * GC /1.5 131 133
+// arg leu WNW exp = total aa * (1-GC) * (1-GC)/1.5 232
+ exp_value[1][3][0][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+ exp_value[1][3][1][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * GCfreq) /(float)1.5;
+ exp_value[1][3][2][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+ exp_value[1][3][3][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * GCfreq) /(float)1.5;
+ exp_value[2][3][2][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* ((1-GCfreq) * (1-GCfreq))/(float)1.5;
+ exp_value[2][3][3][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+// Gly = 330 331 332 333
+ exp_value[3][3][0][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])*(1-GCfreq)/2;
+ exp_value[3][3][1][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])*GCfreq/2;
+ exp_value[3][3][2][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])*(1-GCfreq)/2;
+ exp_value[3][3][3][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])*GCfreq/2;
+
+ chi_square[frame] = 0;
+ exp_value[2][0][3][frame] = 0; // don't count Met
+ exp_value[0][3][3][frame] = 0; // don't count Trp
+ exp_value[0][2][2][frame] = 0; // don't count STOP
+ exp_value[0][2][3][frame] = 0; // don't count STOP
+ exp_value[0][3][2][frame] = 0; // don't count STOP
+
+// having calculated expected, now do chi_square
+ chi_square[0]=0; chi_square[1]=0; chi_square[2]=0;
+ for(int first_base = 0; first_base < 4; ++first_base)
+ {
+ for(int second_base = 0; second_base < 4; ++second_base)
+ {
+ for(int third_base = 0; third_base < 4; ++third_base)
+ {
+ final float obs = obs_value[first_base][second_base][third_base][frame];
+ final float exp = exp_value[first_base][second_base][third_base][frame];
+ if(exp >= 1)
+ {
+ if(exp <= 5)
+ {
+ if(Math.abs(exp-obs)>0.25)
+ chi_square[frame] += (Math.pow((Math.abs(exp-obs)-0.5),2))/exp;
+ else
+ chi_square[frame] += (Math.pow((exp-obs),2))/exp;
+ }
+ else
+ chi_square[frame] += (Math.pow((exp-obs),2))/exp;
+ }
+ }
+ }
+ }
+ values[frame] = chi_square[frame];
+ }
+ }
+
+ /**
+ * Return the number of values a call to getValues() will return - three
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 3;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(500);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(24);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 10)
+ {
+ Integer step = new Integer(24);
+// Integer step = new Integer(window_size/10);
+// int step_int = step.intValue();
+// step_int+=(step_int % 3); // step is multiple of 3
+// Integer step_out = new Integer(step_int);
+// return step_out;
+ return step;
+
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 2.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(1000);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ return null;
+ }
+
+ private static String makeName(final Strand strand)
+ {
+ if(strand.isForwardStrand())
+ return "Corrected Scaled Chi Square";
+ else
+ return "Reverse Corrected Scaled Chi Square";
+ }
+} // end of class
diff --git a/uk/ac/sanger/artemis/plot/Codon12CorrelationAlgorithm.java b/uk/ac/sanger/artemis/plot/Codon12CorrelationAlgorithm.java
new file mode 100644
index 0000000..d88e820
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/Codon12CorrelationAlgorithm.java
@@ -0,0 +1,313 @@
+/* Codon12CorrelationAlgorithm.java
+ *
+ * created: Mon Feb 1 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/Codon12CorrelationAlgorithm.java,v 1.4 2006-06-23 10:40:14 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+
+import java.awt.*;
+
+/**
+ * Objects of this class have one useful method - getValues(), which takes a
+ * range of bases and returns three floating point numbers, which are the
+ * codon position 1 and 2 correlation scores in each of the three frames for
+ * the given strand. The Strand to use is set in the constructor.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Codon12CorrelationAlgorithm.java,v 1.4 2006-06-23 10:40:14 tjc Exp $
+ **/
+public class Codon12CorrelationAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new Codon12CorrelationAlgorithm object.
+ * @param strand The strand to do the calculation on.
+ **/
+ public Codon12CorrelationAlgorithm(final Strand strand)
+ {
+ super(strand, makeName(strand), "correlation_score");
+ setScalingFlag(true);
+ }
+
+ /**
+ * Magic numbers used by getValues() and Feature.get12CorrelationScore()
+ * to calculate the correlation score for codon positions 1 and 2. These
+ * are the values for t, c, a, g in the first position.
+ **/
+ public static double[] correlation_score_factors_1 =
+ {
+ 17.7, 21.1, 27.7, 33.6
+ };
+
+ /**
+ * Magic numbers used by getValues() and Feature.get12CorrelationScore()
+ * to calculate the correlation score for codon positions 1 and 2. These
+ * are the values for t, c, a, g in the second position.
+ **/
+ public static double[] correlation_score_factors_2 =
+ {
+ 27.1, 23.8, 31.0, 18.2
+ };
+
+ /**
+ * Return the codon position 1 and 2 correlation scores between a pair of
+ * bases in each of the three frames.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range). If the start/end pair
+ * doesn't give a multiple of three bases end is moved down so that it is
+ * a multiple of three.
+ * @param values The three results are returned in this array, hence it
+ * should be three long. There is one value for each frame.
+ **/
+ public void getValues(int start, int end, final float[] values)
+ {
+ if(isRevCompDisplay())
+ {
+ final int new_end =
+ getStrand().getBases().getComplementPosition(start);
+ final int new_start =
+ getStrand().getBases().getComplementPosition(end);
+
+ end = new_end;
+ start = new_start;
+ }
+
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ if(getStrand().isForwardStrand())
+ end -= (end - start + 1) % 3;
+ else
+ start += (end - start + 1) % 3;
+
+ final char[] sub_sequence_raw;
+
+ try
+ {
+ sub_sequence_raw = getStrand().getRawSubSequenceC(new Range(start, end));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final float gc_counts[] = new float[3];
+
+ // the first index is the position the second is the base (t,c,a,g)
+ final int[][] positional_base_counts = new int[4][3];
+ final int sub_sequence_length = sub_sequence_raw.length;
+
+ if(getStrand().isForwardStrand())
+ {
+ for(int i = 0 ; i < sub_sequence_length ; ++i)
+ {
+ final int base_index = Bases.getIndexOfBase(sub_sequence_raw[i]);
+ if(base_index < 4)
+ ++positional_base_counts[base_index][i % 3];
+ }
+ }
+ else
+ {
+ final char[] complement_sub_sequence_raw = Bases.complement(sub_sequence_raw);
+
+ for(int i = 0 ; i < sub_sequence_length ; ++i)
+ {
+ final int base_index =
+ Bases.getIndexOfBase(complement_sub_sequence_raw[i]);
+ if(base_index < 4)
+ {
+ final int position_index = i % 3;
+ ++positional_base_counts[base_index][position_index];
+ }
+ }
+ }
+
+ final int whole_sequence_length = getStrand().getSequenceLength();
+ final int whole_sequence_length_mod3 = whole_sequence_length % 3;
+
+ for(int frame = 0 ; frame < 3 ; ++frame)
+ {
+ final double cor1_2_score =
+ 3.0 * (1.0 * positional_base_counts[0][frame]/sub_sequence_length *
+ correlation_score_factors_1[0] +
+
+ 1.0 * positional_base_counts[1][frame]/sub_sequence_length *
+ correlation_score_factors_1[1] +
+
+ 1.0 * positional_base_counts[2][frame]/sub_sequence_length *
+ correlation_score_factors_1[2] +
+
+ 1.0 * positional_base_counts[3][frame]/sub_sequence_length *
+ correlation_score_factors_1[3] +
+
+ 1.0 * positional_base_counts[0][(frame + 1)%3]/
+ sub_sequence_length *
+ correlation_score_factors_2[0] +
+
+ 1.0 * positional_base_counts[1][(frame + 1)%3]/
+ sub_sequence_length *
+ correlation_score_factors_2[1] +
+
+ 1.0 * positional_base_counts[2][(frame + 1)%3]/
+ sub_sequence_length *
+ correlation_score_factors_2[2] +
+
+ 1.0 * positional_base_counts[3][(frame + 1)%3]/
+ sub_sequence_length *
+ correlation_score_factors_2[3]) +
+ 0.5; // add 0.5 because that is what the old uk.ac.sanger.artemis did
+
+ // add 2 brings the frame colouring in-line with codon usage
+ if(getStrand().isForwardStrand())
+ values [(start + frame + 2) % 3] = (float)cor1_2_score;
+ else
+ values [(start + frame + whole_sequence_length_mod3 + 2) % 3] = (float)cor1_2_score;
+ }
+
+ /*
+ for(int frame = 0 ; frame < 3 ; ++frame)
+ {
+ if(getStrand().isForwardStrand())
+ System.out.println("FWD "+frame+" "+((start + frame + 2) % 3));
+ else
+ System.out.println("BWD "+frame+" "+((start + frame + whole_sequence_length_mod3 -1) % 3)+
+ " "+whole_sequence_length_mod3);
+ }
+ */
+ }
+
+
+ /**
+ * Return the number of values a call to getValues () will return - three
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 3;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(240);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(600);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(48);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 8)
+ return new Integer(window_size / 8);
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(100);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ return new Float(52.7);
+ }
+
+ /**
+ * Returns "Codon 1 and 2 Scores" if the given strand is a forward strand
+ * otherwise returns "Reverse Codon 1 and 2 Scores".
+ **/
+ private static String makeName(final Strand strand)
+ {
+ if(strand.isForwardStrand())
+ return "Correlation Scores";
+ else
+ return "Reverse Correlation Scores";
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/CodonUsageAlgorithm.java b/uk/ac/sanger/artemis/plot/CodonUsageAlgorithm.java
new file mode 100644
index 0000000..c5016fd
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/CodonUsageAlgorithm.java
@@ -0,0 +1,299 @@
+/* CodonUsageAlgorithm.java
+ *
+ * created: Tue Apr 13 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/CodonUsageAlgorithm.java,v 1.2 2006-06-23 10:40:14 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns three floating point numbers, which are the
+ * codon usage scores in each frame. The Strand to use is set in the
+ * constructor.
+ * See Gribskov et al. (Nucl. Acids Res. 12(1); 539-549 (1984)).
+ *
+ * @author Kim Rutherford
+ * @version $Id: CodonUsageAlgorithm.java,v 1.2 2006-06-23 10:40:14 tjc Exp $
+ **/
+
+public class CodonUsageAlgorithm extends BaseAlgorithm {
+ /**
+ * Create a new CodonUsageAlgorithm object. See getValues () for detail on
+ * how the weightings object is used.
+ * @param strand The Strand to do the calculation on.
+ * @param usage_data This object is the source of the codon usage data that
+ * will be used by getValues ().
+ **/
+ public CodonUsageAlgorithm (final Strand strand,
+ final CodonUsageWeight usage_data) {
+ super (strand, makeName (strand, usage_data), "codon_usage");
+
+ this.usage_data = usage_data;
+
+ setScalingFlag (true);
+ }
+
+ /**
+ * Return the average of the user weightings for the codons in the given
+ * window.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The three return values for this algorithm are returned in
+ * this array. There is one value for each frame and the value is the
+ * the average of the weightings for the codons in the range.
+ **/
+ public void getValues(int start, int end, final float[] values)
+ {
+ if (isRevCompDisplay ())
+ {
+ final int new_end =
+ getStrand ().getBases ().getComplementPosition (start);
+ final int new_start =
+ getStrand ().getBases ().getComplementPosition (end);
+
+ end = new_end;
+ start = new_start;
+ }
+
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ if(getStrand ().isForwardStrand ())
+ end -= (end - start + 1) % 3;
+ else
+ start += (end - start + 1) % 3;
+
+ final char[] sequence;
+
+ try
+ {
+ sequence = getStrand ().getRawSubSequenceC(new Range (start, end));
+ }
+ catch (OutOfRangeException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ float[] totals = { 0, 0, 0 };
+
+ // a count of the number of codons we have seen
+ int codon_count = 0;
+
+ final int sub_sequence_length = sequence.length;
+
+ if(getStrand ().isForwardStrand ())
+ {
+ for (int frame = 0 ; frame < 3 ; ++frame)
+ {
+ final int real_frame = (frame + start + 2) % 3;
+ for (int i = frame ; i < sub_sequence_length - 3 ; i += 3)
+ {
+ final char base1 = sequence[i];
+ final char base2 = sequence[i + 1];
+ final char base3 = sequence[i + 2];
+
+ final float this_weight =
+ usage_data.getCodonValue(base1, base2, base3);
+
+ ++codon_count;
+ totals[real_frame] += Math.log(this_weight);
+ }
+ }
+ }
+ else
+ {
+ for(int frame = 2; frame >= 0 ; --frame)
+ {
+ final int real_frame = (frame + start + 2) % 3;
+ for (int i = frame ; i < sub_sequence_length - 3 ; i += 3)
+ {
+ final char base1 = Bases.complement(sequence[i + 2]);
+ final char base2 = Bases.complement(sequence[i + 1]);
+ final char base3 = Bases.complement(sequence[i]);
+
+ final float this_weight =
+ usage_data.getCodonValue(base1, base2, base3);
+
+ ++codon_count;
+ totals[real_frame] += Math.log(this_weight);
+ }
+ }
+ }
+
+ for (int frame = 0 ; frame < 3 ; ++frame)
+ {
+ if (codon_count == 0)
+ values[frame] = 0;
+ else
+ values[frame] = (float)Math.exp(totals[frame] / codon_count);
+ }
+ }
+
+
+ /**
+ * Return the number of values a call to getValues () will return - three
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 3;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize () {
+ final Integer super_window_size = super.getDefaultWindowSize ();
+ if (super_window_size != null) {
+ // the superclass version of getDefaultWindowSize () returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer (120);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize () {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize ();
+ if (super_max_window_size != null) {
+ // the superclass version of getDefaultMaxWindowSize () returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer (500);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize () {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize ();
+ if (super_min_window_size != null) {
+ // the superclass version of getDefaultMinWindowSize () returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer (24);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size) {
+ if (window_size > 8) {
+ return new Integer (window_size / 8);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (2);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage () {
+ return null;
+ }
+
+ /**
+ * Calculate the codon usage score for the given Feature.
+ **/
+ public float getFeatureScore (final Feature feature) {
+ final String sequence = feature.getTranslationBases ();
+
+ float total = 0F;
+
+ for (int i = 0 ; i < sequence.length () ; i += 3) {
+
+ final char base1 = sequence.charAt (i);
+ final char base2 = sequence.charAt (i + 1);
+ final char base3 = sequence.charAt (i + 2);
+
+ final float this_weight =
+ usage_data.getCodonValue (base1, base2, base3);
+
+ total += Math.log (this_weight);
+ }
+
+ final int codon_count = sequence.length () / 3;
+
+ return (float) Math.exp (total / codon_count);
+ }
+
+ /**
+ * Returns "Codon Usage Scores" if the given strand is a forward strand
+ * otherwise returns "Reverse Codon Usage Scores".
+ * @param strand The Strand to do the calculation on.
+ * @param codon_weight This is used to get the name of the file that the
+ * usage information came from.
+ **/
+ private static String makeName (final Strand strand,
+ final CodonWeight codon_weight) {
+ if (strand.isForwardStrand ()) {
+ return "Codon Usage Scores from " + codon_weight.getName ();
+ } else {
+ return "Reverse Codon Usage Scores from " + codon_weight.getName ();
+ }
+ }
+
+ /**
+ * The CodonWeight reference that was passed to the constructor.
+ **/
+ final private CodonWeight usage_data;
+}
diff --git a/uk/ac/sanger/artemis/plot/CodonUsageFormatException.java b/uk/ac/sanger/artemis/plot/CodonUsageFormatException.java
new file mode 100644
index 0000000..8e1df3c
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/CodonUsageFormatException.java
@@ -0,0 +1,46 @@
+/* CodonUsageFormatException.java
+ *
+ * created: Wed Apr 14 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/CodonUsageFormatException.java,v 1.1 2004-06-09 09:51:20 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import java.io.*;
+
+/**
+ * This exception is thrown if a file read by a CodonUsageWeight object is
+ * incorrectly formatted.
+ *
+ * @author Kim Rutherford
+ * @version $Id: CodonUsageFormatException.java,v 1.1 2004-06-09 09:51:20 tjc Exp $
+ **/
+
+public class CodonUsageFormatException extends IOException {
+ /**
+ * Create a new CodonUsageFormatException object.
+ **/
+ public CodonUsageFormatException (final String message) {
+ super (message);
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/plot/CodonUsageWeight.java b/uk/ac/sanger/artemis/plot/CodonUsageWeight.java
new file mode 100644
index 0000000..fb1597d
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/CodonUsageWeight.java
@@ -0,0 +1,366 @@
+/* CodonUsageWeight.java
+ *
+ * created: Tue Apr 13 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/CodonUsageWeight.java,v 1.1 2004-06-09 09:51:22 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+
+import uk.ac.sanger.artemis.util.*;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Objects of this class are the source of the codon usage weights that are
+ * used by the methods in the CodonWindowAlgorithm class.
+ * See Gribskov et al. (Nucl. Acids Res. 12(1); 539-549 (1984)).
+ * @author Kim Rutherford
+ * @version $Id: CodonUsageWeight.java,v 1.1 2004-06-09 09:51:22 tjc Exp $
+ **/
+
+public class CodonUsageWeight extends CodonWeight {
+ /**
+ * Create a new CodonUsageWeight object.
+ * @param usage_file The File to read the codon usage stats from.
+ * @param strand The Strand that this object will be generating values from.
+ * This is needed so that we can get the frequencies of t, c, a and g.
+ **/
+ public CodonUsageWeight (final File usage_file, final Strand strand)
+ throws IOException {
+ this.usage_file = usage_file;
+
+ makeSequenceData (strand);
+
+ readFromFile ();
+ }
+
+ /**
+ * Returns the name of the file that the usage information was read from.
+ **/
+ public String getName () {
+ return usage_file.getName ();
+ }
+
+ /**
+ * Return the codon score for the given codon. The returned value will be
+ * between 0 and 1 (higher scores means a better value).
+ * @param codon XXX A lowercase string containing the bases of the codon to
+ * look up.
+ **/
+ public float getCodonValue (final char base1, final char base2,
+ final char base3) {
+ final int index_of_base1 = Bases.getIndexOfBase (base1);
+ final int index_of_base2 = Bases.getIndexOfBase (base2);
+ final int index_of_base3 = Bases.getIndexOfBase (base3);
+
+ if (index_of_base1 > 3 || index_of_base2 > 3 || index_of_base3 > 3) {
+ // there is a non-base character in the sequence
+ return 1.0F;
+ }
+
+ final int index =
+ index_of_base1 * 16 + index_of_base2 * 4 + index_of_base3;
+
+ final char translation_character =
+ AminoAcidSequence.getCodonTranslation (base1, base2, base3);
+
+ final int synonymous_codon_index =
+ AminoAcidSequence.getSymbolIndex (translation_character);
+
+// System.out.println ("-----> " + data[index] + " " +
+// residue_data[synonymous_codon_index] + " " +
+// strand_data[index] + " " +
+// strand_residue_data[synonymous_codon_index]);
+
+ return
+ (data[index]/residue_data[synonymous_codon_index])/
+ (strand_data[index]/strand_residue_data[synonymous_codon_index]);
+ }
+
+ /**
+ * Parse one line of the form:
+ * <pre>
+ * UUU 32.2( 48423) UCU 30.5( 45913) UAU 21.8( 32829) UGU 8.9( 13371)
+ * </pre>
+ * Returns the four frequency count per 1000 from the line - in the
+ * example above it returns 32.2, 30.5, 21.8 and 8.9
+ **/
+ private float [] parseLine (String line)
+ throws IOException {
+ final float [] return_array = new float [4];
+
+ line = line.trim ();
+
+ int return_array_index = 0;
+
+ final StringTokenizer tokeniser = new StringTokenizer (line, " ()");
+
+// System.out.println (line + ": " + tokeniser.countTokens ());
+
+ if (tokeniser.countTokens () != 12) {
+ final String message =
+ "garbage codon usage data file at this point: " + line;
+ throw new CodonUsageFormatException (message);
+ }
+
+
+ for (int i = 0 ; i < 4 ; ++i) {
+
+ // ignore first token of the block - which is a codon string like "GCA"
+ tokeniser.nextToken ();
+
+ final String codon_value = tokeniser.nextToken ();
+
+ return_array[i] = Float.valueOf (codon_value).floatValue ();
+
+// System.out.println (">" + return_array[i]);
+
+ // ignore the occurrence count
+ tokeniser.nextToken ();
+ }
+
+ return return_array;
+ }
+
+ /**
+ * Read the codon usage information from the given stream. Each line of
+ * the input should be in this form:
+ * <pre>
+ * UUU 32.2( 48423) UCU 30.5( 45913) UAU 21.8( 32829) UGU 8.9( 13371)
+ * </pre>
+ * The usage information is read into the data and residue_data arrays.
+ **/
+ private void readDataFromStream (final Document document)
+ throws IOException {
+ final Reader document_reader = document.getReader ();
+
+ final BufferedReader buffered_reader =
+ new BufferedReader (document_reader);
+
+ int current_index = 0;
+
+ while (true) {
+ final String this_line = buffered_reader.readLine ();
+
+ if (this_line == null) {
+ break;
+ }
+
+ if (this_line.trim ().length () == 0) {
+ // ignore blank lines
+ continue;
+ }
+
+ if (current_index >= 64) {
+ // we have all the values we need but there are more non blank lines
+ final String message =
+ "too many lines in the codon usage data file at this point: " +
+ this_line;
+ throw new CodonUsageFormatException (message);
+ }
+
+ // this will return exactly 4 values or will throw a
+ // CodonUsageFormatException object
+ final float [] line_data = parseLine (this_line);
+
+ for (int i = 0 ; i < 4 ; ++i) {
+ final int upper_index = (current_index & 0x0c) * 4;
+
+ final int lower_index = current_index & 0x03;
+
+ final int real_index = upper_index + lower_index + i*4;
+
+ if (line_data[i] < 0.01F) {
+ data[real_index] = 0.01F;
+// System.out.println ("zzzzz: " + line_data[i] + " " + data[real_index]);
+ } else {
+ data[real_index] = line_data[i];
+// System.out.println ("foo: " + line_data[i] + " " + data[real_index]);
+ }
+
+ final char translation_character =
+ codon_translation_array[real_index];
+
+ final int symbol_index =
+ AminoAcidSequence.getSymbolIndex (translation_character);
+
+ residue_data[symbol_index] += data[real_index];
+
+// System.out.println ("--> " + translation_character + " " +
+// symbol_index + " " + line_data[i] + " " +
+// residue_data[symbol_index] + " " + i +
+// " " + current_index + " " +
+// real_index);
+
+ }
+
+ ++current_index;
+ }
+
+// for (int i = 0 ; i < 64 ; ++i) {
+// System.out.println (">>>> " + data[i]);
+// }
+
+ }
+
+ /**
+ * Read the codon usage information from the file_name that was passed to
+ * the constructor.
+ **/
+ private void readFromFile ()
+ throws IOException {
+
+ if (Options.readWritePossible ()) {
+ // try the current directory first
+ final Document current_dir_document = new FileDocument (usage_file);
+
+ readDataFromStream (current_dir_document);
+ }
+
+ // try to read from the installation directory
+// readDataFromStream (Diana.getCodeDirectory ().append (file_name));
+ }
+
+ /**
+ * Fill in the strand_data and strand_residue_data arrays by looking at the
+ * given Strand.
+ * @param strand The Strand that this object will be generating values from.
+ * This is needed so that we can get the frequencies of t, c, a and g.
+ **/
+ private void makeSequenceData (final Strand strand) {
+
+ for (int first_index = 0 ; first_index < 4 ; ++first_index) {
+ final int first_base_count = getStrandBaseCount (first_index, strand);
+
+ for (int second_index = 0 ; second_index < 4 ; ++second_index) {
+ final int second_base_count =
+ getStrandBaseCount (second_index, strand);
+
+ for (int third_index = 0 ; third_index < 4 ; ++third_index) {
+ final int third_base_count =
+ getStrandBaseCount (third_index, strand);
+
+ final int strand_data_index =
+ first_index * 16 + second_index * 4 + third_index;
+
+ strand_data [strand_data_index] =
+ 1.0F * first_base_count * second_base_count * third_base_count /
+ strand.getSequenceLength ();
+
+// System.out.println ("---> " + strand_data [strand_data_index] +
+// " " + first_base_count);
+
+ final char translation_character =
+ AminoAcidSequence.codon_translation_array[strand_data_index];
+
+ final int symbol_index =
+ AminoAcidSequence.getSymbolIndex (translation_character);
+
+ strand_residue_data [symbol_index] += strand_data[strand_data_index];
+
+// System.out.println ("foo: " + strand_residue_data [symbol_index]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return a base count on the given strand.
+ * @param base_index T = 0, C = 1, A = 2 and G = 3.
+ **/
+ private int getStrandBaseCount (final int base_index, final Strand strand) {
+ switch (base_index) {
+ case 0:
+ return strand.getTCount ();
+ case 1:
+ return strand.getCCount ();
+ case 2:
+ return strand.getACount ();
+ case 3:
+ return strand.getGCount ();
+ }
+ return 0;
+ }
+
+ /**
+ * This table is used for fast lookup of codon translations by
+ * readDataFromStream (). There is one entry for each codon and the
+ * entries are in this order: TTT, TTC, TTA, TTG, TCT, TCC, ...
+ **/
+ private static char [] codon_translation_array = {
+ 'f', 's', 'y', 'c',
+ 'f', 's', 'y', 'c',
+ 'l', 's', '#', '*',
+ 'l', 's', '+', 'w',
+
+ 'l', 'p', 'h', 'r',
+ 'l', 'p', 'h', 'r',
+ 'l', 'p', 'q', 'r',
+ 'l', 'p', 'q', 'r',
+
+ 'i', 't', 'n', 's',
+ 'i', 't', 'n', 's',
+ 'i', 't', 'k', 'r',
+ 'm', 't', 'k', 'r',
+
+ 'v', 'a', 'd', 'g',
+ 'v', 'a', 'd', 'g',
+ 'v', 'a', 'e', 'g',
+ 'v', 'a', 'e', 'g',
+ };
+
+ /**
+ * The 64 weight values should appear in this order: ttt, ttc, tta, ttg,
+ * tct, ..., ggg.
+ **/
+ final private float [] data = new float [64];
+
+ /**
+ * These are totals for each residue, derived from the data array above.
+ **/
+ final private float [] residue_data =
+ new float [AminoAcidSequence.symbol_count];
+
+ /**
+ * The 64 codon frequencies (per 1000) of the sequence that was passed to
+ * the constructor.
+ **/
+ final private float [] strand_data = new float [64];
+
+ /**
+ * These are totals for each residue, derived from the strand_data array
+ * above.
+ **/
+ final private float [] strand_residue_data =
+ new float [AminoAcidSequence.symbol_count];
+
+ /**
+ * The File that was passed to the constructor.
+ **/
+ final private File usage_file;
+}
+
diff --git a/uk/ac/sanger/artemis/plot/CodonWeight.java b/uk/ac/sanger/artemis/plot/CodonWeight.java
new file mode 100644
index 0000000..2ec3c36
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/CodonWeight.java
@@ -0,0 +1,50 @@
+/* CodonWeight.java
+ *
+ * created: Tue Apr 13 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/CodonWeight.java,v 1.1 2004-06-09 09:51:23 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+/**
+ * Objects of this class are the source of the codon usage data that is used
+ * by the methods in the CodonWindowAlgorithm class.
+ *
+ * @author Kim Rutherford
+ * @version $Id: CodonWeight.java,v 1.1 2004-06-09 09:51:23 tjc Exp $
+ **/
+
+abstract public class CodonWeight {
+ /**
+ * Return the codon weight for the given codon.
+ * @param codon XXX A lowercase string containing the bases of the codon to
+ * look up.
+ **/
+ abstract public float getCodonValue (final char base1, final char base2,
+ final char base3);
+
+
+ /**
+ * Returns the name of the file that the weights were read from.
+ **/
+ abstract public String getName ();
+}
diff --git a/uk/ac/sanger/artemis/plot/CoilFeatureAlgorithm.java b/uk/ac/sanger/artemis/plot/CoilFeatureAlgorithm.java
new file mode 100644
index 0000000..2c87b2e
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/CoilFeatureAlgorithm.java
@@ -0,0 +1,243 @@
+/* CoilFeatureAlgorithm.java
+ *
+ * created: Mon Dec 21 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/CoilFeatureAlgorithm.java,v 1.1 2004-06-09 09:51:24 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.io.Range;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns a single floating point number. See
+ * "Predicting Coiled Coils from Protein Sequences", Science Vol. 252 page
+ * 1162 for details of the alogrithm used.
+ *
+ * @author Kim Rutherford
+ * @version $Id: CoilFeatureAlgorithm.java,v 1.1 2004-06-09 09:51:24 tjc Exp $
+ **/
+
+public class CoilFeatureAlgorithm extends FeatureAlgorithm {
+ /**
+ * Create a new CoilFeatureAlgorithm object for the given Feature.
+ **/
+ public CoilFeatureAlgorithm (Feature feature) {
+ super (feature, "Coiled Coils", "coiled_coil");
+ }
+
+ private static final int WINDOW_SIZE = 28;
+
+ /**
+ * Return the coiled coil score of the (28) residues in the given range.
+ * The start and end values should be 28*3 bases apart (inclusive).
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues (int start, int end, float [] values) {
+
+ final int number_of_weights = weight_array[0].length;
+
+ final AminoAcidSequence translation = getFeature ().getTranslation ();
+
+ final String translation_string =
+ translation.toString ().substring (start, end + 1);
+
+ final float [] scores = new float [WINDOW_SIZE];
+
+ float max_score = -1.0F;
+
+ int k = 0;
+
+ for (int frame = 0 ; frame < 7 ; ++frame) {
+ k = frame - start % 7;
+ if (k < 0) {
+ k = k + 7;
+ }
+ for (int j = 0 ; j < WINDOW_SIZE ; ++j) {
+// System.out.println (k + " " + frame + " " + j);
+
+ final char this_char = translation_string.charAt (j);
+ final int index =
+ AminoAcidSequence.getSymbolIndex (this_char);
+
+ final float this_score = weight_array [index][k];
+
+ scores [j] = this_score;
+
+ ++k;
+
+ if (k >= 7) {
+ k = 0;
+ }
+ }
+
+ final float score = geometricMean (scores);
+
+ if (score > max_score) {
+ max_score = score;
+ }
+ }
+
+ values[0] = probCoil (max_score);
+ }
+
+ /**
+ * Return the geometric mean of the floating point values in the argument.
+ **/
+ private float geometricMean (final float [] scores) {
+ float total = 1;
+
+ for (int i = 0 ; i < scores.length ; ++i) {
+ total *= scores [i];
+ }
+
+ return (float) Math.pow (total, 1.0 / scores.length);
+ }
+
+
+ /**
+ * Returns probability of coiled-coil for the given score from the
+ * statistics in the original paper.
+ **/
+ private float probCoil (final float score) {
+ final float gcc_mean = 1.63F;
+ final float gg_mean = 0.77F;
+ final float gcc_sd = 0.24F;
+ final float gg_sd =0.20F;
+ final float gcc = gauss (gcc_mean, gcc_sd, score);
+ final float gg = gauss (gg_mean, gg_sd, score);
+
+ return (float) (gcc/(30.*gg+gcc));
+ }
+
+ /**
+ * Calculates probability based on a Gaussian distribution.
+ **/
+ private float gauss (final float mean, final float sd, final float score) {
+ return
+ (float) (Math.pow (Math.E, -0.5 * Math.pow ((score - mean) / sd, 2)) /
+ (sd * 2.0 * Math.PI));
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - one
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize () {
+ return new Integer (WINDOW_SIZE);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize () {
+ return new Integer (WINDOW_SIZE);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize () {
+ return new Integer (WINDOW_SIZE);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size) {
+ return new Integer (1);
+ }
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 4.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (1.01);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is -4..
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (-0.01);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage () {
+ return null;
+ }
+
+ /**
+ * The weighting matrix from the Science paper.
+ **/
+ private final static float [] [] weight_array = {
+ { 1.297F,1.551F,1.084F,2.612F,0.377F,1.248F,0.877F, }, // Ala A 7.59
+ { 0.659F,1.163F,1.210F,0.031F,1.358F,1.937F,1.798F, }, // arg R 5.39
+ { 0.835F,1.475F,1.534F,0.039F,1.722F,2.456F,2.280F, }, // Asn N 4.25
+ { 0.030F,2.352F,2.268F,0.237F,0.663F,1.620F,1.448F, }, // Asp D 5.03
+ { 0.824F,0.022F,0.308F,0.152F,0.180F,0.156F,0.044F, }, // Cys C 1.86
+ { 0.179F,2.114F,1.778F,0.631F,2.550F,1.578F,2.526F, }, // Gln Q 4.27
+ { 0.262F,3.496F,3.108F,0.998F,5.685F,2.494F,3.048F, }, // Glu E 6.10
+ { 0.045F,0.275F,0.578F,0.216F,0.211F,0.426F,0.156F, }, // Gly G 7.10
+ { 0.347F,0.275F,0.679F,0.395F,0.294F,0.579F,0.213F, }, // His H 2.25
+ { 2.597F,0.098F,0.345F,0.894F,0.514F,0.471F,0.431F, }, // Ile I 5.35
+ { 3.167F,0.297F,0.398F,3.902F,0.585F,0.501F,0.483F, }, // Leu L 9.33
+ { 1.375F,2.639F,1.763F,0.191F,1.815F,1.961F,2.795F, }, // Lys K 5.72
+ { 2.240F,0.370F,0.480F,1.409F,0.541F,0.772F,0.663F, }, // Met M 2.34
+ { 0.531F,0.076F,0.403F,0.662F,0.189F,0.106F,0.013F, }, // Phe F 3.88
+ { 0.0F, 0.008F,0.0F, 0.013F,0.0F, 0.0F, 0.0F, }, // Pro P 5.28
+ { 0.382F,0.583F,1.052F,0.419F,0.525F,0.916F,0.628F, }, // Ser S 7.28
+ { 0.169F,0.702F,0.955F,0.654F,0.791F,0.843F,0.647F, }, // Thr T 5.97
+ { 0.240F,0.0F, 0.0F, 0.456F,0.019F,0.0F, 0.0F, }, // Trp W 1.41
+ { 1.417F,0.090F,0.122F,1.659F,0.190F,0.130F,0.155F, }, // Tyr Y 3.16
+ { 1.665F,0.403F,0.386F,0.949F,0.211F,0.342F,0.360F, }, // Val V 6.42
+ { 0.000F,0.000F,0.000F,0.000F,0.000F,0.000F,0.000F, }, // Opl * 0.00
+ { 0.000F,0.000F,0.000F,0.000F,0.000F,0.000F,0.000F, }, // Ocr # 0.00
+ { 0.000F,0.000F,0.000F,0.000F,0.000F,0.000F,0.000F, }, // Amb + 0.00
+ { 0.000F,0.000F,0.000F,0.000F,0.000F,0.000F,0.000F, }, // --- . 0.00
+ };
+}
diff --git a/uk/ac/sanger/artemis/plot/CumulativeATSkewAlgorithm.java b/uk/ac/sanger/artemis/plot/CumulativeATSkewAlgorithm.java
new file mode 100644
index 0000000..77a1607
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/CumulativeATSkewAlgorithm.java
@@ -0,0 +1,208 @@
+/* CumulativeATSkewAlgorithm.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ *
+ * Grigoriev A (1999) Strand-specific compositional asymmetries in
+ * double-stranded DNA viruses.
+ * Virus Research 60, 1-19.
+ *
+ * @author Derek Gatherer
+ **/
+
+public class CumulativeATSkewAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new CumulativeATSkewAlgorithm object
+ * @param strand The strand to do the calculation on.
+ **/
+ public CumulativeATSkewAlgorithm(final Strand strand)
+ {
+ super(strand, "Cumulative AT Skew, (A-T)/(A+T)", "at_skew");
+ setScalingFlag(true);
+ }
+
+ /**
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ final String sequence;
+ String subseq;
+
+// sliding window code here
+
+ int leap = end-start;
+ values[0] = 0;
+
+ float a_count = 0;
+ float t_count = 0;
+
+ for(int window = 0 ; window < end ; window += leap)
+ {
+ try
+ {
+ subseq = getStrand().getSubSequence(new Range(window, window+leap));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ a_count = 0;
+ t_count = 0;
+
+ for(int i = 0 ; i < subseq.length() ; ++i)
+ {
+ final char this_char = subseq.charAt(i);
+
+ if(this_char == 'a')
+ ++a_count;
+
+ if(this_char == 't')
+ ++t_count;
+ }
+
+ if(a_count + t_count > 0)
+ values[0] += (a_count - t_count) / (a_count + t_count);
+ }
+ }
+
+ /**
+ * Return the number of values a call to getValues() will return - one
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(60);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(10);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 10)
+ return new Integer(window_size / 10);
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(1);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(-1);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ final float a_count = getStrand().getACount();
+ final float t_count = getStrand().getTCount();
+
+ final float a_minus_t = a_count - t_count;
+ final float a_plus_t = a_count + t_count;
+
+ if(a_plus_t > 0)
+ return new Float(a_minus_t/a_plus_t);
+ else
+ return new Float(0.0);
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/CumulativeGCSkewAlgorithm.java b/uk/ac/sanger/artemis/plot/CumulativeGCSkewAlgorithm.java
new file mode 100644
index 0000000..af50a96
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/CumulativeGCSkewAlgorithm.java
@@ -0,0 +1,206 @@
+/* CumulativeGCSkewAlgorithm.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ *
+ * Grigoriev A (1999) Strand-specific compositional asymmetries in
+ * double-stranded DNA viruses.
+ * Virus Research 60, 1-19.
+ * @author Derek Gatherer
+ **/
+public class CumulativeGCSkewAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new CumulativeGCSkewAlgorithm object
+ * @param strand The strand to do the calculation on.
+ **/
+ public CumulativeGCSkewAlgorithm(final Strand strand)
+ {
+ super(strand, "Cumulative GC Skew, (G-C)/(G+C)", "gc_skew");
+ setScalingFlag(true);
+ }
+
+ /**
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ final String sequence;
+ String subseq;
+
+// try sliding window code here
+
+ int leap = end-start;
+ values[0] = 0;
+
+ float g_count = 0;
+ float c_count = 0;
+
+ for(int window = 0 ; window < end ; window += leap)
+ {
+ try
+ {
+ subseq = getStrand().getSubSequence(new Range(window, window+leap));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ g_count = 0;
+ c_count = 0;
+
+ for(int i = 0 ; i < subseq.length() ; ++i)
+ {
+ final char this_char = subseq.charAt(i);
+
+ if(this_char == 'g')
+ ++g_count;
+
+ if(this_char == 'c')
+ ++c_count;
+ }
+
+ if(c_count + g_count > 0)
+ values[0] += (g_count - c_count) / (g_count + c_count);
+ }
+ }
+
+ /**
+ * Return the number of values a call to getValues() will return - one
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize ();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(60);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(10);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 10)
+ return new Integer(window_size / 10);
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(1);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(-1);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ final float g_count = getStrand().getGCount();
+ final float c_count = getStrand().getCCount();
+
+ final float g_minus_c = g_count - c_count;
+ final float g_plus_c = g_count + c_count;
+
+ if(g_plus_c > 0)
+ return new Float (g_minus_c/g_plus_c);
+ else
+ return new Float (0.0);
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/EntropyAlgorithm.java b/uk/ac/sanger/artemis/plot/EntropyAlgorithm.java
new file mode 100644
index 0000000..ed6bd70
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/EntropyAlgorithm.java
@@ -0,0 +1,231 @@
+/* EntropyAlgorithm.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+package uk.ac.sanger.artemis.plot;
+
+import java.lang.Math.*;
+import java.util.*;
+import java.text.*;
+
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ *
+ * Informational Entropy (Konopka 1984)
+ * Konopka A (1984) Is the information content of DNA evolutionarily
+ * significant? J Theor Biol 107:697-704
+ *
+ * @author Derek Gatherer
+ **/
+
+public class EntropyAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new EntropyAlgorithm object.
+ * @param strand The strand to do the calculation on.
+ **/
+ public EntropyAlgorithm(final Strand strand)
+ {
+ super(strand, makeName(strand), "entropy");
+ setScalingFlag(true);
+ }
+
+ /**
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ if(getStrand().isForwardStrand()) // rather than isRevCompDisplay()
+ {
+// System.out.println("isRevCompDisplay does not activate here");
+ }
+ else
+ {
+ final int new_end =
+ getStrand().getBases().getComplementPosition(start);
+ final int new_start =
+ getStrand().getBases().getComplementPosition(end);
+
+ end = new_end;
+ start = new_start;
+// System.out.println("Revcomp, so new start:"+start+"new end:"+end);
+ }
+
+ final String sequence;
+
+ Collator co = Collator.getInstance();
+ TreeMap wordSet = new TreeMap(co);
+ int total = 0;
+
+ try
+ {
+ sequence = getStrand().getSubSequence(new Range(start, end));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+// System.out.println("start:"+start+"end:"+end);
+// System.out.println(sequence);
+
+// calculates the overlapping triplet entropy
+
+ for(int i = 0; i < sequence.length()-3; ++i) // increment by 1, therefore overlapping
+ {
+ String codon = sequence.substring(i,i+3); // codon String is 3 in length
+ Integer number = (Integer)wordSet.get(codon);
+ if(number == null)
+ number = new Integer(0);
+ wordSet.put(codon, new Integer(number.intValue()+1)); // count them up
+ total++;
+ }
+
+ Set mappings = wordSet.entrySet();
+ double ent = 0;
+
+ for(Iterator i = mappings.iterator(); i.hasNext();)
+ {
+ Map.Entry e = (Map.Entry)i.next();
+ String in_hash = e.getValue().toString();
+ float as_num = Float.parseFloat(in_hash);
+ float freq = as_num/total;
+ ent -= freq*Math.log(freq)/Math.log(2); // H = sigma(p* log(2) p)
+ }
+ values[0] = (float)ent;
+ }
+
+ /**
+ * Return the number of values a call to getValues() will return - one
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(500);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(25);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 10)
+ return new Integer(window_size / 10);
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(100);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ return new Float(getStrand().getBases().getAverageGCPercent());
+ }
+
+ private static String makeName(final Strand strand)
+ {
+ if(strand.isForwardStrand())
+ return "Informational Entropy";
+ else
+ return "Reverse Informational Entropy";
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/FeatureAlgorithm.java b/uk/ac/sanger/artemis/plot/FeatureAlgorithm.java
new file mode 100644
index 0000000..0d51f5c
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/FeatureAlgorithm.java
@@ -0,0 +1,81 @@
+/* FeatureAlgorithm.java
+ *
+ * created: Wed Dec 16 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/FeatureAlgorithm.java,v 1.1 2004-06-09 09:51:25 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.Feature;
+
+/**
+ * The FeatureAlgorithm class is the base class for algorithms that work
+ * on features. A FeatureAlgorithm has a name and is specific to one
+ * Feature, meaning the algorithm can't change feature.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FeatureAlgorithm.java,v 1.1 2004-06-09 09:51:25 tjc Exp $
+ **/
+
+public abstract class FeatureAlgorithm extends Algorithm {
+ /**
+ * Create a new FeatureAlgorithm object.
+ * @param feature The feature to do the calculation on.
+ * @param algorithm_name A String used to identify this algorithm to the
+ * user.
+ * @param algorithm_short_name A String used to identify this algorithm
+ * internally. See the Algorithm constructor for more details.
+ **/
+ public FeatureAlgorithm (final Feature feature, final String algorithm_name,
+ final String algorithm_short_name) {
+ super (algorithm_name, algorithm_short_name);
+ this.feature = feature;
+ }
+
+ /**
+ * Returns the strand we will do the calculation on.
+ **/
+ public Feature getFeature () {
+ return feature;
+ }
+
+ /**
+ * Return the value of the function between a pair of amino acids.
+ * @param start The start amino acid (included in the range).
+ * @param end The end amino acid (included in the range).
+ * @param values The results are returned in this array, hence it should be
+ * allocated at the size given by getValueCount ().
+ **/
+ public abstract void getValues (int start, int end, float [] values);
+
+ /**
+ * Return the number of values a call to getValues () will return.
+ **/
+ public abstract int getValueCount ();
+
+ /**
+ * The Feature we will do the calculation on.
+ **/
+ private Feature feature;
+}
+
+
diff --git a/uk/ac/sanger/artemis/plot/GCDeviationAlgorithm.java b/uk/ac/sanger/artemis/plot/GCDeviationAlgorithm.java
new file mode 100644
index 0000000..e663495
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/GCDeviationAlgorithm.java
@@ -0,0 +1,195 @@
+/* GCDeviationAlgorithm.java
+ *
+ * created: Tue Mar 16 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/GCDeviationAlgorithm.java,v 1.1 2004-06-09 09:51:27 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns a single floating point number, which is the
+ * value of (G-C)/(G+C) in the range. The Strand to use is set in the
+ * constructor.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GCDeviationAlgorithm.java,v 1.1 2004-06-09 09:51:27 tjc Exp $
+ **/
+
+public class GCDeviationAlgorithm extends BaseAlgorithm {
+ /**
+ * Create a new GCDeviationAlgorithm object
+ * @param strand The strand to do the calculation on.
+ **/
+ public GCDeviationAlgorithm (final Strand strand) {
+ super (strand, "GC Deviation (G-C)/(G+C)", "gc_deviation");
+
+ setScalingFlag (true);
+ }
+
+ /**
+ * Return the value of (G content - C content)/(G content + C content)
+ * between the given pair of bases.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues (int start, int end, final float [] values) {
+ final String sequence;
+
+ try {
+ sequence = getStrand ().getSubSequence (new Range (start, end));
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ float g_count = 0;
+ float c_count = 0;
+
+ for (int i = 0 ; i < sequence.length () ; ++i) {
+ final char this_char = sequence.charAt (i);
+
+ if (this_char == 'g') {
+ ++g_count;
+ }
+
+ if (this_char == 'c') {
+ ++c_count;
+ }
+ }
+
+ if (c_count + g_count > 0) {
+// System.out.println ("start: " + start + " end: " + end +
+// " returning: " +
+// (g_count - c_count) / (g_count + c_count));
+ values[0] = (g_count - c_count) / (g_count + c_count);
+ } else {
+ values[0] = 0;
+ }
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - one
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize () {
+ final Integer super_window_size = super.getDefaultWindowSize ();
+ if (super_window_size != null) {
+ // the superclass version of getDefaultWindowSize () returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer (30);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize () {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize ();
+ if (super_max_window_size != null) {
+ // the superclass version of getDefaultMaxWindowSize () returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer (5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize () {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize ();
+ if (super_min_window_size != null) {
+ // the superclass version of getDefaultMinWindowSize () returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer (10);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size) {
+ if (window_size > 10) {
+ return new Integer (window_size / 10);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (1);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (-1);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage () {
+ final float g_count = getStrand ().getGCount ();
+ final float c_count = getStrand ().getCCount ();
+
+ final float g_minus_c = g_count - c_count;
+ final float g_plus_c = g_count + c_count;
+
+ if (g_plus_c > 0) {
+ return new Float (g_minus_c/g_plus_c);
+ } else {
+ return new Float (0.0);
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/GCFrameAlgorithm.java b/uk/ac/sanger/artemis/plot/GCFrameAlgorithm.java
new file mode 100644
index 0000000..7d57f90
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/GCFrameAlgorithm.java
@@ -0,0 +1,266 @@
+/* GCFrameAlgorithm.java
+ *
+ * created: Tue Dec 15 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/GCFrameAlgorithm.java,v 1.8 2009-06-26 15:52:48 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.sequence.*;
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+
+import java.awt.*;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns three floating point numbers, which are the
+ * percent GC content in each frame. The Strand to use is set in the
+ * constructor.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GCFrameAlgorithm.java,v 1.8 2009-06-26 15:52:48 tjc Exp $
+ **/
+public class GCFrameAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new GCFrameAlgorithm object.
+ * @param strand The Strand to do the calculation on.
+ **/
+ public GCFrameAlgorithm(final Strand strand)
+ {
+ super(strand, makeName(strand), "gc_frame");
+ setScalingFlag(true);
+ }
+
+ /**
+ * This is used as temporary storage by getValues().
+ **/
+ private int gc_counts [] = new int [getValueCount()];
+
+ /**
+ * Return the percent gc between a pair of bases in each of the three
+ * frames.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range). If the start/end pair
+ * doesn't give a multiple of three bases end is moved down so that it is
+ * a multiple of three.
+ * @param values The three results are returned in this array, hence it
+ * should be three long. The first value is the gc content of positions
+ * start, start+3, start+6, etc, the second value is start+1, start+4,
+ * etc.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ if(isRevCompDisplay())
+ {
+ final int new_end =
+ getStrand().getBases().getComplementPosition(start);
+ final int new_start =
+ getStrand().getBases().getComplementPosition(end);
+
+ end = new_end;
+ start = new_start;
+ }
+
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ if(getStrand().isForwardStrand())
+ end -= (end - start + 1) % 3;
+ else
+ start += (end - start + 1) % 3;
+
+ for(int i = 0; i < getValueCount(); ++i)
+ gc_counts[i] = 0;
+
+ final char[] sub_sequence;
+
+ try
+ {
+ sub_sequence = getStrand().getRawSubSequenceC(new Range(start, end));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final int sub_sequence_length = sub_sequence.length;
+
+ if(getStrand().isForwardStrand())
+ {
+ for(int i = 0 ; i < sub_sequence_length ; i += 3)
+ {
+ for(int frame = 0 ; frame < 3 ; ++frame)
+ {
+ final char this_char = sub_sequence[i + frame];
+
+ if(this_char == 'g' || this_char == 'c')
+ ++gc_counts[(frame + start) % 3];
+ }
+ }
+ }
+ else
+ {
+ final int whole_sequence_length = getStrand().getSequenceLength();
+ final int whole_sequence_length_mod3 = whole_sequence_length % 3;
+
+ for(int i = 0; i < sub_sequence_length; i += 3)
+ {
+ for(int frame = 0; frame < 3; ++frame)
+ {
+ final char this_char = sub_sequence[i + frame];
+
+ if(this_char == 'g' || this_char == 'c')
+ ++gc_counts[(frame + start + 3 - whole_sequence_length_mod3) % 3];
+ }
+ }
+ }
+
+ // multiply by 3 because we are taking every third base
+ for(int frame = 0 ; frame < 3 ; ++frame)
+ {
+ values[frame] = 1.0F * gc_counts[frame]/sub_sequence_length * 3 * 100;
+ }
+ }
+
+ /**
+ * Override drawLegend()
+ */
+ public void drawLegend(Graphics g, int font_height,
+ int font_width, LineAttributes[] lines, int numPlots)
+ {
+ return;
+ }
+
+
+ /**
+ * Return the number of values a call to getValues() will return - three
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 3;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(120);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(500);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(24);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 8)
+ return new Integer(window_size / 8);
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(100);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ return new Float(getStrand().getBases().getAverageGCPercent());
+ }
+
+ /**
+ * Returns "GC Frame Plot" if the given strand is a forward strand
+ * otherwise returns "Reverse GC Frame Plot".
+ **/
+ private static String makeName(final Strand strand)
+ {
+ if(strand.isForwardStrand())
+ return "GC Frame Plot";
+ else
+ return "Reverse GC Frame Plot";
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/GCSDWindowAlgorithm.java b/uk/ac/sanger/artemis/plot/GCSDWindowAlgorithm.java
new file mode 100644
index 0000000..7b08e0d
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/GCSDWindowAlgorithm.java
@@ -0,0 +1,285 @@
+/* GCSDWindowAlgorithm.java
+ *
+ * created: Mon Oct 25 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/GCSDWindowAlgorithm.java,v 1.4 2009-03-17 17:47:42 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns a single floating point number, which is the
+ * percent GC content of the range if the GC content of the range is more
+ * than 2.5 standard deviations from the average GC content of the sequence.
+ * It returns the average GC content of the sequence otherwise. The Strand
+ * to use is set in the constructor.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GCSDWindowAlgorithm.java,v 1.4 2009-03-17 17:47:42 tjc Exp $
+ **/
+
+public class GCSDWindowAlgorithm extends BaseAlgorithm {
+ /**
+ * Create a new GCSDWindowAlgorithm object.
+ * @param strand The strand to do the calculation on.
+ **/
+ public GCSDWindowAlgorithm (final Strand strand) {
+ super (strand, "GC Content (%) With A 2.5 SD Cutoff", "sd_gc_content");
+
+ setScalingFlag (false);
+ }
+
+ /**
+ * Return the percent GC between a pair of bases if the GC content of the
+ * range is more than 2.5 standard deviations from the average GC content
+ * of the sequence. It returns the average GC content of the sequence
+ * otherwise.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues (int start, int end, float [] values) {
+ final int window_size = end - start + 1;
+
+ final float standard_deviation;
+
+ if (window_size > getDefaultMaxWindowSize ().intValue ()) {
+ standard_deviation = calculateSD (window_size);
+ } else {
+ if (standard_deviations[window_size - 1] < 0) {
+ // set the cached value
+ standard_deviation = calculateSD (window_size);
+ standard_deviations[window_size - 1] = standard_deviation;
+
+ // System.err.println ("SD: " + standard_deviation);
+ } else {
+ standard_deviation = standard_deviations[window_size - 1];
+ }
+ }
+
+ final String sequence;
+
+ try {
+ sequence = getStrand ().getSubSequence (new Range (start, end));
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ float gc_count = 0;
+
+ for (int i = 0 ; i < sequence.length () ; ++i) {
+ final char this_char = sequence.charAt (i);
+// System.out.println (this_char);
+
+ if (this_char == 'g' || this_char == 'c') {
+ ++gc_count;
+ }
+ }
+
+ final float gc_content = gc_count/sequence.length () * 100;
+
+ final float gc_average =
+ getStrand ().getBases ().getAverageGCPercent ();
+
+ if (Math.abs (gc_content - gc_average) < standard_deviation * 2.5) {
+ values[0] = gc_average;
+ } else {
+ values[0] = gc_content;
+ }
+ }
+
+ /**
+ * Calculate and return the standard deviation of the GC content of the
+ * Bases object of the Strand that was passed to the constructor.
+ **/
+ private float calculateSD (final int window_size) {
+// System.err.println ("calculateSD:");
+
+ final int sequence_length = getStrand ().getBases ().getLength ();
+
+ final String bases = getStrand ().getBases ().toString ();
+
+ // the number of windows to search
+ final int window_count = sequence_length - window_size;
+
+// System.err.println ("window_size: " + window_size);
+// System.err.println ("window_count: " + window_count);
+
+ // this is the sum over all the windows of:
+ // (GC content) * (percent GC) / window_size * window_size
+ double sum_so_far = 0;
+
+ // this is the sum over all the windows of (GC content)/window_size
+ double gc_so_far = 0;
+
+ double current_gc_count = 0;
+
+ for (int i = - window_size ; i < window_count ; ++i) {
+ if (i > 0) {
+ final char previous_char = bases.charAt (i - 1);
+
+ if (previous_char == 'g' || previous_char == 'c') {
+ --current_gc_count;
+ }
+ }
+
+ final char new_char = bases.charAt (i + window_size);
+
+ if (new_char == 'g' || new_char == 'c') {
+ ++current_gc_count;
+ }
+
+ if (i >= 0) {
+ final double this_value = current_gc_count / window_size;
+
+ sum_so_far += this_value * this_value;
+
+ gc_so_far += this_value;
+ }
+ }
+
+// System.err.println ("sum_so_far: " + sum_so_far);
+// System.err.println ("gc_so_far: " + gc_so_far);
+
+ final double gc_average = gc_so_far / window_count;
+
+// System.err.println ("gc_average: " + gc_average);
+// System.err.println ("sum_so_far/window_count: " +
+// (sum_so_far/window_count));
+// System.err.println ("gc_average*gc_average: " + (gc_average * gc_average));
+
+// System.err.println ("sd*sd: " + (sum_so_far / window_count -
+// gc_average * gc_average));
+
+ return (float) Math.sqrt (sum_so_far / window_count -
+ gc_average * gc_average) * 100;
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - one
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize () {
+ final Integer super_window_size = super.getDefaultWindowSize ();
+ if (super_window_size != null) {
+ // the superclass version of getDefaultWindowSize () returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer (1000);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize () {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize ();
+ if (super_max_window_size != null) {
+ // the superclass version of getDefaultMaxWindowSize () returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer (5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize () {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize ();
+ if (super_min_window_size != null) {
+ // the superclass version of getDefaultMinWindowSize () returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer (100);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size) {
+ return new Integer (1);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage () {
+ return new Float (getStrand ().getBases ().getAverageGCPercent ());
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (100);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (0);
+ }
+
+ /**
+ * This array contains a cache of the standard deviations of the GC content
+ * of the Bases object of the Strand that was passed to the constructor.
+ * (Set by the constructor). The array is indexed by window size.
+ **/
+ float [] standard_deviations;
+
+ {
+ final int max_window_size = getDefaultMaxWindowSize ().intValue ();
+ standard_deviations = new float [max_window_size];
+
+ for (int i = 0 ; i < max_window_size ; ++i) {
+ standard_deviations[i] = -1;
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/GCWindowAlgorithm.java b/uk/ac/sanger/artemis/plot/GCWindowAlgorithm.java
new file mode 100644
index 0000000..a4b5bd3
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/GCWindowAlgorithm.java
@@ -0,0 +1,176 @@
+/* GCWindowAlgorithm.java
+ *
+ * created: Tue Dec 15 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/GCWindowAlgorithm.java,v 1.4 2009-03-17 17:47:42 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns a single floating point number, which is the
+ * percent GC content of the range. The Strand to use is set in the
+ * constructor.
+ *
+ * @author Kim Rutherford
+ * @version $Id: GCWindowAlgorithm.java,v 1.4 2009-03-17 17:47:42 tjc Exp $
+ **/
+
+public class GCWindowAlgorithm extends BaseAlgorithm {
+ /**
+ * Create a new GCWindowAlgorithm object.
+ * @param strand The strand to do the calculation on.
+ **/
+ public GCWindowAlgorithm (final Strand strand) {
+ super (strand, "GC Content (%)", "gc_content");
+
+ setScalingFlag (true);
+ }
+
+ /**
+ * Return the percent GC between a pair of bases.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues (int start, int end, final float [] values) {
+ final String sequence;
+
+ try {
+ sequence = getStrand ().getSubSequence (new Range (start, end));
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ float gc_count = 0;
+
+ for (int i = 0 ; i < sequence.length () ; ++i) {
+ final char this_char = sequence.charAt (i);
+// System.out.println (this_char);
+
+ if (this_char == 'g' || this_char == 'c') {
+ ++gc_count;
+ }
+ }
+
+// System.out.println ("start: " + start + " end: " + end + " returning: " + gc_count/sequence.length ());
+
+ values[0] = gc_count/sequence.length () * 100;
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - one
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize () {
+ final Integer super_window_size = super.getDefaultWindowSize ();
+ if (super_window_size != null) {
+ // the superclass version of getDefaultWindowSize () returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer (120);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize () {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize ();
+ if (super_max_window_size != null) {
+ // the superclass version of getDefaultMaxWindowSize () returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer (500);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize () {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize ();
+ if (super_min_window_size != null) {
+ // the superclass version of getDefaultMinWindowSize () returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer (24);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size) {
+ if (window_size > 10) {
+ return new Integer (window_size / 10);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (100);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage () {
+ return new Float (getStrand ().getBases ().getAverageGCPercent ());
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/HydroAlgorithm.java b/uk/ac/sanger/artemis/plot/HydroAlgorithm.java
new file mode 100644
index 0000000..5551b13
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/HydroAlgorithm.java
@@ -0,0 +1,179 @@
+/* HydroAlgorithm.java
+ *
+ * created: Wed Dec 16 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/HydroAlgorithm.java,v 1.2 2005-09-06 07:23:15 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.Bases;
+
+/**
+ * This is the base class for HydrophobicityAlgorithm and
+ * HydrophilicityAlgorithm.
+ *
+ * @author Kim Rutherford
+ * @version $Id: HydroAlgorithm.java,v 1.2 2005-09-06 07:23:15 tjc Exp $
+ **/
+
+public abstract class HydroAlgorithm extends FeatureAlgorithm {
+ /**
+ * Create a new HydroAlgorithm object.
+ * @param feature The feature to do future calculation on.
+ * @param algorithm_name A String used to identify this algorithm to the
+ * user.
+ * @param data_array An array of data values, one value for each amino acid.
+ * The data should correspond to the amino acids in this order:
+ * RKDQNEHSTPYCGAMWLVFI.
+ **/
+ public HydroAlgorithm (final Feature feature, final String algorithm_name,
+ final String algorithm_short_name,
+ final float [] data_array) {
+ super (feature, algorithm_name, algorithm_short_name);
+ this.data_array = data_array;
+ }
+
+ /**
+ * Return the hydrophobicity between a pair of amino acids.
+ * @param start The start amino acid (included in the range), indexed from
+ * zero.
+ * @param end The end amino acid (included in the range), indexed from 0.
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues (int start, int end, float [] values) {
+ final String translation =
+ getFeature().getTranslation().toString().substring(start, end).toLowerCase();
+
+// System.out.println(translation+" "+start + " " + end + " " +
+// getFeature ().getTranslation ().length ());
+
+ float total = 0;
+
+ for (int i = 0 ; i < translation.length () ; ++i) {
+ total += getCodonValue (data_array, translation.charAt (i));
+ }
+
+ values[0] = total / translation.length ();
+
+// System.out.println("foo: " + values[0] + " " + total + " " +
+// translation.length ());
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize () {
+ final Integer super_window_size = super.getDefaultWindowSize ();
+ if (super_window_size != null) {
+ // the superclass version of getDefaultWindowSize () returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer (7);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize () {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize ();
+ if (super_max_window_size != null) {
+ // the superclass version of getDefaultMaxWindowSize () returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer (100);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize () {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize ();
+ if (super_min_window_size != null) {
+ // the superclass version of getDefaultMinWindowSize () returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer (7);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size) {
+ return new Integer (1);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage () {
+ return new Float (0);
+ }
+
+ /**
+ * Return the value in data_array that corresponds to the given amino acid
+ * symbol (uses amino_acid_array to do this).
+ **/
+ private float getCodonValue (float [] data_array, char amino_acid_symbol) {
+ final int index = amino_acid_array.indexOf (amino_acid_symbol);
+
+// System.out.println("getCodonValue (): " + index + " " +
+// amino_acid_symbol) ; // + " " + data_array[index]);
+
+ if (index == -1) {
+ // return the average value in this case.
+ return 0;
+ } else {
+ return data_array[index];
+ }
+ }
+
+ /**
+ * Data values for each amino acid. This array is indexed by comparing to
+ * amino_acid_array.
+ **/
+ /* final */ private float [] data_array;
+
+ /**
+ * The index of the amino acid symbol in this String gives the index to
+ * look up in data_array to get the hydrophobicity value. eg. the symbol
+ * "r" has index 0 hence has value +3.00
+ **/
+ private final static String amino_acid_array = "rkdqnehstpycgamwlvfi";
+}
+
+
diff --git a/uk/ac/sanger/artemis/plot/HydrophilicityAlgorithm.java b/uk/ac/sanger/artemis/plot/HydrophilicityAlgorithm.java
new file mode 100644
index 0000000..7ae6cfd
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/HydrophilicityAlgorithm.java
@@ -0,0 +1,80 @@
+/* HydrophilicityAlgorithm.java
+ *
+ * created: Wed Dec 16 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/HydrophilicityAlgorithm.java,v 1.1 2004-06-09 09:51:33 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.*;
+
+/**
+ * HydrophilicityAlgorithm class
+ *
+ * @author Kim Rutherford
+ * @version $Id: HydrophilicityAlgorithm.java,v 1.1 2004-06-09 09:51:33 tjc Exp $
+ **/
+
+public class HydrophilicityAlgorithm extends HydroAlgorithm {
+ /**
+ * Create a new HydrophilicityAlgorithm object.
+ * @param feature The feature to do future calculation on.
+ **/
+ public HydrophilicityAlgorithm (Feature feature) {
+ super (feature, "Hopp-Woods Hydrophilicity", "hydrophilicity", data_array);
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - three
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 1;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 4.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (3);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is -2.
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (-2);
+ }
+
+ /**
+ * Hydrophobicity value for each amino acid. This array is indexed by
+ * comparing to amino_acid_array.
+ **/
+ private final static float [] data_array = {
+ +3.00F, +3.00F, +3.00F, +0.20F, +0.20F,
+ +3.00F, -0.50F, +0.30F, -0.40F, +0.00F,
+ -2.30F, -1.00F, +0.00F, -0.50F, -1.30F,
+ -3.40F, -1.80F, -1.50F, -2.50F, -1.80F
+ };
+}
diff --git a/uk/ac/sanger/artemis/plot/HydrophobicityAlgorithm.java b/uk/ac/sanger/artemis/plot/HydrophobicityAlgorithm.java
new file mode 100644
index 0000000..3de6487
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/HydrophobicityAlgorithm.java
@@ -0,0 +1,84 @@
+/* HydrophobicityAlgorithm.java
+ *
+ * created: Wed Dec 16 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/HydrophobicityAlgorithm.java,v 1.1 2004-06-09 09:51:34 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.*;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns a single floating point number. The feature to
+ * use is set in the constructor.
+ *
+ * @author Kim Rutherford
+ * @version $Id: HydrophobicityAlgorithm.java,v 1.1 2004-06-09 09:51:34 tjc Exp $
+ **/
+
+public class HydrophobicityAlgorithm extends HydroAlgorithm {
+ /**
+ * Create a new HydrophobicityAlgorithm object.
+ * @param feature The feature to do future calculation on.
+ **/
+ public HydrophobicityAlgorithm (Feature feature) {
+ super (feature, "Kyte-Doolittle Hydrophobicity", "hydrophobicity",
+ data_array);
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - three
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 1;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 4.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (4);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is -4..
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (-3);
+ }
+
+ /**
+ * Hydrophobicity value for each amino acid. This array is indexed by
+ * comparing to amino_acid_array.
+ **/
+ private final static float [] data_array = {
+ -4.50F, -3.90F, -3.50F, -3.50F, -3.50F,
+ -3.50F, -3.20F, -0.80F, -0.70F, -1.60F,
+ -1.30F, +2.50F, -0.40F, +1.80F, +1.90F,
+ -0.90F, +3.80F, +4.20F, +2.80F, +4.50F
+ };
+
+}
diff --git a/uk/ac/sanger/artemis/plot/ICDIAlgorithm.java b/uk/ac/sanger/artemis/plot/ICDIAlgorithm.java
new file mode 100644
index 0000000..b5a52a3
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/ICDIAlgorithm.java
@@ -0,0 +1,489 @@
+/* ICDIAlgorithm.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ *
+ * Intrinsic Codon Deviation Index (Freire-Picos et al. 1994)
+ * Freire-Picos MA, Gonzalez-Siso MI, Rodriguez-Belmonte E, Rodriguez-Torres
+ * AM, Ramil E, Cerdan ME (1994) Codon usage in Kluyveromyces lactis and in
+ * yeast cytochrome c-encoding genes. Gene 139:43-9
+ *
+ * @author Derek Gatherer
+ * original version 09-09-03
+ * rewritten 01-12-04
+ **/
+
+public class ICDIAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new ICDIAlgorithm object.
+ * @param strand The Strand to do the calculation on.
+ **/
+ public ICDIAlgorithm(final Strand strand)
+ {
+ super(strand, makeName(strand), "ICDI");
+ setScalingFlag(true);
+ }
+
+ public void drawLegend(Graphics g, int font_height,
+ int font_width, Color[] frameColour)
+ {
+
+ }
+
+ /**
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range). If the start/end pair
+ * doesn't give a multiple of three bases end is moved down so that it is
+ * a multiple of three.
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ if(getStrand().isForwardStrand()) // rather than isRevCompDisplay()
+ {
+// System.out.println("isRevCompDisplay does not activate here");
+ }
+ else
+ {
+ final int new_end =
+ getStrand().getBases().getComplementPosition(start);
+ final int new_start =
+ getStrand().getBases().getComplementPosition(end);
+
+ end = new_end;
+ start = new_start;
+// System.out.println("Revcomp, so new start:"+start+"new end:"+end);
+ }
+
+ final String sub_sequence;
+
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ if(getStrand().isForwardStrand())
+ end -= (end - start + 1) % 3;
+ else
+ start += (end - start + 1) % 3;
+
+ try
+ {
+ sub_sequence = getStrand().getSubSequence(new Range(start, end));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ float exp = 0;
+ float rscu = 0; // can simplify
+
+ final char[] sequence_forward_raw = sub_sequence.toCharArray();
+ int[][][][] obs_value = new int[4][4][4][4];
+ float[] icdi = new float[3];
+
+ // initialise all arrays to zero
+
+ for(int x = 0 ; x < 4 ; ++x)
+ {
+ for(int y = 0 ; y < 4 ; ++y)
+ {
+ for(int z = 0 ; z < 4 ; ++z)
+ {
+ for(int a = 0 ; a < 4 ; ++a)
+ {
+ obs_value[x][y][z][a] = 0;
+ }
+ }
+ }
+ }
+
+ char this_f_base = 0;
+ char next_f_base = 0;
+ char last_f_base = 0;
+ int this_f_base_index = 0;
+ int next_f_base_index = 0;
+ int last_f_base_index = 0;
+
+ for(int i = 0 ; i < sequence_forward_raw.length - 5 ; i+=3)
+ {
+ for(int frame = 0; frame < 3; frame++)
+ {
+ this_f_base = sequence_forward_raw[i + frame];
+ next_f_base = sequence_forward_raw[i + 1 + frame];
+ last_f_base = sequence_forward_raw[i + 2 + frame];
+
+ this_f_base_index = Bases.getIndexOfBase(this_f_base);
+ next_f_base_index = Bases.getIndexOfBase(next_f_base);
+ last_f_base_index = Bases.getIndexOfBase(last_f_base);
+
+ // ignore Ns
+ if(this_f_base_index < 4 && next_f_base_index < 4 && last_f_base_index < 4)
+ ++obs_value[this_f_base_index][next_f_base_index][last_f_base_index][(frame+start)%3];
+ }
+ }
+ // having collected the observed values in all 3 frames, formulate the expected
+
+ for(int frame = 0; frame < 3; ++frame)
+ {
+ icdi[frame] =0;
+ // Phe 001 000
+ exp = (obs_value[0][0][0][frame]+obs_value[0][0][1][frame])/2;
+ if(exp>0)
+ {
+ rscu = obs_value[0][0][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ rscu = obs_value[0][0][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ }
+ // Leu = 002 003 100 101 102 103
+ exp = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ if(exp>0)
+ {
+ rscu = obs_value[0][0][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[0][0][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[1][0][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[1][0][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[1][0][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[1][0][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ }
+// Ile = 200 201 202
+ exp = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])/3;
+ if(exp>0)
+ {
+ rscu = obs_value[2][0][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/6;
+ rscu = obs_value[2][0][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/6;
+ rscu = obs_value[2][0][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/6;
+ }
+// Val = 300 301 302 303
+ exp = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])/4;
+ if(exp>0)
+ {
+ rscu = obs_value[3][0][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[3][0][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[3][0][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[3][0][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ }
+// Ser = 010 011 012 013 230 231
+ exp = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ if(exp>0)
+ {
+ rscu = obs_value[0][1][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[0][1][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[0][1][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[0][1][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[2][3][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[2][3][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ }
+// Pro = 110 111 112 113
+ exp = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])/4;
+ if(exp>0)
+ {
+ rscu = obs_value[1][1][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[1][1][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[1][1][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[1][1][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ }
+// Thr = 210 211 212 213
+ exp = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])/4;
+ if(exp>0)
+ {
+ rscu = obs_value[2][1][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[2][1][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[2][1][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[2][1][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ }
+// Ala = 310 311 312 313
+ exp = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])/4;
+ if(exp>0)
+ {
+ rscu = obs_value[3][1][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[3][1][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[3][1][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[3][1][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ }
+// Tyr = 020 021
+ exp = (obs_value[0][2][0][frame]+obs_value[0][2][1][frame])/2;
+ if(exp>0)
+ {
+ rscu = obs_value[0][2][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ rscu = obs_value[0][2][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ }
+// His = 120 121
+ exp = (obs_value[1][2][0][frame]+obs_value[1][2][1][frame])/2;
+ if(exp>0)
+ {
+ rscu = obs_value[1][2][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ rscu = obs_value[1][2][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ }
+// Gln = 122 123
+ exp = (obs_value[1][2][2][frame]+obs_value[1][2][3][frame])/2;
+ if(exp>0)
+ {
+ rscu = obs_value[1][2][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ rscu = obs_value[1][2][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ }
+// Asn = 220 221
+ exp = (obs_value[2][2][0][frame]+obs_value[2][2][1][frame])/2;
+ if(exp>0)
+ {
+ rscu = obs_value[2][2][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ rscu = obs_value[2][2][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ }
+// Lys = 222 223
+ exp = (obs_value[2][2][2][frame]+obs_value[2][2][3][frame])/2;
+ if(exp>0)
+ {
+ rscu = obs_value[2][2][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ rscu = obs_value[2][2][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ }
+// Asp = 320 321
+ exp = (obs_value[3][2][0][frame]+obs_value[3][2][1][frame])/2;
+ if(exp>0)
+ {
+ rscu = obs_value[3][2][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ rscu = obs_value[3][2][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ }
+// Glu = 322 323
+ exp = (obs_value[3][2][2][frame]+obs_value[3][2][3][frame])/2;
+ if(exp>0)
+ {
+ rscu = obs_value[3][2][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ rscu = obs_value[3][2][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ }
+//Cys = 030 031
+ exp = (obs_value[0][3][0][frame]+obs_value[0][3][1][frame])/2;
+ if(exp>0)
+ {
+ rscu = obs_value[0][3][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ rscu = obs_value[0][3][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/2;
+ }
+// Arg = 130 131 132 133 232 233
+ exp = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ if(exp>0)
+ {
+ rscu = obs_value[1][3][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[1][3][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[1][3][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[1][3][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[2][3][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ rscu = obs_value[2][3][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/30;
+ }
+// Gly = 330 331 332 333
+ exp = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])/4;
+ if(exp>0)
+ {
+ rscu = obs_value[3][3][0][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[3][3][1][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[3][3][2][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ rscu = obs_value[3][3][3][frame]/exp;
+ icdi[frame] += Math.pow((rscu-1),2)/12;
+ }
+ values[frame] = icdi[frame]/18;
+ }
+ }
+
+ /**
+ * Return the number of values a call to getValues() will return - three
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 3;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(500);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(24);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 10)
+ {
+ Integer step = new Integer(24);
+// Integer step = new Integer(window_size/10);
+// int step_int = step.intValue();
+// step_int+=(step_int % 3); // step is multiple of 3
+// Integer step_out = new Integer(step_int);
+// return step_out;
+ return step;
+
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 2.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(1000);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ return null;
+ }
+
+ private static String makeName(final Strand strand)
+ {
+ if(strand.isForwardStrand())
+ return "Intrinsic Codon Deviation Index";
+ else
+ return "Reverse Intrinsic Codon Deviation Index";
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/KarlinSigAlgorithm.java b/uk/ac/sanger/artemis/plot/KarlinSigAlgorithm.java
new file mode 100644
index 0000000..3a950d2
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/KarlinSigAlgorithm.java
@@ -0,0 +1,300 @@
+/* KarlinSigAlgorithm.java
+ *
+ * created: Thu Aug 9 2001
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/plot/KarlinSigAlgorithm.java,v 1.4 2008-06-27 10:01:30 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ * This class contains the code for calculating the absolute "genomic
+ * signature" difference.
+ * For details see "Global dinucleotide signatures and analysis of genomic
+ * heterogeneity" Samuel Karlin - Current Opinion in Microbiology 1998,
+ * 1:598-610.
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns three floating point numbers, which are the
+ * codon usage scores in each frame. The Strand to use is set in the
+ * constructor.
+ *
+ * @author Kim Rutherford
+ * @version $Id: KarlinSigAlgorithm.java,v 1.4 2008-06-27 10:01:30 tjc Exp $
+ **/
+
+public class KarlinSigAlgorithm extends BaseAlgorithm {
+ /**
+ * Create a new KarlinSigAlgorithm object.
+ * @param strand The Strand to do the calculation on.
+ **/
+ public KarlinSigAlgorithm (final Strand strand) {
+ super (strand, "Karlin Signature Difference", "karlin_sig");
+
+ setScalingFlag (true);
+ }
+
+ /**
+ * Return the Karlin genomic signature for the given range of bases.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range). If the start/end pair
+ * doesn't give a multiple of three bases end is moved down so that it is
+ * a multiple of three.
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues (int start, int end, final float [] values) {
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ end -= (end - start + 1) % 3;
+
+ final char[] sub_sequence;
+
+ try {
+ // reset
+ if(end-start > 1000)
+ ((uk.ac.sanger.artemis.io.StreamSequence)(getStrand().getBases().getSequence())).forceReset();
+ sub_sequence = getStrand().getRawSubSequenceC(new Range (start, end));
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ final float [][] global_relative_abundance_values =
+ getGlobalRelativeAbundance ();
+ final float [][] subseq_relative_abundance_values =
+ getRelativeAbundance (sub_sequence);
+
+ float signature_difference = 0;
+
+ for (int first_base = 0 ; first_base < 4 ; ++first_base) {
+ for (int second_base = 0 ; second_base < 4 ; ++second_base) {
+ final float global_value =
+ global_relative_abundance_values[first_base][second_base];
+ final float subseq_value =
+ subseq_relative_abundance_values[first_base][second_base];
+ signature_difference += Math.abs (global_value - subseq_value);
+ }
+ }
+
+ values [0] = (float) signature_difference / 16f ;
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - three
+ * in this case.
+ **/
+ public int getValueCount () {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize () {
+ final Integer super_window_size = super.getDefaultWindowSize ();
+ if (super_window_size != null) {
+ // the superclass version of getDefaultWindowSize () returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer (240);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize () {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize ();
+ if (super_max_window_size != null) {
+ // the superclass version of getDefaultMaxWindowSize () returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer (5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize () {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize ();
+ if (super_min_window_size != null) {
+ // the superclass version of getDefaultMinWindowSize () returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer (24);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size) {
+ if (window_size > 10) {
+ return new Integer (window_size / 10);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 2.
+ **/
+ protected Float getMaximumInternal () {
+ return new Float (2);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal () {
+ return new Float (0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage () {
+ return null;
+ }
+
+ /**
+ * Return a 4x4 array containing the relative abundance values for each
+ * dinucleotide pair. Indexed by base (t,c,a,g). The value for the
+ * dinucleotide "TT" is stored in global_signature[0][0], "TC" is stored in
+ * [0][1], etc.
+ **/
+ private float [][] getRelativeAbundance (final char [] sequence_forward_raw) {
+ final float [][] return_value = new float [4][4];
+
+ final char [] sequence_reverse_raw =
+ Bases.reverseComplement (sequence_forward_raw);
+
+ final int [] base_counts = new int [4];
+ final int [][] dinucleotide_base_counts = new int [4][4];
+
+ int this_f_base_index = Bases.getIndexOfBase (sequence_forward_raw[0]);
+ int next_f_base_index = 0;
+ int this_r_base_index = Bases.getIndexOfBase (sequence_reverse_raw[0]);
+ int next_r_base_index = 0;
+
+ for (int i = 0 ; i < sequence_forward_raw.length - 1 ; ++i)
+ {
+ next_f_base_index = Bases.getIndexOfBase (sequence_forward_raw[i + 1]);
+
+ if (this_f_base_index < 4 && next_f_base_index < 4) {
+ ++base_counts[this_f_base_index];
+ ++dinucleotide_base_counts[this_f_base_index][next_f_base_index];
+ } else {
+ // ignore Ns
+ }
+
+ next_r_base_index = Bases.getIndexOfBase (sequence_reverse_raw[i + 1]);
+
+ if (this_r_base_index < 4 && next_r_base_index < 4) {
+ ++base_counts[this_r_base_index];
+ ++dinucleotide_base_counts[this_r_base_index][next_r_base_index];
+ } else {
+ // ignore Ns
+ }
+
+ this_f_base_index = next_f_base_index;
+ this_r_base_index = next_r_base_index;
+ }
+
+ // remember to add the last base
+ if (next_f_base_index < 4)
+ ++base_counts[next_f_base_index];
+
+ if (next_r_base_index < 4)
+ ++base_counts[next_r_base_index];
+
+
+ for (int first_base_index = 0 ;
+ first_base_index < 4 ;
+ ++first_base_index)
+ {
+ for (int second_base_index = 0 ;
+ second_base_index < 4 ;
+ ++second_base_index)
+ {
+ final float dinucleotide_frequency =
+ 1f * dinucleotide_base_counts[first_base_index][second_base_index] /
+ (sequence_reverse_raw.length - 1) / 2;
+ final float first_base_frequency =
+ 1f * base_counts[first_base_index] /
+ sequence_reverse_raw.length / 2;
+ final float second_base_frequency =
+ 1f * base_counts[second_base_index] /
+ sequence_reverse_raw.length / 2;
+
+ return_value[first_base_index][second_base_index] =
+ dinucleotide_frequency /
+ (first_base_frequency * second_base_frequency);
+ }
+ }
+
+ return return_value;
+ }
+
+ /**
+ * Return the relative abundance values for the complete sequence. Indexed
+ * by base (t,c,a,g). The value for the dinucleotide "TT" is stored in
+ * global_signature[0][0], "TC" is stored in [0][1], etc.
+ **/
+ private float [][] getGlobalRelativeAbundance () {
+ if (global_relative_abundance_values == null) {
+ try {
+ final Range whole_range =
+ new Range (1, getStrand ().getSequenceLength ());
+ final char[] sequence =
+ getStrand().getRawSubSequenceC(whole_range);
+ global_relative_abundance_values = getRelativeAbundance (sequence);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ return global_relative_abundance_values;
+ }
+
+ /**
+ * Storage for relative abundance values for the complete sequence.
+ * Indexed by base (t,c,a,g). The value for the dinucleotide "TT" is
+ * stored in global_signature[0][0], "TC" is stored in [0][1], etc.
+ **/
+ private float [][] global_relative_abundance_values = null;
+}
diff --git a/uk/ac/sanger/artemis/plot/LineAttributes.java b/uk/ac/sanger/artemis/plot/LineAttributes.java
new file mode 100644
index 0000000..49374a8
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/LineAttributes.java
@@ -0,0 +1,449 @@
+/* LineAttributes.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+
+package uk.ac.sanger.artemis.plot;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JColorChooser;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.ListCellRenderer;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import uk.ac.sanger.artemis.components.Plot;
+
+/**
+ * Define line attributes for graphs lines
+ */
+public class LineAttributes
+{
+ /** defines the colour */
+ private Color lineColour = Color.black;
+
+ /** line label */
+ private String label;
+
+ /** defines the line Stroke - size and style */
+ private BasicStroke stroke;
+
+ private static float dotDash[] = {10.f, 5.f, 3.f, 5.f};
+ private static float dash[] = {5.f};
+
+ private static BasicStroke style1 =
+ new BasicStroke(1.f);
+ private static BasicStroke style2 =
+ new BasicStroke(1.f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER,
+ 1.f, dash, 0.f);
+ private static BasicStroke style3 =
+ new BasicStroke(1.f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER,
+ 1.f, dotDash, 0.f);
+
+ /** available stroke types */
+ private static BasicStroke[] STROKES =
+ new BasicStroke[]{ style1, style2, style3 };
+
+ /** fill in underneath wiggle plots */
+ public static String PLOT_TYPES[] =
+ { "Open", "Filled", "Heat Map" };
+ private String plotType = PLOT_TYPES[0];
+
+ /**
+ * Contruct a LineAttributes instance
+ * @param lineColour
+ */
+ public LineAttributes(Color lineColour)
+ {
+ this.lineColour = lineColour;
+ this.stroke = new BasicStroke(1.f);
+ }
+
+ public Color getLineColour()
+ {
+ return lineColour;
+ }
+
+ public void setLineColour(Color lineColour)
+ {
+ this.lineColour = lineColour;
+ }
+
+ public BasicStroke getStroke()
+ {
+ return stroke;
+ }
+
+ public void setStroke(BasicStroke stroke)
+ {
+ this.stroke = stroke;
+ }
+
+ protected static BasicStroke[] getStrokes()
+ {
+ return STROKES;
+ }
+
+ /**
+ * Given a stroke return the index for it based on the style.
+ * @param stroke
+ * @return
+ */
+ private static int getStyleIndex(BasicStroke stroke)
+ {
+ if(stroke == null)
+ stroke = style1;
+ float myDash[] = stroke.getDashArray();
+ if(myDash != null &&
+ myDash.length == dotDash.length)
+ return 2;
+ else if(myDash != null &&
+ myDash.length == dash.length &&
+ myDash[0] == dash[0])
+ return 1;
+ else
+ return 0;
+ }
+
+ public String getPlotType()
+ {
+ return plotType;
+ }
+
+ public static LineAttributes[] init(int numPlots)
+ {
+ final Color frameColour[] = {
+ Color.red,
+ Color.blue,
+ Color.black,
+ new Color(0,200,0),
+ Color.magenta,
+ new Color(50, 255, 255),
+ Color.yellow };
+ LineAttributes lines[] = new LineAttributes[numPlots];
+
+ if(numPlots == 1)
+ {
+ lines[0] = new LineAttributes(Color.black);
+ return lines;
+ }
+
+ for(int i=0; i<numPlots; i++)
+ {
+ if(i < frameColour.length)
+ lines[i] = new LineAttributes(frameColour[i]);
+ else
+ lines[i] = new LineAttributes(Color.black);
+ }
+ return lines;
+ }
+
+ /**
+ * From a string representation of a colour return the
+ * corresponding <code>Color</code>.
+ * @param colourStr the string to parse
+ * @return
+ */
+ public static Color parse(String colourStr)
+ {
+ if(colourStr.indexOf(":") > -1 ||
+ colourStr.indexOf(",") > -1)
+ {
+ String colourStrs[] = colourStr.split("[:,]");
+ return new Color(Integer.parseInt(colourStrs[0]),
+ Integer.parseInt(colourStrs[1]),
+ Integer.parseInt(colourStrs[2]));
+ }
+
+ if ( colourStr.startsWith("#") )
+ colourStr = colourStr.substring(1);
+
+ colourStr = colourStr.toLowerCase();
+ if (colourStr.length() > 6)
+ throw new NumberFormatException(colourStr+
+ " not a 24 bit representation of the color");
+
+ Color color = new Color( Integer.parseInt( colourStr , 16 ) );
+ return color;
+ }
+
+ /**
+ * Utility used by uk.ac.sanger.artemis.components.Plot
+ * @param numPlots
+ * @param lines
+ * @param plot
+ * @return
+ */
+ public static LineAttributes[] configurePlots(
+ final int numPlots,
+ final LineAttributes[] lines,
+ final Plot plot)
+ {
+ JPanel panel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+
+ final LineAttributes[] thislines;
+ if(lines.length < numPlots)
+ thislines = init(numPlots);
+ else
+ thislines = lines;
+
+ boolean isWiggle = false;
+ if(plot.getAlgorithm() instanceof UserDataAlgorithm)
+ {
+ if(((UserDataAlgorithm)plot.getAlgorithm()).isWiggleFormat())
+ isWiggle = true;
+ }
+
+ int gridx = 0;
+ int gridy = 0;
+ c.gridy = gridy++;
+ c.gridx = gridx;
+ // open / filled
+ panel.add(new JLabel("Plot Options:"), c);
+ final JComboBox openPlot = new JComboBox(PLOT_TYPES);
+ openPlot.setSelectedItem(thislines[0].plotType);
+ openPlot.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ for(int i=0; i<numPlots; i++)
+ thislines[i].plotType =
+ (String) openPlot.getSelectedItem();
+ plot.repaint();
+ }
+ });
+ c.gridy = gridy++;
+ panel.add(openPlot, c);
+
+ c.gridy = gridy++;
+ panel.add(new JLabel("Line Options:"), c);
+ c.gridx = gridx++;
+ c.gridy = gridy++;
+ gridx++;
+
+ final JCheckBox applyToAll = new JCheckBox("Apply colour to all");
+
+ panel.add(new JLabel("Colour"), c);
+ c.gridx = gridx++;
+ panel.add(new JLabel("Line style"), c);
+
+ c.gridx = gridx;
+ panel.add(new JLabel("Line size"), c);
+
+ for(int i=0; i<numPlots; i++)
+ {
+ c.gridy = gridy++;
+ final int colourNumber = i;
+ gridx = 0;
+
+ final JLabel colourLabel = new JLabel(" ");
+ colourLabel.setBackground(thislines[i].getLineColour());
+ colourLabel.setOpaque(true);
+ c.gridx = gridx++;
+ panel.add(colourLabel,c);
+
+ JButton butt = new JButton("Select");
+ butt.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent _)
+ {
+ Color newColour = JColorChooser.showDialog(null, "Colour Chooser",
+ thislines[colourNumber].getLineColour());
+
+ if(applyToAll.isSelected())
+ {
+ for(int iplot=0; iplot<numPlots; iplot++)
+ thislines[iplot].setLineColour(newColour);
+ }
+ else
+ thislines[colourNumber].setLineColour(newColour);
+ colourLabel.setBackground(thislines[colourNumber].getLineColour());
+ plot.repaint();
+ }
+ });
+ c.gridx = gridx++;
+ panel.add(butt, c);
+
+ // line style
+ final int lineWidth;
+ if(lines[colourNumber].getStroke() == null)
+ lineWidth = 0;
+ else
+ lineWidth = (int)lines[colourNumber].getStroke().getLineWidth();
+
+ final JSlider slider = new JSlider(0, 10, lineWidth);
+ Integer index[] = new Integer[STROKES.length];
+ for(int j=0; j<index.length; j++)
+ index[j] = j;
+ final JComboBox lineStyle = new JComboBox(index);
+ lineStyle.setRenderer(new LineStyleListRenderer());
+ lineStyle.setSelectedIndex(
+ LineAttributes.getStyleIndex(thislines[colourNumber].getStroke()));
+ lineStyle.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent e)
+ {
+ thislines[colourNumber].setStroke(
+ STROKES[lineStyle.getSelectedIndex()]);
+ setLineSize(plot, slider, thislines, colourNumber);
+ }
+ });
+ c.gridx = gridx++;
+ panel.add(lineStyle, c);
+
+
+ // line size
+ slider.addChangeListener(new ChangeListener()
+ {
+ public void stateChanged(ChangeEvent e)
+ {
+ thislines[colourNumber].setStroke(
+ STROKES[lineStyle.getSelectedIndex()]);
+ setLineSize(plot, slider, thislines, colourNumber);
+ }
+ });
+ c.gridx = gridx;
+ panel.add(slider, c);
+
+ }
+
+ c.gridx = 0;
+ c.gridy = gridy++;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.anchor = GridBagConstraints.WEST;
+ panel.add(applyToAll, c);
+
+ JScrollPane jsp = new JScrollPane(panel,
+ JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+ JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
+
+ if(panel.getPreferredSize().height > d.height/2)
+ {
+ d.setSize(jsp.getPreferredSize().width, d.height/2);
+ jsp.setPreferredSize(d);
+ }
+
+ String config_options[] = { "OK" };
+ JOptionPane.showOptionDialog(null,
+ jsp, "Configure Lines",
+ JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null, config_options, config_options[0]);
+ return thislines;
+ }
+
+
+ private static void setLineSize(Plot plot, JSlider slider,
+ LineAttributes[] thislines, int number)
+ {
+ if(slider.getValue() == 0.f)
+ thislines[number].setStroke(null);
+ else
+ {
+ BasicStroke oldStroke = thislines[number].getStroke();
+ BasicStroke newStroke = new BasicStroke(slider.getValue(),
+ BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER,
+ 1.f, oldStroke.getDashArray(), 0.f);
+ thislines[number].setStroke(newStroke);
+ }
+ plot.repaint();
+ }
+
+ protected void setLabel(String label)
+ {
+ this.label = label;
+ }
+
+ public String getLabel()
+ {
+ return label;
+ }
+
+ public int getLabelWidth(final FontMetrics fm)
+ {
+ if(label != null)
+ return fm.stringWidth(label) + fm.stringWidth("2")*4;
+ return fm.stringWidth("2")*5;
+ }
+}
+
+
+/**
+ * Renderer for the JComboBox to define different line styles.
+ */
+class LineStyleListRenderer extends JComponent implements ListCellRenderer
+{
+ private static final long serialVersionUID = 1L;
+ private int selectedIndex = 0;
+ public LineStyleListRenderer()
+ {
+ super();
+ setMinimumSize(new Dimension(100, 20));
+ setPreferredSize(new Dimension(100, 20));
+ }
+
+ /**
+ * This method finds the selected index.
+ */
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus)
+ {
+ selectedIndex = ((Integer) value).intValue();
+ return this;
+ }
+
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D)g;
+ g2.setStroke(LineAttributes.getStrokes()[selectedIndex]);
+ g2.drawLine(10, 10, 90, 10);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/plot/MRIAlgorithm.java b/uk/ac/sanger/artemis/plot/MRIAlgorithm.java
new file mode 100644
index 0000000..684a3c6
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/MRIAlgorithm.java
@@ -0,0 +1,555 @@
+/* MRIAlgorithm.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ *
+ * Mutational Response Index (Gatherer and McEwan 1997)
+ * Gatherer D, McEwan NR (1997) Small regions of preferential codon usage and
+ * their effect on overall codon bias--the case of the plp gene. Biochem Mol
+ * Biol Int 43:107-14
+ *
+ * @author Derek Gatherer
+ * @version $Id: MRIAlgorithm.java,v 1.5 2006-06-20 13:40:42 tjc Exp $
+ * original version 10-09-03
+ * revised 01-12-04
+ **/
+
+public class MRIAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new MRIAlgorithm object.
+ * @param strand The Strand to do the calculation on.
+ **/
+ public MRIAlgorithm(final Strand strand)
+ {
+ super(strand, makeName(strand), "");
+ setScalingFlag(true);
+ }
+
+ public void drawLegend(Graphics g, int font_height,
+ int font_width, Color[] frameColour)
+ {
+
+ }
+
+ /**
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range). If the start/end pair
+ * doesn't give a multiple of three bases end is moved down so that it is
+ * a multiple of three.
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ if(getStrand().isForwardStrand()) // rather than isRevCompDisplay()
+ {
+// System.out.println("isRevCompDisplay does not activate here");
+ }
+ else
+ {
+ final int new_end =
+ getStrand().getBases().getComplementPosition(start);
+ final int new_start =
+ getStrand().getBases().getComplementPosition(end);
+
+ end = new_end;
+ start = new_start;
+// System.out.println("Revcomp, so new start:"+start+"new end:"+end);
+ }
+
+ final String sub_sequence;
+
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ if(getStrand().isForwardStrand())
+ end -= (end - start + 1) % 3;
+ else
+ start += (end - start + 1) % 3;
+
+ try
+ {
+ sub_sequence = getStrand().getSubSequence(new Range(start, end));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final float [][][][] exp_value = new float [4][4][4][4]; // 3D for bases, 1 for frame
+ final float [][][][] uncorr_exp_value = new float [4][4][4][4];
+
+ final char [] sequence_raw;
+ sequence_raw = sub_sequence.toCharArray();
+
+ int [][][][] obs_value = new int [4][4][4][4];
+ float [] chi_square = new float [3];
+ float [] uncorr_chi_square = new float [3];
+
+ for(int x = 0; x < 4; ++x)
+ {
+ for(int y = 0; y < 4; ++y)
+ {
+ for(int z = 0; z < 4; ++z)
+ {
+ for(int a = 0; a < 4; ++a)
+ {
+ obs_value[x][y][z][a] = 0;
+ exp_value[x][y][z][a] = 0;
+ exp_value[x][y][z][a] = 0;
+ }
+ }
+ }
+ }
+
+ char this_f_base = 0;
+ char next_f_base = 0;
+ char last_f_base = 0;
+ int this_f_base_index = 0;
+ int next_f_base_index = 0;
+ int last_f_base_index = 0;
+
+ int GC = 0; // GC count
+ float GCfreq = 0; // GC/length
+
+// get GC content
+ for(int c = 0 ; c < sequence_raw.length; c++)
+ {
+ char this_base = sequence_raw[c];
+ if(this_base == 'g' || this_base == 'c')
+ GC++;
+ }
+
+ GCfreq = (float)GC/sequence_raw.length;
+
+ chi_square = new float [3];
+ for(int i = 0 ; i < sequence_raw.length - 5 ; i+=3)
+ {
+ for(int frame = 0; frame < 3; frame++)
+ {
+ this_f_base = sequence_raw[i + frame];
+ next_f_base = sequence_raw[i + 1 + frame];
+ last_f_base = sequence_raw[i + 2 + frame];
+
+ this_f_base_index = Bases.getIndexOfBase(this_f_base);
+ next_f_base_index = Bases.getIndexOfBase(next_f_base);
+ last_f_base_index = Bases.getIndexOfBase(last_f_base);
+
+ // ignore Ns
+ if(this_f_base_index < 4 && next_f_base_index < 4 && last_f_base_index < 4)
+ ++obs_value[this_f_base_index][next_f_base_index][last_f_base_index][(frame+start)%3];
+ }
+ }
+
+ // having collected the observed values in all 3 frames, formulate the expected
+
+ for(int frame = 0; frame < 3; ++frame)
+ {
+ // Phe 001 000
+ // 2-mer AT exp = total aa * (1-GC)
+ exp_value[0][0][0][frame] = (obs_value[0][0][0][frame]+obs_value[0][0][1][frame])*(1-GCfreq);
+ // 2-mer GC exp = total aa * GC
+ exp_value[0][0][1][frame] = (obs_value[0][0][0][frame]+obs_value[0][0][1][frame])*GCfreq;
+ uncorr_exp_value[0][0][0][frame] = (obs_value[0][0][0][frame]+obs_value[0][0][1][frame])/2;
+ uncorr_exp_value[0][0][1][frame] = (obs_value[0][0][0][frame]+obs_value[0][0][1][frame])/2;
+ // Leu = 002 003 100 101 102 103
+ // arg leu SNW exp = total aa * GC * (1-GC) /1.5 100 003 102
+ // arg leu SNS exp = total aa * GC * GC /1.5 101 103
+ // arg leu WNW exp = total aa * (1-GC) * (1-GC)/1.5 002
+ exp_value[0][0][2][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* ((1-GCfreq) * (1-GCfreq))/(float)1.5;
+ exp_value[0][0][3][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+ exp_value[1][0][0][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+ exp_value[1][0][1][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* (GCfreq * GCfreq) /(float)1.5;
+ exp_value[1][0][2][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* ((1-GCfreq) * (1-GCfreq))/(float)1.5;
+ exp_value[1][0][3][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])* (GCfreq * GCfreq) /(float)1.5;
+
+ uncorr_exp_value[0][0][2][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ uncorr_exp_value[0][0][3][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ uncorr_exp_value[1][0][0][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ uncorr_exp_value[1][0][1][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ uncorr_exp_value[1][0][2][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ uncorr_exp_value[1][0][3][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+
+
+// Ile = 200 201 202
+// ile A/T exp = total aa * (1-GC)/1.5
+ exp_value[2][0][0][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])* (1-GCfreq)/(float)1.5;
+ exp_value[2][0][1][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])* GCfreq/(float)1.5;
+ exp_value[2][0][2][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])* (1-GCfreq)/(float)1.5;
+
+ uncorr_exp_value[2][0][0][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])/3;
+ uncorr_exp_value[2][0][1][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])/3;
+ uncorr_exp_value[2][0][2][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])/3;
+
+
+// Val = 300 301 302 303
+// 4-mer AT exp = total aa * (1-GC)/2
+ exp_value[3][0][0][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])*(1-GCfreq)/2;
+// 4-mer GC exp = total aa * GC/2
+ exp_value[3][0][1][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])*GCfreq/2;
+ exp_value[3][0][2][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])*(1-GCfreq)/2;
+ exp_value[3][0][3][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])*GCfreq/2;
+
+ uncorr_exp_value[3][0][0][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])/4;
+ uncorr_exp_value[3][0][1][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])/4;
+ uncorr_exp_value[3][0][2][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])/4;
+ uncorr_exp_value[3][0][3][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])/4;
+
+
+
+// Ser = 010 011 012 013 230 231
+// ser AT exp = total aa * (1-GC)/3
+ exp_value[0][1][0][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*(1-GCfreq)/3;
+// ser GC exp = total aa * GC/3
+ exp_value[0][1][1][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*GCfreq/3;
+ exp_value[0][1][2][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*(1-GCfreq)/3;
+ exp_value[0][1][3][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*GCfreq/3;
+ exp_value[2][3][0][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*(1-GCfreq)/3;
+ exp_value[2][3][1][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])*GCfreq/3;
+
+ uncorr_exp_value[0][1][0][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ uncorr_exp_value[0][1][1][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ uncorr_exp_value[0][1][2][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ uncorr_exp_value[0][1][3][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ uncorr_exp_value[2][3][0][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ uncorr_exp_value[2][3][1][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+
+// Pro = 110 111 112 113
+ exp_value[1][1][0][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])*(1-GCfreq)/2;
+ exp_value[1][1][1][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])*GCfreq/2;
+ exp_value[1][1][2][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])*(1-GCfreq)/2;
+ exp_value[1][1][3][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])*GCfreq/2;
+
+ uncorr_exp_value[1][1][0][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])/4;
+ uncorr_exp_value[1][1][1][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])/4;
+ uncorr_exp_value[1][1][2][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])/4;
+ uncorr_exp_value[1][1][3][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])/4;
+
+
+// Thr = 210 211 212 213
+ exp_value[2][1][0][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])*(1-GCfreq)/2;
+ exp_value[2][1][1][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])*GCfreq/2;
+ exp_value[2][1][2][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])*(1-GCfreq)/2;
+ exp_value[2][1][3][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])*GCfreq/2;
+ uncorr_exp_value[2][1][0][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])/4;
+ uncorr_exp_value[2][1][1][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])/4;
+ uncorr_exp_value[2][1][2][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])/4;
+ uncorr_exp_value[2][1][3][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])/4;
+
+
+// Ala = 310 311 312 313
+ exp_value[3][1][0][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])*(1-GCfreq)/2;
+ exp_value[3][1][1][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])*GCfreq/2;
+ exp_value[3][1][2][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])*(1-GCfreq)/2;
+ exp_value[3][1][3][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])*GCfreq/2;
+ uncorr_exp_value[3][1][0][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])/4;
+ uncorr_exp_value[3][1][1][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])/4;
+ uncorr_exp_value[3][1][2][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])/4;
+ uncorr_exp_value[3][1][3][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])/4;
+
+
+// Tyr = 020 021
+ exp_value[0][2][0][frame] = (obs_value[0][2][0][frame]+obs_value[0][2][1][frame])*(1-GCfreq);
+ exp_value[0][2][1][frame] = (obs_value[0][2][0][frame]+obs_value[0][2][1][frame])*GCfreq;
+ uncorr_exp_value[0][2][0][frame] = (obs_value[0][2][0][frame]+obs_value[0][2][1][frame])/2;
+ uncorr_exp_value[0][2][1][frame] = (obs_value[0][2][0][frame]+obs_value[0][2][1][frame])/2;
+// His = 120 121
+ exp_value[1][2][0][frame] = (obs_value[1][2][0][frame]+obs_value[1][2][1][frame])*(1-GCfreq);
+ exp_value[1][2][1][frame] = (obs_value[1][2][0][frame]+obs_value[1][2][1][frame])*GCfreq;
+ uncorr_exp_value[1][2][0][frame] = (obs_value[1][2][0][frame]+obs_value[1][2][1][frame])/2;
+ uncorr_exp_value[1][2][1][frame] = (obs_value[1][2][0][frame]+obs_value[1][2][1][frame])/2;
+// Gln = 122 123
+ exp_value[1][2][2][frame] = (obs_value[1][2][2][frame]+obs_value[1][2][3][frame])*(1-GCfreq);
+ exp_value[1][2][3][frame] = (obs_value[1][2][2][frame]+obs_value[1][2][3][frame])*GCfreq;
+ uncorr_exp_value[1][2][2][frame] = (obs_value[1][2][2][frame]+obs_value[1][2][3][frame])/2;
+ uncorr_exp_value[1][2][3][frame] = (obs_value[1][2][2][frame]+obs_value[1][2][3][frame])/2;
+// Asn = 220 221
+ exp_value[2][2][0][frame] = (obs_value[2][2][0][frame]+obs_value[2][2][1][frame])*(1-GCfreq);
+ exp_value[2][2][1][frame] = (obs_value[2][2][0][frame]+obs_value[2][2][1][frame])*GCfreq;
+ uncorr_exp_value[2][2][0][frame] = (obs_value[2][2][0][frame]+obs_value[2][2][1][frame])/2;
+ uncorr_exp_value[2][2][1][frame] = (obs_value[2][2][0][frame]+obs_value[2][2][1][frame])/2;
+// Lys = 222 223
+ exp_value[2][2][2][frame] = (obs_value[2][2][2][frame]+obs_value[2][2][3][frame])*(1-GCfreq);
+ exp_value[2][2][3][frame] = (obs_value[2][2][2][frame]+obs_value[2][2][3][frame])*GCfreq;
+ uncorr_exp_value[2][2][2][frame] = (obs_value[2][2][2][frame]+obs_value[2][2][3][frame])/2;
+ uncorr_exp_value[2][2][3][frame] = (obs_value[2][2][2][frame]+obs_value[2][2][3][frame])/2;
+// Asp = 320 321
+ exp_value[3][2][0][frame] = (obs_value[3][2][0][frame]+obs_value[3][2][1][frame])*(1-GCfreq);
+ exp_value[3][2][1][frame] = (obs_value[3][2][0][frame]+obs_value[3][2][1][frame])*GCfreq;
+ uncorr_exp_value[3][2][0][frame] = (obs_value[3][2][0][frame]+obs_value[3][2][1][frame])/2;
+ uncorr_exp_value[3][2][1][frame] = (obs_value[3][2][0][frame]+obs_value[3][2][1][frame])/2;
+// Glu = 322 323
+ exp_value[3][2][2][frame] = (obs_value[3][2][2][frame]+obs_value[3][2][3][frame])*(1-GCfreq);
+ exp_value[3][2][3][frame] = (obs_value[3][2][2][frame]+obs_value[3][2][3][frame])*GCfreq;
+ uncorr_exp_value[3][2][2][frame] = (obs_value[3][2][2][frame]+obs_value[3][2][3][frame])/2;
+ uncorr_exp_value[3][2][3][frame] = (obs_value[3][2][2][frame]+obs_value[3][2][3][frame])/2;
+//Cys = 030 031
+ exp_value[0][3][0][frame] = (obs_value[0][3][0][frame]+obs_value[0][3][1][frame])*(1-GCfreq);
+ exp_value[0][3][1][frame] = (obs_value[0][3][0][frame]+obs_value[0][3][1][frame])*GCfreq;
+ uncorr_exp_value[0][3][0][frame] = (obs_value[0][3][0][frame]+obs_value[0][3][1][frame])/2;
+ uncorr_exp_value[0][3][1][frame] = (obs_value[0][3][0][frame]+obs_value[0][3][1][frame])/2;
+// Arg = 130 131 132 133 232 233
+// arg leu SNW exp = total aa * GC * (1-GC) /1.5 130 233 132
+// arg leu SNS exp = total aa * GC * GC /1.5 131 133
+// arg leu WNW exp = total aa * (1-GC) * (1-GC)/1.5 232
+ exp_value[1][3][0][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+ exp_value[1][3][1][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * GCfreq) /(float)1.5;
+ exp_value[1][3][2][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+ exp_value[1][3][3][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * GCfreq) /(float)1.5;
+ exp_value[2][3][2][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* ((1-GCfreq) * (1-GCfreq))/(float)1.5;
+ exp_value[2][3][3][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])* (GCfreq * (1-GCfreq)) /(float)1.5;
+
+ uncorr_exp_value[1][3][0][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ uncorr_exp_value[1][3][1][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ uncorr_exp_value[1][3][2][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ uncorr_exp_value[1][3][3][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ uncorr_exp_value[2][3][2][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ uncorr_exp_value[2][3][3][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+
+// Gly = 330 331 332 333
+ exp_value[3][3][0][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])*(1-GCfreq)/2;
+ exp_value[3][3][1][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])*GCfreq/2;
+ exp_value[3][3][2][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])*(1-GCfreq)/2;
+ exp_value[3][3][3][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])*GCfreq/2;
+ uncorr_exp_value[3][3][0][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])/4;
+ uncorr_exp_value[3][3][1][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])/4;
+ uncorr_exp_value[3][3][2][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])/4;
+ uncorr_exp_value[3][3][3][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])/4;
+
+ chi_square[frame] = 0;
+ exp_value[2][0][3][frame] = 0; // don't count Met
+ exp_value[0][3][3][frame] = 0; // don't count Trp
+ exp_value[0][2][2][frame] = 0; // don't count STOP
+ exp_value[0][2][3][frame] = 0; // don't count STOP
+ exp_value[0][3][2][frame] = 0; // don't count STOP
+ uncorr_exp_value[2][0][3][frame] = 0; // don't count Met
+ uncorr_exp_value[0][3][3][frame] = 0; // don't count Trp
+ uncorr_exp_value[0][2][2][frame] = 0; // don't count STOP
+ uncorr_exp_value[0][2][3][frame] = 0; // don't count STOP
+ uncorr_exp_value[0][3][2][frame] = 0; // don't count STOP
+
+// having calculated expected, now do chi_square
+ chi_square[0]=0; chi_square[1]=0; chi_square[2]=0;
+ uncorr_chi_square[0]=0; uncorr_chi_square[1]=0; uncorr_chi_square[2]=0;
+ for(int first_base = 0; first_base < 4; ++first_base)
+ {
+ for(int second_base = 0; second_base < 4; ++second_base)
+ {
+ for(int third_base = 0; third_base < 4; ++third_base)
+ {
+ final float obs = obs_value[first_base][second_base][third_base][frame];
+ final float exp = exp_value[first_base][second_base][third_base][frame];
+ final float uncorr_exp = uncorr_exp_value[first_base][second_base][third_base][frame];
+ if(exp >= 1)
+ {
+ if(exp <= 5)
+ {
+ if(Math.abs(exp-obs)>0.25)
+ chi_square[frame] += (Math.pow((Math.abs(exp-obs)-0.5),2))/exp;
+ else
+ chi_square[frame] += (Math.pow((exp-obs),2))/exp;
+ }
+ else
+ chi_square[frame] += (Math.pow((exp-obs),2))/exp;
+ }
+ if(uncorr_exp >= 1)
+ {
+ if(uncorr_exp <= 5)
+ {
+ if(Math.abs(uncorr_exp-obs)>0.25)
+ uncorr_chi_square[frame] += (Math.pow((Math.abs(uncorr_exp-obs)-0.5),2))/uncorr_exp;
+ else
+ uncorr_chi_square[frame] += (Math.pow((uncorr_exp-obs),2))/exp;
+ }
+ else
+ uncorr_chi_square[frame] += (Math.pow((uncorr_exp-obs),2))/exp;
+ }
+ }
+ }
+ }
+ values[frame] = uncorr_chi_square[frame]-chi_square[frame];
+ }
+ }
+
+ /**
+ * Return the number of values a call to getValues() will return - three
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 3;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(500);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(24);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 10)
+ {
+ Integer step = new Integer(24);
+// Integer step = new Integer(window_size/10);
+// int step_int = step.intValue();
+// step_int+=(step_int % 3); // step is multiple of 3
+// Integer step_out = new Integer(step_int);
+// return step_out;
+ return step;
+
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 2.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(1000);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ Float zero = new Float(0);
+ return zero;
+ }
+
+ private static String makeName(final Strand strand)
+ {
+ if(strand.isForwardStrand())
+ return "Mutational Response Index";
+ else
+ return "Reverse Mutational Response Index";
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/NcAlgorithm.java b/uk/ac/sanger/artemis/plot/NcAlgorithm.java
new file mode 100644
index 0000000..a60123b
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/NcAlgorithm.java
@@ -0,0 +1,563 @@
+/* NcAlgorithm.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ *
+ * Nc (Wright 1990)
+ * Wright F (1990) The 'effective number of codons' used in a gene. Gene 87:23-9
+ *
+ * @author Derek Gatherer
+ * original version 09-09-03
+ * revised 01-12-04
+ * division by zero bugremoved 08-12-04
+ **/
+
+public class NcAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new NcAlgorithm object.
+ * @param strand The Strand to do the calculation on.
+ **/
+ public NcAlgorithm(final Strand strand)
+ {
+ super(strand, makeName(strand), "Nc");
+ setScalingFlag(true);
+ }
+
+ public void drawLegend(Graphics g, int font_height,
+ int font_width, Color[] frameColour)
+ {
+
+ }
+
+ /**
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range). If the start/end pair
+ * doesn't give a multiple of three bases end is moved down so that it is
+ * a multiple of three.
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ if(getStrand().isForwardStrand()) // rather than isRevCompDisplay()
+ {
+// System.out.println("isRevCompDisplay does not activate here");
+ }
+ else
+ {
+ final int new_end =
+ getStrand().getBases().getComplementPosition(start);
+ final int new_start =
+ getStrand().getBases().getComplementPosition(end);
+
+ end = new_end;
+ start = new_start;
+ }
+ final String sub_sequence;
+
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ if(getStrand().isForwardStrand())
+ end -= (end - start + 1) % 3;
+ else
+ start += (end - start + 1) % 3;
+
+ try
+ {
+ sub_sequence = getStrand().getSubSequence(new Range(start, end));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ final char[] sequence_raw;
+ sequence_raw = sub_sequence.toCharArray();
+
+ float[][][][] p = new float [4][4][4][4]; // holds p
+ float[][][][] obs_value = new float [4][4][4][4];
+ float[] p2_phe = new float [3]; // hold p2 totals for each amino
+ float[] p2_leu = new float [3];
+ float[] p2_ile = new float [3];
+ float[] p2_val = new float [3];
+ float[] p2_ser = new float [3];
+ float[] p2_pro = new float [3];
+ float[] p2_thr = new float [3];
+ float[] p2_ala = new float [3];
+ float[] p2_tyr = new float [3];
+ float[] p2_his = new float [3];
+ float[] p2_gln = new float [3];
+ float[] p2_asn = new float [3];
+ float[] p2_lys = new float [3];
+ float[] p2_asp = new float [3];
+ float[] p2_glu = new float [3];
+ float[] p2_cys = new float [3];
+ float[] p2_arg = new float [3];
+ float[] p2_gly = new float [3];
+ float[] F2 = new float [3]; // hold Faa for each syn group
+ float[] F3 = new float [3];
+ float[] F4 = new float [3];
+ float[] F6 = new float [3];
+ float[] Nc = new float [3]; // hold Nc for each frame
+ float NC2 = 0;
+ float NC3 = 0;
+ float NC4 = 0;
+ float NC6 = 0;
+
+
+// initialise all arrays to zero, or 1 to prevent infinity (check paper is this valid?)
+
+ for(int x = 0 ; x < 4 ; ++x) {
+ for(int y = 0 ; y < 4 ; ++y) {
+ for(int z = 0 ; z < 4 ; ++z) {
+ for(int a = 0 ; a < 4 ; ++a) {
+ obs_value[x][y][z][a] = 0;
+ p[x][y][z][a] = 0;
+ }
+ }
+ }
+ }
+
+ char this_f_base = 0;
+ char next_f_base = 0;
+ char last_f_base = 0;
+ int this_f_base_index = 0;
+ int next_f_base_index = 0;
+ int last_f_base_index = 0;
+
+ for(int i = 0 ; i < sequence_raw.length - 5 ; i+=3)
+ {
+ for(int frame = 0; frame < 3; frame++)
+ {
+ this_f_base = sequence_raw[i + frame];
+ next_f_base = sequence_raw[i + 1 + frame];
+ last_f_base = sequence_raw[i + 2 + frame];
+
+ this_f_base_index = Bases.getIndexOfBase(this_f_base);
+ next_f_base_index = Bases.getIndexOfBase(next_f_base);
+ last_f_base_index = Bases.getIndexOfBase(last_f_base);
+
+ // ignore Ns
+ if(this_f_base_index < 4 && next_f_base_index < 4 && last_f_base_index < 4)
+ ++obs_value[this_f_base_index][next_f_base_index][last_f_base_index][(frame+start)%3];
+ }
+ }
+
+ // having collected the observed values in all 3 frames, formulate p and p2
+
+ for(int frame = 0; frame < 3; ++frame)
+ {
+ // Phe 001 000
+ if(obs_value[0][0][0][frame]>0 || obs_value[0][0][1][frame]>0)
+ {
+ p[0][0][0][frame] = obs_value[0][0][0][frame]/(obs_value[0][0][0][frame]+obs_value[0][0][1][frame]);
+ p[0][0][1][frame] = obs_value[0][0][1][frame]/(obs_value[0][0][0][frame]+obs_value[0][0][1][frame]);
+ }
+ p2_phe[frame] = (float)Math.pow(p[0][0][0][frame],2)+(float)Math.pow(p[0][0][1][frame],2);
+ // Leu = 002 003 100 101 102 103
+ if(obs_value[0][0][2][frame]>0 || obs_value[0][0][3][frame]>0 || obs_value[1][0][0][frame]>0 ||
+ obs_value[1][0][1][frame]>0 || obs_value[1][0][2][frame]>0 || obs_value[1][0][3][frame]>0)
+ {
+ p[0][0][2][frame] = obs_value[0][0][2][frame]/(obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame]);
+ p[0][0][3][frame] = obs_value[0][0][3][frame]/(obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame]);
+ p[1][0][0][frame] = obs_value[1][0][0][frame]/(obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame]);
+ p[1][0][1][frame] = obs_value[1][0][1][frame]/(obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame]);
+ p[1][0][2][frame] = obs_value[1][0][2][frame]/(obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame]);
+ p[1][0][3][frame] = obs_value[1][0][3][frame]/(obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame]);
+ }
+ p2_leu[frame] = (float)Math.pow(p[0][0][2][frame],2)+(float)Math.pow(p[0][0][3][frame],2)+(float)Math.pow(p[1][0][0][frame],2)+
+ (float)Math.pow(p[1][0][1][frame],2)+(float)Math.pow(p[1][0][2][frame],2)+(float)Math.pow(p[1][0][3][frame],2);
+// Ile = 200 201 202
+ if(obs_value[2][0][0][frame]>0 || obs_value[2][0][1][frame]>0 || obs_value[2][0][2][frame]>0)
+ {
+ p[2][0][0][frame] = obs_value[2][0][0][frame]/(obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame]);
+ p[2][0][1][frame] = obs_value[2][0][1][frame]/(obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame]);
+ p[2][0][2][frame] = obs_value[2][0][2][frame]/(obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame]);
+ }
+ p2_ile[frame] = (float)Math.pow(p[2][0][0][frame],2)+(float)Math.pow(p[2][0][1][frame],2)+(float)Math.pow(p[2][0][2][frame],2);
+// Val = 300 301 302 303
+ if(obs_value[3][0][0][frame]>0 || obs_value[3][0][1][frame]>0 || obs_value[3][0][2][frame]>0 ||
+ obs_value[3][0][3][frame]>0)
+ {
+ p[3][0][0][frame] = obs_value[3][0][0][frame]/(obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame]);
+ p[3][0][1][frame] = obs_value[3][0][1][frame]/(obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame]);
+ p[3][0][2][frame] = obs_value[3][0][2][frame]/(obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame]);
+ p[3][0][3][frame] = obs_value[3][0][3][frame]/(obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame]);
+ }
+ p2_val[frame] = (float)Math.pow(p[3][0][0][frame],2)+(float)Math.pow(p[3][0][1][frame],2)+(float)Math.pow(p[3][0][2][frame],2)+(float)Math.pow(p[3][0][3][frame],2);
+// Ser = 010 011 012 013 230 231
+ if(obs_value[0][1][0][frame]>0 || obs_value[0][1][1][frame]>0 || obs_value[0][1][2][frame]>0 ||
+ obs_value[0][1][3][frame]>0 || obs_value[2][3][0][frame]>0 || obs_value[2][3][1][frame]>0)
+ {
+ p[0][1][0][frame] = obs_value[0][1][0][frame]/(obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame]);
+ p[0][1][1][frame] = obs_value[0][1][1][frame]/(obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame]);
+ p[0][1][2][frame] = obs_value[0][1][2][frame]/(obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame]);
+ p[0][1][3][frame] = obs_value[0][1][3][frame]/(obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame]);
+ p[2][3][0][frame] = obs_value[2][3][0][frame]/(obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame]);
+ p[2][3][1][frame] = obs_value[2][3][1][frame]/(obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame]);
+ }
+ p2_ser[frame] = (float)Math.pow(p[0][1][0][frame],2)+(float)Math.pow(p[0][1][1][frame],2)+(float)Math.pow(p[0][1][2][frame],2)+
+ (float)Math.pow(p[0][1][3][frame],2)+(float)Math.pow(p[2][3][0][frame],2)+(float)Math.pow(p[2][3][1][frame],2);
+// Pro = 110 111 112 113
+ if(obs_value[1][1][0][frame]>0 || obs_value[1][1][1][frame]>0 || obs_value[1][1][2][frame]>0 ||
+ obs_value[1][1][3][frame]>0)
+ {
+ p[1][1][0][frame] = obs_value[1][1][0][frame]/(obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame]);
+ p[1][1][1][frame] = obs_value[1][1][1][frame]/(obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame]);
+ p[1][1][2][frame] = obs_value[1][1][2][frame]/(obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame]);
+ p[1][1][3][frame] = obs_value[1][1][3][frame]/(obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame]);
+ }
+ p2_val[frame] = (float)Math.pow(p[1][1][0][frame],2)+(float)Math.pow(p[1][1][1][frame],2)+(float)Math.pow(p[1][1][2][frame],2)+(float)Math.pow(p[1][1][3][frame],2);
+// Thr = 210 211 212 213
+ if(obs_value[2][1][0][frame]>0 || obs_value[2][1][1][frame]>0 || obs_value[2][1][2][frame]>0 ||
+ obs_value[2][1][3][frame]>0)
+ {
+ p[2][1][0][frame] = obs_value[2][1][0][frame]/(obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame]);
+ p[2][1][1][frame] = obs_value[2][1][1][frame]/(obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame]);
+ p[2][1][2][frame] = obs_value[2][1][2][frame]/(obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame]);
+ p[2][1][3][frame] = obs_value[2][1][3][frame]/(obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame]);
+ }
+ p2_thr[frame] = (float)Math.pow(p[2][1][0][frame],2)+(float)Math.pow(p[2][1][1][frame],2)+(float)Math.pow(p[2][1][2][frame],2)+(float)Math.pow(p[2][1][3][frame],2);
+// Ala = 310 311 312 313
+ if(obs_value[3][1][0][frame]>0 || obs_value[3][1][1][frame]>0 || obs_value[3][1][2][frame]>0 ||
+ obs_value[3][1][3][frame]>0)
+ {
+ p[3][1][0][frame] = obs_value[3][1][0][frame]/(obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame]);
+ p[3][1][1][frame] = obs_value[3][1][1][frame]/(obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame]);
+ p[3][1][2][frame] = obs_value[3][1][2][frame]/(obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame]);
+ p[3][1][3][frame] = obs_value[3][1][3][frame]/(obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame]);
+ }
+ p2_ala[frame] = (float)Math.pow(p[3][1][0][frame],2)+(float)Math.pow(p[3][1][1][frame],2)+(float)Math.pow(p[3][1][2][frame],2)+(float)Math.pow(p[3][1][3][frame],2);
+// Tyr = 020 021
+ if(obs_value[0][2][0][frame]>0 || obs_value[0][2][1][frame]>0)
+ {
+ p[0][2][0][frame] = obs_value[0][2][0][frame]/(obs_value[0][2][0][frame]+obs_value[0][2][1][frame]);
+ p[0][2][1][frame] = obs_value[0][2][1][frame]/(obs_value[0][2][0][frame]+obs_value[0][2][1][frame]);
+ }
+ p2_tyr[frame] = (float)Math.pow(p[0][2][0][frame],2)+(float)Math.pow(p[0][2][1][frame],2);
+// His = 120 121
+ if(obs_value[1][2][0][frame]>0 || obs_value[1][2][1][frame]>0)
+ {
+ p[1][2][0][frame] = obs_value[1][2][0][frame]/(obs_value[1][2][0][frame]+obs_value[1][2][1][frame]);
+ p[1][2][1][frame] = obs_value[1][2][1][frame]/(obs_value[1][2][0][frame]+obs_value[1][2][1][frame]);
+ }
+ p2_his[frame] = (float)Math.pow(p[1][2][0][frame],2)+(float)Math.pow(p[1][2][1][frame],2);
+// Gln = 122 123
+ if(obs_value[1][2][2][frame]>0 || obs_value[1][2][3][frame]>0)
+ {
+ p[1][2][2][frame] = obs_value[1][2][2][frame]/(obs_value[1][2][2][frame]+obs_value[1][2][3][frame]);
+ p[1][2][3][frame] = obs_value[1][2][3][frame]/(obs_value[1][2][2][frame]+obs_value[1][2][3][frame]);
+ }
+ p2_gln[frame] = (float)Math.pow(p[1][2][2][frame],2)+(float)Math.pow(p[1][2][3][frame],2);
+// Asn = 220 221
+ if(obs_value[2][2][0][frame]>0 || obs_value[2][2][1][frame]>0)
+ {
+ p[2][2][0][frame] = obs_value[2][2][0][frame]/(obs_value[2][2][0][frame]+obs_value[2][2][1][frame]);
+ p[2][2][1][frame] = obs_value[2][2][1][frame]/(obs_value[2][2][0][frame]+obs_value[2][2][1][frame]);
+ }
+ p2_asn[frame] = (float)Math.pow(p[2][2][0][frame],2)+(float)Math.pow(p[2][2][1][frame],2);
+// Lys = 222 223
+ if(obs_value[2][2][2][frame]>0 || obs_value[2][2][3][frame]>0)
+ {
+ p[2][2][2][frame] = obs_value[2][2][2][frame]/(obs_value[2][2][2][frame]+obs_value[2][2][3][frame]);
+ p[2][2][3][frame] = obs_value[2][2][3][frame]/(obs_value[2][2][2][frame]+obs_value[2][2][3][frame]);
+ }
+ p2_lys[frame] = (float)Math.pow(p[2][2][2][frame],2)+(float)Math.pow(p[2][2][3][frame],2);
+// Asp = 320 321
+ if(obs_value[3][2][0][frame]>0 || obs_value[3][2][1][frame]>0)
+ {
+ p[3][2][0][frame] = obs_value[3][2][0][frame]/(obs_value[3][2][0][frame]+obs_value[3][2][1][frame]);
+ p[3][2][1][frame] = obs_value[3][2][1][frame]/(obs_value[3][2][0][frame]+obs_value[3][2][1][frame]);
+ }
+ p2_asp[frame] = (float)Math.pow(p[3][2][0][frame],2)+(float)Math.pow(p[3][2][1][frame],2);
+// Glu = 322 323
+ if(obs_value[3][2][2][frame]>0 || obs_value[3][2][3][frame]>0)
+ {
+ p[3][2][2][frame] = obs_value[3][2][2][frame]/(obs_value[3][2][2][frame]+obs_value[3][2][3][frame]);
+ p[3][2][3][frame] = obs_value[3][2][3][frame]/(obs_value[3][2][2][frame]+obs_value[3][2][3][frame]);
+ }
+ p2_glu[frame] = (float)Math.pow(p[3][2][2][frame],2)+(float)Math.pow(p[3][2][3][frame],2);
+//Cys = 030 031
+ if(obs_value[0][3][0][frame]>0 || obs_value[0][3][1][frame]>0)
+ {
+ p[0][3][0][frame] = obs_value[0][3][0][frame]/(obs_value[0][3][0][frame]+obs_value[0][3][1][frame]);
+ p[0][3][1][frame] = obs_value[0][3][1][frame]/(obs_value[0][3][0][frame]+obs_value[0][3][1][frame]);
+ }
+ p2_cys[frame] = (float)Math.pow(p[0][3][0][frame],2)+(float)Math.pow(p[0][3][1][frame],2);
+// Arg = 130 131 132 133 232 233
+ if(obs_value[1][3][0][frame]>0 || obs_value[1][3][1][frame]>0 || obs_value[1][3][2][frame]>0 ||
+ obs_value[1][3][3][frame]>0 || obs_value[2][3][2][frame]>0 || obs_value[2][3][3][frame]>0)
+ {
+ p[1][3][0][frame] = obs_value[1][3][0][frame]/(obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame]);
+ p[1][3][1][frame] = obs_value[1][3][1][frame]/(obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame]);
+ p[1][3][2][frame] = obs_value[1][3][2][frame]/(obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame]);
+ p[1][3][3][frame] = obs_value[1][3][3][frame]/(obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame]);
+ p[2][3][2][frame] = obs_value[2][3][2][frame]/(obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame]);
+ p[2][3][3][frame] = obs_value[2][3][3][frame]/(obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame]);
+ }
+ p2_arg[frame] = (float)Math.pow(p[1][3][0][frame],2)+(float)Math.pow(p[1][3][1][frame],2)+(float)Math.pow(p[1][3][2][frame],2)+
+ (float)Math.pow(p[1][3][3][frame],2)+(float)Math.pow(p[2][3][2][frame],2)+(float)Math.pow(p[2][3][3][frame],2);
+// Gly = 330 331 332 333
+ if(obs_value[3][3][0][frame]>0 || obs_value[3][3][1][frame]>0 || obs_value[3][3][2][frame]>0 ||
+ obs_value[3][3][3][frame]>0)
+ {
+ p[3][3][0][frame] = obs_value[3][3][0][frame]/(obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame]);
+ p[3][3][1][frame] = obs_value[3][3][1][frame]/(obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame]);
+ p[3][3][2][frame] = obs_value[3][3][2][frame]/(obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame]);
+ p[3][3][3][frame] = obs_value[3][3][3][frame]/(obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame]);
+ }
+ p2_gly[frame] = (float)Math.pow(p[3][1][0][frame],2)+(float)Math.pow(p[3][1][1][frame],2)+(float)Math.pow(p[3][1][2][frame],2)+(float)Math.pow(p[3][1][3][frame],2);
+
+ p[2][0][3][frame] = 0; // don't count Met
+ p[0][3][3][frame] = 0; // don't count Trp
+ p[0][2][2][frame] = 0; // don't count STOP
+ p[0][2][3][frame] = 0; // don't count STOP
+ p[0][3][2][frame] = 0; // don't count STOP
+
+// having calculated p2 value, now do Faa, ie. F for each amino acid.
+// various tedious routines to catch abset amino acids
+
+ int F2_aa = 0;
+ if(p2_cys[frame]>0) { F2_aa++; }
+ if(p2_asp[frame]>0) { F2_aa++; }
+ if(p2_glu[frame]>0) { F2_aa++; }
+ if(p2_phe[frame]>0) { F2_aa++; }
+ if(p2_his[frame]>0) { F2_aa++; }
+ if(p2_lys[frame]>0) { F2_aa++; }
+ if(p2_asn[frame]>0) { F2_aa++; }
+ if(p2_gln[frame]>0) { F2_aa++; }
+ if(p2_tyr[frame]>0) { F2_aa++; }
+ if(F2_aa>0)
+ {
+ F2[frame] = (p2_cys[frame]+p2_asp[frame]+p2_glu[frame]+p2_phe[frame]+p2_his[frame]+p2_lys[frame]+
+ p2_asn[frame]+p2_gln[frame]+p2_tyr[frame])/F2_aa;
+ }
+ else { F2[frame] = 0; }
+// System.out.println("F2["+frame+"]="+F2[frame]+" cys: "+p2_cys[frame]+" asp: "+p2_asp[frame]+" glu: "+p2_glu[frame]
+// +" phe: "+p2_phe[frame]+" his: "+p2_his[frame]+" lys: "+p2_lys[frame]+" asn: "+p2_asn[frame]+" gln: "+p2_gln[frame]+" tyr: "+p2_tyr[frame]);
+
+ int F6_aa = 0;
+ if(p2_leu[frame]>0) { F6_aa++; }
+ if(p2_arg[frame]>0) { F6_aa++; }
+ if(p2_ser[frame]>0) { F6_aa++; }
+ if(F6_aa>0)
+ {
+ F6[frame] = (p2_leu[frame]+p2_arg[frame]+p2_ser[frame])/F6_aa;
+ }
+ else { F6[frame] = 0; }
+// System.out.println("F6["+frame+"]="+F6[frame]);
+
+ int F4_aa = 0;
+ if(p2_gly[frame]>0) { F4_aa++; }
+ if(p2_pro[frame]>0) { F4_aa++; }
+ if(p2_thr[frame]>0) { F4_aa++; }
+ if(p2_val[frame]>0) { F4_aa++; }
+ if(p2_ala[frame]>0) { F4_aa++; }
+ if(F4_aa>0)
+ {
+ F4[frame] = (p2_gly[frame]+p2_pro[frame]+p2_thr[frame]+p2_val[frame]+p2_ala[frame])/F4_aa;
+ }
+ else { F4[frame] = 0; }
+// System.out.println("F4["+frame+"]="+F4[frame]);
+
+ if(p2_ile[frame]>0)
+ {
+ F3[frame] = p2_ile[frame];
+ } else {
+ F3[frame]= (F2[frame] + F4[frame])/2;
+ }
+// System.out.println("F3["+frame+"]="+F3[frame]);
+
+// the line below gives the occasional div by zero error
+// Nc[frame] = (9/F2[frame])+(5/F4[frame])+(3/F6[frame])+(1/F3[frame])+2;
+ if(F2[frame]>0)
+ {
+ NC2 = F2_aa/F2[frame];
+ }
+ else { NC2 = 0; }
+ if(F4[frame]>0)
+ {
+ NC4 = F4_aa/F4[frame];
+ }
+ else { NC4 = 0; }
+ if(F6[frame]>0)
+ {
+ NC6 = F6_aa/F6[frame];
+ }
+ else { NC6 = 0; }
+ if(F3[frame]>0)
+ {
+ NC3 = 1/F3[frame];
+ }
+ else { NC3 = 0; }
+
+ Nc[frame] = NC2+NC4+NC6+NC3+2;
+// System.out.println("Nc["+frame+"] is "+"NC2: "+NC2+" NC4: "+NC4+" NC6: "+NC6+" NC3: "+NC3+" + 2 = "+Nc[frame]);
+ values[frame] = Nc[frame];
+ }
+ }
+
+ /**
+ * Return the number of values a call to getValues() will return - three
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 3;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(500);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(24);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 10)
+ {
+ Integer step = new Integer(24);
+// Integer step = new Integer(window_size/10);
+// int step_int = step.intValue();
+// step_int+=(step_int % 3); // step is multiple of 3
+// Integer step_out = new Integer(step_int);
+// return step_out;
+ return step;
+
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 2.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(10000000);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ return null;
+ }
+
+ private static String makeName(final Strand strand)
+ {
+ if(strand.isForwardStrand())
+ return "Effective Codon Number(Nc)";
+ else
+ return "Reverse Effective Codon Number(Nc)";
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/PositionalAsymmetryAlgorithm.java b/uk/ac/sanger/artemis/plot/PositionalAsymmetryAlgorithm.java
new file mode 100644
index 0000000..7bc34f3
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/PositionalAsymmetryAlgorithm.java
@@ -0,0 +1,283 @@
+/* PositionalAsymmetryAlgorithm.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+
+
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+import java.lang.Math.*;
+
+/**
+ *
+ * Positional asymmetry (Shulman et al. 1981)
+ * Shulman MJ, Steinberg CM, Westmoreland N (1981) The coding function of
+ * nucleotide sequences can be discerned by statistical analysis. J Theor Biol
+ * 88:409-20
+ *
+ * @author Derek Gatherer
+ **/
+
+public class PositionalAsymmetryAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new GCWindowAlgorithm object.
+ * @param strand The strand to do the calculation on.
+ **/
+ public PositionalAsymmetryAlgorithm(final Strand strand)
+ {
+ super(strand, makeName(strand), "positional_asymmetry");
+ setScalingFlag(true);
+ }
+
+ /**
+ * Positional asymmetry
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ if(getStrand().isForwardStrand()) // rather than isRevCompDisplay()
+ {
+// System.out.println("isRevCompDisplay does not activate here");
+ }
+ else
+ {
+ final int new_end =
+ getStrand().getBases().getComplementPosition(start);
+ final int new_start =
+ getStrand().getBases().getComplementPosition(end);
+
+ end = new_end;
+ start = new_start;
+// System.out.println("Revcomp, so new start:"+start+"new end:"+end);
+ }
+
+ final String sequence;
+
+ try
+ {
+ sequence = getStrand().getSubSequence(new Range(start, end));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+ float g_count = 0;
+ float c_count = 0;
+ float a_count = 0;
+ float t_count = 0;
+ float[] a_pos = {0,0,0};
+ float[] c_pos = {0,0,0};
+ float[] g_pos = {0,0,0};
+ float[] t_pos = {0,0,0};
+
+ float a_chi = 0;
+ float c_chi = 0;
+ float g_chi = 0;
+ float t_chi= 0;
+ float chi_total = 0;
+
+ for(int i = 0; i < sequence.length(); ++i)
+ {
+ char this_char = sequence.charAt(i);
+
+ if(this_char == 'g')
+ ++g_count;
+ else if(this_char == 'c')
+ ++c_count;
+ else if(this_char == 't')
+ ++a_count;
+ else if(this_char == 'a')
+ ++t_count;
+ }
+
+ float exp_a = a_count/3;
+ float exp_c = c_count/3;
+ float exp_g = g_count/3;
+ float exp_t = t_count/3;
+
+ for(int frame = 0; frame < 3; ++frame)
+ {
+ for(int i = frame; i < sequence.length(); i=i+3)
+ {
+ char this_char = sequence.charAt(i);
+ if(this_char == 'g')
+ ++g_pos[frame];
+ else if(this_char == 'c')
+ ++c_pos[frame];
+ else if(this_char == 't')
+ ++t_pos[frame];
+ else if(this_char == 'a')
+ ++a_pos[frame];
+ }
+ }
+
+ for(int frame = 0; frame < 3; ++frame)
+ {
+ if(exp_a >= 1)
+ {
+ if(exp_a <= 5)
+ a_chi += (Math.pow((Math.abs(exp_a-a_pos[frame])-0.5),2))/exp_a;
+ else
+ a_chi += (Math.pow((exp_a-a_pos[frame]),2))/exp_a;
+ }
+ if(exp_c >= 1)
+ {
+ if(exp_c <= 5)
+ c_chi += (Math.pow((Math.abs(exp_c-c_pos[frame])-0.5),2))/exp_c;
+ else
+ c_chi += (Math.pow((exp_c-c_pos[frame]),2))/exp_c;
+ }
+ if(exp_g >= 1)
+ {
+ if(exp_g <= 5)
+ g_chi += (Math.pow((Math.abs(exp_g-g_pos[frame])-0.5),2))/exp_g;
+ else
+ g_chi += (Math.pow((exp_g-g_pos[frame]),2))/exp_g;
+ }
+ if(exp_t >= 1)
+ {
+ if(exp_t <= 5)
+ t_chi += (Math.pow((Math.abs(exp_t-t_pos[frame])-0.5),2))/exp_t;
+ else
+ t_chi += (Math.pow((exp_t-t_pos[frame]),2))/exp_t;
+ }
+ }
+ chi_total = a_chi + c_chi + g_chi + t_chi;
+
+
+// System.out.println ("start: " + start + " end: " + end + " returning: " + gc_count/sequence.length());
+
+ values[0] = chi_total;
+ }
+
+ /**
+ * Return the number of values a call to getValues() will return - one
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 1;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(500);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(10);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 10)
+ return new Integer(window_size / 10);
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 100.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(10000);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+
+ private static String makeName(final Strand strand)
+ {
+ if(strand.isForwardStrand())
+ return "Positional Asymmetry";
+ else
+ return "Reverse Positional Asymmetry";
+ }
+}
diff --git a/uk/ac/sanger/artemis/plot/ScaledChiAlgorithm.java b/uk/ac/sanger/artemis/plot/ScaledChiAlgorithm.java
new file mode 100644
index 0000000..a1a8daf
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/ScaledChiAlgorithm.java
@@ -0,0 +1,410 @@
+/* ScaledChiAlgorithm.java
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2004 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.sequence.*;
+
+/**
+ *
+ * Scaled Chi Square (Shields and Sharp 1987)
+ * Shields DC, Sharp PM (1987) Synonymous codon usage in Bacillus subtilis
+ * reflects both translational selection and mutational biases. Nucleic Acids
+ * Res 15:8023-40
+ *
+ * @author Derek Gatherer
+ * original version 27-08-03
+ * revised 01-12-04
+ **/
+
+public class ScaledChiAlgorithm extends BaseAlgorithm
+{
+ /**
+ * Create a new ScaledChiAlgorithm object.
+ * @param strand The Strand to do the calculation on.
+ **/
+ public ScaledChiAlgorithm(final Strand strand)
+ {
+ super(strand, makeName(strand), "Scaled Chi Square");
+ setScalingFlag(true);
+ }
+
+ public void drawLegend(Graphics g, int font_height,
+ int font_width, Color[] frameColour)
+ {
+
+ }
+
+ /**
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range). If the start/end pair
+ * doesn't give a multiple of three bases end is moved down so that it is
+ * a multiple of three.
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues(int start, int end, final float [] values)
+ {
+ if(getStrand().isForwardStrand()) // rather than isRevCompDisplay()
+ {
+// System.out.println("isRevCompDisplay does not activate here");
+ }
+ else
+ {
+ final int new_end =
+ getStrand().getBases().getComplementPosition(start);
+ final int new_start =
+ getStrand().getBases().getComplementPosition(end);
+
+ end = new_end;
+ start = new_start;
+// System.out.println("Revcomp, so new start:"+start+"new end:"+end);
+ }
+
+ final String sub_sequence;
+
+ // add 1 or 2 if necessary to make the range a multiple of 3
+ if(getStrand().isForwardStrand())
+ end -= (end - start + 1) % 3;
+ else
+ start += (end - start + 1) % 3;
+
+ try
+ {
+ sub_sequence = getStrand().getSubSequence(new Range(start, end));
+ }
+ catch(OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+
+// System.out.println("start:"+start+"end:"+end);
+// System.out.println(sub_sequence);
+
+ final float[][][][] exp_value = new float[4][4][4][4]; // 3D for bases, 1 for frame
+
+ final char[] sequence_raw;
+ sequence_raw = sub_sequence.toCharArray();
+
+ int[][][][] obs_value = new int [4][4][4][4];
+ float[] chi_square = new float [3];
+
+ for(int x = 0 ; x < 4 ; ++x)
+ {
+ for(int y = 0 ; y < 4 ; ++y)
+ {
+ for(int z = 0 ; z < 4 ; ++z)
+ {
+ for(int a = 0 ; a < 4 ; ++a)
+ {
+ obs_value[x][y][z][a] = 0;
+ exp_value[x][y][z][a] = 0;
+ }
+ }
+ }
+ }
+
+ char this_f_base = 0;
+ char next_f_base = 0;
+ char last_f_base = 0;
+ int this_f_base_index = 0;
+ int next_f_base_index = 0;
+ int last_f_base_index = 0;
+
+ chi_square = new float [3];
+ for(int i = 0 ; i < sequence_raw.length - 5 ; i+=3)
+ {
+ for(int frame = 0; frame < 3; frame++)
+ {
+ this_f_base = sequence_raw[i + frame];
+ next_f_base = sequence_raw[i + 1 + frame];
+ last_f_base = sequence_raw[i + 2 + frame];
+
+ this_f_base_index = Bases.getIndexOfBase(this_f_base);
+ next_f_base_index = Bases.getIndexOfBase(next_f_base);
+ last_f_base_index = Bases.getIndexOfBase(last_f_base);
+
+ // ignore Ns
+ if(this_f_base_index < 4 && next_f_base_index < 4 && last_f_base_index < 4)
+ ++obs_value[this_f_base_index][next_f_base_index][last_f_base_index][(frame+start)%3];
+ }
+ }
+
+ // having collected the observed values in all 3 frames, formulate the expected
+
+ for(int frame = 0; frame < 3; ++frame)
+ {
+ // Phe 001 000
+ exp_value[0][0][0][frame] = (obs_value[0][0][0][frame]+obs_value[0][0][1][frame])/2;
+ exp_value[0][0][1][frame] = (obs_value[0][0][0][frame]+obs_value[0][0][1][frame])/2;
+ // Leu = 002 003 100 101 102 103
+ exp_value[0][0][2][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ exp_value[0][0][3][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ exp_value[1][0][0][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ exp_value[1][0][1][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ exp_value[1][0][2][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+ exp_value[1][0][3][frame] = (obs_value[0][0][2][frame]+obs_value[0][0][3][frame]+obs_value[1][0][0][frame]
+ +obs_value[1][0][1][frame]+obs_value[1][0][2][frame]+obs_value[1][0][3][frame])/6;
+// Ile = 200 201 202
+ exp_value[2][0][0][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])/3;
+ exp_value[2][0][1][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])/3;
+ exp_value[2][0][2][frame] = (obs_value[2][0][0][frame]+obs_value[2][0][1][frame]+obs_value[2][0][2][frame])/3;
+// Val = 300 301 302 303
+ exp_value[3][0][0][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])/4;
+ exp_value[3][0][1][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])/4;
+ exp_value[3][0][2][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])/4;
+ exp_value[3][0][3][frame] = (obs_value[3][0][0][frame]+obs_value[3][0][1][frame]+obs_value[3][0][2][frame]+obs_value[3][0][3][frame])/4;
+// Ser = 010 011 012 013 230 231
+ exp_value[0][1][0][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ exp_value[0][1][1][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ exp_value[0][1][2][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ exp_value[0][1][3][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ exp_value[2][3][0][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+ exp_value[2][3][1][frame] = (obs_value[0][1][0][frame]+obs_value[0][1][1][frame]+obs_value[0][1][2][frame]
+ +obs_value[0][1][3][frame]+obs_value[2][3][0][frame]+obs_value[2][3][1][frame])/6;
+// Pro = 110 111 112 113
+ exp_value[1][1][0][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])/4;
+ exp_value[1][1][1][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])/4;
+ exp_value[1][1][2][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])/4;
+ exp_value[1][1][3][frame] = (obs_value[1][1][0][frame]+obs_value[1][1][1][frame]+obs_value[1][1][2][frame]+obs_value[1][1][3][frame])/4;
+// Thr = 210 211 212 213
+ exp_value[2][1][0][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])/4;
+ exp_value[2][1][1][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])/4;
+ exp_value[2][1][2][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])/4;
+ exp_value[2][1][3][frame] = (obs_value[2][1][0][frame]+obs_value[2][1][1][frame]+obs_value[2][1][2][frame]+obs_value[2][1][3][frame])/4;
+// Ala = 310 311 312 313
+ exp_value[3][1][0][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])/4;
+ exp_value[3][1][1][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])/4;
+ exp_value[3][1][2][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])/4;
+ exp_value[3][1][3][frame] = (obs_value[3][1][0][frame]+obs_value[3][1][1][frame]+obs_value[3][1][2][frame]+obs_value[3][1][3][frame])/4;
+// Tyr = 020 021
+ exp_value[0][2][0][frame] = (obs_value[0][2][0][frame]+obs_value[0][2][1][frame])/2;
+ exp_value[0][2][1][frame] = (obs_value[0][2][0][frame]+obs_value[0][2][1][frame])/2;
+// His = 120 121
+ exp_value[1][2][0][frame] = (obs_value[1][2][0][frame]+obs_value[1][2][1][frame])/2;
+ exp_value[1][2][1][frame] = (obs_value[1][2][0][frame]+obs_value[1][2][1][frame])/2;
+// Gln = 122 123
+ exp_value[1][2][2][frame] = (obs_value[1][2][2][frame]+obs_value[1][2][3][frame])/2;
+ exp_value[1][2][3][frame] = (obs_value[1][2][2][frame]+obs_value[1][2][3][frame])/2;
+// Asn = 220 221
+ exp_value[2][2][0][frame] = (obs_value[2][2][0][frame]+obs_value[2][2][1][frame])/2;
+ exp_value[2][2][1][frame] = (obs_value[2][2][0][frame]+obs_value[2][2][1][frame])/2;
+// Lys = 222 223
+ exp_value[2][2][2][frame] = (obs_value[2][2][2][frame]+obs_value[2][2][3][frame])/2;
+ exp_value[2][2][3][frame] = (obs_value[2][2][2][frame]+obs_value[2][2][3][frame])/2;
+// Asp = 320 321
+ exp_value[3][2][0][frame] = (obs_value[3][2][0][frame]+obs_value[3][2][1][frame])/2;
+ exp_value[3][2][1][frame] = (obs_value[3][2][0][frame]+obs_value[3][2][1][frame])/2;
+// Glu = 322 323
+ exp_value[3][2][2][frame] = (obs_value[3][2][2][frame]+obs_value[3][2][3][frame])/2;
+ exp_value[3][2][3][frame] = (obs_value[3][2][2][frame]+obs_value[3][2][3][frame])/2;
+//Cys = 030 031
+ exp_value[0][3][0][frame] = (obs_value[0][3][0][frame]+obs_value[0][3][1][frame])/2;
+ exp_value[0][3][1][frame] = (obs_value[0][3][0][frame]+obs_value[0][3][1][frame])/2;
+// Arg = 130 131 132 133 232 233
+ exp_value[1][3][0][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ exp_value[1][3][1][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ exp_value[1][3][2][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ exp_value[1][3][3][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ exp_value[2][3][2][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+ exp_value[2][3][3][frame] = (obs_value[1][3][0][frame]+obs_value[1][3][1][frame]+obs_value[1][3][2][frame]
+ +obs_value[1][3][3][frame]+obs_value[2][3][2][frame]+obs_value[2][3][3][frame])/6;
+// Gly = 330 331 332 333
+ exp_value[3][3][0][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])/4;
+ exp_value[3][3][1][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])/4;
+ exp_value[3][3][2][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])/4;
+ exp_value[3][3][3][frame] = (obs_value[3][3][0][frame]+obs_value[3][3][1][frame]+obs_value[3][3][2][frame]+obs_value[3][3][3][frame])/4;
+
+// chi_square[frame] = 0;
+ exp_value[2][0][3][frame] = 0; // don't count Met
+ exp_value[0][3][3][frame] = 0; // don't count Trp
+ exp_value[0][2][2][frame] = 0; // don't count STOP
+ exp_value[0][2][3][frame] = 0; // don't count STOP
+ exp_value[0][3][2][frame] = 0; // don't count STOP
+
+// having calculated expected, now do chi_square
+ chi_square[0]=0; chi_square[1]=0; chi_square[2]=0;
+ for(int first_base = 0 ; first_base < 4 ; ++first_base)
+ {
+ for(int second_base = 0 ; second_base < 4 ; ++second_base)
+ {
+ for(int third_base = 0 ; third_base < 4 ; ++third_base)
+ {
+ final float obs = obs_value[first_base][second_base][third_base][frame];
+ final float exp = exp_value[first_base][second_base][third_base][frame];
+ if(exp >= 1)
+ {
+ if(exp <= 5)
+ {
+ if(Math.abs(exp-obs)>0.25)
+ chi_square[frame] += (Math.pow((Math.abs(exp-obs)-0.5),2))/exp;
+ else
+ chi_square[frame] += (Math.pow((exp-obs),2))/exp;
+ }
+ else
+ chi_square[frame] += (Math.pow((exp-obs),2))/exp;
+ }
+ }
+ }
+ }
+ values[frame] = chi_square[frame];
+ } // end of final frame loop
+ }
+
+ /**
+ * Return the number of values a call to getValues() will return - three
+ * in this case.
+ **/
+ public int getValueCount()
+ {
+ return 3;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize()
+ {
+ final Integer super_window_size = super.getDefaultWindowSize();
+ if(super_window_size != null)
+ {
+ // the superclass version of getDefaultWindowSize() returns non-null
+ // iff the user has set the window size in the options file
+ return super_window_size;
+ }
+ return new Integer(500);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize()
+ {
+ final Integer super_max_window_size = super.getDefaultMaxWindowSize();
+ if(super_max_window_size != null)
+ {
+ // the superclass version of getDefaultMaxWindowSize() returns non-null
+ // iff the user has set the max window size in the options file
+ return super_max_window_size;
+ }
+ return new Integer(5000);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize()
+ {
+ final Integer super_min_window_size = super.getDefaultMinWindowSize();
+ if(super_min_window_size != null)
+ {
+ // the superclass version of getDefaultMinWindowSize() returns non-null
+ // iff the user has set the minimum window size in the options file
+ return super_min_window_size;
+ }
+ return new Integer(24);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize(int window_size)
+ {
+ if(window_size > 10)
+ {
+ Integer step = new Integer(24);
+// Integer step = new Integer(window_size/10);
+// int step_int = step.intValue();
+// step_int+=(step_int % 3); // step is multiple of 3
+// Integer step_out = new Integer(step_int);
+// return step_out;
+ return step;
+
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ * @return The maximum is 2.
+ **/
+ protected Float getMaximumInternal()
+ {
+ return new Float(1000);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ * @return The minimum is 0.
+ **/
+ protected Float getMinimumInternal()
+ {
+ return new Float(0);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ * @return null is returned if this algorithm doesn't have an average or if
+ * the average can't be calculated.
+ **/
+ public Float getAverage()
+ {
+ return null;
+ }
+
+ private static String makeName(final Strand strand)
+ {
+ if(strand.isForwardStrand())
+ return "Scaled Chi Square";
+ else
+ return "Reverse Scaled Chi Square";
+ }
+} // end of class
diff --git a/uk/ac/sanger/artemis/plot/UserDataAlgorithm.java b/uk/ac/sanger/artemis/plot/UserDataAlgorithm.java
new file mode 100644
index 0000000..fb4b8b0
--- /dev/null
+++ b/uk/ac/sanger/artemis/plot/UserDataAlgorithm.java
@@ -0,0 +1,1022 @@
+/* UserDataAlgorithm.java
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000-2012 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package uk.ac.sanger.artemis.plot;
+
+import uk.ac.sanger.artemis.Entry;
+import uk.ac.sanger.artemis.sequence.Bases;
+import uk.ac.sanger.artemis.sequence.Strand;
+import uk.ac.sanger.artemis.util.Document;
+import uk.ac.sanger.artemis.util.FileDocument;
+import uk.ac.sanger.artemis.util.LinePushBackReader;
+import uk.ac.sanger.artemis.util.StringVector;
+import uk.ac.sanger.artemis.components.variant.TabixReader;
+import uk.ac.sanger.artemis.io.IndexFastaStream;
+import uk.ac.sanger.artemis.io.ReadFormatException;
+import java.awt.Color;
+import java.awt.GridLayout;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.regex.Pattern;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import net.sf.samtools.util.BlockCompressedInputStream;
+
+/**
+ * Objects of this class have one useful method - getValues (), which takes a
+ * range of bases and returns a single floating point number. The number is
+ * calculated by averaging the values from a data file. The Strand to use is
+ * set in the constructor.
+ * @author Kim Rutherford
+ **/
+
+public class UserDataAlgorithm extends BaseAlgorithm
+{
+ /** A base per line file format */
+ public static int BASE_PER_LINE_FORMAT = 1;
+ /** Base position is specified in the first column file format */
+ public static int BASE_SPECIFIED_FORMAT = 2;
+ /** Wiggle format */
+ public static int WIGGLE_VARIABLE_STEP_FORMAT = 3;
+ public static int WIGGLE_FIXED_STEP_FORMAT = 4;
+ public static int BLAST_FORMAT = 5;
+ public static int MSPCRUNCH_BLAST_FORMAT = 6;
+ public static int TABIX_INDEXED_FORMAT = 7;
+
+ /** The data read by the constructor - for BASE_PER_LINE_FORMAT */
+ private float data[][] = null;
+
+ /** The data read by the constructor - for BASE_SPECIFIED_FORMAT
+ - and WIGGLE_VARIABLE_STEP_FORMAT
+ - and WIGGLE_FIXED_STEP_FORMAT */
+ private HashMap<Integer, Float[]> dataMap;
+
+ /** The maximum value in the data array. */
+ private float data_max = Float.MIN_VALUE;
+ /** The minimum value in the data array. */
+ private float data_min = Float.MAX_VALUE;
+
+ /** Default window size */
+ private int default_window_size = 3;
+
+ /** The average calculated by readData (). */
+ private float average_value = 0;
+
+ /** The value returned by getValueCount (). */
+ private int number_of_values;
+
+ private boolean logTransform;
+
+ /** Format type for this instance */
+ public int FORMAT = BASE_PER_LINE_FORMAT;
+
+ private LineAttributes lines[];
+ private Wiggle wiggle[];
+ private TabixIdxGraph idxReader;
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(UserDataAlgorithm.class);
+
+ /**
+ * Create a new UserDataAlgorithm object. This reads a file
+ * which can be one of two types of formats:
+ * a. one line of values per base.
+ * b. the first column specifies the base position with
+ * subsequent columns being values.
+ * @param strand The strand to do the calculation on.
+ * @param document The Document to read the data from.
+ * @param logTransform true if the log transformation is to be
+ * shown.
+ **/
+ public UserDataAlgorithm (final Strand strand, final Document doc,
+ final boolean logTransform)
+ throws IOException
+ {
+ super (strand,
+ (logTransform ? "User log plot " + doc.getName () :
+ "User plot " + doc.getName ()), "user");
+ this.logTransform = logTransform;
+
+ if(isIndexed(doc) && doc.getInputStream() instanceof BlockCompressedInputStream)
+ {
+ setAlgorithmName(
+ (logTransform ? "Indexed log plot " + doc.getName () :
+ "Indexed plot " + doc.getName ()));
+ FORMAT = TABIX_INDEXED_FORMAT;
+ idxReader = new TabixIdxGraph(
+ ((FileDocument) doc).getFile().getAbsolutePath());
+ number_of_values = idxReader.getNumberOfValues();
+ return;
+ }
+
+ final Reader doc_reader = doc.getReader ();
+ final LinePushBackReader pushback_reader = new LinePushBackReader (doc_reader);
+ String firstLn = pushback_reader.readLine ();
+
+ final Pattern dataPattern = Pattern.compile("^\\s*([\\d\\.-]+\\s*)+$");
+ final Pattern blastPattern = Pattern.compile(
+ "^(\\S+\\t+){2}[\\d\\.]+\\t+(\\d+\\t+){7}\\S+\\t+(\\s*\\d+)$");
+ final Pattern mspCrunchPattern = Pattern.compile(
+ "^\\d+\\s[\\d\\.]+(\\s\\d+){2}\\s\\D\\S+(\\s\\d+){2}\\s\\D\\S+.*");
+
+ if(dataPattern.matcher(firstLn).matches())
+ FORMAT = BASE_PER_LINE_FORMAT;
+ else if(blastPattern.matcher(firstLn).matches())
+ FORMAT = BLAST_FORMAT;
+ else if(mspCrunchPattern.matcher(firstLn).matches())
+ FORMAT = MSPCRUNCH_BLAST_FORMAT;
+ else
+ {
+ StringBuffer header = new StringBuffer(firstLn+"\n");
+ while(!dataPattern.matcher(firstLn).matches())
+ {
+ firstLn = pushback_reader.readLine ().trim();
+ header.append(firstLn+"\n");
+ }
+
+ FORMAT = parseHeader(header);
+ }
+
+ final Pattern patt = Pattern.compile("\\s+");
+ String tokens[] = patt.split(firstLn);
+
+ if (tokens.length < 1)
+ throw new ReadFormatException ("unknown file type");
+
+ this.number_of_values = tokens.length;
+ pushback_reader.pushBack (firstLn);
+
+ if(FORMAT == BASE_PER_LINE_FORMAT)
+ data = new float [strand.getSequenceLength ()][tokens.length];
+
+ if(FORMAT == BASE_SPECIFIED_FORMAT ||
+ FORMAT == BASE_PER_LINE_FORMAT)
+ readData(pushback_reader);
+ else if(FORMAT == BLAST_FORMAT ||
+ FORMAT == MSPCRUNCH_BLAST_FORMAT)
+ readBlast(pushback_reader);
+ else
+ readWiggle(pushback_reader);
+ pushback_reader.close();
+ doc_reader.close();
+ }
+
+ /**
+ * Test if the tabix (.tbi) index is present.
+ * @param doc
+ * @return
+ */
+ private static boolean isIndexed(Document doc)
+ {
+ if(doc instanceof FileDocument)
+ return (new File(
+ ((FileDocument)doc).getFile().getAbsolutePath() + ".tbi")).exists();
+ return false;
+ }
+
+ /**
+ * Read all from buffered_reader into data.
+ **/
+ private void readData (final LinePushBackReader pushback_reader)
+ throws IOException
+ {
+ String line = null;
+ int count = 0;
+ int countAll = 0;
+ int estimate_window_size = Integer.MAX_VALUE;
+ final int seqLength = getStrand ().getSequenceLength ();
+ final Pattern patt = Pattern.compile("\\s+");
+
+ while ((line = pushback_reader.readLine ()) != null)
+ {
+ if (count >= seqLength)
+ throw new ReadFormatException ("too many values in input file");
+
+ String tokens[] = patt.split(line);
+ if (FORMAT == BASE_PER_LINE_FORMAT && tokens.length != data[0].length)
+ throw new ReadFormatException ("line has the wrong number of fields:\n"+line);
+
+ int base = 0;
+ Float line_data[] = new Float[tokens.length-1];
+ for (int i = 0 ; i < tokens.length ; ++i)
+ {
+ try
+ {
+ if( FORMAT == BASE_SPECIFIED_FORMAT &&
+ i == 0)
+ {
+ int last_base = base;
+ base = (int) Float.parseFloat(tokens[i]);
+
+ if(base > seqLength)
+ throw new ReadFormatException (
+ "a base position is greater than the sequence length:\n"+line);
+
+ if((base - last_base) < estimate_window_size &&
+ (base - last_base) > 0)
+ estimate_window_size = base - last_base;
+ if(dataMap == null)
+ dataMap = new HashMap<Integer, Float[]>();
+ continue;
+ }
+
+ float value = Float.parseFloat(tokens[i]);
+ if(logTransform)
+ value = (float) Math.log(value+1);
+
+ if (value > data_max)
+ data_max = value;
+ if (value < data_min)
+ data_min = value;
+
+ if(FORMAT == BASE_PER_LINE_FORMAT)
+ data[count][i] = value;
+ else
+ {
+ line_data[i-1] = value;
+ countAll++;
+ }
+ average_value += value;
+ }
+ catch (NumberFormatException e)
+ {
+ throw new ReadFormatException ("at line number: "+(count+1)+" cannot understand this number: "+
+ tokens[i] + " - " +e.getMessage ());
+ }
+ }
+ ++count;
+ if(FORMAT == BASE_SPECIFIED_FORMAT)
+ dataMap.put(base, line_data);
+ }
+
+ if (FORMAT == BASE_PER_LINE_FORMAT)
+ average_value /= data[0].length * seqLength;
+ else
+ {
+ average_value = average_value/countAll;
+ if(estimate_window_size != Integer.MAX_VALUE)
+ default_window_size = estimate_window_size;
+ }
+ }
+
+ /**
+ * Read all from buffered_reader into data.
+ **/
+ private void readWiggle (final LinePushBackReader pushback_reader)
+ throws IOException
+ {
+ String line = null;
+ int count = 0;
+ int stepCount = 0;
+ final int seqLength = getStrand ().getSequenceLength ();
+ final Pattern patt = Pattern.compile("\\s+");
+
+ dataMap = new HashMap<Integer, Float[]>();
+ this.number_of_values = 1;
+
+ while ((line = pushback_reader.readLine ()) != null)
+ {
+ if(line.startsWith("track"))
+ {
+ parseTrackLine(line);
+ continue;
+ }
+ else if(line.startsWith("variableStep ") ||
+ line.startsWith("fixedStep"))
+ {
+ FORMAT = parseWiggle(line);
+ stepCount = 0;
+ this.number_of_values++;
+ continue;
+ }
+ else if(line.startsWith("#"))
+ continue;
+
+ String tokens[] = patt.split(line.trim());
+ int base = 0;
+
+ int valueIndex = 0;
+ try
+ {
+ if(FORMAT == WIGGLE_VARIABLE_STEP_FORMAT)
+ {
+ base = (int) Float.parseFloat(tokens[0]);
+ valueIndex = 1;
+ }
+ else
+ {
+ base = wiggle[wiggle.length-1].start +
+ (stepCount*wiggle[wiggle.length-1].step);
+ }
+
+ if(base > seqLength)
+ throw new ReadFormatException (
+ "the base position ("+base+") is greater than the sequence length:\n"+line);
+
+ float value = Float.parseFloat(tokens[valueIndex]);
+ if(logTransform)
+ value = (float) Math.log(value+1);
+
+ if (value > data_max)
+ data_max = value;
+ if (value < data_min)
+ data_min = value;
+
+ final Float valueArray[] = new Float[number_of_values];;
+ if(dataMap.containsKey(base))
+ {
+ Float oldValues[] = dataMap.get(base);
+ for(int i=0; i<oldValues.length; i++)
+ valueArray[i] = oldValues[i];
+ }
+
+ valueArray[number_of_values-1] = value;
+ dataMap.put(base, valueArray);
+
+ count++;
+ stepCount++;
+ average_value += value;
+ }
+ catch (NumberFormatException e)
+ {
+ throw new ReadFormatException ("cannot understand this number: " +
+ tokens[valueIndex] + " - " +e.getMessage ());
+ }
+ }
+
+ average_value = average_value/count;
+ default_window_size = 1;
+ }
+
+
+ /**
+ * Read all from buffered_reader into data.
+ **/
+ private void readBlast (final LinePushBackReader pushback_reader)
+ throws IOException
+ {
+ String line = null;
+ int count = 0;
+ int lineNum = 0;
+ final int seqLength = getStrand ().getSequenceLength ();
+ final Pattern patt;
+
+ if(FORMAT == BLAST_FORMAT)
+ patt = Pattern.compile("\\t+");
+ else
+ patt = Pattern.compile("\\s");
+
+ dataMap = new HashMap<Integer, Float[]>();
+ this.number_of_values = 1;
+
+ int coordIndexStart = 6;
+ int coordIndexEnd = 7;
+
+ if(FORMAT == MSPCRUNCH_BLAST_FORMAT)
+ {
+ coordIndexStart = 2;
+ coordIndexEnd = 3;
+ }
+
+ while ((line = pushback_reader.readLine ()) != null)
+ {
+ String tokens[] = patt.split(line.trim());
+
+ if(lineNum == 0)
+ {
+ final JPanel message = new JPanel(new GridLayout(2,1));
+
+ String queryStr = (FORMAT == BLAST_FORMAT) ? tokens[0] : tokens[4];
+ String subjStr = (FORMAT == BLAST_FORMAT) ? tokens[1] : tokens[7];
+
+ JCheckBox query = new JCheckBox(queryStr, true);
+ message.add(query);
+ JCheckBox subj = new JCheckBox(subjStr, false);
+ message.add(subj);
+ ButtonGroup group = new ButtonGroup();
+ group.add(query);
+ group.add(subj);
+
+ JOptionPane.showConfirmDialog(null, message,
+ "Use Coordinates From", JOptionPane.OK_OPTION);
+ if(subj.isSelected())
+ {
+ if(FORMAT == BLAST_FORMAT)
+ {
+ coordIndexStart = 8;
+ coordIndexEnd = 9;
+ }
+ else
+ {
+ coordIndexStart = 5;
+ coordIndexEnd = 6;
+ }
+ }
+ }
+ lineNum++;
+
+ int startBase = Integer.parseInt(tokens[coordIndexStart]);
+ int endBase = Integer.parseInt(tokens[coordIndexEnd]);
+ float value;
+ if(FORMAT == BLAST_FORMAT)
+ value = Float.parseFloat(tokens[11]);
+ else
+ value = Float.parseFloat(tokens[0]);
+
+ int valueIndex = 0;
+ try
+ {
+ if(startBase > seqLength || endBase > seqLength)
+ throw new ReadFormatException (
+ "the location ("+startBase+".."+endBase+
+ ") is outside than the sequence length:\n"+line);
+
+ if(logTransform)
+ value = (float) Math.log(value+1);
+
+ if (value > data_max)
+ data_max = value;
+ if (value < data_min)
+ data_min = value;
+
+ if(startBase > endBase)
+ {
+ int tmpStart = startBase;
+ startBase = endBase;
+ endBase = tmpStart;
+ }
+
+ final Float valueArray[] = new Float[number_of_values];
+ for (int base = startBase; base <= endBase; base++)
+ {
+ if (dataMap.containsKey(base))
+ {
+ Float oldValues[] = dataMap.get(base);
+ if(oldValues[0] < value)
+ {
+ valueArray[number_of_values - 1] = value;
+ dataMap.put(base, valueArray);
+ }
+ }
+ else
+ {
+ valueArray[number_of_values - 1] = value;
+ dataMap.put(base, valueArray);
+ }
+ count++;
+ }
+
+ average_value += value;
+ }
+ catch (NumberFormatException e)
+ {
+ throw new ReadFormatException ("cannot understand this number: " +
+ tokens[valueIndex] + " - " +e.getMessage ());
+ }
+ }
+
+ average_value = average_value/count;
+ default_window_size = 1;
+
+ FORMAT = BLAST_FORMAT;
+ }
+
+ /**
+ * Return the value of the function between a pair of bases.
+ * @param start The start base (included in the range).
+ * @param end The end base (included in the range).
+ * @param values The one return value for this algorithm is returned in
+ * this array.
+ **/
+ public void getValues (int start, int end, final float [] values)
+ {
+ final int value_count = getValueCount ();
+ if(getStrand ().getDirection() == Bases.REVERSE)
+ {
+ int tstart = start;
+ int tend = end;
+ end = getStrand().getBases().getComplementPosition(tstart);
+ start = getStrand().getBases().getComplementPosition(tend);
+ }
+
+ if(FORMAT == BASE_SPECIFIED_FORMAT ||
+ FORMAT == WIGGLE_VARIABLE_STEP_FORMAT ||
+ FORMAT == WIGGLE_FIXED_STEP_FORMAT ||
+ FORMAT == BLAST_FORMAT)
+ {
+ for (int i = 0 ; i < value_count ; ++i)
+ {
+ values[i] = 0;
+ int count = 0;
+ for (int base = start ; base <= end ; ++base)
+ {
+ if(dataMap.containsKey(base) &&
+ ((Float[])dataMap.get(base)).length > i &&
+ ((Float[])dataMap.get(base))[i] != null)
+ {
+ values[i] += ((Float[])dataMap.get(base))[i];
+ count++;
+ }
+ }
+
+ if(count > 1)
+ values[i] = values[i]/count;
+ }
+ }
+ else if(FORMAT == TABIX_INDEXED_FORMAT)
+ idxReader.getValues(start, end, values);
+ else
+ {
+ for (int i = 0 ; i < value_count ; ++i)
+ {
+ values [i] = 0;
+ for (int base = start ; base <= end ; ++base)
+ values [i] += data[base - 1][i] / (end - start + 1);
+ }
+ }
+ }
+
+ /**
+ * Determine the graph file format.
+ * Read the line colour from the header, there should be
+ * one per line and space separated.
+ * @param line
+ * @throws ReadFormatException
+ */
+ private int parseHeader(StringBuffer headerText) throws ReadFormatException
+ {
+ FORMAT = BASE_SPECIFIED_FORMAT;
+ BufferedReader reader = new BufferedReader(
+ new StringReader(headerText.toString()));
+
+ String line = null;
+ try
+ {
+ while ((line = reader.readLine()) != null)
+ {
+ if((line.indexOf("colour") == -1 &&
+ line.indexOf("color") == -1 &&
+ line.indexOf("label ") == -1) ||
+ line.startsWith("track"))
+ {
+ if(line.startsWith("track "))
+ parseTrackLine(line);
+ else if(line.startsWith("variableStep ") ||
+ line.startsWith("fixedStep"))
+ FORMAT = parseWiggle(line);
+
+ continue;
+ }
+
+ int idx = line.indexOf("colour");
+ if (idx == -1)
+ idx = line.indexOf("color");
+
+ if (idx != -1)
+ {
+ idx = line.indexOf(" ", idx + 1);
+ line = line.substring(idx).trim();
+ String rgbValues[] = line.split(" ");
+
+ lines = new LineAttributes[rgbValues.length];
+ for (int j = 0; j < rgbValues.length; j++)
+ lines[j] = new LineAttributes(LineAttributes.parse(rgbValues[j]));
+ }
+ else if(lines != null)
+ {
+ idx = line.indexOf("label ");
+ if (idx != -1)
+ {
+ line = line.substring(idx+6).trim();
+ String labels[] = line.split(" ");
+ for (int j = 0; j < labels.length; j++)
+ lines[j].setLabel(labels[j]);
+ }
+ }
+ }
+ }
+ catch (NumberFormatException e)
+ {
+ throw new ReadFormatException ("cannot understand this number: " +
+ line + " - " +e.getMessage ());
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ return FORMAT;
+ }
+
+ /**
+ * http://genome.ucsc.edu/goldenPath/help/hgWiggleTrackHelp.html
+ *
+ * All options are placed in a single line separated by spaces:
+ *
+ * track type=wiggle_0 name=track_label description=center_label
+ * visibility=display_mode color=r,g,b altColor=r,g,b
+ * priority=priority autoScale=on|off
+ * gridDefault=on|off maxHeightPixels=max:default:min
+ * graphType=bar|points viewLimits=lower:upper
+ * yLineMark=real-value yLineOnOff=on|off
+ * windowingFunction=maximum|mean|minimum smoothingWindow=off|2-16
+ *
+ * @param trackLine
+ */
+ private void parseTrackLine(final String trackLine)
+ {
+ String colour = "0,0,0";
+ int beginIndex = trackLine.indexOf(" color=");
+ if(beginIndex > -1)
+ {
+ beginIndex+=7;
+ int endIndex = trackLine.indexOf(" ", beginIndex);
+ colour = trackLine.substring(beginIndex, endIndex);
+ }
+
+ incrementLines(LineAttributes.parse(colour));
+ }
+
+ private void incrementLines(final Color c)
+ {
+ LineAttributes line = new LineAttributes(c);
+
+ if(lines == null)
+ lines = new LineAttributes[1];
+ else
+ {
+ LineAttributes linesTmp[] = new LineAttributes[lines.length];
+ System.arraycopy(lines, 0, linesTmp, 0, lines.length);
+ lines = new LineAttributes[linesTmp.length+1];
+ System.arraycopy(linesTmp, 0, lines, 0, linesTmp.length);
+ }
+ lines[lines.length-1] = line;
+ }
+
+ /**
+ * Wiggle formats (default: span=1) :
+ * variableStep chrom=chrN [span=windowSize]
+ * fixedStep chrom=chrN start=position step=stepInterval [span=windowSize]
+ * @param line
+ * @return
+ */
+ private int parseWiggle(String line) throws NumberFormatException
+ {
+ if(line.startsWith("variableStep "))
+ FORMAT = WIGGLE_VARIABLE_STEP_FORMAT;
+ else if (line.startsWith("fixedStep "))
+ FORMAT = WIGGLE_FIXED_STEP_FORMAT;
+
+ if(wiggle == null)
+ {
+ wiggle = new Wiggle[lines.length];
+ for(int i=0; i<wiggle.length; i++)
+ wiggle[i] = new Wiggle();
+ }
+ else
+ {
+ Wiggle wiggleTmp[] = new Wiggle[wiggle.length];
+ System.arraycopy(wiggle, 0, wiggleTmp, 0, wiggle.length);
+ wiggle = new Wiggle[wiggleTmp.length+1];
+ System.arraycopy(wiggleTmp, 0, wiggle, 0, wiggleTmp.length);
+ wiggle[wiggle.length-1] = new Wiggle();
+ }
+
+ if(wiggle.length > lines.length)
+ incrementLines(lines[lines.length-1].getLineColour());
+
+ if(FORMAT == WIGGLE_FIXED_STEP_FORMAT)
+ {
+ wiggle[wiggle.length-1].start =
+ Integer.parseInt(getSubString(" start=" ,line));
+
+ wiggle[wiggle.length-1].step =
+ Integer.parseInt(getSubString(" step=" ,line));
+ }
+
+ int beginIndex = line.indexOf(" span=");
+ if(beginIndex > -1)
+ {
+ wiggle[wiggle.length-1].span =
+ Integer.parseInt(getSubString(" span=" ,line));
+ }
+
+ return FORMAT;
+ }
+
+
+ /**
+ * Find the value of a key within a string.
+ * @param key
+ * @param line
+ * @return
+ */
+ private String getSubString(String key, String line)
+ {
+ int beginIndex = line.indexOf(key)+key.length();
+ int endIndex = line.indexOf(" ", beginIndex);
+ if(endIndex == -1)
+ endIndex = line.length();
+ return line.substring(beginIndex, endIndex);
+ }
+
+
+ /**
+ * Return any LineAttributes read from the header (for
+ * BASE_SPECIFIED_FORMAT).
+ * @return
+ */
+ public LineAttributes[] getLineAttributes()
+ {
+ return lines;
+ }
+
+ /**
+ * Return the number of values a call to getValues () will return - one
+ * in this case.
+ **/
+ public int getValueCount ()
+ {
+ if(FORMAT == BASE_SPECIFIED_FORMAT)
+ return number_of_values - 1;
+ return number_of_values;
+ }
+
+ /**
+ * Return the default or optimal window size.
+ * @return null is returned if this algorithm doesn't have optimal window
+ * size.
+ **/
+ public Integer getDefaultWindowSize ()
+ {
+ return new Integer (default_window_size);
+ }
+
+ /**
+ * Return the default maximum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have maximum window
+ * size.
+ **/
+ public Integer getDefaultMaxWindowSize ()
+ {
+ return new Integer (100);
+ }
+
+ /**
+ * Return the default minimum window size for this algorithm.
+ * @return null is returned if this algorithm doesn't have minimum window
+ * size.
+ **/
+ public Integer getDefaultMinWindowSize ()
+ {
+ return new Integer (1);
+ }
+
+ /**
+ * Return the default or optimal step size.
+ * @return null is returned if this algorithm doesn't have optimal step
+ * size.
+ **/
+ public Integer getDefaultStepSize (int window_size)
+ {
+ if (window_size > 10)
+ return new Integer (window_size / 10);
+ else
+ return null;
+ }
+
+ /**
+ * Return the maximum value of this algorithm.
+ **/
+ protected Float getMaximumInternal ()
+ {
+ return new Float (data_max);
+ }
+
+ /**
+ * Return the minimum value of this algorithm.
+ **/
+ protected Float getMinimumInternal ()
+ {
+ return new Float (data_min);
+ }
+
+ /**
+ * Return the average value of function over the whole strand.
+ **/
+ public Float getAverage ()
+ {
+ return new Float (average_value);
+ }
+
+ public int getWiggleStart(int index)
+ {
+ return wiggle[index].start;
+ }
+
+ public int getWiggleSpan(int index)
+ {
+ return wiggle[index].span;
+ }
+
+ protected boolean isWiggleFormat()
+ {
+ if(FORMAT == WIGGLE_VARIABLE_STEP_FORMAT ||
+ FORMAT == WIGGLE_FIXED_STEP_FORMAT)
+ return true;
+ return false;
+ }
+
+ public void readIndexValues(boolean recalculate_flag, Entry seqEntry, int start, int end)
+ {
+ if(start<1)
+ start = 1;
+ idxReader.readValuesForRange(recalculate_flag, seqEntry, start, end);
+ }
+
+ class Wiggle
+ {
+ int start;
+ int step;
+ int span = 0;
+ }
+
+ class TabixIdxGraph
+ {
+ private TabixReader reader;
+ /** number of columns with values */
+ private int nValues = 1;
+ private float[][] rvalues;
+ private boolean startColIsEndCol = true;
+ private int sbeg, send;
+
+ TabixIdxGraph(final String fName) throws IOException
+ {
+ this.reader = new TabixReader(fName);
+ if(getStartColumn() != getEndColumn())
+ startColIsEndCol = false;
+
+ final StringBuffer headerBuffer = new StringBuffer();
+ String metaChar = String.valueOf(reader.getCommentChar());
+ String hdr;
+ while( (hdr = reader.readLine() ) != null && hdr.startsWith(metaChar) )
+ headerBuffer.append(hdr + "\n");
+ //System.out.println("Header "+headerBuffer.toString());
+
+ // assume base end column is last column before columns of values
+ nValues = reader.readLine().split("\\t").length - getEndColumn();
+ }
+
+ /**
+ * Return the values between a pair of bases
+ * @param start
+ * @param end
+ * @param values
+ */
+ private void getValues(int start, int end, final float[] values)
+ {
+ for (int i = 0 ; i < getNumberOfValues() ; ++i)
+ {
+ values [i] = 0;
+ for (int base = start ; base <= end ; ++base)
+ values [i] += rvalues[base - this.sbeg][i] / (end - start + 1);
+ }
+ }
+
+ /**
+ * Get the sequence name
+ * @param seqEntry - sequence entry
+ * @return
+ */
+ private String getReferenceName(Entry seqEntry)
+ {
+ String refStr = null;
+ if(seqEntry.getEMBLEntry().getSequence() instanceof IndexFastaStream)
+ refStr =
+ ((IndexFastaStream)seqEntry.getEMBLEntry().getSequence()).getContig();
+ else if(seqEntry.getHeaderText() != null)
+ {
+ final String hdr = seqEntry.getHeaderText();
+ int idx = hdr.indexOf("ID ");
+ if (idx == -1)
+ {
+ idx = hdr.indexOf("LOCUS "); // genbank
+ if (idx > -1)
+ refStr = hdr.substring(idx + 12).split("[;\\s]")[0];
+ }
+ else
+ refStr = hdr.substring(idx + 5).split("[;\\s]")[0];
+ }
+
+ final String seqNames[] = reader.getSeqNames();
+ if(refStr == null || !Arrays.asList(seqNames).contains(refStr))
+ {
+ logger4j.debug(refStr+" NOT FOUND IN "+reader.getFileName()+
+ " SET TO DEFAULT "+seqNames[0]);
+ refStr = reader.getSeqNames()[0];
+ }
+ return refStr;
+ }
+
+ /**
+ * Read the values in a range
+ * @param recalculate_flag
+ * @param seqEntry - sequence entry
+ * @param start - start base
+ * @param end - end base
+ */
+ private void readValuesForRange(boolean recalculate_flag, Entry seqEntry, int start, int end)
+ {
+ if(!recalculate_flag && (end <= start || (start == this.sbeg && end == this.send)) )
+ return;
+
+ this.sbeg = start;
+ this.send = end;
+ rvalues = new float[end-start+1][getNumberOfValues()];
+ final String r = getReferenceName(seqEntry)+":"+start+"-"+end;
+
+ try
+ {
+ final TabixReader.Iterator tbxIt = reader.query(r);
+ String ln;
+ while( tbxIt != null && (ln = tbxIt.next()) != null )
+ {
+ StringVector parts = StringVector.getStrings(ln, "\t", true);
+ final int base;
+ if(startColIsEndCol)
+ base = Integer.parseInt((String)parts.get(getStartColumn()-1)) - start;
+ else
+ {
+ int b = Integer.parseInt(parts.get(getStartColumn()-1));
+ int e = Integer.parseInt(parts.get(getEndColumn()-1));
+ base = b + ((b - e)/2) - start;
+ }
+ for(int i=0; i<rvalues[base].length; i++)
+ {
+ float val = Float.parseFloat( parts.get(i+getEndColumn()) );
+ if(logTransform)
+ val = (float) Math.log(val+1);
+
+ if (val > data_max)
+ data_max = val;
+ if (val < data_min)
+ data_min = val;
+ rvalues[base][i] = val;
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ logger4j.debug("IOException READING RANGE "+r+" FROM "+reader.getFileName());
+ e.printStackTrace();
+ }
+ catch (NumberFormatException e)
+ {
+ logger4j.debug("NumberFormatException READING RANGE "+r+" FROM "+reader.getFileName());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Number of columns with values
+ * @return
+ */
+ private int getNumberOfValues()
+ {
+ return nValues;
+ }
+
+ private int getStartColumn()
+ {
+ return reader.getStartColumn();
+ }
+
+ private int getEndColumn()
+ {
+ return reader.getEndColumn();
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/sequence/AminoAcidSequence.java b/uk/ac/sanger/artemis/sequence/AminoAcidSequence.java
new file mode 100644
index 0000000..aed54a5
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/AminoAcidSequence.java
@@ -0,0 +1,1086 @@
+/* AminoAcidSequence.java
+ *
+ * created: Sat Dec 19 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/AminoAcidSequence.java,v 1.11 2007-07-09 12:38:51 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.util.OutOfRangeException;
+import uk.ac.sanger.artemis.util.StringVector;
+
+/**
+ * Objects of this class represent a string of amino acids.
+ *
+ * @author Kim Rutherford
+ * @version $Id: AminoAcidSequence.java,v 1.11 2007-07-09 12:38:51 tjc Exp $
+ **/
+
+public class AminoAcidSequence
+{
+ /**
+ * Create a new AminoAcidSequence object from a string containing single
+ * character amino acids symbols.
+ **/
+ public AminoAcidSequence(String amino_acid_string)
+ {
+ this.amino_acid_string = amino_acid_string;
+ }
+
+ /**
+ * Translate a sequence of bases into the corresponding single letter amino
+ * acid codes.
+ * @param bases The bases to translated. If the string length is not a
+ * multiple of three the last codon is incomplete and will not be
+ * translated.
+ * @param unknown_is_x If this parameter is true codons that contain
+ * ambiguous bases will be translated as 'x', if false they will be
+ * translated as '.'
+ * @return The translated sequence in one letter abbreviated form.
+ **/
+ public static AminoAcidSequence getTranslation(final String bases,
+ final boolean unknown_is_x)
+ {
+// this is set in Splash.java
+// setGeneCode();
+//
+ final StringBuffer aa_buffer = new StringBuffer();
+ final int number_of_codons = bases.length() / 3;
+
+ for(int i = 0 ; i < number_of_codons * 3 ; i += 3)
+ {
+ final char aa = getCodonTranslation(bases.charAt(i),
+ bases.charAt(i+1),
+ bases.charAt(i+2));
+ if(aa == '.' && unknown_is_x)
+ aa_buffer.append ('x');
+ else
+ aa_buffer.append (aa);
+ }
+ return new AminoAcidSequence(aa_buffer.toString());
+ }
+
+ /**
+ * Translate a sequence of bases into the corresponding single letter amino
+ * acid codes.
+ * @param bases The bases to translated. If the string length is not a
+ * multiple of three the last codon is incomplete and will not be
+ * translated.
+ * @param unknown_is_x If this parameter is true codons that contain
+ * ambiguous bases will be translated as 'x', if false they will be
+ * translated as '.'
+ * @return The translated sequence in one letter abbreviated form.
+ **/
+ public static AminoAcidSequence getTranslation(final char[] bases,
+ final boolean unknown_is_x)
+ {
+// this is set in Splash.java
+// setGeneCode();
+//
+ final StringBuffer aa_buffer = new StringBuffer();
+ final int number_of_codons = bases.length / 3;
+
+ for(int i = 0 ; i < number_of_codons * 3 ; i += 3)
+ {
+ final char aa = getCodonTranslation(bases[i],
+ bases[i+1],
+ bases[i+2]);
+ if(aa == '.' && unknown_is_x)
+ aa_buffer.append ('x');
+ else
+ aa_buffer.append (aa);
+ }
+ return new AminoAcidSequence(aa_buffer.toString());
+ }
+
+ /**
+ * Translate a sequence of bases into the corresponding single letter amino
+ * acid codes and appending 2 spaces after each amino acid character.
+ * @param bases The bases to translated. If the string length is not a
+ * multiple of three the last codon is incomplete and will not be
+ * translated.
+ * @param unknown_is_x If this parameter is true codons that contain
+ * ambiguous bases will be translated as 'x', if false they will be
+ * translated as '.'
+ * @return The translated sequence in one letter abbreviated form.
+ **/
+ public static AminoAcidSequence getSpacedTranslation(final String bases,
+ final boolean unknown_is_x)
+ {
+ final StringBuffer aa_buffer = new StringBuffer();
+ final int number_of_codons = bases.length() / 3;
+ for(int i = 0 ; i < number_of_codons * 3 ; i += 3)
+ {
+ final char aa = getCodonTranslation(bases.charAt(i),
+ bases.charAt(i+1),
+ bases.charAt(i+2));
+ if(aa == '.' && unknown_is_x)
+ aa_buffer.append('x');
+ else
+ aa_buffer.append(aa);
+ aa_buffer.append(" ");
+ }
+ return new AminoAcidSequence(aa_buffer.toString());
+ }
+
+ /**
+ * Translate a sequence of bases into the corresponding single letter amino
+ * acid codes and appending 2 spaces after each amino acid character.
+ * @param bases The bases to translated. If the string length is not a
+ * multiple of three the last codon is incomplete and will not be
+ * translated.
+ * @param unknown_is_x If this parameter is true codons that contain
+ * ambiguous bases will be translated as 'x', if false they will be
+ * translated as '.'
+ * @return The translated sequence in one letter abbreviated form.
+ **/
+ public static AminoAcidSequence getSpacedTranslation(final char bases[],
+ final boolean unknown_is_x)
+ {
+ final StringBuffer aa_buffer = new StringBuffer();
+ final int number_of_codons = bases.length / 3;
+ for(int i = 0 ; i < number_of_codons * 3 ; i += 3)
+ {
+ final char aa = getCodonTranslation(bases[i],
+ bases[i+1],
+ bases[i+2]);
+ if(aa == '.' && unknown_is_x)
+ aa_buffer.append('x');
+ else
+ aa_buffer.append(aa);
+ aa_buffer.append(" ");
+ }
+ return new AminoAcidSequence(aa_buffer.toString());
+ }
+
+ /**
+ * Translate a single codon into the corresponding single letter amino acid
+ * code.
+ * @param codon_string A three character lowercase String containing the
+ * bases to translate.
+ * @return The translated sequence in one letter abbreviated form. The
+ * return value will be '.' if the codon_string isn't a codon (eg. it
+ * contains more or less than three letters or the letters aren't from
+ * "CTAG")
+ **/
+ public static char getCodonTranslation(String codon_string)
+ {
+ if(codon_string.length() < 3)
+ return '.';
+
+ return getCodonTranslation(codon_string.charAt(0),
+ codon_string.charAt(1),
+ codon_string.charAt(2));
+ }
+
+ /**
+ * Translate a single codon into the corresponding single letter amino acid
+ * code.
+ * @param first_letter The first base of the codon.
+ * @param second_letter The second base of the codon.
+ * @param third_letter The third base of the codon.
+ * @return The translated sequence in one letter abbreviated form. The
+ * return value will be '.' if the letters do not form a codon.
+ **/
+ public final static char getCodonTranslation(char first_letter,
+ char second_letter,
+ char third_letter)
+ {
+ final int first_index = getIndexOfBase(first_letter);
+ if(first_index >= 4)
+ return '.';
+
+ final int second_index = getIndexOfBase(second_letter);
+ if(second_index >= 4)
+ return '.';
+
+ final int third_index = getIndexOfBase(third_letter);
+ if(third_index >= 4)
+ return '.';
+
+ final int codon_index = first_index * 16 + second_index * 4 + third_index;
+
+ return codon_translation_array[codon_index];
+ }
+
+ /**
+ * Given a base letter return its index where t = 0, c = 1, a = 2, g = 3, 4
+ * otherwise.
+ * See letter_index.
+ **/
+ private final static int getIndexOfBase(final char base)
+ {
+ switch(base)
+ {
+ case 'c':
+ return 1;
+ case 'a':
+ return 2;
+ case 'g':
+ return 3;
+ case 't':
+ case 'u':
+ return 0;
+ }
+
+ return 4;
+ }
+
+
+ /**
+ * Return the number of units in this amino acid sequence.
+ **/
+ public int length()
+ {
+ return amino_acid_string.length();
+ }
+
+ /**
+ * Return the one letter codon code of the codon at the given index
+ * (counting from zero).
+ **/
+ public char elementAt(final int index)
+ {
+ return amino_acid_string.charAt(index);
+ }
+
+ /**
+ * Return the total molecular weight of the amino acids in this
+ * AminoAcidSequence..
+ **/
+ public float getMolecularWeight()
+ {
+ float return_weight = 0;
+
+ for(int i = 0 ; i < amino_acid_string.length() ; ++i)
+ {
+ final char this_char = amino_acid_string.charAt(i);
+
+ return_weight +=
+ molecular_weights[getSymbolIndex(this_char)];
+ }
+
+ if(amino_acid_string.length() > 1)
+ {
+ // need to take off the weight of a water molecule for each peptide bond
+ return return_weight -
+ molecular_weight_of_water * (amino_acid_string.length () - 1);
+ }
+ else
+ return return_weight;
+ }
+
+ /**
+ * Return a string representation of this object. This string will contain
+ * the one character amino acid codes for each acid in sequence.
+ **/
+ public String toString()
+ {
+ return amino_acid_string;
+ }
+
+ /**
+ * Search the subject_sequence for this AminoAcidSequence as a substring.
+ * 'X' AAs are treated as wildcards in both sequences.
+ **/
+ public boolean checkForMatch(final AminoAcidSequence subject_sequence)
+ {
+ final String subject_sequence_string = subject_sequence.toString();
+
+ for(int subject_index = 0;
+ subject_index < subject_sequence_string.length() -
+ toString ().length () + 1;
+ ++subject_index)
+ {
+ int query_index = 0;
+ //boolean is_matching = true;
+ for(; query_index < toString().length();
+ ++query_index)
+ {
+ final char this_query_char =
+ toString().charAt(query_index);
+ final char this_subject_char =
+ subject_sequence_string.charAt(subject_index + query_index);
+ if(!aminoAcidMatches(this_subject_char,
+ this_query_char))
+ break;
+ }
+
+ if(query_index == toString().length())
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return true if and only if the two argument are the same AA or if one is
+ * an X.
+ **/
+ private static boolean aminoAcidMatches(final char aa_char1,
+ final char aa_char2)
+ {
+ if (aa_char1 == aa_char2)
+ return true;
+ else
+ {
+ if(aa_char1 == 'x' || aa_char2 == 'x')
+ return true;
+ else
+ return false;
+ }
+ }
+
+ /**
+ * Find the next occurrence of this seqeuence on either Strand of the given
+ * Bases object. This method searches both strands simultaneously by
+ * searching the underlying Bases object directly.
+ * @param bases This holds the Strand objects to search.
+ * @param search_start_marker The match that will be returned will be
+ * after this base Marker position. Position 1 on the reverse strand is
+ * considered to be after position 1 on the forward strand, forward
+ * position 2 is after reverse position 1, reverse position 2 is after
+ * forward position 2, etc. This scheme allows the caller to iterate
+ * through all matches.
+ * @param search_end_position The search will not extend past this
+ * position.
+ * @param search_backwards If true the search will move from last base to
+ * first base, otherwise first to last.
+ * @return A MarkerRange covering the matching bases or null if there is no
+ * match in the given range.
+ **/
+ public MarkerRange findMatch(final Bases bases,
+ final Marker search_start_marker,
+ final boolean search_backwards,
+ final boolean search_fwd_strand,
+ final boolean search_bwd_strand)
+ {
+ final String bases_string = bases.toString();
+
+ // search the bases_string forward for the pattern_string and its
+ // complement
+
+ // the String index position in bases_string at which to start the search
+ // for this pattern
+ final int forward_search_start_index;
+
+ // the String index position in bases_string at which to start the search
+ // for the reverse complement of this position
+ final int complement_search_start_index;
+
+ if(search_backwards)
+ {
+ if(search_start_marker == null)
+ {
+ forward_search_start_index = bases.getLength () - 1;
+ complement_search_start_index = bases.getLength () - 1;
+ }
+ else
+ {
+ complement_search_start_index =
+ search_start_marker.getRawPosition() - 2;
+ if(search_start_marker.getStrand().isForwardStrand())
+ {
+ forward_search_start_index =
+ search_start_marker.getRawPosition() - 2;
+ }
+ else
+ {
+ forward_search_start_index =
+ search_start_marker.getRawPosition() - 1;
+ }
+ }
+ }
+ else
+ {
+ if(search_start_marker == null)
+ {
+ forward_search_start_index = 0;
+ complement_search_start_index = 0;
+ }
+ else
+ {
+ forward_search_start_index = search_start_marker.getRawPosition();
+ if(search_start_marker.getStrand().isForwardStrand())
+ {
+ complement_search_start_index =
+ search_start_marker.getRawPosition() - 1;
+ }
+ else
+ {
+ complement_search_start_index =
+ search_start_marker.getRawPosition();
+ }
+ }
+ }
+
+ final int forward_search_result;
+ if(search_fwd_strand)
+ forward_search_result = searchFor(bases_string,
+ forward_search_start_index,
+ search_backwards);
+ else
+ forward_search_result = -1;
+
+ final int complement_search_result;
+ if(search_bwd_strand)
+ complement_search_result = reverseComplementSearchFor(bases_string,
+ complement_search_start_index,
+ search_backwards);
+ else
+ complement_search_result = -1;
+
+ final int match_first_base;
+ final int match_last_base;
+
+ final Strand match_strand;
+
+ if(forward_search_result == -1)
+ {
+ // no match
+ if(complement_search_result == -1)
+ return null;
+ }
+
+ if(search_backwards)
+ {
+ // take the match that is closest to the end, or the complement match if
+ // there is a tie
+ if(complement_search_result != -1 &&
+ (forward_search_result == -1 ||
+ complement_search_result >= forward_search_result))
+ {
+ match_first_base =
+ bases.getComplementPosition (complement_search_result + 1);
+ match_last_base = match_first_base - (length () * 3 - 1);
+ match_strand = bases.getReverseStrand ();
+ }
+ else
+ {
+ match_first_base = forward_search_result + 1;
+ match_last_base = match_first_base + length () * 3 - 1;
+ match_strand = bases.getForwardStrand ();
+ }
+ }
+ else
+ {
+ // take the match that is closest to base 1, or the forward match if
+ // there is a tie
+ if(forward_search_result != -1 &&
+ (complement_search_result == -1 ||
+ forward_search_result <= complement_search_result))
+ {
+ match_first_base = forward_search_result + 1;
+ match_last_base = match_first_base + length () * 3 - 1;
+ match_strand = bases.getForwardStrand ();
+ }
+ else
+ {
+ match_first_base =
+ bases.getComplementPosition (complement_search_result + 1);
+ match_last_base = match_first_base - (length () * 3 - 1);
+ match_strand = bases.getReverseStrand ();
+ }
+ }
+
+ try
+ {
+ return new MarkerRange(match_strand,
+ match_first_base,
+ match_last_base);
+ }
+ catch (OutOfRangeException e)
+ {
+ throw new Error("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Search for this AminoAcidSequence in the given String of bases. The
+ * String is treated as a sequence of bases and this AminoAcidSequence is
+ * searched for in each of the three reading frames.
+ * @param bases_string Search this String for the amino acid sequence.
+ * @param start_index This is the index in bases_string where the search
+ * should start.
+ * @param search_backwards If true the search will move from last base to
+ * first base, otherwise first to last.
+ * @return The index of the match or -1 if there is no match.
+ **/
+ private int searchFor(final String bases_string,
+ final int start_index,
+ final boolean search_backwards)
+ {
+ if(search_backwards)
+ return searchBackwardFor(bases_string, start_index);
+ else
+ return searchForwardFor(bases_string, start_index);
+ }
+
+ /**
+ * Search forward for this AminoAcidSequence in the given String of bases.
+ * The String is treated as a sequence of bases and this AminoAcidSequence
+ * is searched for in each of the three reading frames.
+ * @param bases_string Search this String for the amino acid sequence.
+ * @param start_index This is the index in bases_string where the search
+ * should start.
+ * @return The index of the match or -1 if there is no match.
+ **/
+ private int searchForwardFor(final String bases_string,
+ final int start_index)
+ {
+ final int pattern_base_length = length() * 3;
+
+ for(int base_index = start_index;
+ base_index <= bases_string.length() - pattern_base_length ;
+ ++base_index)
+ {
+ boolean matched = true;
+
+ for(int offset = 0 ; offset < length(); ++offset)
+ {
+ final char search_aa = amino_acid_string.charAt(offset);
+
+ // X matches any AA
+ if(search_aa == 'x' || search_aa == 'X')
+ continue;
+
+ final char base1 = bases_string.charAt(base_index + offset * 3 + 0);
+ final char base2 = bases_string.charAt(base_index + offset * 3 + 1);
+ final char base3 = bases_string.charAt(base_index + offset * 3 + 2);
+
+ if(getCodonTranslation(base1, base2, base3) != search_aa)
+ {
+ matched = false;
+ break;
+ }
+ }
+
+ if(matched)
+ return base_index;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Search backward for this AminoAcidSequence in the given String of bases.
+ * The String is treated as a sequence of bases and this AminoAcidSequence
+ * is searched for in each of the three reading frames.
+ * @param bases_string Search this String for the amino acid sequence.
+ * @param start_index This is the index in bases_string where the search
+ * should start.
+ * @return The index of the match or -1 if there is no match.
+ **/
+ private int searchBackwardFor(final String bases_string,
+ int start_index)
+ {
+ if(bases_string.length() - start_index < length() * 3)
+ start_index = bases_string.length() - length() * 3;
+
+ for(int base_index = start_index; base_index >= 0;
+ --base_index)
+ {
+ boolean matched = true;
+
+ for(int offset = 0 ; offset < length() ; ++offset)
+ {
+ final char search_aa = amino_acid_string.charAt(offset);
+
+ // X matches any AA
+ if(search_aa == 'x' || search_aa == 'X')
+ continue;
+
+ final char base1 = bases_string.charAt(base_index + offset * 3 + 0);
+ final char base2 = bases_string.charAt(base_index + offset * 3 + 1);
+ final char base3 = bases_string.charAt(base_index + offset * 3 + 2);
+
+ if(getCodonTranslation(base1, base2, base3) != search_aa)
+ {
+ matched = false;
+ break;
+ }
+ }
+
+ if(matched)
+ return base_index;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Search for this AminoAcidSequence in the reverse complement of the given
+ * String of bases. The String is treated as a sequence of bases and this
+ * AminoAcidSequence is searched for in each of the three reading frames.
+ * @param bases_string Search this String for the amino acid sequence.
+ * @param start_index This is the index in bases_string where the search
+ * should start.
+ * @param search_backwards If true the search will move from last base to
+ * first base, otherwise first to last.
+ * @return The index of the match or -1 if there is no match.
+ **/
+ private int reverseComplementSearchFor(final String bases_string,
+ final int start_index,
+ final boolean search_backwards)
+ {
+ if(search_backwards)
+ return reverseComplementSearchBackwardFor(bases_string, start_index);
+ else
+ return reverseComplementSearchForwardFor(bases_string, start_index);
+ }
+
+ /**
+ * Search forward for this AminoAcidSequence in the reverse complement of
+ * the given String of bases. The String is treated as a sequence of bases
+ * and this AminoAcidSequence is searched for in each of the three reading
+ * frames.
+ * @param bases_string Search this String for the amino acid sequence.
+ * @param start_index This is the index in bases_string where the search
+ * should start.
+ * @return The index of the match or -1 if there is no match.
+ **/
+ private int reverseComplementSearchForwardFor(final String bases_string,
+ final int start_index)
+ {
+ final int pattern_base_length = length() * 3;
+
+ for(int base_index = start_index ;
+ base_index <= bases_string.length() - pattern_base_length ;
+ ++base_index)
+ {
+ boolean matched = true;
+
+ for(int offset = 0; offset < length (); ++offset)
+ {
+ final char base1 =
+ Bases.complement(bases_string.charAt(base_index + offset * 3 + 0));
+ final char base2 =
+ Bases.complement(bases_string.charAt(base_index + offset * 3 + 1));
+ final char base3 =
+ Bases.complement(bases_string.charAt(base_index + offset * 3 + 2));
+
+ final char amino_acid_char =
+ amino_acid_string.charAt(amino_acid_string.length() - offset - 1);
+
+ // X matches any AA
+ if(amino_acid_char == 'x' || amino_acid_char == 'X')
+ // X matches any AA
+ continue;
+
+ if(getCodonTranslation(base3, base2, base1) != amino_acid_char)
+ {
+ matched = false;
+ break;
+ }
+ }
+
+ if(matched)
+ return base_index;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Search backward for this AminoAcidSequence in the reverse complement of
+ * the given String of bases. The String is treated as a sequence of bases
+ * and this AminoAcidSequence is searched for in each of the three reading
+ * frames.
+ * @param bases_string Search this String for the amino acid sequence.
+ * @param start_index This is the index in bases_string where the search
+ * should start.
+ * @return The index of the match or -1 if there is no match.
+ **/
+ private int reverseComplementSearchBackwardFor(final String bases_string,
+ int start_index)
+ {
+ if(bases_string.length() - start_index < length() * 3)
+ start_index = bases_string.length() - length() * 3;
+
+ for(int base_index = start_index; base_index >= 0;
+ --base_index)
+ {
+ boolean matched = true;
+
+ for(int offset = 0 ; offset < length() ; ++offset)
+ {
+ final char base1 =
+ Bases.complement(bases_string.charAt(base_index + offset * 3 + 0));
+ final char base2 =
+ Bases.complement(bases_string.charAt(base_index + offset * 3 + 1));
+ final char base3 =
+ Bases.complement(bases_string.charAt(base_index + offset * 3 + 2));
+
+ final char amino_acid_char =
+ amino_acid_string.charAt(amino_acid_string.length() - offset - 1);
+
+ // X matches any AA
+ if(amino_acid_char == 'x' || amino_acid_char == 'X')
+ continue;
+
+ if(getCodonTranslation(base3, base2, base1) != amino_acid_char)
+ {
+ matched = false;
+ break;
+ }
+ }
+
+ if(matched)
+ return base_index;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Return true if and only if this sequence contains a stop codon.
+ **/
+ public boolean containsStopCodon()
+ {
+ for(int i = 0 ; i < amino_acid_string.length() ; ++i)
+ {
+ final char this_char = amino_acid_string.charAt(i);
+
+ if(isStopCodon (this_char))
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return true if and only if the given amino acid symbol is the
+ * translation of a stop codon. ie #, * or +.
+ **/
+ public static boolean isStopCodon(final char amino_acid_char)
+ {
+ if(amino_acid_char == '#' ||
+ amino_acid_char == '*' ||
+ amino_acid_char == '+')
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Return true if and only if the given one letter code symbol is the a
+ * legal amino acid or stop symbol.
+ **/
+ protected static boolean isLegalCodon(char one_letter_code)
+ {
+ one_letter_code = Character.toLowerCase(one_letter_code);
+ switch(one_letter_code)
+ {
+ case 'a': case 'r': case 'n': case 'd': case 'c': case 'q': case 'e':
+ case 'g': case 'h': case 'i': case 'l': case 'k': case 'm': case 'f':
+ case 'p': case 's': case 't': case 'w': case 'y': case 'v': case '*':
+ case '#': case '+':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * This table is used for fast lookup of codon translations by
+ * getCodonTranslation(). There is one entry for each codon and the
+ * entries are in this order: TTT, TTC, TTA, TTG, TCT, TCC, ...
+ **/
+ final public static char [] codon_translation_array = {
+ 'f', 'f', 'l', 'l',
+ 's', 's', 's', 's',
+ 'y', 'y', '#', '+',
+ 'c', 'c', '*', 'w',
+
+ 'l', 'l', 'l', 'l',
+ 'p', 'p', 'p', 'p',
+ 'h', 'h', 'q', 'q',
+ 'r', 'r', 'r', 'r',
+
+ 'i', 'i', 'i', 'm',
+ 't', 't', 't', 't',
+ 'n', 'n', 'k', 'k',
+ 's', 's', 'r', 'r',
+
+ 'v', 'v', 'v', 'v',
+ 'a', 'a', 'a', 'a',
+ 'd', 'd', 'e', 'e',
+ 'g', 'g', 'g', 'g'
+ };
+
+ /**
+ * Used by getAminoAcidType().
+ **/
+ public final static int POLAR_UNCHARGED_AA = 0;
+ /**
+ * Used by getAminoAcidType().
+ **/
+ public final static int POSITIVELY_CHARGED_AA = 1;
+ /**
+ * Used by getAminoAcidType().
+ **/
+ public final static int NEGATIVELY_CHARGED_AA = 2;
+ /**
+ * Used by getAminoAcidType().
+ **/
+ public final static int HYDROPHOBIC_AA = 3;
+ /**
+ * Used by getAminoAcidType().
+ **/
+ public final static int SPECIAL_AA = 4;
+ /**
+ * Used by getAminoAcidType().
+ **/
+ public final static int STOP_AA = 5;
+ /**
+ * Used by getAminoAcidType().
+ **/
+ public final static int UNKNOWN_AA = 6;
+ /**
+ * Used by getAminoAcidType().
+ **/
+ public final static int ILLEGAL_AA = 7;
+
+ /**
+ * Returns one of POLAR_UNCHARGED_AA, POSITIVELY_CHARGED_AA,
+ * NEGATIVELY_CHARGED_AA, HYDROPHOBIC_AA, SPECIAL_AA or STOP_AA depending
+ * on the aa_char argument.
+ **/
+ public static int getAminoAcidType(final char aa_char)
+ {
+ switch (aa_char)
+ {
+ case 'S': case 'T': case 'N': case 'Q':
+ return POLAR_UNCHARGED_AA;
+
+ case 'K': case 'R': case 'H':
+ return POSITIVELY_CHARGED_AA;
+
+ case 'E': case 'D':
+ return NEGATIVELY_CHARGED_AA;
+
+ case 'A': case 'I': case 'L': case 'M': case 'F': case 'W': case 'V':
+ case 'Y':
+ return HYDROPHOBIC_AA;
+
+ case 'C': case 'G': case 'P':
+ return SPECIAL_AA;
+
+ case '#': case '*': case '+':
+ return STOP_AA;
+
+ default:
+ return ILLEGAL_AA;
+ }
+ }
+
+
+ /**
+ * Return the one letter abbreviation for the given three letter amino acid
+ * name.
+ * @return A one letter code or -1 if three_letter_code can't be understood.
+ **/
+ public static char getOneLetterCode(final String three_letter_code)
+ {
+ final String real_code =
+ three_letter_code.substring(0, 1).toUpperCase() +
+ three_letter_code.substring(1).toLowerCase();
+
+ for(int i = 0 ; i < amino_acid_one_letter_names.length ; ++i)
+ {
+ if(real_code.equals(amino_acid_abbreviated_names[i]))
+ return amino_acid_one_letter_names[i];
+ }
+
+ return (char) -1;
+ }
+
+ /**
+ * Return the three letter abbreviation for the given one letter amino acid
+ * code.
+ **/
+ public static String getThreeLetterAbbreviation(char one_letter_code)
+ {
+ one_letter_code = Character.toLowerCase(one_letter_code);
+ for(int i = 0 ; i < amino_acid_one_letter_names.length ; ++i)
+ {
+ if(one_letter_code == amino_acid_one_letter_names[i])
+ return amino_acid_abbreviated_names[i];
+ }
+
+ throw new Error("internal error - illegal one letter amino acid code");
+ }
+
+ /**
+ * Return the three letter abbreviation for the given index code.
+ **/
+ public static String getThreeLetterAbbreviation(final int index)
+ {
+ return amino_acid_abbreviated_names[index];
+ }
+
+ /**
+ * Return an integer from 0 to 22 representing the index of a codon
+ * symbol.
+ **/
+ public static int getSymbolIndex(char one_letter_code)
+ {
+ one_letter_code = Character.toLowerCase(one_letter_code);
+ switch(one_letter_code)
+ {
+ case 'a': return 0;
+ case 'r': return 1;
+ case 'n': return 2;
+ case 'd': return 3;
+ case 'c': return 4;
+ case 'q': return 5;
+ case 'e': return 6;
+ case 'g': return 7;
+ case 'h': return 8;
+ case 'i': return 9;
+ case 'l': return 10;
+ case 'k': return 11;
+ case 'm': return 12;
+ case 'f': return 13;
+ case 'p': return 14;
+ case 's': return 15;
+ case 't': return 16;
+ case 'w': return 17;
+ case 'y': return 18;
+ case 'v': return 19;
+ case '*': return 20;
+ case '#': return 21;
+ case '+': return 22;
+ case '.': return 23;
+ case 'x': return 23;
+ case 'u': return 24;
+ default:
+ throw new Error("Internal error - illegal one letter codon symbol: " +
+ one_letter_code);
+ }
+ }
+
+ /**
+ * Given an index return a one letter codon symbol. This method is the
+ * inverse of getSymbolIndex().
+ **/
+ public static char getSymbolFromIndex(final int index)
+ {
+ return amino_acid_one_letter_names[index];
+ }
+
+ /**
+ * A String containg the amino acids symbols of this object.
+ **/
+ private String amino_acid_string = null;
+
+ /**
+ * The three letter abbreviated names for the amino acids and stop codons.
+ * The names here correspond to the letter codes at the same indices in
+ * amino_acid_one_letter_names.
+ **/
+ private final static String [] amino_acid_abbreviated_names =
+ {
+ "Ala", "Arg", "Asn", "Asp", "Cys",
+ "Gln", "Glu", "Gly", "His", "Ile",
+ "Leu", "Lys", "Met", "Phe", "Pro",
+ "Ser", "Thr", "Trp", "Tyr", "Val",
+ "Opl", "Ocr", "Amb", "---", "Sel"
+ };
+
+
+ /**
+ * The one letter abbreviated names for the amino acids and stop codons.
+ * The names here correspond to the three letter codes at the same indices
+ * in amino_acid_abbreviated_names.
+ **/
+ private final static char [] amino_acid_one_letter_names =
+ {
+ 'a', 'r', 'n', 'd', 'c',
+ 'q', 'e', 'g', 'h', 'i',
+ 'l', 'k', 'm', 'f', 'p',
+ 's', 't', 'w', 'y', 'v',
+ '*', '#', '+', '.', 'u'
+ };
+
+ /**
+ * The molecular weights of the amino acids. The values correspond to the
+ * three letter codes at the same indices in amino_acid_abbreviated_names.
+ * For example "Met" corresponds to a weight of 149.22
+ **/
+ private final static float [] molecular_weights =
+ {
+ 89.09F, 174.21F, 132.12F, 133.10F, 121.15F,
+ 146.15F, 147.13F, 75.07F, 155.16F, 131.18F,
+ 131.18F, 146.19F, 149.22F, 165.19F, 115.13F,
+ 105.09F, 119.12F, 204.22F, 181.19F, 117.15F,
+ 0.0F, 0.0F, 0.0F, 0.0F, 334.1F
+ };
+
+ /**
+ * The average molecular weight of all amino acids.
+ **/
+ //private final static float average_molecular_weight = 136.90F;
+
+ /**
+ * The molecular weight of water.
+ **/
+ private final static float molecular_weight_of_water = 18.015F;
+
+ /**
+ * The number of amino acid symbols, including stop codons and a symbol for
+ * unknown (---): 23.
+ **/
+ public final static int symbol_count = amino_acid_abbreviated_names.length;
+
+ /**
+ * The number of amino acid symbols, not including stop codons: 20.
+ **/
+ //private final static int amino_acid_symbol_count = 20;
+
+
+ public static void setGeneCode()
+ {
+ // if translation_table is in the options file use it to set
+ // codon_translation_array
+ final StringVector options_file_table =
+ Options.getOptions().getOptionValues("translation_table");
+
+ if(options_file_table != null)
+ {
+ if(options_file_table.size () == 64)
+ {
+ for(int i = 0 ; i < 64 ; ++i)
+ {
+ final char new_table_char = Character.toUpperCase(
+ ((String)(options_file_table.elementAt(i))).charAt(0));
+
+ if(isLegalCodon (new_table_char))
+ codon_translation_array[i] = new_table_char;
+ else
+ codon_translation_array[i] = '.';
+ }
+ }
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/sequence/BasePattern.java b/uk/ac/sanger/artemis/sequence/BasePattern.java
new file mode 100644
index 0000000..86ea74f
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/BasePattern.java
@@ -0,0 +1,454 @@
+/* BasePattern.java
+ *
+ * created: Sun Jan 10 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/BasePattern.java,v 1.1 2004-06-09 09:52:12 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+import uk.ac.sanger.artemis.util.*;
+
+/**
+ * A BasePattern is a String that describes a sequence of bases.
+ *
+ * @author Kim Rutherford
+ * @version $Id: BasePattern.java,v 1.1 2004-06-09 09:52:12 tjc Exp $
+ **/
+
+public class BasePattern {
+ /**
+ * Create a new BasePattern object.
+ * @param pattern_string The is a String representation of the pattern.
+ **/
+ public BasePattern (final String pattern_string)
+ throws BasePatternFormatException {
+ if (pattern_string.length () < 1) {
+ throw new BasePatternFormatException ("pattern too short");
+ }
+ this.pattern_string = pattern_string.toLowerCase ();
+
+ this.pattern_type = patternType (this.pattern_string);
+
+ if (pattern_type == ILLEGAL_PATTERN) {
+ throw new BasePatternFormatException ("illegal characters in pattern");
+ }
+ }
+
+ /**
+ * A return value of patternType () - illegal characters in the pattern.
+ **/
+ public static int ILLEGAL_PATTERN = -1;
+
+ /**
+ * A return value of patternType () - the pattern contains only the
+ * characters 'a', 't', 'g' and 'c'.
+ **/
+ private static int SIMPLE_PATTERN = 0;
+
+ /**
+ * A return value of patternType () - the pattern contains only the
+ * characters a,t,g,c,r,y,k,m,s,w,n,b,d,h and v, ie IUC base codes.
+ **/
+ private static int IUC_PATTERN = 1;
+
+ /**
+ * Return a String representation of this BasePattern.
+ **/
+ public String toString () {
+ return pattern_string;
+ }
+
+ /**
+ * Returns true if and only if this pattern matches the given String. The
+ * match must be exact so the pattern and the string must be the same
+ * length.
+ **/
+ public boolean matches (final String match_string) {
+ if (match_string.length () == pattern_string.length () &&
+ searchFor (match_string, 0) == 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Find the next match of this pattern in either Strand of the given
+ * Bases object. This method searches both strands simultaneously by
+ * searching the underlying Bases object directly.
+ * @param bases This holds the Strand objects to search.
+ * @param search_start_marker The match that will be returned will be
+ * after this base Marker position. Position 1 on the reverse strand is
+ * considered to be after position 1 on the forward strand, forward
+ * position 2 is after reverse position 1, reverse position 2 is after
+ * forward position 2, etc. This scheme allows the caller to iterate
+ * through all matches.
+ * @param search_end_position The search will not extend past this
+ * position.
+ * @param search_backwards If true the search will move from last base to
+ * first base, otherwise first to last.
+ * @return A MarkerRange covering the matching bases or null if there is no
+ * match in the given range.
+ **/
+ public MarkerRange findMatch (final Bases bases,
+ final Marker search_start_marker,
+ final int search_end_position,
+ final boolean search_backwards,
+ final boolean search_fwd_strand,
+ final boolean search_bwd_strand) {
+ final String bases_string = bases.toString ();
+
+ // search the bases_string forward for the pattern_string and its
+ // complement
+
+ // the String index position in bases_string at which to start the search
+ // for this pattern
+ final int forward_search_start_index;
+
+ // the String index position in bases_string at which to start the search
+ // for the reverse complement of this position
+ final int complement_search_start_index;
+
+ if (search_backwards) {
+ if (search_start_marker == null) {
+ forward_search_start_index = bases.getLength () - 1;
+ complement_search_start_index = bases.getLength () - 1;
+ } else {
+ complement_search_start_index =
+ search_start_marker.getRawPosition () - 2;
+ if (search_start_marker.getStrand ().isForwardStrand ()) {
+ forward_search_start_index =
+ search_start_marker.getRawPosition () - 2;
+ } else {
+ forward_search_start_index =
+ search_start_marker.getRawPosition () - 1;
+ }
+ }
+ } else {
+ if (search_start_marker == null) {
+ forward_search_start_index = 0;
+ complement_search_start_index = 0;
+ } else {
+ forward_search_start_index = search_start_marker.getRawPosition ();
+ if (search_start_marker.getStrand ().isForwardStrand ()) {
+ complement_search_start_index =
+ search_start_marker.getRawPosition () - 1;
+ } else {
+ complement_search_start_index =
+ search_start_marker.getRawPosition ();
+ }
+ }
+ }
+
+ final int forward_search_result;
+ if(search_fwd_strand)
+ forward_search_result = searchFor (bases_string, pattern_string,
+ forward_search_start_index, search_backwards);
+ else
+ forward_search_result = -1;
+
+ final int complement_search_result;
+ if(search_bwd_strand)
+ complement_search_result = searchFor (bases_string, Bases.reverseComplement (pattern_string),
+ complement_search_start_index, search_backwards);
+ else
+ complement_search_result = -1;
+
+ final int match_first_base;
+ final int match_last_base;
+
+ final Strand match_strand;
+
+ if (forward_search_result == -1) {
+ if (complement_search_result == -1) {
+ // no match
+ return null;
+ }
+ }
+
+ if (search_backwards) {
+ // take the match that is closest to the end, or the complement match if
+ // there is a tie
+ if (complement_search_result != -1 &&
+ (forward_search_result == -1 ||
+ forward_search_result != -1 &&
+ complement_search_result >= forward_search_result)) {
+ match_first_base =
+ bases.getComplementPosition (complement_search_result + 1);
+ match_last_base = match_first_base - (pattern_string.length () - 1);
+ match_strand = bases.getReverseStrand ();
+ } else {
+ match_first_base = forward_search_result + 1;
+ match_last_base = match_first_base + pattern_string.length () - 1;
+ match_strand = bases.getForwardStrand ();
+ }
+ } else {
+ // take the match that is closest to base 1, or the forward match if
+ // there is a tie
+ if (forward_search_result != -1 &&
+ (complement_search_result == -1 ||
+ complement_search_result != -1 &&
+ forward_search_result <= complement_search_result)) {
+ match_first_base = forward_search_result + 1;
+ match_last_base = match_first_base + pattern_string.length () - 1;
+ match_strand = bases.getForwardStrand ();
+ } else {
+ match_first_base =
+ bases.getComplementPosition (complement_search_result + 1);
+ match_last_base = match_first_base - (pattern_string.length () - 1);
+ match_strand = bases.getReverseStrand ();
+ }
+ }
+
+ if (match_last_base > search_end_position) {
+ // there is no match within the range
+ return null;
+ }
+
+ try {
+ return new MarkerRange (match_strand,
+ match_first_base,
+ match_last_base);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Find the all matches of this pattern in either Strand of the given
+ * Bases object. This method searches both strands simultaneously by
+ * searching the underlying Bases object directly.
+ * @param bases This holds the Strand objects to search.
+ * @param search_start_marker The search will start at this Marker
+ * position. See the comments on the search_start_marker argument to
+ * findMatch ().
+ * @param search_end_position The search will not extend past this base
+ * position.
+ * @return A MarkerRangeVector holding all the matches or null if there are
+ * no matches in the given range.
+ **/
+ public MarkerRangeVector findMatches (final Bases bases,
+ final Marker search_start_marker,
+ final int search_end_position) {
+ final MarkerRangeVector return_vector = new MarkerRangeVector ();
+
+ Marker current_position_marker = search_start_marker;
+
+ while (true) {
+ final MarkerRange new_match_position =
+ findMatch (bases, current_position_marker, search_end_position, false, true, true);
+
+ if (new_match_position == null) {
+ break;
+ } else {
+ current_position_marker = new_match_position.getRawStart ();
+ return_vector.add (new_match_position);
+ }
+ }
+
+ return return_vector;
+ }
+
+ /**
+ * Search for this BasePattern in the given string.
+ * @param bases_string Search this String for the pattern.
+ * @param start_index This is the index in bases_string where the search
+ * should start.
+ * @return The index of the match or -1 if there is no match.
+ **/
+ private int searchFor (final String bases_string,
+ final int start_index) {
+ return searchFor (bases_string, pattern_string, start_index, false);
+ }
+
+ /**
+ * Search for a pattern in a string.
+ * @param bases_string Search this String for the pattern.
+ * @param pattern_string This contains the pattern to search for and may
+ * include IUB base codes.
+ * @param start_index This is the index in bases_string where the search
+ * should start.
+ * @param search_backwards If true the search will move from last base to
+ * first base, otherwise first to last.
+ * @return The index of the match or -1 if there is no match.
+ **/
+ private int searchFor (final String bases_string,
+ final String pattern_string,
+ int start_index,
+ final boolean search_backwards) {
+
+ if (search_backwards) {
+ if (pattern_type == SIMPLE_PATTERN) {
+ // indexOf () is much faster in this case
+ return bases_string.lastIndexOf (pattern_string, start_index);
+ }
+
+ if (bases_string.length () - start_index < toString ().length ()) {
+ start_index = bases_string.length () - toString ().length ();
+ }
+
+ for (int i = start_index ; i > 0 ; --i) {
+ boolean match_failed = false;
+ for (int pattern_index = 0 ;
+ pattern_index < pattern_string.length () ;
+ ++pattern_index) {
+ if (charMatch (bases_string.charAt (i + pattern_index),
+ pattern_string.charAt (pattern_index))) {
+ // OK, so continue with the inner loop
+ } else {
+ match_failed = true;
+ break;
+ }
+ }
+
+ if (match_failed) {
+ // go around main loop again
+ } else {
+ // found a match
+ return i;
+ }
+ }
+ } else {
+ if (pattern_type == SIMPLE_PATTERN) {
+ // indexOf () is much faster in this case
+ return bases_string.indexOf (pattern_string, start_index);
+ }
+
+ for (int i = start_index ;
+ i < bases_string.length () - pattern_string.length () + 1;
+ ++i) {
+ boolean match_failed = false;
+ for (int pattern_index = 0 ;
+ pattern_index < pattern_string.length () ;
+ ++pattern_index) {
+
+ if (charMatch (bases_string.charAt (i + pattern_index),
+ pattern_string.charAt (pattern_index))) {
+ // OK, so continue with the inner loop
+ } else {
+ match_failed = true;
+ break;
+ }
+ }
+
+ if (match_failed) {
+ // go around main loop again
+ } else {
+ // found a match
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Check a base to see if it matches an IUC base code.
+ * @param base_char The base character to be checked.
+ * @param pattern_char The single letter IUC base code to match the
+ * character against.
+ **/
+ private boolean charMatch (final char base_char,
+ final char pattern_char) {
+ switch (base_char) {
+ case 'c':
+ switch (pattern_char) {
+ case 'c':
+ case 'y': case 'm': case 's': case 'n': case 'b': case 'h': case 'v':
+ return true;
+ }
+ break;
+ case 't':
+ switch (pattern_char) {
+ case 't':
+ case 'y': case 'k': case 'w': case 'n': case 'b': case 'd': case 'h':
+ return true;
+ }
+ break;
+ case 'a':
+ switch (pattern_char) {
+ case 'a':
+ case 'r': case 'm': case 'w': case 'n': case 'd': case 'h': case 'v':
+ return true;
+ }
+ break;
+ case 'g':
+ switch (pattern_char) {
+ case 'g':
+ case 'r': case 'k': case 's': case 'n': case 'b': case 'd': case 'v':
+ return true;
+ }
+ break;
+ default:
+ if (pattern_char == 'n') {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the pattern type of the argument. Returns ILLEGAL_PATTERN if
+ * there are illegal characters in the pattern. SIMPLE_PATTERN if the
+ * pattern contains only the characters 'a', 't', 'g' and 'c'. IUC_PATTERN
+ * if the pattern contains only the characters a,t,g,c,r,y,k,m,s,w,n,b,d,h
+ * and v, ie IUC base codes.
+ **/
+ public static int patternType (String pattern_string) {
+ boolean seen_iuc = false;
+
+ for (int i = 0 ; i < pattern_string.length () ; ++i) {
+ switch (pattern_string.charAt (i)) {
+ case 'r': case 'y': case 'k': case 'm': case 's':
+ case 'w': case 'n': case 'b': case 'd': case 'h': case 'v':
+ seen_iuc = true;
+ break;
+ case 'a': case 't': case 'g': case 'c':
+ // no problem
+ break;
+ default:
+ // anything else is illegal
+ return ILLEGAL_PATTERN;
+ }
+ }
+
+ if (seen_iuc) {
+ return IUC_PATTERN;
+ } else {
+ return SIMPLE_PATTERN;
+ }
+ }
+
+ /**
+ * The pattern that was passed to the constructor.
+ **/
+ final String pattern_string;
+
+ /**
+ * The type of this pattern, SIMPLE_PATTERN, IUC_PATTERN etc.
+ **/
+ final int pattern_type;
+}
+
+
diff --git a/uk/ac/sanger/artemis/sequence/BasePatternFormatException.java b/uk/ac/sanger/artemis/sequence/BasePatternFormatException.java
new file mode 100644
index 0000000..a8703b0
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/BasePatternFormatException.java
@@ -0,0 +1,46 @@
+/* BasePatternFormatException.java
+ *
+ * created: Sun Jan 10 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/BasePatternFormatException.java,v 1.1 2004-06-09 09:52:13 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+/**
+ * This Exception is thrown if the String passed to the BasePattern
+ * constructor is not a valid pattern.
+ *
+ * @author Kim Rutherford
+ * @version $Id: BasePatternFormatException.java,v 1.1 2004-06-09 09:52:13 tjc Exp $
+ **/
+
+public class BasePatternFormatException extends Exception {
+ /**
+ * Create a new BasePatternFormatException with the given message.
+ * @param message the detail message
+ **/
+ public BasePatternFormatException (String message) {
+ super (message);
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/sequence/Bases.java b/uk/ac/sanger/artemis/sequence/Bases.java
new file mode 100644
index 0000000..0cb5bea
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/Bases.java
@@ -0,0 +1,1550 @@
+/* Bases.java
+ *
+ * created: Sun Oct 11 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998-2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/Bases.java,v 1.26 2009-03-27 14:00:51 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+import uk.ac.sanger.artemis.Feature;
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.EmblStreamSequence;
+import uk.ac.sanger.artemis.io.Sequence;
+import uk.ac.sanger.artemis.io.StreamSequence;
+
+import org.biojava.bio.symbol.IllegalSymbolException;
+
+import java.util.WeakHashMap;
+import java.util.Iterator;
+
+/**
+ * This class is a wrapper for the uk.ac.sanger.artemis.io.Sequence class
+ * that allows us to control what is done to the sequence and to send events
+ * to interested objects when changes happen. Note: a '@' character is used
+ * as a marker when we don't have a base letter, for example complementing a
+ * non-base letter returns '@'.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Bases.java,v 1.26 2009-03-27 14:00:51 tjc Exp $ */
+
+public class Bases
+{
+ /**
+ * Indicates the bases should be read in the forward direction for a
+ * particular operation.
+ **/
+ static public final int FORWARD = 1;
+
+ /**
+ * Indicates the bases should be read in the reverse direction for a
+ * particular operation.
+ **/
+ static public final int REVERSE = 2;
+
+ /**
+ * The lowest possible value for use with addSequenceChangeListener ().
+ **/
+ static public final int MIN_PRIORITY = -5;
+
+ /**
+ * An arbitrary value for use with addSequenceChangeListener ().
+ **/
+ static public final int MEDIUM_PRIORITY = 0;
+
+ /**
+ * The highest possible value for use with addSequenceChangeListener ().
+ **/
+ static public final int MAX_PRIORITY = 5;
+
+ /**
+ * A cache of the forward & reverse stop codon positions.
+ * 0 means not set/cached yet, 1 not a stop codon, 2 and 3 are a
+ * stop codon on fwd or reverse strand respectively.
+ **/
+ private byte [] stop_codon_cache = null;
+
+ /**
+ * A cache of the forward & reverse start codon positions.
+ * 0 means not set/cached yet, 1 not a start codon, 2 and 3 are a
+ * start codon on fwd or reverse strand repectively.
+ **/
+ private byte [] start_codon_cache = null;
+
+ /**
+ * Create a new Bases object.
+ * @param sequence The raw sequence that the new object will use.
+ **/
+ public Bases(final Sequence sequence)
+ {
+ this.embl_sequence = sequence;
+
+ stop_codon_cache = null;
+
+ forward_strand = new Strand(this, FORWARD);
+ reverse_strand = new Strand(this, REVERSE);
+
+ for(int i = 0 ; i < listener_hash_map_array.length ; ++i)
+ listener_hash_map_array [i] = new WeakHashMap();
+ }
+
+ /**
+ * Return the object representing the forward sequence of bases for this
+ * object.
+ **/
+ public Strand getForwardStrand()
+ {
+ return forward_strand;
+ }
+
+ /**
+ * Return the object representing the reverse complemented sequence of
+ * bases for this Bases objects.
+ **/
+ public Strand getReverseStrand()
+ {
+ return reverse_strand;
+ }
+
+ /**
+ * Returns the length of the sequence in bases.
+ **/
+ public int getLength()
+ {
+ return embl_sequence.length();
+ }
+
+ /**
+ * Return a String representation of the sequence.
+ **/
+ public String toString()
+ {
+ return embl_sequence.getSubSequence(1,getLength());
+ }
+
+ /**
+ * Reverse and complement both of the Strand objects (by swapping them and
+ * reverse complementing the sequence).
+ * @exception ReadOnlyException If the Bases cannot be changed.
+ **/
+ public void reverseComplement()
+ throws ReadOnlyException
+ {
+ stop_codon_cache = null;
+
+ final Strand temp = forward_strand;
+ forward_strand = reverse_strand;
+ reverse_strand = temp;
+
+// final String new_sequence =
+// reverseComplement(getSequence().getSubSequence(1, getLength()));
+
+ final char[] new_sequence =
+ reverseComplement(getSequence().getCharSubSequence(1, getLength()));
+
+ try
+ {
+// getSequence().setFromChar(new_sequence.toCharArray());
+ getSequence().setFromChar(new_sequence);
+ }
+ catch (IllegalSymbolException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ final SequenceChangeEvent event =
+ new SequenceChangeEvent (this, SequenceChangeEvent.REVERSE_COMPLEMENT);
+
+ fireSequenceChangeEvent (event);
+ }
+
+ /**
+ * This array is used to convert between bases and indices. See
+ * getIndexOfBase()
+ **/
+ public final static char[] letter_index =
+ {
+ 't', 'c', 'a', 'g', 'n'
+ };
+
+ /**
+ * Given a base letter return its index where t = 0, c = 1, a = 2, g = 3, 4
+ * otherwise.
+ * See letter_index.
+ **/
+ public final static int getIndexOfBase(final char base)
+ {
+ switch(base)
+ {
+ case 'c':
+ return 1;
+ case 'a':
+ return 2;
+ case 'g':
+ return 3;
+ case 't':
+ case 'u':
+ return 0;
+ }
+
+ return 4;
+ }
+
+ /**
+ * Return the complement of the given Range. eg. if the sequence length is
+ * 100 and the Range is 1..10 then the return value will be 90..100.
+ **/
+ private Range complementRange (final Range range) {
+ final int real_start = getComplementPosition (range.getEnd ());
+ final int real_end = getComplementPosition (range.getStart ());
+
+ try {
+ final Range real_range = new Range (real_start, real_end);
+
+ return real_range;
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return the complement of the given position on the sequence. eg. if the
+ * sequence length is 100 and the position is 10 then the return value will
+ * be 90.
+ **/
+ public int getComplementPosition (final int position) {
+ return getLength () - position + 1;
+ }
+
+ /**
+ * Return the raw of a base position on this object. The raw position of a
+ * base on the forward strand is the same as the position itself. The raw
+ * position of a base on the reverse strand is position of the
+ * corresponding bases on the forward strand.
+ * @param position The position of the base.
+ * @param direction The direction (strand) that the position refers to.
+ **/
+ public int getRawPosition (final int position, final int direction) {
+ if (direction == FORWARD) {
+ return position;
+ } else {
+ return getComplementPosition (position);
+ }
+ }
+
+ /**
+ * Translate a sequence of bases into the corresponding single letter amino
+ * acid codes.
+ * @param range The range of the bases to translated. If the range.start
+ * - range.end + 1 is not a multiple of three the last codon is
+ * incomplete and will not be translated. If the range is out of range
+ * ie. it has a start or end less than one or greater than the length of
+ * the sequence, then the out of range codons will be translated as '.'.
+ * @param direction The direction of the translation. If FORWARD the
+ * translation will happen as expected, if REVERSE the translation will
+ * be done on the reverse complement.
+ * @param unknown_is_x If this parameter is true codons that contain
+ * ambiguous bases will be translated as 'x', if false they will be
+ * translated as '.'
+ * @return The translated sequence in one letter abbreviated form.
+ **/
+ public AminoAcidSequence getTranslation(final Range range,
+ final int direction,
+ final boolean unknown_is_x)
+ {
+ // getSubSequenceC() will return a sequence going in the right direction
+ // so we don't have to worry.
+ final char[] sub_sequence = getSubSequenceC(range, direction);
+ return AminoAcidSequence.getTranslation(sub_sequence, unknown_is_x);
+ }
+
+
+ public AminoAcidSequence getSpacedTranslation(final Range range,
+ final int direction,
+ final boolean unknown_is_x)
+ {
+ // getSubSequenceC() will return a sequence going in the right direction
+ // so we don't have to worry.
+ final char[] sub_sequence = getSubSequenceC(range, direction);
+ return AminoAcidSequence.getSpacedTranslation(sub_sequence, unknown_is_x);
+ }
+
+ /**
+ * Return an array containing the positions of the codons that match the
+ * strings given by the query_codons argument. Only those codons that are
+ * in the same frame as the first base of the range are returned.
+ * @param range The inclusive range of bases to get the codons from.
+ * @param direction The direction of the translation. REVERSE means
+ * translate the reverse complement bases (the positions in the range
+ * argument are complemented first.)
+ * @param query_codons The codons to search for. Each element of this
+ * vector should be a string that is 3 characters long.
+ * @return An array containing the positions of the first base of the
+ * codons. This array is padded with zeros at the end.
+ **/
+ public int [] getMatchingCodons (final Range range, final int direction,
+ final StringVector query_codons) {
+ final Range real_range;
+
+ if(direction == FORWARD)
+ real_range = range;
+ else
+ real_range = complementRange(range);
+
+ // guess the number of codons in getCount () bases - there are
+ // query_codons.size() search codons in every 64 codons if G+C is 50%
+ // and we have getCount()/3 codons to look at.
+
+ float at_content = (100 - getAverageGCPercent()) / 100;
+
+ int array_start_size =
+ (int) (range.getCount () *
+ at_content * at_content * (2-at_content) *
+ query_codons.size () / 64);
+
+ if(array_start_size < 20)
+ array_start_size = 20;
+
+ // this array will be resized as necessary
+ int[] return_positions = new int[array_start_size];
+
+ int current_return_array_index = 0;
+
+ final String sequence_string =
+ getSequence ().getSubSequence (1, getLength ());
+
+ final int range_start_index = real_range.getStart () - 1;
+ final int range_end_index = real_range.getEnd () - 1;
+
+ if(direction == FORWARD)
+ {
+ for (int i = range_start_index ; i < range_end_index - 2 ; i += 3) {
+ if (i < 0 || i >= sequence_string.length () - 2) {
+ continue;
+ }
+
+ boolean is_matching_codon =
+ isMatchingCodon (sequence_string, i, direction, query_codons);
+
+ if (is_matching_codon) {
+ if (current_return_array_index == return_positions.length) {
+ // first reallocate the array
+ final int [] new_array =
+ new int [return_positions.length * 3 / 2 + 1];
+
+ System.arraycopy (return_positions, 0,
+ new_array, 0,
+ return_positions.length);
+ return_positions = new_array;
+ }
+
+ return_positions[current_return_array_index] = i + 1;
+
+ ++current_return_array_index;
+ }
+ }
+ } else {
+
+ for (int i = range_end_index ; i > range_start_index + 2 ; i -= 3) {
+ if (i < 2 || i >= sequence_string.length ()) {
+ continue;
+ }
+
+ boolean is_matching_codon =
+ isMatchingCodon (sequence_string, i, direction, query_codons);
+
+ if (is_matching_codon) {
+ if (current_return_array_index == return_positions.length) {
+ // first reallocate the array
+ final int [] new_array =
+ new int [return_positions.length * 3 / 2 + 1];
+
+ System.arraycopy (return_positions, 0,
+ new_array, 0,
+ return_positions.length);
+ return_positions = new_array;
+ }
+
+ // return the complemented base position
+ return_positions[current_return_array_index] =
+ sequence_string.length () - i;
+
+ ++current_return_array_index;
+ }
+ }
+ }
+
+ return return_positions;
+
+ }
+
+ /**
+ * Check a three character substring and return true if and only if the
+ * three bases match an element of the query_codons argument. If the
+ * direction is REVERSE then the three bases to check are at start_index,
+ * start_index - 1 and start_index - 2. In that case true is returned if
+ * and only the complement of those three bases matches.
+ **/
+ private boolean isMatchingCodon (final String sequence_string,
+ final int start_index,
+ final int direction,
+ final StringVector query_codons) {
+ for (int query_codon_index = 0 ;
+ query_codon_index < query_codons.size () ;
+ ++query_codon_index) {
+ if (isMatchingCodon (sequence_string, start_index, direction,
+ (String)query_codons.elementAt (query_codon_index))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check a three character substring and return true if and only if the
+ * three bases match the query_codon argument. If the direction is
+ * REVERSE then the three bases to check are at start_index, start_index -
+ * 1 and start_index - 2. In that case true is returned if and only the
+ * complement of those three bases matches.
+ **/
+ private boolean isMatchingCodon (final String sequence_string,
+ final int start_index,
+ final int direction,
+ final String query_codon) {
+ if (direction == FORWARD) {
+ if (query_codon.charAt (0) == sequence_string.charAt (start_index) &&
+ query_codon.charAt (1) == sequence_string.charAt (start_index + 1) &&
+ query_codon.charAt (2) == sequence_string.charAt (start_index + 2)) {
+ return true;
+ }
+ } else {
+ final char first_letter =
+ complement (sequence_string.charAt (start_index));
+ final char second_letter =
+ complement (sequence_string.charAt (start_index - 1));
+ final char third_letter =
+ complement (sequence_string.charAt (start_index - 2));
+
+ if (query_codon.charAt (0) == first_letter &&
+ query_codon.charAt (1) == second_letter &&
+ query_codon.charAt (2) == third_letter) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns stop_codon_cache after allocating it (if it is null).
+ **/
+ private byte[] getStopCodonCache()
+ {
+ if (stop_codon_cache == null)
+ {
+ final int nbytes = getLength() >> 1 >> 1;
+ stop_codon_cache = new byte[nbytes+1];
+ }
+
+ return stop_codon_cache;
+ }
+
+
+ /**
+ * Returns start_codon_cache after allocating it (if it is null).
+ **/
+ private byte[] getStartCodonCache()
+ {
+ if (start_codon_cache == null)
+ {
+ final int nbytes = getLength() >> 1 >> 1;
+ start_codon_cache = new byte[nbytes+1];
+ }
+
+ return start_codon_cache;
+ }
+
+ /**
+ * Clear stop codon cache (forward and reverse).
+ **/
+ public void clearCodonCache()
+ {
+ stop_codon_cache = null;
+ start_codon_cache = null;
+ }
+
+
+ /**
+ * Return an array containing the positions of the stop codons. Only those
+ * codons that are in the same frame as the first base of the range are
+ * returned.
+ * @param range The inclusive range of bases to get the stop codons from.
+ * @param direction The direction of the translation. REVERSE means
+ * translate the reverse complement bases (the positions in the range
+ * argument are complemented first.)
+ * @return An array containing the positions of the first base of the stop
+ * codons. This array is padded with zeros at the end.
+ **/
+ protected int[] getStopCodons(final Range range, final int direction)
+ {
+ final Range real_range;
+
+ if(direction == FORWARD)
+ real_range = range;
+ else
+ real_range = complementRange (range);
+
+ // guess the number of stop codons in getCount() bases - there are 3
+ // stop codons in every 64 codons if G+C is 50% and we have getCount()/3
+ // codons to look at.
+
+ float at_content = (100 - getAverageGCPercent()) / 100;
+
+ int array_start_size =
+ (int)(range.getCount() *
+ at_content * at_content * (2-at_content) * 3 / 64);
+
+ if(array_start_size < 20)
+ array_start_size = 20;
+
+ // this array will be resized as necessary
+ int[] return_positions = new int[array_start_size];
+
+ int current_return_array_index = 0;
+ int range_start_index = real_range.getStart();
+ int range_end_index = real_range.getEnd();
+
+ final int sequence_length = getLength();
+
+ if(range_start_index < 1)
+ {
+ if(direction == FORWARD)
+ range_start_index = 3 + (range_start_index % 3);
+ else
+ range_start_index = 1;
+ }
+ if(range_end_index > sequence_length)
+ range_end_index = sequence_length;
+
+ final char sequence_string[] =
+ getSequence().getCharSubSequence(range_start_index, range_end_index);
+
+ range_start_index--;
+ range_end_index--;
+
+ // whether a codon is a stop codon or not is cached in
+ // 2 bit chunks (i.e. 4 per byte)
+ int ncurrent_byte;
+ int bit_position;
+ byte bitty;
+
+ final byte[] this_stop_codon_flags = getStopCodonCache();
+ if(direction == FORWARD)
+ {
+
+ for(int i = range_start_index; i < range_end_index + 1; i += 3)
+ {
+ if(i < 0 || i >= sequence_length-1)
+ continue;
+
+ ncurrent_byte = i >> 1 >> 1;
+ bit_position = i % 4;
+
+ // determine if codon type is cached or not
+ bitty = (byte) ((this_stop_codon_flags[ncurrent_byte]
+ >> (2*bit_position) ) & 0x0003);
+ if(bitty == 0)
+ {
+ // not cached yet
+ setCache(range_start_index, range_end_index, sequence_string, i,
+ null, this_stop_codon_flags, ncurrent_byte, bit_position);
+ }
+
+ bitty = (byte) ((this_stop_codon_flags[ncurrent_byte]
+ >> (2*bit_position) ) & 0x0003);
+ if( bitty == 1 || bitty == 3 )
+ continue;
+
+ // if we reach here this is a stop codon
+ if(current_return_array_index == return_positions.length)
+ {
+ // first reallocate the array
+ final int[] new_array =
+ new int[return_positions.length * 3 / 2 + 1];
+
+ System.arraycopy(return_positions, 0,
+ new_array, 0,
+ return_positions.length);
+ return_positions = new_array;
+ }
+
+ return_positions[current_return_array_index] = i + 1;
+ ++current_return_array_index;
+ }
+ }
+ else
+ {
+ for (int i = range_end_index ; i > range_start_index + 2 ; i -= 3)
+ {
+ if(i < 2 || i >= sequence_length)
+ continue;
+
+ ncurrent_byte = i >> 1 >> 1;
+ bit_position = i % 4;
+ bitty = (byte) ((this_stop_codon_flags[ncurrent_byte]
+ >> (2*bit_position) ) & 0x0003);
+
+ if(bitty == 0)
+ {
+ // not cached yet
+ setCache(range_start_index, range_end_index, sequence_string, i,
+ null, this_stop_codon_flags, ncurrent_byte, bit_position);
+ }
+
+ bitty = (byte) ((this_stop_codon_flags[ncurrent_byte]
+ >> (2*bit_position) ) & 0x0003);
+
+ if( bitty == 1 || bitty != 3 )
+ continue;
+
+ // if we reach here this is a stop codon
+ if(current_return_array_index == return_positions.length)
+ {
+ // first reallocate the array
+ final int[] new_array =
+ new int[return_positions.length * 3 / 2 + 1];
+
+ System.arraycopy(return_positions, 0,
+ new_array, 0,
+ return_positions.length);
+ return_positions = new_array;
+ }
+
+ return_positions[current_return_array_index] =
+ sequence_length - i;
+ ++current_return_array_index;
+ }
+ }
+
+ return return_positions;
+ }
+
+ /**
+ * Return an 2D array containing the stop or start codons in a range for
+ * all 3 frames of the strand.
+ * @param range The inclusive range of bases to get the codons from.
+ * @param direction The direction of the translation. REVERSE means
+ * translate the reverse complement bases (the positions in the range
+ * argument are complemented first.)
+ * @param query_codons if this is NULL then this assumes we are looking
+ * for stop codons, otherwise this is used to look for start codons.
+ * @return An array containing the positions of the first base of the stop
+ * codons. This array is padded with zeros at the end.
+ **/
+ protected int[][] getStopOrStartCodons(final Range range,
+ final int direction,
+ final StringVector query_codons)
+ {
+ final Range real_range;
+
+ if(direction == FORWARD)
+ real_range = range;
+ else
+ real_range = complementRange(range);
+
+ // guess the number of stop codons in getCount() bases - there are 3
+ // stop codons in every 64 codons if G+C is 50% and we have getCount()/3
+ // codons to look at.
+
+ float at_content = (100 - getAverageGCPercent()) / 100;
+
+ int array_start_size =
+ (int)(range.getCount() *
+ at_content * at_content * (2-at_content) * 3 / 64);
+
+ if(array_start_size < 20)
+ array_start_size = 20;
+ // this array will be resized as necessary
+ int[][] return_positions = new int[3][array_start_size];
+
+ int[] current_return_array_index = new int[3];
+ current_return_array_index[0] = 0;
+ current_return_array_index[1] = 0;
+ current_return_array_index[2] = 0;
+
+ int range_start_index = real_range.getStart();
+ int range_end_index = real_range.getEnd();
+
+ final int sequence_length = getLength();
+
+ if(range_start_index < 1)
+ {
+ if(direction == FORWARD)
+ range_start_index = 3 + (range_start_index % 3);
+ else
+ range_start_index = 1;
+ }
+
+ if(range_end_index > sequence_length)
+ range_end_index = sequence_length;
+
+ range_start_index--;
+ range_end_index--;
+ char[] sequence_string = null;
+
+ // whether a codon is a stp codon or not is cached in
+ // 2 bit chunks (i.e. 4 per byte)
+ int ncurrent_byte;
+ int bit_position;
+ int nframe = 0;
+ byte bitty;
+
+ final byte[] this_forward_codon_flags;
+ // if this is null then searching for stop codons
+ if(query_codons == null)
+ this_forward_codon_flags = getStopCodonCache();
+ else
+ this_forward_codon_flags = getStartCodonCache();
+
+ for(int i = range_start_index; i < range_end_index+1; i += 1)
+ {
+ if(i < 0 || i >= sequence_length)
+ continue;
+
+ ncurrent_byte = i >> 1 >> 1;
+ bit_position = i % 4;
+
+ // determine if codon type is cached or not
+ bitty = (byte) ((this_forward_codon_flags[ncurrent_byte]
+ >> (2*bit_position) ) & 0x0003);
+
+ if(bitty == 0) // not cached yet
+ {
+ if(sequence_string == null)
+ sequence_string = getSequence().getCharSubSequence(range_start_index+1,
+ range_end_index+1);
+
+ setCache(range_start_index, range_end_index, sequence_string, i,
+ query_codons, this_forward_codon_flags, ncurrent_byte,
+ bit_position);
+ bitty = (byte) ((this_forward_codon_flags[ncurrent_byte]
+ >> (2*bit_position) ) & 0x0003);
+ }
+
+ if( bitty == 1 || // not a stop/start codon
+ (direction == FORWARD && bitty == 3) ||
+ (direction != FORWARD && bitty != 3 ))
+ continue;
+
+ if(direction == FORWARD)
+ nframe = (i-range_start_index) % 3;
+ else
+ nframe = (range_end_index-i) % 3;
+
+ // if we reach here this is a stop/start codon
+ if(current_return_array_index[nframe] == return_positions[nframe].length)
+ {
+ // first reallocate the array
+ final int[][] new_array =
+ new int[3][return_positions[nframe].length * 3 / 2 + 1];
+
+ for(int j=0; j<3; j++)
+ System.arraycopy(return_positions[j], 0,
+ new_array[j], 0,
+ return_positions[j].length);
+ return_positions = new_array;
+ }
+
+ if(direction == FORWARD)
+ {
+ if(i==0)
+ return_positions[nframe][current_return_array_index[nframe]] = i + 1;
+ else
+ return_positions[nframe][current_return_array_index[nframe]] = i;
+ }
+ else
+ return_positions[nframe][current_return_array_index[nframe]] =
+ sequence_length - i;
+ ++current_return_array_index[nframe];
+ }
+
+ return return_positions;
+ }
+
+ /**
+ * Set the codon cache for forward and reverse strand.
+ * @param range_start_index
+ * @param range_end_index
+ * @param sequence_string
+ * @param i
+ * @param query_codons
+ * @param this_codon_flags
+ * @param ncurrent_byte
+ * @param bit_position
+ */
+ private void setCache(int range_start_index,
+ int range_end_index,
+ char[] sequence_string,
+ int i,
+ final StringVector query_codons,
+ final byte[] this_codon_flags,
+ int ncurrent_byte,
+ int bit_position)
+ {
+ // test if stop (or start) codon
+ boolean ismatch = false;
+
+ // forward codon
+ if(i < range_end_index-1)
+ if(query_codons == null)
+ ismatch = isStopCodon(sequence_string[i-range_start_index],
+ sequence_string[i-range_start_index+1],
+ sequence_string[i-range_start_index+2]);
+ else
+ ismatch = isCodon(sequence_string[i-range_start_index],
+ sequence_string[i-range_start_index+1],
+ sequence_string[i-range_start_index+2],
+ query_codons);
+
+ if(ismatch)
+ {
+ this_codon_flags[ncurrent_byte] = // forward strand stop/start = 2
+ (byte)(this_codon_flags[ncurrent_byte]
+ | (0x0002 << 2*bit_position));
+ }
+ else
+ {
+ this_codon_flags[ncurrent_byte] = // cached no stop/start = 1
+ (byte)(this_codon_flags[ncurrent_byte]
+ | (0x0001 << 2*bit_position));
+ }
+
+ // reverse codon
+ ismatch = false;
+ if(i-range_start_index > 1 && i-range_start_index < sequence_string.length)
+ if(query_codons == null)
+ ismatch = isStopCodon(complement(sequence_string[i-range_start_index]),
+ complement(sequence_string[i-range_start_index-1]),
+ complement(sequence_string[i-range_start_index-2]));
+ else
+ ismatch = isCodon(complement(sequence_string[i-range_start_index]),
+ complement(sequence_string[i-range_start_index-1]),
+ complement(sequence_string[i-range_start_index-2]),
+ query_codons);
+ if(ismatch)
+ this_codon_flags[ncurrent_byte] = // reverse strand stop/start = 3
+ (byte)(this_codon_flags[ncurrent_byte]
+ | (0x0003 << 2*bit_position));
+ }
+
+ /**
+ * Return the base at the given position.
+ **/
+ public char getBaseAt (final int position)
+ throws OutOfRangeException
+ {
+ if(position > getLength())
+ throw new OutOfRangeException(position + " > " + getLength());
+
+ if(position < 1)
+ throw new OutOfRangeException(position + " < " + 1);
+
+ return getSequence().charAt(position);
+ }
+
+ /**
+ * Return a sub sequence of the bases from this object.
+ * @param range The range of the bases to be extracted.
+ * @param direction The direction of the returned sequence. If FORWARD the
+ * sub sequence will be as expected, if REVERSE it will be reverse
+ * complemented.
+ * @return The extracted sequence, which will include the end bases of the
+ * range.
+ **/
+ public String getSubSequence (final Range range, final int direction) {
+ final Range real_range;
+
+ if(direction == FORWARD)
+ real_range = range;
+ else
+ real_range = complementRange (range);
+
+ // we need to make sure that we pass in-range coordinates to
+ // Sequence.getSubSequence()
+ final int sub_seq_start_index;
+ final int sub_seq_end_index;
+
+ if(real_range.getStart () < 1)
+ sub_seq_start_index = 1;
+ else
+ sub_seq_start_index = real_range.getStart ();
+
+ if(real_range.getEnd () > getLength ())
+ sub_seq_end_index = getLength ();
+ else
+ sub_seq_end_index = real_range.getEnd ();
+
+ String sub_sequence =
+ getSequence().getSubSequence(sub_seq_start_index, sub_seq_end_index);
+
+ // sanity checks - if the user asks for more bases than we
+ // have, we return the symbol "@" for the out-of-range bases.
+ if (real_range.getStart () < 1) {
+ final int dummy_base_count = 1 - real_range.getStart ();
+ final char [] dummy_bases = new char [dummy_base_count];
+
+ for (int i = 0 ; i < dummy_base_count ; ++i) {
+ dummy_bases[i] = '@';
+ }
+
+ sub_sequence = new String (dummy_bases) + sub_sequence;
+ }
+
+ if (real_range.getEnd () > getLength ()) {
+ final int dummy_base_count = real_range.getEnd () - getLength ();
+ final char [] dummy_bases = new char [dummy_base_count];
+
+ for (int i = 0 ; i < dummy_base_count ; ++i) {
+ dummy_bases[i] = '@';
+ }
+
+ sub_sequence = sub_sequence + new String (dummy_bases);
+ }
+
+ if (FORWARD == direction) {
+ return sub_sequence;
+ } else {
+ return reverseComplement (sub_sequence);
+ }
+ }
+
+ public char[] getSubSequenceC(final Range range, final int direction)
+ {
+ final Range real_range;
+
+ if(direction == FORWARD)
+ real_range = range;
+ else
+ real_range = complementRange (range);
+
+ // we need to make sure that we pass in-range coordinates to
+ // Sequence.getSubSequence()
+ final int sub_seq_start_index;
+ final int sub_seq_end_index;
+
+ if(real_range.getStart () < 1)
+ sub_seq_start_index = 1;
+ else
+ sub_seq_start_index = real_range.getStart ();
+
+ if(real_range.getEnd () > getLength ())
+ sub_seq_end_index = getLength ();
+ else
+ sub_seq_end_index = real_range.getEnd ();
+
+ char[] sub_sequence =
+ getSequence().getCharSubSequence(sub_seq_start_index, sub_seq_end_index);
+
+ if(real_range.getStart() < 1)
+ {
+ final int dummy_base_count = 1 - real_range.getStart();
+ final char[] dummy_bases = new char[dummy_base_count+sub_sequence.length];
+
+ for(int i = 0; i < dummy_base_count; ++i)
+ dummy_bases[i] = '@';
+
+ System.arraycopy(sub_sequence, 0, dummy_bases, dummy_base_count, sub_sequence.length);
+ sub_sequence = dummy_bases;
+ }
+
+ if(real_range.getEnd() > getLength())
+ {
+ final int dummy_base_count = real_range.getEnd() - getLength();
+ final char[] dummy_bases = new char[dummy_base_count+sub_sequence.length];
+
+ for(int i = sub_sequence.length; i < dummy_bases.length; ++i)
+ dummy_bases[i] = '@';
+
+ System.arraycopy(sub_sequence, 0, dummy_bases, 0, sub_sequence.length);
+ sub_sequence = dummy_bases;
+ }
+
+
+ if(FORWARD == direction)
+ return sub_sequence;
+ else
+ return reverseComplement(sub_sequence);
+ }
+
+ /**
+ * This method truncates the sequence use the start and end of the argument.
+ * @param constraint This contains the start and end base of the new
+ * sequence.
+ * @return the Bases truncated into the new coordinate system.
+ **/
+ public Bases truncate (final Range constraint) {
+ final String bases_string = getSubSequence (constraint, FORWARD);
+
+ final Sequence new_sequence = new EmblStreamSequence (bases_string);
+
+ return new Bases (new_sequence);
+ }
+
+
+ /**
+ *
+ * Reverse complement a range of the sequence.
+ *
+ */
+ public void reverseComplement(final Feature feature)
+ throws ReadOnlyException
+ {
+ stop_codon_cache = null;
+
+ final Range range = feature.getMaxRawRange();
+ final int range_start_index = range.getStart();
+ final int range_end_index = range.getEnd();
+
+ // ensure we just get subsequence of interest
+ ((StreamSequence)getSequence()).forceReset();
+ // sequence to reverse complement
+ final char[] sub_sequence = reverseComplement(getSequence().getCharSubSequence(
+ range_start_index, range_end_index));
+ final char[] new_sequence = new char[getLength()];
+ final char[] old_sequence = ((StreamSequence)getSequence()).getCharSequence();
+
+// System.out.println("range_start_index "+range_start_index);
+// System.out.println("range_end_index "+range_end_index);
+// System.out.println("getLength "+getLength());
+// System.out.println("sub_sequence.length "+sub_sequence.length);
+// System.out.println(feature.getEntry().getEMBLEntry().toString());
+// System.out.println(new String(sub_sequence));
+
+ // if not first contig
+ if(range_start_index != 1)
+ System.arraycopy(old_sequence, 0, new_sequence, 0, range_start_index-1);
+
+ // copy in new sequence fragment that has been reverse complemented
+ System.arraycopy(sub_sequence, 0, new_sequence, range_start_index-1,
+ sub_sequence.length);
+
+ // if not last contig
+ if(range_end_index != getLength())
+ System.arraycopy(old_sequence, range.getEnd(), new_sequence, range_end_index,
+ getLength()-range_end_index);
+
+ try
+ {
+ embl_sequence.setFromChar(new_sequence);
+ }
+ catch (IllegalSymbolException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ final SequenceChangeEvent event =
+ new SequenceChangeEvent(this, SequenceChangeEvent.CONTIG_REVERSE_COMPLEMENT,
+ range, sub_sequence.length);
+
+ fireSequenceChangeEvent(event);
+ }
+
+
+ public void contigRearrange(final Feature feature, final int new_base_pos)
+ throws ReadOnlyException
+ {
+ stop_codon_cache = null;
+
+ final Range range = feature.getMaxRawRange();
+ final int range_start_index = range.getStart();
+ final int range_end_index = range.getEnd();
+
+ if(new_base_pos == range_start_index)
+ return;
+
+ final char[] new_sequence = new char[getLength()];
+ final char[] old_sequence = ((StreamSequence)getSequence()).getCharSequence();
+
+ int contig_length = 0;
+ if(new_base_pos < range_start_index)
+ {
+ // if not first contig
+ if(new_base_pos != 1)
+ System.arraycopy(old_sequence, 0, new_sequence, 0, new_base_pos-1);
+
+ contig_length = range_end_index - range_start_index + 1;
+ // copy in new sequence fragment that has been reverse complemented
+ System.arraycopy(old_sequence, range_start_index-1,
+ new_sequence, new_base_pos-1, contig_length);
+
+ System.arraycopy(old_sequence, new_base_pos-1,
+ new_sequence, new_base_pos+contig_length-1,
+ range_start_index-new_base_pos);
+
+ // if not last contig
+ if(new_base_pos < getLength()+1)
+ System.arraycopy(old_sequence, range_end_index,
+ new_sequence, range_end_index,
+ getLength()-range_end_index);
+ }
+ else
+ {
+ System.arraycopy(old_sequence, 0, new_sequence, 0, range_start_index-1);
+
+ System.arraycopy(old_sequence, range_end_index,
+ new_sequence, range_start_index-1,
+ new_base_pos-range_end_index-1);
+
+ System.arraycopy(old_sequence, range_start_index-1,
+ new_sequence, (range_start_index-1)+(new_base_pos-range_end_index)-1,
+ range_end_index - range_start_index + 1);
+
+ // if not last contig
+ if(new_base_pos < getLength()+1)
+ System.arraycopy(old_sequence, new_base_pos-1,
+ new_sequence, new_base_pos-1,
+ getLength()-new_base_pos+1);
+ }
+
+
+ try
+ {
+ embl_sequence.setFromChar(new_sequence);
+ }
+ catch (IllegalSymbolException e)
+ {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ final SequenceChangeEvent event =
+ new SequenceChangeEvent(SequenceChangeEvent.CONTIG_REORDER,
+ new_base_pos, range);
+
+ fireSequenceChangeEvent(event);
+ }
+
+
+ /**
+ * Delete the bases in the given range and send out a SequenceChange event
+ * to all the listeners.
+ * @param range The inclusive range of bases to delete.
+ * @return A String containing the deleted bases.
+ * @exception ReadOnlyException If this Bases object cannot be changed.
+ **/
+ public String deleteRange (final Range range)
+ throws ReadOnlyException {
+ stop_codon_cache = null;
+
+ final String removed_bases =
+ getSequence ().getSubSequence (range.getStart (), range.getEnd ());
+
+ final String new_sequence =
+ getSequence ().getSubSequence (1, range.getStart () - 1) +
+ getSequence ().getSubSequence (range.getEnd () + 1,
+ embl_sequence.length ());
+
+ try {
+ embl_sequence.setFromChar(new_sequence.toCharArray());
+ } catch (IllegalSymbolException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ final SequenceChangeEvent event =
+ new SequenceChangeEvent (this,
+ SequenceChangeEvent.DELETION,
+ range.getStart (),
+ removed_bases);
+
+ fireSequenceChangeEvent (event);
+
+ return removed_bases;
+ }
+
+ /**
+ * Insert the given bases at the given base position and send out a
+ * SequenceChange event to all the listeners.
+ * @param position The bases are inserted just before this base position if
+ * direction is FORWARD or just after if direction is REVERSE.
+ * @param direction If this is FORWARD, then the bases is the bases String
+ * will be inserted just before the base given by position. If this is
+ * REVERSE the bases will be reversed, complemented and inserted just
+ * after the position.
+ * @param bases The bases to add (or the reverse complement of the bases to
+ * add if direction is REVERSE).
+ * @exception ReadOnlyException If this Bases object cannot be changed.
+ **/
+ public void addBases (final int position, final int direction,
+ final String bases)
+ throws ReadOnlyException, IllegalSymbolException {
+ stop_codon_cache = null;
+
+ final String new_sequence;
+ final int real_position;
+ final String real_bases;
+
+ if (direction == FORWARD) {
+ real_position = position;
+ real_bases = bases.toLowerCase ();
+ } else {
+ real_position = position + 1;
+ real_bases = reverseComplement (bases.toLowerCase ());
+ }
+
+ new_sequence =
+ getSequence ().getSubSequence (1, real_position - 1) +
+ real_bases +
+ getSequence ().getSubSequence (real_position, getLength ());
+
+ getSequence ().setFromChar(new_sequence.toCharArray());
+
+ final SequenceChangeEvent event =
+ new SequenceChangeEvent (this,
+ SequenceChangeEvent.INSERTION,
+ real_position,
+ real_bases);
+
+ fireSequenceChangeEvent (event);
+
+ return;
+ }
+
+ /**
+ * There is one element in this array for each possible
+ * SequenceChangeListener priority. This array is changed by
+ * addSequenceChangeListener() and removeSequenceChangeListener().
+ **/
+ final private WeakHashMap listener_hash_map_array [] =
+ new WeakHashMap [MAX_PRIORITY - MIN_PRIORITY + 1];
+
+ /**
+ * Adds the specified event listener to the list of object that receive
+ * sequence change events from this object.
+ * @param l the event change listener.
+ * @param priority The listeners are stored in a priority queue using this
+ * value. Larger priority means that the listener will receive the event
+ * sooner (than lower priority listeners). Values less than MIN_PRIORITY
+ * will be treated like MIN_PRIORITY values higher than MAX_PRIORITY will
+ * be treated like MAX_PRIORITY.
+ **/
+ public void addSequenceChangeListener (final SequenceChangeListener l,
+ int priority) {
+ if (priority < MIN_PRIORITY) {
+ priority = MIN_PRIORITY;
+ }
+
+ if (priority > MAX_PRIORITY) {
+ priority = MAX_PRIORITY;
+ }
+
+ listener_hash_map_array [priority - MIN_PRIORITY].put (l, null);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * sequence change events from this object.
+ * @param l the event change listener.
+ **/
+ public void removeSequenceChangeListener (final SequenceChangeListener l) {
+ for (int i = 0 ; i < listener_hash_map_array.length ; ++i) {
+ final WeakHashMap this_hash_map = listener_hash_map_array [i];
+
+ if (this_hash_map.containsKey (l)) {
+ this_hash_map.remove (l);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Send a SequenceChangeEvent to each object that is listening for it.
+ **/
+ private void fireSequenceChangeEvent (final SequenceChangeEvent event) {
+ for (int i = listener_hash_map_array.length - 1 ; i >= 0 ; --i) {
+ final WeakHashMap this_hash_map = listener_hash_map_array [i];
+
+ if (this_hash_map != null) {
+ final Iterator iter = this_hash_map.keySet ().iterator ();
+
+ while (iter.hasNext())
+ {
+ final SequenceChangeListener this_listener =
+ (SequenceChangeListener) iter.next();
+ this_listener.sequenceChanged (event);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the average gc percent for the sequence.
+ **/
+ public float getAverageGCPercent () {
+ return ((float)(getSequence ().getCCount () +
+ getSequence ().getGCount ())) /
+ getSequence ().length () * 100;
+ }
+
+ /**
+ * Return the average AG percent for the sequence as a percentage.
+ **/
+ public float getAverageAGPercent () {
+ return ((float)(getSequence ().getACount () +
+ getSequence ().getGCount ())) /
+ getSequence ().length () * 100;
+ }
+
+ /**
+ * Return the number of 'A's in this Bases object.
+ **/
+ public int getACount () {
+ return getSequence ().getACount ();
+ }
+
+ /**
+ * Return the number of 'T's in this Bases object.
+ **/
+ public int getTCount () {
+ return getSequence ().getTCount ();
+ }
+
+ /**
+ * Return the number of 'G's in this Bases object.
+ **/
+ public int getGCount () {
+ return getSequence ().getGCount ();
+ }
+
+ /**
+ * Return the number of 'C's in this Bases object.
+ **/
+ public int getCCount () {
+ return getSequence ().getCCount ();
+ }
+
+ /**
+ * Return a String containing the reverse complement of the argument
+ * String. For example an argument of "aatc" will result in "gatt".
+ **/
+ public static String reverseComplement (final String sequence_string) {
+ StringBuffer return_buffer = new StringBuffer (sequence_string.length ());
+
+ for (int i = sequence_string.length () - 1 ; i >= 0 ; --i) {
+ return_buffer.append (complement (sequence_string.charAt (i)));
+ }
+
+ return return_buffer.toString ();
+ }
+
+ /**
+ * Return a char[] containing the reverse complement of the argument
+ * String. For example an argument of "aatc" will result in "gatt".
+ **/
+ public static char[] reverseComplement (final char[] sequence_char)
+ {
+ final int length = sequence_char.length;
+ final char[] return_sequence = new char[length];
+ int j = 0;
+
+ for(int i = length - 1 ; i >= 0 ; --i)
+ {
+ return_sequence[j] = complement(sequence_char[i]);
+ j++;
+ }
+
+ return return_sequence;
+ }
+
+
+ /**
+ * Return a String containing the complement of the argument String. For
+ * example an argument of "aatc" will result in "ttag".
+ **/
+ public static String complement (final String sequence_string) {
+ StringBuffer return_buffer = new StringBuffer (sequence_string.length ());
+
+ for (int i = 0 ; i < sequence_string.length () ; ++i) {
+ return_buffer.append (complement (sequence_string.charAt (i)));
+ }
+
+ return return_buffer.toString ();
+ }
+
+ /**
+ * Return a char array containing the complement of the argument char[]. For
+ * example an argument of "aatc" will result in "ttag".
+ **/
+ public static char[] complement (final char sequence[])
+ {
+ final char[] seq_comp = new char[sequence.length];
+
+ for (int i = 0 ; i < sequence.length; ++i)
+ seq_comp[i] = complement(sequence[i]);
+
+ return seq_comp;
+ }
+
+ /**
+ * Returns the complement base of it's argument - c for g, a for t etc.
+ * The argument may be upper or lower case, but the result is always lower
+ * case. This also works for IUB base codes: the complement of 'y' is 'r'
+ * because 'y' is 'c' or 't' and 'r' is 'a' or 'g', the complement of 'n'
+ * or 'x' (any base) is 'n'.
+ **/
+ public final static char complement (final char base) {
+
+ switch (base) {
+ case 'a': case 'A': return 't';
+ case 't': case 'T': case 'u': case 'U': return 'a';
+ case 'g': case 'G': return 'c';
+ case 'c': case 'C': return 'g';
+ case 'r': case 'R': return 'y';
+ case 'y': case 'Y': return 'r';
+ case 'k': case 'K': return 'm';
+ case 'm': case 'M': return 'k';
+ case 's': case 'S': return 's';
+ case 'w': case 'W': return 'w';
+ case 'b': case 'B': return 'v';
+ case 'd': case 'D': return 'h';
+ case 'h': case 'H': return 'd';
+ case 'v': case 'V': return 'b';
+ case 'n': case 'N': return 'n';
+ case 'x': case 'X': return 'x';
+ default:
+ return '@';
+// throw new Error ("in Bases.complement - tried to complement a letter " +
+// "that isn't a base");
+ }
+ }
+
+ /**
+ * Return the Sequence object that was passed to the constructor.
+ **/
+ public Sequence getSequence ()
+ {
+ return embl_sequence;
+ }
+
+
+ /**
+ * Check a three character substring and return true if and only if the
+ * three bases translate to a stop codon. If the direction is REVERSE
+ * then the three bases to check are at start_index, start_index - 1 and
+ * start_index - 2. In that case true is returned if and only the
+ * complement of those three bases is a stop codon.
+ * Codons that contain an X are considered to be stop codons.
+ **/
+ private static boolean isCodon(char first_letter, char second_letter, char third_letter,
+ final StringVector query_codons)
+ {
+ char[] tran = {first_letter, second_letter, third_letter };
+
+ if(query_codons.contains( new String(tran) ))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Check a three character substring and return true if and only if the
+ * three bases translate to a stop codon. If the direction is REVERSE
+ * then the three bases to check are at start_index, start_index - 1 and
+ * start_index - 2. In that case true is returned if and only the
+ * complement of those three bases is a stop codon.
+ * Codons that contain an X are considered to be stop codons.
+ **/
+ private static boolean isStopCodon(char first_letter, char second_letter, char third_letter)
+ {
+ // codons that contain an X are considered to be stop codons.
+ if(first_letter == 'x' || second_letter == 'x' || third_letter == 'x')
+ return true;
+
+ final char translation = AminoAcidSequence.getCodonTranslation(first_letter,
+ second_letter,
+ third_letter);
+
+ if(translation == '+' || translation == '*' || translation == '#')
+ return true;
+ else
+ return false;
+ }
+
+
+ /**
+ * Check a three character substring and return true if and only if the
+ * three bases are legal (see isLegalBase ()).
+ **/
+ /*private static boolean isLegalCodon (final String sequence_string,
+ final int start_index,
+ final int direction) {
+ if (direction == FORWARD) {
+ if (isLegalBase (sequence_string.charAt (start_index)) &&
+ isLegalBase (sequence_string.charAt (start_index + 1)) &&
+ isLegalBase (sequence_string.charAt (start_index + 2))) {
+ return true;
+ }
+ } else {
+ if (isLegalBase (sequence_string.charAt (start_index)) &&
+ isLegalBase (sequence_string.charAt (start_index - 1)) &&
+ isLegalBase (sequence_string.charAt (start_index - 2))) {
+ return true;
+ }
+ }
+
+ // this isn't a stop codon
+ return false;
+
+ }*/
+
+ /**
+ * Return true if and only if the given base character is one of 'a', 't',
+ * 'c', 'g' or 'u'.
+ **/
+ public final static boolean isLegalBase (final char base_char) {
+ switch (base_char) {
+ case 'a': case 'A': return true;
+ case 't': case 'T': return true;
+ case 'u': case 'U': return true;
+ case 'g': case 'G': return true;
+ case 'c': case 'C': return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * The underlying sequence object that holds the data for this object.
+ * This is the same object that was passed to the constructor.
+ **/
+ private Sequence embl_sequence;
+
+ /**
+ * The object representing the forward sequence of bases.
+ **/
+ private Strand forward_strand;
+
+ /**
+ * The object representing the reverse (reverse complemented)
+ * sequence of bases.
+ **/
+ private Strand reverse_strand;
+}
diff --git a/uk/ac/sanger/artemis/sequence/Marker.java b/uk/ac/sanger/artemis/sequence/Marker.java
new file mode 100644
index 0000000..8611ff9
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/Marker.java
@@ -0,0 +1,362 @@
+/* Marker.java
+ *
+ * created: Wed Oct 28 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/Marker.java,v 1.4 2007-07-05 11:58:16 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+import uk.ac.sanger.artemis.io.PartialSequence;
+import uk.ac.sanger.artemis.util.*;
+import java.util.Vector;
+
+/**
+ * Objects of this class mark postions on one strand of DNA.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Marker.java,v 1.4 2007-07-05 11:58:16 tjc Exp $
+ *
+ **/
+
+public class Marker {
+
+ /**
+ * Create a new strand marker. Methods Outside this package should use
+ * Strand.makeMarker () to make a new Marker.
+ * @param strand The strand that this marker is associated with - a marker
+ * only makes sense in the context of a Strand.
+ * @param position The position on the strand that this marker points to.
+ * @exception OutOfRangeException Thrown if the position is less than 1 or
+ * greater than the length of the sequence.
+ **/
+ Marker (final Strand strand, final int position)
+ throws OutOfRangeException {
+ markerinternal = new MarkerInternal (strand, position);
+
+ strand.getBases ().addSequenceChangeListener (markerinternal,
+ LISTENER_PRIORITY);
+ }
+
+ /**
+ * The priority value use when adding a MarkerInternal object as a
+ * SequenceChangeListener.
+ **/
+ public static final int LISTENER_PRIORITY = Bases.MEDIUM_PRIORITY;
+
+ /**
+ * Return the strand that this marker is associated with.
+ **/
+ public Strand getStrand () {
+ return getInternal ().getStrand ();
+ }
+
+ /**
+ * Return the position on the strand that this marker points to.
+ **/
+ public int getPosition () {
+ return getInternal ().getPosition ();
+ }
+
+ /**
+ * Set the position of the Marker, without changing the Strand.
+ * @exception OutOfRangeException Thrown if the new position is less than 1
+ * or greater than the length of the sequence.
+ **/
+ public void setPosition (final int position)
+ throws OutOfRangeException {
+ getInternal ().setPosition (position);
+ }
+
+ /**
+ * Return postion of this Marker on the underlying Bases object.
+ **/
+ public int getRawPosition () {
+ return getStrand ().getRawPosition (getPosition ());
+ }
+
+ /**
+ * Set the raw position of the Marker, without changing the Strand.
+ * @exception OutOfRangeException Thrown if the new position is less than 1
+ * or greater than the length of the sequence.
+ **/
+ public void setRawPosition (final int position)
+ throws OutOfRangeException {
+ getInternal ().setPosition (getStrand ().getRawPosition (position));
+ }
+
+ /**
+ * Returns true if and only this Marker and argument Marker refer to the
+ * same position on the same Strand.
+ **/
+ public boolean equals (final Marker test_marker) {
+ if (test_marker.getPosition () == getPosition () &&
+ test_marker.getStrand () == getStrand ()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Adds the specified event listener to receive marker change events from
+ * this object.
+ * @param l the change event listener.
+ **/
+ public void addMarkerChangeListener (MarkerChangeListener l) {
+ if (marker_listener_list.size () == 0) {
+ getInternal ().parent = this;
+ }
+ marker_listener_list.addElement (l);
+ }
+
+ /**
+ * Removes the specified event listener so that it no longer receives
+ * marker change events from this object.
+ * @param l the change event listener.
+ **/
+ public void removeMarkerChangeListener (MarkerChangeListener l) {
+ marker_listener_list.removeElement (l);
+ if (marker_listener_list.size () == 0) {
+ getInternal ().parent = null;
+ }
+ }
+
+ /**
+ * Return the argument which has the lowest raw position or the first if
+ * the have equal raw positions.
+ **/
+ public static Marker getRawLowest (final Marker marker_1,
+ final Marker marker_2) {
+ if (marker_1.getRawPosition () <= marker_2.getRawPosition ()) {
+ return marker_1;
+ } else {
+ return marker_2;
+ }
+ }
+
+ /**
+ * Send a MarkerChangeEvent to those object listening for it.
+ * @param event The event to send
+ **/
+ void fireEvent (MarkerChangeEvent event) {
+ final Vector targets;
+ // copied from a book - synchronising the whole method might cause a
+ // deadlock
+ synchronized (this) {
+ targets = (Vector) marker_listener_list.clone ();
+ }
+
+ for (int i = 0 ; i < targets.size () ; ++i) {
+ final MarkerChangeListener target =
+ (MarkerChangeListener) targets.elementAt (i);
+
+ target.markerChanged (event);
+ }
+ }
+
+ /**
+ * Return a new Marker object that is on the same Strand as this Marker but
+ * has it's position moved by the given amount. For example if this Marker
+ * has position 100 then calling moveBy (-50) will return a Marker at
+ * position 50.
+ * @exception OutOfRangeException Thrown if the new position is less than 1
+ * or greater than the length of the sequence.
+ **/
+ public Marker moveBy (final int offset)
+ throws OutOfRangeException {
+ return new Marker (getStrand (), getPosition () + offset);
+ }
+
+ /**
+ * Calls Strand.removeSequenceChangeListener () for the MarkerInternal
+ * object of this Marker.
+ **/
+ public void finalize () {
+ getStrand ().getBases ().removeSequenceChangeListener (markerinternal);
+ }
+
+ /**
+ * Return the MarkerInternal that was created by the constructor and which
+ * contains the guts of the Marker.
+ **/
+ private MarkerInternal getInternal () {
+ return markerinternal;
+ }
+
+ /**
+ * This is created by the constructor. See the documentation for the
+ * MarkerInternal class.
+ **/
+ private /*final*/ MarkerInternal markerinternal;
+
+ /**
+ * A vector containing the references of those objects listening for
+ * marker change events.
+ **/
+ private final Vector marker_listener_list = new Vector ();
+}
+
+/**
+ * This is an internal class used by Marker to make sure everything is
+ * garbage collected correctly. The constructor of the Marker class makes a
+ * new MarkerInternal object and calls
+ * Strand.addSequenceChangeListener(new_markerinternal). The finalizer of the
+ * Marker class calls Strand.removeSequenceChangeListener(). This ensures
+ * that when there are no explicit references to the Marker it won't be keep
+ * alive by having a reference on the sequence change listener list.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Marker.java,v 1.4 2007-07-05 11:58:16 tjc Exp $
+ *
+ **/
+class MarkerInternal
+ implements SequenceChangeListener {
+ /**
+ * Create a new strand marker. Methods Outside this package should use
+ * Strand.makeMarker () to make a new Marker.
+ * @param strand The strand that this marker is associated with - a marker
+ * only makes sense in the context of a Strand.
+ * @param position The position on the strand that this marker points to.
+ * @exception OutOfRangeException Thrown if the position is less than 1 or
+ * greater than the length of the sequence.
+ **/
+ public MarkerInternal (final Strand strand, final int position)
+ throws OutOfRangeException {
+ this.strand = strand;
+ this.position = position;
+
+ checkPosition (position);
+ }
+
+ /**
+ * Return the strand that this marker is associated with.
+ **/
+ public Strand getStrand () {
+ return strand;
+ }
+
+ /**
+ * Return the position on the strand that this marker points to.
+ **/
+ public int getPosition () {
+ return position;
+ }
+
+ /**
+ * Set the position of the Marker, without changing the Strand.
+ * @exception OutOfRangeException Thrown if the new position is less than 1
+ * or greater than the length of the sequence.
+ **/
+ public void setPosition (final int position)
+ throws OutOfRangeException {
+ final int old_position = this.position;
+
+ checkPosition (position);
+
+ this.position = position;
+
+ if (parent != null) {
+ parent.fireEvent (new MarkerChangeEvent (parent,
+ getStrand (),
+ old_position));
+ }
+ }
+
+ /**
+ * Implementation of the SequenceChangeListener interface. We listen here
+ * so that the Marker can be updated when the sequence changes.
+ **/
+ public void sequenceChanged (final SequenceChangeEvent event) {
+ if(event.getType () == SequenceChangeEvent.REVERSE_COMPLEMENT ||
+ event.getType () == SequenceChangeEvent.CONTIG_REVERSE_COMPLEMENT ||
+ event.getType () == SequenceChangeEvent.CONTIG_REORDER)
+ return;
+
+ final int sub_sequence_length = event.getSubSequence ().length ();
+
+ final Bases bases = getStrand ().getBases ();
+
+ final int event_start_base;
+
+ if (getStrand ().isForwardStrand ()) {
+ event_start_base = event.getPosition ();
+ } else {
+ if (event.getType () == SequenceChangeEvent.INSERTION) {
+ // this is the length of sequence before the insertion
+ final int new_sequence_length =
+ bases.getLength () - sub_sequence_length;
+ event_start_base = new_sequence_length - event.getPosition ();
+ } else {
+ event_start_base = bases.getLength () - event.getPosition ();
+ }
+ }
+
+ if (event.getType () == SequenceChangeEvent.DELETION) {
+ if (position <= event_start_base) {
+ return;
+ } else {
+ if (event_start_base + sub_sequence_length > position) {
+ position = event_start_base;
+ } else {
+ position -= sub_sequence_length;
+ }
+ }
+ } else {
+ if (position < event_start_base) {
+ return;
+ } else {
+ position += sub_sequence_length;
+ }
+ }
+ }
+
+ /**
+ * Check that the given position is within the range of the Strand.
+ * @exception OutOfRangeException Thrown if the position is less than 1
+ * or greater than the length of the sequence.
+ **/
+ private void checkPosition (final int position)
+ throws OutOfRangeException {
+ if (position < 1 || position > strand.getSequenceLength ())
+ {
+ if(!(strand.getBases().getSequence() instanceof PartialSequence))
+ throw new OutOfRangeException ("position: " + position);
+ }
+ }
+
+ /**
+ * The strand that this marker is associated with.
+ **/
+ private Strand strand;
+
+ /**
+ * The position on the strand that this marker points to.
+ **/
+ private int position;
+
+ /**
+ * This will be null when the Marker object has know registered listeners
+ * and will contain the reference of the Marker that created this
+ * MarkerInternal object when the Marker has any listeners.
+ **/
+ Marker parent = null;
+}
diff --git a/uk/ac/sanger/artemis/sequence/MarkerChangeEvent.java b/uk/ac/sanger/artemis/sequence/MarkerChangeEvent.java
new file mode 100644
index 0000000..8d46068
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/MarkerChangeEvent.java
@@ -0,0 +1,82 @@
+/* MarkerChangeEvent.java
+ *
+ * created: Sun Mar 21 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/MarkerChangeEvent.java,v 1.1 2004-06-09 09:52:18 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+/**
+ * This event is sent when the position or Strand of a Marker changes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: MarkerChangeEvent.java,v 1.1 2004-06-09 09:52:18 tjc Exp $
+ **/
+
+public class MarkerChangeEvent extends java.util.EventObject {
+ /**
+ * Create a new MarkerChange event
+ * @param marker The Marker that has changed.
+ * @param strand The Strand that the Marker was associated with before
+ * the MarkerChange event.
+ * @param position The position on the Strand of the Marker before the
+ * event.
+ **/
+ public MarkerChangeEvent (final Marker marker,
+ final Strand strand, final int position) {
+ super (marker);
+ this.marker = marker;
+ this.strand = strand;
+ this.position = position;
+ }
+
+ /**
+ * Return the Marker that was passed to the constructor.
+ **/
+ public Marker getMarker () {
+ return marker;
+ }
+
+ /**
+ * Return the Strand that was passed to the constructor.
+ **/
+ public Strand getStrand () {
+ return strand;
+ }
+
+ /**
+ * The Marker that was passed to the constructor.
+ **/
+ final private Marker marker;
+
+ /**
+ * The Strand that was passed to the constructor.
+ **/
+ final private Strand strand;
+
+ /**
+ * The position that was passed to the constructor.
+ **/
+ final private int position;
+}
+
+
diff --git a/uk/ac/sanger/artemis/sequence/MarkerChangeListener.java b/uk/ac/sanger/artemis/sequence/MarkerChangeListener.java
new file mode 100644
index 0000000..e5a8102
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/MarkerChangeListener.java
@@ -0,0 +1,43 @@
+/* MarkerChangeListener.java
+ *
+ * created: Sun Mar 21 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/MarkerChangeListener.java,v 1.1 2004-06-09 09:52:19 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+/**
+ * The MarkerChangeListener interface is implemented by those classes that
+ * need to know when a Marker changes it's position or Strand.
+ *
+ * @author Kim Rutherford
+ * @version $Id: MarkerChangeListener.java,v 1.1 2004-06-09 09:52:19 tjc Exp $
+ **/
+
+public interface MarkerChangeListener extends uk.ac.sanger.artemis.ChangeListener {
+ /**
+ * Invoked when a Marker changes it's position or Strand.
+ **/
+ void markerChanged (MarkerChangeEvent event);
+}
+
+
diff --git a/uk/ac/sanger/artemis/sequence/MarkerRange.java b/uk/ac/sanger/artemis/sequence/MarkerRange.java
new file mode 100644
index 0000000..4339324
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/MarkerRange.java
@@ -0,0 +1,293 @@
+/* MarkerRange.java
+ *
+ * created: Tue Dec 29 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/MarkerRange.java,v 1.1 2004-06-09 09:52:20 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+import uk.ac.sanger.artemis.util.*;
+
+import uk.ac.sanger.artemis.io.Range;
+
+/**
+ * This class implements a range that has end points specified by a pair of
+ * positions in a Strand object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: MarkerRange.java,v 1.1 2004-06-09 09:52:20 tjc Exp $
+ **/
+
+public class MarkerRange {
+ /**
+ * Create a new MarkerRange object. If the start position is greater than
+ * the end position the two positions will be silently swapped.
+ * @param strand The Strand that contains this range.
+ * @param start The start position.
+ * @param end The end position.
+ **/
+ public MarkerRange (final Strand strand, final int start, final int end)
+ throws OutOfRangeException {
+ if (start <= end) {
+ this.start = strand.makeMarker (start);
+ this.end = strand.makeMarker (end);
+ } else {
+ this.start = strand.makeMarker (end);
+ this.end = strand.makeMarker (start);
+ }
+ }
+
+ /**
+ * Create a new MarkerRange object that covers one base (given by a Marker
+ * object).
+ * @param marker The position of the new MarkerRange.
+ **/
+ public MarkerRange (final Marker marker) {
+ this.start = marker;
+ this.end = marker;
+ }
+
+ /**
+ * Return the Strand object that was passed to the constructor.
+ **/
+ public Strand getStrand () {
+ return getStart ().getStrand ();
+ }
+
+ /**
+ * Return true if and only if this MarkerRange is a MarkerRange on the
+ * forward strand.
+ **/
+ public boolean isForwardMarker () {
+ return getStrand ().isForwardStrand ();
+ }
+
+ /**
+ * Return the start Marker of this range.
+ **/
+ public Marker getStart () {
+ return start;
+ }
+
+ /**
+ * Return the end Marker of this range.
+ **/
+ public Marker getEnd () {
+ return end;
+ }
+
+ /**
+ * Return the number of bases in the range, inclusive of the end points.
+ **/
+ public int getCount () {
+ return
+ getRawEnd ().getRawPosition () - getRawStart ().getRawPosition () + 1;
+ }
+
+ /**
+ * Return the end marker if it marks a lower position on the underlying
+ * Bases of the Strand than the start Marker, otherwise return the start
+ * Marker.
+ **/
+ public Marker getRawStart () {
+ if (getStart ().getRawPosition () <= getEnd ().getRawPosition ()) {
+ return getStart ();
+ } else {
+ return getEnd ();
+ }
+ }
+
+ /**
+ * Return the start marker if it marks a lower position on the underlying
+ * Bases of the Strand than the end Marker, otherwise return the end
+ * Marker.
+ **/
+ public Marker getRawEnd () {
+ if (getEnd ().getRawPosition () <= getStart ().getRawPosition ()) {
+ return getStart ();
+ } else {
+ return getEnd ();
+ }
+ }
+
+ /**
+ * Return an embl.Range object that corresponds a range of bases on the
+ * underlying Bases object of this MarkerRange.
+ **/
+ public Range getRawRange () {
+ try {
+ if (getStrand ().isForwardStrand ()) {
+ return new Range (getStart ().getRawPosition (),
+ getEnd ().getRawPosition ());
+ } else {
+ return new Range (getEnd ().getRawPosition (),
+ getStart ().getRawPosition ());
+ }
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return an embl.Range object for this MarkerRange. The start and end
+ * positions of the Range will be the same as the start and end that was
+ * passed to the constructor.
+ **/
+ public Range getRange () {
+ try {
+ return new Range (getStart ().getPosition (), getEnd ().getPosition ());
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return true if and only if this MarkerRange and the given MarkerRange
+ * overlap.
+ **/
+ public boolean overlaps (final MarkerRange arg_range) {
+ if (getStart ().getPosition () < arg_range.getStart ().getPosition () &&
+ getEnd ().getPosition () < arg_range.getStart ().getPosition ()) {
+ return false;
+ }
+ if (getStart ().getPosition () > arg_range.getEnd ().getPosition () &&
+ getEnd ().getPosition () > arg_range.getEnd ().getPosition ()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return a new MarkerRange object that is the same as this object but has
+ * been extended to contain the given range.
+ * @param arg_range The returned MarkerRange will cover this range too.
+ **/
+ public MarkerRange extendRange (final MarkerRange arg_range) {
+ return combineRanges (arg_range, true);
+ }
+
+ /**
+ * Return a new MarkerRange object that is the same as this object but is
+ * guaranteed to contain the given range.
+ * @param arg_range The returned MarkerRange will cover this range too.
+ * @param truncate_if_contained If the arg_range is completely contained in
+ * this range then the returned range will be truncated if and only if
+ * this is true. If true then one extreme of the returned range will be
+ * moved so that it coincides with the closest extreme of the arg_range.
+ * The returned range will be only as small as necessary to achieve this.
+ * eg. if this is 1..100, the arg is 20..30 and truncate_if_contained is
+ * true then the returned range will be 20..100
+ **/
+ public MarkerRange combineRanges (final MarkerRange arg_range,
+ final boolean truncate_if_contained) {
+ if (getStrand () != arg_range.getStrand ()) {
+ throw new Error ("internal error - strands do not match");
+ }
+
+ final MarkerRange return_range;
+
+ try {
+ return_range =
+ new MarkerRange (getStrand (),
+ getStart ().getPosition (),
+ getEnd ().getPosition ());
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ if (return_range.contains (arg_range)) {
+ if (truncate_if_contained) {
+ // truncate the return_range
+ if ((arg_range.getStart ().getPosition () -
+ return_range.getStart ().getPosition ()) >
+ (return_range.getEnd ().getPosition () -
+ arg_range.getEnd ().getPosition ())) {
+ return_range.end = arg_range.getEnd ();
+ } else {
+ return_range.start = arg_range.getStart ();
+ }
+ } else {
+ // do nothing
+ }
+ } else {
+ if (return_range.getStart ().getPosition () >
+ arg_range.getStart ().getPosition ()) {
+ return_range.start = arg_range.getStart ();
+ }
+
+ if (return_range.getEnd ().getPosition () <
+ arg_range.getEnd ().getPosition ()) {
+ return_range.end = arg_range.getEnd ();
+ }
+ }
+
+ return return_range;
+ }
+
+ /**
+ * Return true if and only if the given MarkerRange is contained within
+ * this range.
+ **/
+ public boolean contains (final MarkerRange arg_range) {
+ if (getStart ().getPosition () <= arg_range.getStart ().getPosition () &&
+ getEnd ().getPosition () >= arg_range.getEnd ().getPosition ()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Create and return a Location object from the embl package that describes
+ * this MarkerRange object. For example if the sequence is 1000 bases long
+ * and the MarkerRange object covers the reverse strand bases 100..200 then
+ * the Location object will be "complement(800..900)".
+ **/
+ public uk.ac.sanger.artemis.io.Location createLocation () {
+ try {
+ final uk.ac.sanger.artemis.io.Location new_location =
+ new uk.ac.sanger.artemis.io.Location (getRawRange ());
+
+ if (getStrand ().isForwardStrand ()) {
+ return new_location;
+ } else {
+ return new_location.getComplement ();
+ }
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * The start Marker created from the Strand object and the start position
+ * passed to the constructor.
+ **/
+ private Marker start = null;
+
+ /**
+ * The end Marker created from the Strand object and the end position
+ * passed to the constructor.
+ **/
+ private Marker end = null;
+}
+
+
diff --git a/uk/ac/sanger/artemis/sequence/MarkerRangeVector.java b/uk/ac/sanger/artemis/sequence/MarkerRangeVector.java
new file mode 100644
index 0000000..fd18006
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/MarkerRangeVector.java
@@ -0,0 +1,90 @@
+/* MarkerRangeVector.java
+ *
+ * created: Sun Jan 24 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/MarkerRangeVector.java,v 1.1 2004-06-09 09:52:21 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+import java.util.Vector;
+
+/**
+ * This class implements a Vector of MarkerRange objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: MarkerRangeVector.java,v 1.1 2004-06-09 09:52:21 tjc Exp $
+ **/
+
+public class MarkerRangeVector {
+ /**
+ * Create a new (empty) MarkerRangeVector.
+ **/
+ public MarkerRangeVector () {
+
+ }
+
+ /**
+ * Performs the same function as Vector.addElement ()
+ */
+ public void add (final MarkerRange range) {
+ vector.addElement (range);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ */
+ public MarkerRange elementAt (final int index) {
+ return (MarkerRange) vector.elementAt (index);
+ }
+
+ /**
+ * Return true if this object contains the given MarkerRange.
+ **/
+ public boolean contains (final MarkerRange range) {
+ if (indexOf (range) == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Performs the same function as Vector.removeElement ()
+ **/
+ public int indexOf (final MarkerRange range) {
+ return vector.indexOf (range);
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ */
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Storage for MarkerRange objects.
+ */
+ private Vector vector = new Vector ();
+}
+
+
diff --git a/uk/ac/sanger/artemis/sequence/NoSequenceException.java b/uk/ac/sanger/artemis/sequence/NoSequenceException.java
new file mode 100644
index 0000000..84aeb90
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/NoSequenceException.java
@@ -0,0 +1,43 @@
+/* NoSequenceException.java
+ *
+ * created: Thu Jun 8 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/NoSequenceException.java,v 1.1 2004-06-09 09:52:23 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+/**
+ * This Exception is thrown by FileEntrySource.getEntry () when an Entry is
+ * read that has no sequence.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: NoSequenceException.java,v 1.1 2004-06-09 09:52:23 tjc Exp $
+ **/
+
+public class NoSequenceException extends Exception {
+ /**
+ * Create a new NoSequenceException.
+ **/
+ public NoSequenceException () {
+
+ }
+}
diff --git a/uk/ac/sanger/artemis/sequence/SequenceChangeEvent.java b/uk/ac/sanger/artemis/sequence/SequenceChangeEvent.java
new file mode 100644
index 0000000..3df96ca
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/SequenceChangeEvent.java
@@ -0,0 +1,173 @@
+/* SequenceChangeEvent.java
+ *
+ * created: Wed Oct 28 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/SequenceChangeEvent.java,v 1.3 2005-11-28 16:46:38 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+import uk.ac.sanger.artemis.io.Range;
+
+/**
+ * This event is sent when the sequence of bases in a strand changes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: SequenceChangeEvent.java,v 1.3 2005-11-28 16:46:38 tjc Exp $
+ *
+ **/
+
+public class SequenceChangeEvent extends uk.ac.sanger.artemis.ChangeEvent
+{
+ /**
+ * The type that was passed to the constructor.
+ **/
+ /* final */ private int type;
+
+ /**
+ * The position that was passed to the constructor.
+ **/
+ /* final */ private int position;
+
+ /**
+ * The sub sequence of bases that was passed to the constructor.
+ **/
+ /* final */ private String sub_sequence;
+
+ private int length;
+
+ private Range range;
+
+ final public static int DELETION = 1;
+
+ final public static int INSERTION = 2;
+
+ final public static int REVERSE_COMPLEMENT = 3;
+
+ final public static int CONTIG_REVERSE_COMPLEMENT = 4;
+
+ final public static int CONTIG_REORDER = 5;
+
+ /**
+ * Create a new SequenceChangeEvent object.
+ * @param bases The Bases object that generated the event.
+ * @param type The type of the event (INSERTION or DELETION).
+ * @param position The position of the first base that was deleted or the
+ * position base immediately before the insertion. If the insertion is
+ * at the start of the sequence the position will be 0.
+ * @param sub_sequence The bases that were inserted or deleted.
+ **/
+ public SequenceChangeEvent(final Bases bases,
+ final int type,
+ final int position,
+ final String sub_sequence)
+ {
+ super(bases);
+ this.type = type;
+ this.position = position;
+ this.sub_sequence = sub_sequence;
+ }
+
+ /**
+ * Create a new SequenceChangeEvent object (CONTIG_REORDER).
+ **/
+ public SequenceChangeEvent(final int type,
+ final int position,
+ final Range range)
+ {
+ super(range);
+ this.type = type;
+ this.position = position;
+ this.range = range;
+ }
+
+ public SequenceChangeEvent(final Bases bases,
+ final int type,
+ final Range range,
+ final int length)
+ {
+ super(bases);
+ this.type = type;
+ this.range = range;
+ this.length = length;
+ }
+
+
+ /**
+ * Create a new SequenceChangeEvent object.
+ * @param bases The Bases object that generated the event.
+ * @param type The type of the event (should be REVERSE_COMPLEMENT).
+ **/
+ public SequenceChangeEvent(final Bases bases,
+ final int type)
+ {
+ super (bases);
+ this.type = type;
+ this.position = 0;
+ this.sub_sequence = null;
+ }
+
+ /**
+ * Return the Bases reference that was passed to the constructor.
+ **/
+ public Bases getBases()
+ {
+ return (Bases)getSource();
+ }
+
+ /**
+ * Return the type of this event (INSERTION or DELETION).
+ **/
+ public int getType()
+ {
+ return type;
+ }
+
+ /**
+ * Return the position of the deletion or insertion (as passed to the
+ * constructor).
+ **/
+ public int getPosition()
+ {
+ return position;
+ }
+
+ /**
+ * Return a String containing the bases that were inserted or deleted.
+ **/
+ public String getSubSequence()
+ {
+ return sub_sequence;
+ }
+
+ public int getLength()
+ {
+ return length;
+ }
+
+ public Range getRange()
+ {
+ return range;
+ }
+
+}
+
+
diff --git a/uk/ac/sanger/artemis/sequence/SequenceChangeListener.java b/uk/ac/sanger/artemis/sequence/SequenceChangeListener.java
new file mode 100644
index 0000000..2544bca
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/SequenceChangeListener.java
@@ -0,0 +1,42 @@
+/* SequenceChangeListener.java
+ *
+ * created: Wed Oct 28 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/SequenceChangeListener.java,v 1.1 2004-06-09 09:52:25 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+/**
+ * The SequenceChangeListener interface is implemented by those classes that
+ * need to know when the sequence of bases in a Strand changes.
+ *
+ * @author Kim Rutherford
+ * @version $Id: SequenceChangeListener.java,v 1.1 2004-06-09 09:52:25 tjc Exp $
+ *
+ **/
+
+public interface SequenceChangeListener extends java.util.EventListener {
+ /**
+ * Invoked when a deletion or insertion occurs in a Bases object.
+ **/
+ void sequenceChanged (SequenceChangeEvent event);
+}
diff --git a/uk/ac/sanger/artemis/sequence/Strand.java b/uk/ac/sanger/artemis/sequence/Strand.java
new file mode 100644
index 0000000..a6e2132
--- /dev/null
+++ b/uk/ac/sanger/artemis/sequence/Strand.java
@@ -0,0 +1,693 @@
+/* Strand.java
+ *
+ * created: Sun Oct 11 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/sequence/Strand.java,v 1.7 2007-06-26 13:59:04 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.sequence;
+
+import uk.ac.sanger.artemis.util.*;
+import uk.ac.sanger.artemis.io.Range;
+
+import org.biojava.bio.symbol.IllegalSymbolException;
+
+/**
+ * This represents one strand of DNA. The bases of the strand must be passed
+ * in to the constructor as a Bases object. All operations where the
+ * direction of reading matters should be handled by this class. After the
+ * constructor has been called with the correct direction (by the Bases
+ * constructor) other classes should be able to use this class without caring
+ * what direction the Strand represents.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Strand.java,v 1.7 2007-06-26 13:59:04 tjc Exp $
+ **/
+
+public class Strand {
+ /**
+ * When passed to the constructor, FORWARD indicates the bases should be
+ * read in the forward direction.
+ **/
+ static public final int FORWARD = Bases.FORWARD;
+
+ /**
+ * When passed to the constructor, REVERSE indicates the bases should be
+ * read in the forward direction.
+ **/
+ static public final int REVERSE = Bases.REVERSE;
+
+ /**
+ * Create a new Strand object with an associated direction. Typically this
+ * constructor is called twice with the sames Bases object, but with a
+ * different value for direction.
+ * @param bases The bases of this strand.
+ * @param direction The direction the bases should read. If REVERSE then
+ * the bases will be automatically read in the reverse direction and
+ * complemented before use.
+ **/
+ Strand (final Bases bases, final int direction) {
+ this.bases = bases;
+ }
+
+ /**
+ * Return the Bases reference that was passed to the constructor.
+ **/
+ public Bases getBases () {
+ return bases;
+ }
+
+ /**
+ * Return a String containing the bases of this strand.
+ **/
+ public String getStrandBases () {
+ if (getDirection () == FORWARD) {
+ return getBases ().toString ();
+ } else {
+ return Bases.reverseComplement (getBases ().toString ());
+ }
+ }
+
+ /**
+ * Return the direction of this strand.
+ **/
+ public int getDirection () {
+ if (getBases ().getForwardStrand () == this) {
+ return FORWARD;
+ } else {
+ return REVERSE;
+ }
+ }
+
+ /**
+ * Return true if and only if this Strand is a forward strand.
+ **/
+ public boolean isForwardStrand () {
+ if (getDirection () == FORWARD) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return the length of this Strand in bases.
+ **/
+ public int getSequenceLength () {
+ return getBases ().getLength ();
+ }
+
+ /**
+ * Return an 2D array containing the stop or start codons in a range for
+ * all 3 frames of the strand.
+ * @param range The inclusive range of bases to get the stop codons from.
+ * @param query_codons if this is NULL then this assumes we are looking
+ * for stop codons, otherwise this is used to look for start codons.
+ **/
+ public int [][] getStopOrStartCodons(final Range range, final StringVector query_codons) {
+ return bases.getStopOrStartCodons(range, getDirection (), query_codons);
+ }
+
+ private int [] getStopCodons (final Range range) {
+ return bases.getStopCodons (range, getDirection ());
+ }
+
+
+ /**
+ * Return an array containing the positions of the codons that match the
+ * strings given by the query_codons argument. Only those codons that are
+ * in the same frame as the first base of the range are returned.
+ * @param range The inclusive range of bases to get the codons from.
+ * @param query_codons The codons to search for. Each element of this
+ * vector should be a string that is 3 characters long.
+ * @return An array containing the positions of the first base of the
+ * codons. This array is padded with zeros at the end.
+ **/
+ public int [] getMatchingCodons (final Range range,
+ final StringVector query_codons) {
+ return bases.getMatchingCodons (range, getDirection (), query_codons);
+ }
+
+ /**
+ * Return a String containing the bases (base letters) of the codon that
+ * starts at the given Marker.
+ **/
+ public static String getCodonAtMarker (final Marker marker) {
+ try {
+ final Range codon_range =
+ new Range (marker.getPosition (), marker.getPosition () + 2);
+
+ return marker.getStrand ().getSubSequence (codon_range);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+ }
+
+ /**
+ * Return an array of MarkerRange objects - one object for each open
+ * reading frame bigger than the given size.
+ * @param search_range All the returned MarkerRanges must overlap this
+ * range.
+ * @param minimum_size All the returned MarkerRanges must be at least
+ * this many residues long.
+ **/
+ public static MarkerRange [] getOpenReadingFrameRanges (final MarkerRange search_range,
+ final int minimum_size,
+ final int sequence_end,
+ final int sequence_start) {
+ final Strand search_strand = search_range.getStrand ();
+
+ final MarkerRange [] [] frame_ranges = new MarkerRange [3] [];
+
+ // get an array of MarkerRange objects for each frame
+
+ for (int frame_offset = 0 ; frame_offset < 3 ; ++frame_offset) {
+ final Range sequence_range;
+
+ try {
+ sequence_range =
+ new Range (sequence_start + frame_offset, search_strand.getSequenceLength ());
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ final int [] frame_stop_codons =
+ search_strand.getStopCodons (sequence_range);
+
+ frame_ranges [frame_offset] =
+ search_strand.getORFsFromStopCodons (frame_stop_codons,
+ minimum_size,
+ frame_offset,
+ search_range,
+ sequence_end,
+ sequence_start);
+ }
+
+ // now copy the MarkerRange objects into a single array to return
+
+ final int max_range_count =
+ frame_ranges[0].length + frame_ranges[1].length + frame_ranges[2].length;
+
+ final MarkerRange [] temp_range_array = new MarkerRange [max_range_count];
+
+ int temp_range_array_index = 0;
+
+ for (int i = 0 ; i < 3 ; ++i) {
+ final MarkerRange [] frame_range_array = frame_ranges[i];
+
+ for (int range_index = 0 ;
+ range_index < frame_range_array.length &&
+ frame_ranges[i][range_index] != null ;
+ ++range_index) {
+
+ final MarkerRange this_range = frame_ranges[i][range_index];
+
+ if (search_range.overlaps (this_range)) {
+ temp_range_array[temp_range_array_index] = this_range;
+
+ ++temp_range_array_index;
+ } else {
+ // ignore this range
+ continue;
+ }
+ }
+ }
+
+ final MarkerRange [] return_array =
+ new MarkerRange [temp_range_array_index];
+
+ System.arraycopy (temp_range_array, 0,
+ return_array, 0,
+ temp_range_array_index);
+
+ return return_array;
+ }
+
+ /**
+ * Find an ORF around the given Marker object. The Strand and frame to
+ * search are read from the Marker.
+ * @param search_marker Search from this marker. The frame to search is
+ * the frame in which this marker point to the first base of a codon.
+ * @param ignore_illegal_codons If true this method will read through
+ * illegal bases (like N and the other ambiguity codes). If false codons
+ * containing illegal bases will be treated like stop codons.
+ * @return A MarkerRange for the ORF containing search_marker or null is
+ * the marker points to a stop codon.
+ **/
+ public static MarkerRange getORFAroundMarker (final Marker search_marker,
+ final boolean
+ ignore_illegal_codons) {
+ // first search backwards
+
+ final Range search_codon_range;
+
+ try {
+ search_codon_range = new Range (search_marker.getPosition (),
+ search_marker.getPosition () + 2);
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected exception: " + e);
+ }
+
+ final Strand search_strand = search_marker.getStrand ();
+
+ final String search_codon_sequence =
+ search_strand.getSubSequence (search_codon_range);
+
+ final char search_codon_char =
+ AminoAcidSequence.getCodonTranslation (search_codon_sequence);
+
+ if (AminoAcidSequence.isStopCodon (search_codon_char)) {
+ return null;
+ }
+
+ final Marker start_marker = getStartOfORF (search_marker,
+ ignore_illegal_codons);
+
+ final Marker end_marker = getEndOfORF (search_marker,
+ ignore_illegal_codons);
+
+ try {
+ return new MarkerRange (search_strand,
+ start_marker.getPosition (),
+ end_marker.getPosition ());
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected OutOfRangeException");
+ }
+ }
+
+ /**
+ * Return the Marker of the start of the ORF that contains the given
+ * Marker. This method works by stepping backwards three bases at a time
+ * until the next codon is stop codon or until the start of sequence is
+ * reached.
+ * @param ignore_illegal_codons If true this method will read through
+ * illegal bases (like N and the other ambiguity codes). If false codons
+ * containing illegal bases will be treated like stop codons.
+ * @return A Marker that points to a position just after a stop codon or
+ * to the first base of the Strand (in this frame).
+ **/
+ private static Marker getStartOfORF (final Marker search_marker,
+ final boolean ignore_illegal_codons) {
+ final Strand search_strand = search_marker.getStrand ();
+
+ Marker current_marker = search_marker;
+
+ Marker previous_marker = search_marker;
+
+ while (true) {
+ try {
+ // this should be optimised to avoid using moveBy() which is slow
+
+ current_marker = current_marker.moveBy (-3);
+
+ final Range search_codon_range =
+ new Range (current_marker.getPosition (),
+ current_marker.getPosition () + 2);
+
+ final String codon_sequence =
+ search_strand.getSubSequence (search_codon_range);
+
+ if (codon_sequence.charAt (0) == 'x' ||
+ codon_sequence.charAt (1) == 'x' ||
+ codon_sequence.charAt (2) == 'x') {
+ return previous_marker;
+ }
+
+ final char this_codon_char =
+ AminoAcidSequence.getCodonTranslation (codon_sequence);
+
+ if (AminoAcidSequence.isStopCodon (this_codon_char) ||
+ !ignore_illegal_codons &&
+ !AminoAcidSequence.isLegalCodon (this_codon_char)) {
+ return previous_marker;
+ }
+
+ previous_marker = current_marker;
+ } catch (OutOfRangeException e) {
+ return previous_marker;
+ }
+ }
+ }
+
+ /**
+ * Return the Marker of the end of the ORF that contains the given
+ * Marker. This method works by stepping forwards three bases at a time
+ * until the next codon is stop codon or until the end of sequence is
+ * reached.
+ * @param ignore_illegal_codons If true this method will read through
+ * illegal bases (like N and the other ambiguity codes). If false codons
+ * containing illegal bases will be treated like stop codons.
+ * @return A Marker that points to a position just before a stop codon or
+ * to the last base of the Strand (in this frame).
+ **/
+ private static Marker getEndOfORF (final Marker search_marker,
+ final boolean ignore_illegal_codons) {
+ final Strand search_strand = search_marker.getStrand ();
+
+ Marker current_marker = null;
+
+ try {
+ // use the end of the codon as the start position
+ current_marker = search_marker.moveBy (2);
+ } catch (OutOfRangeException e) {
+ // end of sequence
+ return search_marker;
+ }
+
+ Marker previous_marker = current_marker;
+
+ while (true) {
+ try {
+ // this should be optimised to avoid using moveBy() which is slow
+
+ current_marker = current_marker.moveBy (3);
+
+ final Range search_codon_range =
+ new Range (current_marker.getPosition () - 2,
+ current_marker.getPosition ());
+
+ final String codon_sequence =
+ search_strand.getSubSequence (search_codon_range);
+
+ if (codon_sequence.charAt (0) == 'x' ||
+ codon_sequence.charAt (1) == 'x' ||
+ codon_sequence.charAt (2) == 'x') {
+ return previous_marker;
+ }
+
+ final char this_codon_char =
+ AminoAcidSequence.getCodonTranslation (codon_sequence);
+
+ if (AminoAcidSequence.isStopCodon (this_codon_char) ||
+ !ignore_illegal_codons &&
+ !AminoAcidSequence.isLegalCodon (this_codon_char)) {
+ return previous_marker;
+ }
+
+ previous_marker = current_marker;
+ } catch (OutOfRangeException e) {
+ return previous_marker;
+ }
+ }
+ }
+
+ /**
+ * Return an array of MarkerRange objects - one object for each open
+ * reading frame bigger than the given size.
+ * @param stop_codons An array containing the positions of the stop codons
+ * for one of the three translation frames.
+ * @param minimum_size All the returned ORFs will be at least this many
+ * amino acids long.
+ * @param frame_offset The frame to that the stop codons are in (0, 1 or 2)
+ * @param test_range All the returned MarkerRanges must overlap this
+ * range.
+ * @return An array of MarkerRange objects. The array may contain some
+ * null references at the end.
+ **/
+ private MarkerRange [] getORFsFromStopCodons (final int [] stop_codons,
+ final int minimum_size,
+ final int frame_offset,
+ final MarkerRange test_range,
+ final int sequence_end,
+ final int sequence_start) {
+ // this array is the maximum possible size - the last elements of the
+ // array will almost certainly be empty when we return
+ final MarkerRange [] return_array =
+ new MarkerRange [stop_codons.length + 1];
+
+ int return_array_index = 0;
+
+ // special case for the first ORF, the one between the first base of the
+ // sequence and the first stop codon
+
+ for (int i = -1 ;
+ i < stop_codons.length && (i == -1 || stop_codons[i] != 0) ;
+ ++i) {
+ final int first_base_of_range;
+
+ // an index of -1 indicates that the start of this ORF is the first base
+ // of the sequence (offset to be in the correct frame).
+ if (i == -1) {
+ first_base_of_range = sequence_start + frame_offset;
+ } else {
+ // use the next base after the stop codon as the start of the range
+ first_base_of_range = stop_codons[i] + 3;
+ }
+
+ if (first_base_of_range >= sequence_end ||
+ first_base_of_range < sequence_start) {
+ continue;
+ }
+
+ int last_base_of_range;
+
+ // the last base in the range is the last base of the next stop codon or
+ // the end of sequence (if there are no more stop codons).
+ if (i + 1 == stop_codons.length || stop_codons[i + 1] == 0) {
+ last_base_of_range = sequence_end;
+ } else {
+ // use the last base of the next stop codon as the end of the range
+ last_base_of_range = stop_codons[i + 1] + 2;
+ }
+
+ if (last_base_of_range >= sequence_end) {
+ last_base_of_range = sequence_end;
+ }
+
+ final int aa_count = (last_base_of_range - first_base_of_range) / 3;
+
+ if (aa_count >= minimum_size &&
+ last_base_of_range >= test_range.getStart ().getPosition () &&
+ first_base_of_range <= test_range.getEnd ().getPosition ()) {
+ try {
+
+ return_array[return_array_index] =
+ makeMarkerRangeFromPositions (first_base_of_range,
+ last_base_of_range);
+
+ ++return_array_index;
+ } catch (OutOfRangeException e) {
+ throw new Error ("internal error - unexpected OutOfRangeException");
+ }
+ }
+ }
+
+ return return_array;
+ }
+
+ /**
+ * Create and return a Marker on this Strand at the given position. The
+ * position should refer to this strand not the underlying Bases object.
+ **/
+ public Marker makeMarker (int position)
+ throws OutOfRangeException {
+ final Marker new_marker = new Marker (this, position);
+
+ return new_marker;
+ }
+
+ /**
+ * Make a Marker on this Strand from a raw position. "Raw" means that if
+ * this is the REVERSE Strand then the position refers to a base in the the
+ * underlying sequence.
+ **/
+ public Marker makeMarkerFromRawPosition (int position)
+ throws OutOfRangeException {
+ if (getDirection () == FORWARD) {
+ return makeMarker (position);
+ } else {
+ return makeMarker (getBases ().getComplementPosition (position));
+ }
+ }
+
+ /**
+ * Make a MarkerRange on this Strand from two positions.
+ **/
+ public MarkerRange makeMarkerRangeFromPositions (int start_position,
+ int end_position)
+ throws OutOfRangeException {
+ return new MarkerRange (this, start_position, end_position);
+ }
+
+ /**
+ * Make a MarkerRange on this Strand from two raw positions. The
+ * positions should be positions on the underlying Bases object rather than
+ * positions on this Strand.
+ **/
+ public MarkerRange makeMarkerRangeFromRawPositions (int raw_start_position,
+ int raw_end_position)
+ throws OutOfRangeException {
+ if (getDirection () == FORWARD) {
+ return new MarkerRange (this, raw_start_position, raw_end_position);
+ } else {
+ final int real_start_position =
+ getBases ().getComplementPosition (raw_start_position);
+ final int real_end_position =
+ getBases ().getComplementPosition (raw_end_position);
+ return new MarkerRange (this, real_start_position, real_end_position);
+ }
+ }
+
+ /**
+ * Return the position on the Bases object of the argument base.
+ **/
+ public int getRawPosition (int strand_position) {
+ return getBases ().getRawPosition (strand_position, getDirection ());
+ }
+
+ /**
+ * Delete the bases in the given MarkerRange and send out a SequenceChange
+ * event to all the listeners.
+ * @exception ReadOnlyException If this Strand object cannot be changed.
+ **/
+ public static void deleteRange (final MarkerRange range)
+ throws ReadOnlyException {
+ range.getStrand ().getBases ().deleteRange (range.getRawRange ());
+ }
+
+ /**
+ * Add the given bases just before the given Marker position and send out a
+ * SequenceChange event to all the listeners.
+ * @param position The new bases are inserted just before this Marker
+ * position.
+ * @param bases_string The bases to insert.
+ * @exception ReadOnlyException If this Strand object cannot be changed.
+ **/
+ public static void addBases (final Marker position,
+ final String bases_string)
+ throws ReadOnlyException, IllegalSymbolException {
+ final Bases bases = position.getStrand ().getBases ();
+
+ if (position.getStrand ().isForwardStrand ()) {
+ bases.addBases (position.getRawPosition (), FORWARD, bases_string);
+ } else {
+ bases.addBases (position.getRawPosition (), REVERSE, bases_string);
+ }
+ }
+
+ /**
+ * Translate a sequence of bases into the corresponding single letter amino
+ * acid codes.
+ * @param range The range of the bases to translated. If the range.start
+ * - range.end + 1 is not a multiple of three the last codon is
+ * incomplete and will not be translated.
+ * @param unknown_is_x If this parameter is true codons that contain
+ * ambiguous bases will be translated as 'x', if false they will be
+ * translated as '.'
+ * @return The translated sequence in one letter abbreviated form.
+ **/
+ public AminoAcidSequence getTranslation(final Range range,
+ final boolean unknown_is_x) {
+ return getBases ().getTranslation (range, getDirection (), unknown_is_x);
+ }
+
+ public AminoAcidSequence getSpacedTranslation(final Range range,
+ final boolean unknown_is_x) {
+ return getBases ().getSpacedTranslation (range, getDirection (), unknown_is_x);
+ }
+
+
+ /**
+ * Return a sub-sequence of bases from this strand.
+ * @param range The inclusive range of bases to return.
+ **/
+ public String getSubSequence (Range range) {
+ return getBases ().getSubSequence (range, getDirection ());
+ }
+
+ /**
+ * Return a sub-sequence of bases from this Bases object that underlies
+ * this Strand object.
+ * @param range The inclusive range of bases to return.
+ **/
+ public char[] getRawSubSequenceC (Range range) {
+ return getBases ().getSubSequenceC (range, FORWARD);
+ }
+
+ /**
+ * Return a sub-sequence of bases from this Bases object that underlies
+ * this Strand object. This returns the same as getSubSequence () for
+ * FORWARD strands.
+ * @param range The inclusive range of bases to return.
+ **/
+ public String getRawSubSequence (Range range) {
+ return getBases ().getSubSequence (range, FORWARD);
+ }
+
+ /**
+ * Return the bases referenced by the given MarkerRange.
+ **/
+ public static String markerRangeBases (final MarkerRange marker_range) {
+ return marker_range.getStrand ().getSubSequence (marker_range.getRange ());
+ }
+
+ /**
+ * Return the number of 'A's on this Strand.
+ **/
+ public int getACount () {
+ if (isForwardStrand ()) {
+ return getBases ().getACount ();
+ } else {
+ return getBases ().getTCount ();
+ }
+ }
+
+ /**
+ * Return the number of 'T's on this Strand.
+ **/
+ public int getTCount () {
+ if (isForwardStrand ()) {
+ return getBases ().getTCount ();
+ } else {
+ return getBases ().getACount ();
+ }
+ }
+
+ /**
+ * Return the number of 'G's on this Strand.
+ **/
+ public int getGCount () {
+ if (isForwardStrand ()) {
+ return getBases ().getGCount ();
+ } else {
+ return getBases ().getCCount ();
+ }
+ }
+
+ /**
+ * Return the number of 'C's on this Strand.
+ **/
+ public int getCCount () {
+ if (isForwardStrand ()) {
+ return getBases ().getCCount ();
+ } else {
+ return getBases ().getGCount ();
+ }
+ }
+
+ /**
+ * The reference the was passed to the constructor. This object does most
+ * of the real work for us, such as reverse complementing and translating.
+ **/
+ private Bases bases;
+}
+
+
diff --git a/uk/ac/sanger/artemis/util/ByteBuffer.java b/uk/ac/sanger/artemis/util/ByteBuffer.java
new file mode 100644
index 0000000..9a329db
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/ByteBuffer.java
@@ -0,0 +1,119 @@
+/* ByteBuffer.java
+ *
+ * created: 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.util;
+
+/**
+*
+* Buffer for appending to a byte array.
+*
+*/
+public class ByteBuffer
+{
+
+ private byte buff[];
+ private static int byteCapacity = 128;
+ private int count = 0;
+
+ /**
+ * Creates new ByteBuffer
+ */
+ public ByteBuffer()
+ {
+ }
+
+ public int size()
+ {
+ return count;
+ }
+
+ /**
+ * Appends the subarray of the <CODE>byte</CODE> array.
+ * @param b the array to be appended
+ * @param off the offset to the start of the array
+ * @param len the length of bytes to append
+ * @return a reference to this <CODE>ByteBuffer</CODE> object
+ */
+ public void append(byte b[], int off, int len)
+ {
+ int newcount = count + len;
+
+ if(buff == null || newcount > buff.length)
+ {
+ if(buff == null)
+ {
+ byteCapacity = newcount;
+ buff = new byte[byteCapacity];
+ }
+ else
+ {
+ byteCapacity = Math.max(buff.length << 1, newcount);
+ byte newbuff[] = new byte[byteCapacity];
+ System.arraycopy(buff, 0, newbuff, 0, count);
+ buff = newbuff;
+ }
+ }
+ System.arraycopy(b, off, buff, count, len);
+
+ count = newcount;
+ }
+
+ /**
+ * Appends the byte array to the end of the buffer.
+ */
+ public void append(byte b[])
+ {
+ append(b, 0, b.length);
+ }
+
+ /**
+ * Convert the string to a byte array and appends
+ * to the end of the buffer.
+ */
+ public void append(String s)
+ {
+ byte b[] = s.getBytes();
+ append(b);
+ }
+
+ /**
+ * Appends the ByteBuffer to the end of the buffer.
+ */
+ public void append(ByteBuffer newbuff)
+ {
+ append(newbuff.getBytes());
+ }
+
+ /**
+ * Get the byte[] that has been filled.
+ * @return the byte array
+ */
+ public byte[] getBytes()
+ {
+ byte[] newbuff = new byte[count];
+ System.arraycopy(buff, 0, newbuff, 0, count);
+ return newbuff;
+ }
+}
+
diff --git a/uk/ac/sanger/artemis/util/CacheHashMap.java b/uk/ac/sanger/artemis/util/CacheHashMap.java
new file mode 100644
index 0000000..8ef271c
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/CacheHashMap.java
@@ -0,0 +1,92 @@
+/* CacheHashMap.java
+ *
+ * created: 2012
+ *
+ * This file is part of Artemis
+ *
+ * Copyright(C) 2012 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or(at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ **/
+package uk.ac.sanger.artemis.util;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * Cache using a HashMap.
+ */
+public class CacheHashMap extends HashMap<Object, Object>
+{
+ private static final long serialVersionUID = 1L;
+ private final int maxSize;
+ private final int shrinkSize;
+ private final LinkedList<Object> accessList = new LinkedList<Object>();
+
+ /**
+ * When the maximum size is exceeded key/value entries are removed
+ * from the map.
+ * @param maxSize maximum size of the map
+ * @param shrinkSize number to shrink when maxSize is reached
+ */
+ public CacheHashMap(int maxSize, int shrinkSize)
+ {
+ super(maxSize);
+ this.maxSize = maxSize;
+ this.shrinkSize = shrinkSize;
+ }
+
+ public Object put(Object key, Object val)
+ {
+ if(size() >= maxSize)
+ shrink();
+ updateAccessList(key);
+ return super.put(key, val);
+ }
+
+ public Object get(Object key)
+ {
+ updateAccessList(key);
+ return super.get(key);
+ }
+
+ public Object getLastKey()
+ {
+ return accessList.getLast();
+ }
+
+ private void shrink()
+ {
+ final Iterator<Object> it = accessList.iterator();
+ for(int i = 0; i < shrinkSize; i++)
+ {
+ if(!it.hasNext())
+ return;
+ Object key = it.next();
+ this.remove(key);
+ it.remove();
+ }
+ }
+
+ private void updateAccessList(Object key)
+ {
+ int idx = accessList.indexOf(key);
+ if (idx >= 0)
+ accessList.remove(idx);
+ accessList.add(key);
+ }
+}
\ No newline at end of file
diff --git a/uk/ac/sanger/artemis/util/DatabaseDocument.java b/uk/ac/sanger/artemis/util/DatabaseDocument.java
new file mode 100644
index 0000000..6cba75d
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/DatabaseDocument.java
@@ -0,0 +1,3434 @@
+/* DatabaseDocument.java
+ *
+ * created: 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import uk.ac.sanger.artemis.Options;
+import uk.ac.sanger.artemis.io.ChadoCanonicalGene;
+import uk.ac.sanger.artemis.io.DocumentEntry;
+import uk.ac.sanger.artemis.io.GFFStreamFeature;
+import uk.ac.sanger.artemis.io.PartialSequence;
+import uk.ac.sanger.artemis.io.Range;
+import uk.ac.sanger.artemis.io.ReadFormatException;
+
+import uk.ac.sanger.artemis.chado.ArtemisUtils;
+import uk.ac.sanger.artemis.chado.ChadoCvTermView;
+import uk.ac.sanger.artemis.chado.ChadoTransactionManager;
+import uk.ac.sanger.artemis.chado.FeatureForUpdatingResidues;
+import uk.ac.sanger.artemis.chado.IBatisDAO;
+import uk.ac.sanger.artemis.chado.JdbcDAO;
+import uk.ac.sanger.artemis.chado.GmodDAO;
+import uk.ac.sanger.artemis.chado.ChadoTransaction;
+import uk.ac.sanger.artemis.components.database.DatabaseEntrySource;
+import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
+import uk.ac.sanger.artemis.components.Splash;
+import uk.ac.sanger.artemis.util.DatabaseLocationParser;
+
+import org.gmod.schema.sequence.Feature;
+import org.gmod.schema.sequence.FeatureProp;
+import org.gmod.schema.sequence.FeatureLoc;
+import org.gmod.schema.sequence.FeaturePub;
+import org.gmod.schema.sequence.FeatureRelationship;
+import org.gmod.schema.sequence.FeatureSynonym;
+import org.gmod.schema.sequence.FeatureCvTerm;
+import org.gmod.schema.sequence.FeatureCvTermProp;
+import org.gmod.schema.sequence.FeatureCvTermDbXRef;
+import org.gmod.schema.sequence.FeatureCvTermPub;
+import org.gmod.schema.cv.Cv;
+import org.gmod.schema.cv.CvTerm;
+import org.gmod.schema.general.Db;
+import org.gmod.schema.general.DbXRef;
+import org.gmod.schema.organism.Organism;
+import org.gmod.schema.pub.PubDbXRef;
+import org.gmod.schema.pub.Pub;
+import org.postgresql.largeobject.LargeObjectManager;
+
+import com.ibatis.common.jdbc.SimpleDataSource;
+
+import java.sql.*;
+import java.text.SimpleDateFormat;
+import java.io.*;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Iterator;
+import java.util.Collection;
+
+import javax.swing.JOptionPane;
+import javax.swing.JPasswordField;
+
+/**
+ * Objects of this class are Documents created from a relational database.
+ */
+public class DatabaseDocument extends Document
+{
+ private String name = null;
+
+ /** source feature_id */
+ private String srcFeatureId = "1";
+
+ /** database schema */
+ private String schema = "public";
+
+ private static Hashtable<Integer, CvTerm> cvterms;
+
+ private InputStreamProgressListener progress_listener;
+
+ /** JDBC DAO */
+ private JdbcDAO jdbcDAO = null;
+
+ /** iBatis DAO */
+ private static IBatisDAO connIB = null;
+
+ private ByteBuffer[] gff_buffer;
+
+ private ByteBuffer gff_buff;
+
+ /** entries to split into - each is given a name and the features within the entry */
+ private static String[][][] TYPES =
+ {
+ { {"repeats"} , {"repeat_region", "direct_repeat"} },
+ { {"EST"} , {"EST_match", "match_part"} },
+ { {"contig+gap"}, {"contig", "gap"}}
+ };
+
+ /** true if splitting the GFF into entries */
+ private boolean splitGFFEntry;
+
+ private boolean iBatis = false;
+
+ private JPasswordField pfield;
+
+ private boolean singleSchema = true;
+
+ //private List schema_list;
+
+ private static List<String> organismNames;
+
+ private boolean gene_builder;
+
+ // include children in reading from the database
+ private boolean readChildren = true;
+
+ // range to retrieve features for
+ private Range range;
+
+ private Feature geneFeature;
+
+ private Hashtable<String, Feature> idFeatureStore;
+
+ private boolean lazyFeatureLoad = true;
+
+ public static String EXONMODEL = "exon-model";
+ public static String TRANSCRIPT = "mRNA";
+ public static boolean CHADO_INFER_CDS = false;
+
+ /** list of controlled_curation CV names */
+ private static Vector<String> cvControledCuratioNames;
+
+ private static CvTermThread cvThread;
+
+ // controlled vocabulary
+ /** controlled_curation controlled vocabulary */
+ public static String CONTROLLED_CURATION_TAG_CVNAME =
+ "CC_";
+ /** controlled vocabulary */
+ public static String PRODUCTS_TAG_CVNAME = "genedb_products";
+ public static String RILEY_TAG_CVNAME = "RILEY";
+
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(DatabaseDocument.class);
+
+
+ /**
+ *
+ * Create a new Document from a database.
+ *
+ * @param location
+ * This should be a URL string giving:
+ * jdbc:postgresql://host:port/database_name?user=username
+ *
+ */
+ public DatabaseDocument(String location, JPasswordField pfield)
+ {
+ super(location);
+ this.pfield = pfield;
+
+ if(location.indexOf('=') > -1)
+ this.schema = location.substring( location.indexOf('=')+ 1);
+
+ if(System.getProperty("ibatis") != null ||
+ System.getProperty("jdbc") == null)
+ {
+ iBatis = true;
+ System.setProperty("chado", location);
+ }
+ initMDC(this);
+ }
+
+
+ /**
+ *
+ * Create a new Document from a database.
+ *
+ * @param location
+ * This should be a URL string giving:
+ * jdbc:postgresql://host:port/database_name?user=username
+ * @param feature_id
+ * ID of a feature to be extracted.
+ *
+ */
+ public DatabaseDocument(String location, JPasswordField pfield,
+ String srcFeatureId, String schema)
+ {
+ super(location);
+ this.pfield = pfield;
+
+ this.srcFeatureId = srcFeatureId;
+ this.schema = schema;
+
+ if(System.getProperty("ibatis") != null ||
+ System.getProperty("jdbc") == null)
+ {
+ iBatis = true;
+ System.setProperty("chado", location);
+ }
+ initMDC(this);
+ }
+
+ /**
+ *
+ * Create a new Document from a database.
+ *
+ * @param location
+ * This should be a URL string giving:
+ * jdbc:postgresql://host:port/database_name?user=username
+ * @param srcFeatureId
+ * ID of a feature to be extracted.
+ * @param splitGFFEntry
+ * split into separate entries based on feature types.
+ * @param progress_listener
+ * input stream progress listener
+ *
+ */
+ public DatabaseDocument(String location, JPasswordField pfield,
+ String srcFeatureId, String schema, boolean splitGFFEntry,
+ InputStreamProgressListener progress_listener)
+ {
+ super(location);
+ this.pfield = pfield;
+ this.srcFeatureId = srcFeatureId;
+ this.schema = schema;
+ this.splitGFFEntry = splitGFFEntry;
+ this.progress_listener = progress_listener;
+ if(System.getProperty("ibatis") != null ||
+ System.getProperty("jdbc") == null)
+ {
+ iBatis = true;
+ System.setProperty("chado", location);
+ }
+
+ reset(location, schema);
+ initMDC(this);
+ }
+
+ /**
+ * Used by the gene builder to read a database entry
+ * for a single gene.
+ * @param location
+ * @param pfield
+ * @param srcFeatureId
+ * @param schema
+ * @param gene_builder
+ */
+ public DatabaseDocument(String location, JPasswordField pfield,
+ String srcFeatureId, String schema, boolean gene_builder)
+ {
+ super(location);
+ this.pfield = pfield;
+ this.srcFeatureId = srcFeatureId;
+ this.schema = schema;
+ this.gene_builder = gene_builder;
+
+ if(System.getProperty("ibatis") != null ||
+ System.getProperty("jdbc") == null)
+ {
+ iBatis = true;
+ System.setProperty("chado", location);
+ }
+ initMDC(this);
+ }
+
+ public DatabaseDocument(String location, JPasswordField pfield,
+ String srcFeatureId, String schema,
+ ByteBuffer gff_buff, String name)
+ {
+ super(location);
+ this.pfield = pfield;
+ this.srcFeatureId = srcFeatureId;
+ this.schema = schema;
+ this.gff_buff = gff_buff;
+ this.name = name;
+ if(System.getProperty("ibatis") != null ||
+ System.getProperty("jdbc") == null)
+ {
+ iBatis = true;
+ System.setProperty("chado", location);
+ }
+ initMDC(this);
+ }
+
+ /**
+ * Use another DatabaseDocument to make a new document.
+ * @param originalDocument
+ * @param srcFeatureId
+ * @param schema
+ * @param gene_builder
+ * @param region_grab
+ * @param progress_listener
+ */
+ public DatabaseDocument (final DatabaseDocument originalDocument,
+ final String schema, final Feature geneFeature,
+ final Range range,
+ final InputStreamProgressListener progress_listener)
+ {
+ this((String)originalDocument.getLocation(),
+ originalDocument.getPfield(),
+ "-1", schema, false);
+ this.progress_listener = progress_listener;
+ this.range = range;
+ this.geneFeature = geneFeature;
+ }
+
+ /**
+ * Use another DatabaseDocument to make a new document.
+ * @param originalDocument
+ * @param srcFeatureId
+ * @param schema
+ * @param gene_builder
+ * @param region_grab
+ * @param progress_listener
+ */
+ public DatabaseDocument (final DatabaseDocument originalDocument,
+ final String srcFeatureId,
+ final String schema,
+ final boolean gene_builder,
+ final InputStreamProgressListener progress_listener)
+ {
+ this((String)originalDocument.getLocation(),
+ originalDocument.getPfield(),
+ srcFeatureId, schema, gene_builder);
+ this.progress_listener = progress_listener;
+ }
+
+
+ public static void initMDC(final DatabaseDocument doc)
+ {
+ // add username & host to MDC data for logging
+ try
+ {
+ org.apache.log4j.MDC.put("username",doc.getUserName());
+ }
+ catch(NullPointerException npe)
+ {
+ org.apache.log4j.MDC.put("username",System.getProperty("user.name"));
+ }
+
+ try
+ {
+ org.apache.log4j.MDC.put("host",
+ InetAddress.getLocalHost().getHostAddress());
+ }
+ catch(Exception e) {}
+ }
+
+ public void setReadChildren(final boolean readChildren)
+ {
+ this.readChildren = readChildren;
+ }
+
+ /**
+ * Reset the schema.
+ * @param location
+ * @param schema
+ */
+ private void reset(String location, String schema)
+ {
+ this.schema = schema;
+
+ if(!location.endsWith("="+schema))
+ {
+ int index = location.lastIndexOf('=');
+ setLocation(location.substring(0,index+1) + schema);
+ if(iBatis && connIB != null)
+ {
+ try
+ {
+ connIB.close();
+ }
+ catch(SQLException e)
+ {
+ logger4j.warn(e.getMessage());
+ }
+ connIB = null;
+ }
+
+ jdbcDAO = null;
+ System.setProperty("chado", (String)getLocation());
+ logger4j.debug((String)getLocation());
+ }
+ }
+
+ /**
+ * Reset connection.
+ */
+ public void reset()
+ {
+ if(iBatis && connIB != null)
+ {
+ try
+ {
+ connIB.close();
+ }
+ catch(SQLException e)
+ {
+ logger4j.warn(e.getMessage());
+ }
+ connIB = null;
+ }
+
+ jdbcDAO = null;
+ }
+
+ /**
+ * Append a String to the Document location.
+ * @param name the name to append.
+ */
+ public Document append(String name) throws IOException
+ {
+ return new DatabaseDocument( ((String)getLocation()) + name, pfield);
+ }
+
+ /**
+ * Return the name of this Document (the last element of the Document
+ * location).
+ */
+ public String getName()
+ {
+ if(name == null)
+ {
+ int ind = ((String) getLocation()).indexOf("?");
+ String name = ((String) getLocation()).substring(0, ind);
+ ind = name.lastIndexOf("/");
+ return name.substring(ind + 1);
+ }
+ return name;
+ }
+
+
+ /**
+ * Set the name of this document.
+ */
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+
+ public DatabaseDocument createDatabaseDocument()
+ {
+ return new DatabaseDocument( (String)getLocation(), pfield,
+ srcFeatureId, schema );
+ }
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and is readable. Always returns true.
+ */
+ public boolean readable()
+ {
+ return true;
+ }
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and can be written to. Always returns false.
+ */
+ public boolean writable()
+ {
+ return true;
+ }
+
+ /**
+ * Create a new InputStream object from this Document. The contents of the
+ * Document can be read from the InputStream.
+ *
+ * @exception IOException
+ * Thrown if the Document can't be read from (for example if it
+ * doesn't exist).
+ */
+ public InputStream getInputStream() throws IOException
+ {
+ ByteArrayInputStream instream;
+
+ if(gff_buff != null)
+ {
+ instream = new ByteArrayInputStream(gff_buff.getBytes());
+ return instream;
+ }
+
+ try
+ {
+ GmodDAO dao = getDAO();
+
+ if(gene_builder)
+ {
+ // creating a gene builder
+ List<String> schemaList = new Vector<String>();
+ schemaList.add(schema);
+
+ ByteBuffer bb = getGeneFeature(srcFeatureId,
+ schemaList, dao, readChildren);
+ return new ByteArrayInputStream(bb.getBytes());
+ }
+ else if(range != null)
+ {
+ //
+ // Retrieve all features within a range
+ // List schemaList = new Vector();
+ // schemaList.add(schema);
+ final Feature srcFeature;
+ if(geneFeature != null)
+ {
+ Collection<FeatureLoc> featureLocs = geneFeature.getFeatureLocsForFeatureId();
+ Iterator<FeatureLoc> it = featureLocs.iterator();
+ final FeatureLoc featureLoc = it.next();
+
+ int srcfeatureid = featureLoc.getFeatureBySrcFeatureId().getFeatureId();
+ srcFeature = dao.getFeatureById(srcfeatureid);
+ setName(srcFeature.getUniqueName());
+ this.srcFeatureId = Integer.toString(srcfeatureid);
+ }
+ else
+ {
+ srcFeature = dao.getFeatureById(Integer.parseInt(srcFeatureId));
+ }
+
+ final ByteBuffer entryBuffer = getFeaturesInRange(srcFeature, range, dao);
+ getChadoSequence(srcFeature, entryBuffer);
+
+ return new ByteArrayInputStream(entryBuffer.getBytes());
+ }
+
+ ByteBuffer entryBuffer = new ByteBuffer();
+ try
+ {
+ entryBuffer.append("##gff-version 3\n");
+
+ ByteBuffer sequenceBuffer = new ByteBuffer();
+ if(dao instanceof IBatisDAO)
+ ((IBatisDAO) dao).startTransaction();
+
+ logger4j.debug("RETRIEVE SOURCE FEATURE FROM: "+getLocation());
+ Feature srcFeature = getChadoSequence(dao, sequenceBuffer);
+
+ entryBuffer.append("##sequence-region " + srcFeature.getUniqueName() +
+ " 1 " + srcFeature.getResidues().length + "\n");
+ gff_buffer = getGff(dao, srcFeature);
+
+ if(splitGFFEntry)
+ {
+ if(gff_buffer[0].size() > 0)
+ entryBuffer.append(gff_buffer[0]);
+ }
+ else
+ {
+ for(int i = 0; i < gff_buffer.length; i++)
+ {
+ if(gff_buffer[i].size() > 0)
+ entryBuffer.append(gff_buffer[i]);
+ }
+ }
+
+ if(dao instanceof IBatisDAO)
+ ((IBatisDAO) dao).commitTransaction();
+ entryBuffer.append(sequenceBuffer);
+ }
+ finally
+ {
+ if(dao instanceof IBatisDAO)
+ ((IBatisDAO) dao).endTransaction();
+ }
+
+ instream = new ByteArrayInputStream(entryBuffer.getBytes());
+ return instream;
+ }
+ catch(RuntimeException re)
+ {
+ JOptionPane.showMessageDialog(null, "Problems Reading...\n" +
+ re.getMessage(),
+ "Problems Reading From the Database ",
+ JOptionPane.ERROR_MESSAGE);
+
+ re.printStackTrace();
+ }
+ catch(java.sql.SQLException sqlExp)
+ {
+ JOptionPane.showMessageDialog(null, "Problems Reading...\n" +
+ sqlExp.getMessage(),
+ "Problems Reading From the Database ",
+ JOptionPane.ERROR_MESSAGE);
+
+ sqlExp.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ *
+ * Called (by DatabaseEntrySource) to retrieve all the documents for each
+ * entry created.
+ *
+ */
+ public DatabaseDocument[] getGffDocuments(String location, String id,
+ String schema)
+ {
+ int nentries = 0;
+ for(int i = 1; i < gff_buffer.length; i++)
+ {
+ if(gff_buffer[i].size() > 0)
+ nentries++;
+ }
+
+ DatabaseDocument[] new_docs = new DatabaseDocument[nentries];
+ nentries = 0;
+ for(int i = 1; i < gff_buffer.length; i++)
+ {
+ if(gff_buffer[i].size() == 0)
+ continue;
+
+ String name = TYPES[i-1][0][0];
+
+ new_docs[nentries] = new DatabaseDocument(location, pfield, id, schema,
+ gff_buffer[i], name);
+ nentries++;
+ }
+
+ return new_docs;
+ }
+
+ /**
+ * Create an array of GFF lines.
+ * @param dao the data access object
+ * @param parentFeatureID the parent identifier for the features to
+ * extract
+ * @return the <code>ByteBuffer</code> array of GFF lines
+ */
+ private ByteBuffer[] getGff(final GmodDAO dao,
+ final Feature srcFeature)
+ {
+ //final int srcfeature_id = Integer.parseInt(srcFeatureId);
+
+ logger4j.debug("BUILD GFF FEATURES");
+
+ // build srcfeature object
+ FeatureLoc featureloc = new FeatureLoc();
+ featureloc.setFeatureBySrcFeatureId(srcFeature);
+ Feature child = new Feature();
+
+ // ignore match_part (BLAST HSPs)
+ CvTerm cvTerm;
+ try
+ {
+ cvTerm = getCvTermByCvAndCvTerm("match_part", "sequence");
+ }
+ catch(NullPointerException ne)
+ {
+ cvTerm = dao.getCvTermByNameAndCvName("match_part", "sequence");
+ }
+
+ child.setFeatureLoc(featureloc);
+ child.setAnalysis(false);
+ child.setCvTerm(cvTerm);
+
+ final List<Feature> featList = dao.getFeaturesByLocatedOnFeature(child);
+ final ByteBuffer[] buffers = new ByteBuffer[TYPES.length + 1];
+ for(int i = 0; i < buffers.length; i++)
+ buffers[i] = new ByteBuffer();
+
+ ByteBuffer this_buff;
+ int feature_size = featList.size();
+ final Hashtable<String, Feature> id_store = new Hashtable<String, Feature>(feature_size);
+
+ // build feature store
+ for(int i = 0; i < feature_size; i++)
+ {
+ Feature feat = featList.get(i);
+ id_store.put(Integer.toString(feat.getFeatureId()), feat);
+ }
+
+ if(lazyFeatureLoad)
+ idFeatureStore = id_store;
+
+ // get all dbrefs & synonyms etc
+ final Hashtable<Integer, List<String>> dbxrefs;
+ final Hashtable<Integer, List<FeatureSynonym>> synonym;
+ final Hashtable<Integer, List<FeatureCvTerm>> featureCvTerms;
+ final Hashtable<Integer, List<FeatureCvTermDbXRef>> featureCvTermDbXRefs;
+ Hashtable<Integer, List<FeatureCvTermPub>> featureCvTermPubs = null;
+ final Hashtable<Integer, List<FeaturePub>> featurePubs;
+ final List<PubDbXRef> pubDbXRefs;
+
+ if(lazyFeatureLoad)
+ {
+ dbxrefs = null;
+ synonym = null;
+ featureCvTerms = null;
+ featureCvTermDbXRefs = null;
+ featureCvTermPubs = null;
+ featurePubs = null;
+ pubDbXRefs = null;
+ }
+ else
+ {
+ dbxrefs= IBatisDAO.mergeDbXRef(
+ dao.getFeatureDbXRefsBySrcFeature(srcFeature));
+ synonym = getAllFeatureSynonyms(
+ dao.getFeatureSynonymsBySrcFeature(srcFeature));
+ featureCvTerms = getFeatureCvTermsByFeature(dao,
+ dao.getFeatureCvTermsBySrcFeature(srcFeature));
+ featureCvTermDbXRefs = getFeatureCvTermDbXRef(dao,
+ dao.getFeatureCvTermDbXRefBySrcFeature(srcFeature));
+
+ try
+ {
+ featureCvTermPubs = getFeatureCvTermPub(dao,
+ dao.getFeatureCvTermPubBySrcFeature(srcFeature));
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ if(dao instanceof IBatisDAO)
+ {
+ try
+ {
+ ((IBatisDAO) dao).endTransaction();
+ ((IBatisDAO) dao).startTransaction();
+ }
+ catch(SQLException sqle){}
+ }
+ }
+ featurePubs = getFeaturePubs(dao,
+ dao.getFeaturePubsBySrcFeature(srcFeature));
+ pubDbXRefs= dao.getPubDbXRef();
+ }
+
+ // create gff byte stream
+ for(int i = 0; i < feature_size; i++)
+ {
+ // select buffer based on feature type
+ Feature feat = featList.get(i);
+ int type_id = feat.getCvTerm().getCvTermId();
+ String typeName = getCvtermName(type_id, dao, gene_builder);
+ this_buff = buffers[0];
+
+ for(int j = 0; j < TYPES.length; j++)
+ {
+ for(int k=0; k<TYPES[j][1].length; k++)
+ if(TYPES[j][1][k].equals(typeName))
+ this_buff = buffers[j+1];
+ }
+
+ chadoToGFF(feat, srcFeature.getUniqueName(),
+ dbxrefs, synonym, featureCvTerms,
+ pubDbXRefs, featureCvTermDbXRefs, featureCvTermPubs,
+ featurePubs,
+ id_store, dao,
+ feat.getFeatureLoc(), this_buff, gene_builder);
+
+ if( i%10 == 0 || i == feature_size-1)
+ progress_listener.progressMade("Read from database: " +
+ feat.getUniqueName());
+ }
+
+ return buffers;
+ }
+
+ /**
+ * Get a <code>Hashtable</code> of feature_id keys and their corresponding
+ * feature_synonym
+ *
+ */
+ private Hashtable<Integer, List<FeatureSynonym>> getAllFeatureSynonyms(final List<FeatureSynonym> list)
+ {
+ Hashtable<Integer, List<FeatureSynonym>> synonym = new Hashtable<Integer, List<FeatureSynonym>>();
+ Integer featureId;
+ List<FeatureSynonym> value;
+ FeatureSynonym alias;
+
+ for(int i=0; i<list.size(); i++)
+ {
+ alias = list.get(i);
+ featureId = new Integer(alias.getFeature().getFeatureId());
+ if(synonym.containsKey(featureId))
+ value = synonym.get(featureId);
+ else
+ value = new Vector<FeatureSynonym>();
+
+ value.add(alias);
+ synonym.put(featureId, value);
+ }
+
+ return synonym;
+ }
+
+ /**
+ * Get FeaturePub's (i.e. /literature qualifiers).
+ * @param dao
+ * @param list
+ * @return
+ */
+ private Hashtable<Integer, List<FeaturePub>> getFeaturePubs(final GmodDAO dao,
+ final List<FeaturePub> list)
+ {
+ final Hashtable<Integer, List<FeaturePub>> featurePubs = new Hashtable<Integer, List<FeaturePub>>();
+ Integer featureId;
+ List<FeaturePub> value;
+ FeaturePub featurePub;
+
+ for(int i=0; i<list.size(); i++)
+ {
+ featurePub = list.get(i);
+ featureId = new Integer(featurePub.getFeature().getFeatureId());
+ if(featurePubs.containsKey(featureId))
+ value = featurePubs.get(featureId);
+ else
+ value = new Vector<FeaturePub>();
+
+ value.add(featurePub);
+ featurePubs.put(featureId, value);
+ }
+
+ return featurePubs;
+ }
+
+ /**
+ * @param dao
+ * @param chadoFeature null if we want them all
+ * @return
+ */
+ private Hashtable<Integer, List<FeatureCvTerm>> getFeatureCvTermsByFeature(
+ final GmodDAO dao,
+ final List<FeatureCvTerm> list)
+ {
+ Hashtable<Integer, List<FeatureCvTerm>> featureCvTerms = new Hashtable<Integer, List<FeatureCvTerm>>();
+ Integer featureId;
+ List<FeatureCvTerm> value;
+ FeatureCvTerm feature_cvterm;
+
+ for(int i=0; i<list.size(); i++)
+ {
+ feature_cvterm = list.get(i);
+ featureId = new Integer(feature_cvterm.getFeature().getFeatureId());
+ if(featureCvTerms.containsKey(featureId))
+ value = featureCvTerms.get(featureId);
+ else
+ value = new Vector<FeatureCvTerm>();
+
+ value.add(feature_cvterm);
+ featureCvTerms.put(featureId, value);
+ }
+ return featureCvTerms;
+ }
+
+ /**
+ *
+ * @param dao
+ * @param chadoFeature null if we want all
+ * @return
+ */
+ private Hashtable<Integer, List<FeatureCvTermDbXRef>> getFeatureCvTermDbXRef(
+ final GmodDAO dao, final List<FeatureCvTermDbXRef> list)
+ {
+ if(list == null || list.size() == 0)
+ return null;
+
+ Integer featureCvTermDbXRefId;
+ List<FeatureCvTermDbXRef> value;
+
+ Hashtable<Integer, List<FeatureCvTermDbXRef>> featureCvTermDbXRefs =
+ new Hashtable<Integer, List<FeatureCvTermDbXRef>>(list.size());
+ for(int i=0; i<list.size(); i++)
+ {
+ FeatureCvTermDbXRef featureCvTermDbXRef =
+ list.get(i);
+
+ featureCvTermDbXRefId = new Integer(
+ featureCvTermDbXRef.getFeatureCvTerm().getFeatureCvTermId());
+
+ if(featureCvTermDbXRefs.containsKey(featureCvTermDbXRefId))
+ value = featureCvTermDbXRefs.get(featureCvTermDbXRefId);
+ else
+ value = new Vector<FeatureCvTermDbXRef>();
+
+ value.add(featureCvTermDbXRef);
+ featureCvTermDbXRefs.put(featureCvTermDbXRefId, value);
+ }
+
+ return featureCvTermDbXRefs;
+ }
+
+ private Hashtable<Integer, List<FeatureCvTermPub>> getFeatureCvTermPub(
+ final GmodDAO dao,
+ final List<FeatureCvTermPub> list)
+ {
+ if(list == null || list.size() == 0)
+ return null;
+
+ Integer featureCvTermId;
+ List<FeatureCvTermPub> value;
+
+ Hashtable<Integer, List<FeatureCvTermPub>> featureCvTermPubs =
+ new Hashtable<Integer, List<FeatureCvTermPub>>(list.size());
+ for(int i=0; i<list.size(); i++)
+ {
+ FeatureCvTermPub featureCvTermPub =
+ list.get(i);
+
+ featureCvTermId = new Integer(
+ featureCvTermPub.getFeatureCvTerm().getFeatureCvTermId());
+
+ if(featureCvTermPubs.containsKey(featureCvTermId))
+ value = featureCvTermPubs.get(featureCvTermId);
+ else
+ value = new Vector<FeatureCvTermPub>();
+
+ value.add(featureCvTermPub);
+ featureCvTermPubs.put(featureCvTermId, value);
+ }
+ return featureCvTermPubs;
+ }
+
+ /**
+ * Retrieve the features in a given range
+ * @param srcFeature
+ * @param range
+ * @param dao
+ * @return
+ */
+ private ByteBuffer getFeaturesInRange(final Feature srcFeature,
+ final Range range,
+ final GmodDAO dao)
+ {
+ ByteBuffer buff = new ByteBuffer();
+
+ logger4j.debug("GET FEATURES IN RANGE:: "+range.toString());
+ List featuresInRange = dao.getFeaturesByRange(range.getStart()-1,
+ range.getEnd(), 0, srcFeature, null);
+
+ List<Integer> featureIds = new Vector<Integer>(featuresInRange.size());
+ for(int i=0; i<featuresInRange.size(); i++)
+ {
+ Feature thisFeature = (Feature)featuresInRange.get(i);
+ featureIds.add(new Integer(thisFeature.getFeatureId()));
+ }
+
+ FeatureLoc featureLoc = new FeatureLoc();
+ featureLoc.setFmin(new Integer(range.getStart()));
+ featureLoc.setFmax(new Integer(range.getEnd()));
+ srcFeature.setFeatureLoc(featureLoc);
+
+ Hashtable<Integer, List<String>> dbxrefs = IBatisDAO.mergeDbXRef(
+ dao.getFeatureDbXRefsBySrcFeature(srcFeature));
+ Hashtable<Integer, List<FeatureSynonym>> synonym = getAllFeatureSynonyms(
+ dao.getFeatureSynonymsBySrcFeature(srcFeature));
+ Hashtable<Integer, List<FeatureCvTerm>> featureCvTerms = getFeatureCvTermsByFeature(dao,
+ dao.getFeatureCvTermsBySrcFeature(srcFeature));
+ Hashtable<Integer, List<FeatureCvTermDbXRef>> featureCvTermDbXRefs = getFeatureCvTermDbXRef(dao,
+ dao.getFeatureCvTermDbXRefBySrcFeature(srcFeature));
+ Hashtable<Integer, List<FeatureCvTermPub>> featureCvTermPubs = getFeatureCvTermPub(dao,
+ dao.getFeatureCvTermPubBySrcFeature(srcFeature));
+ Hashtable<Integer, List<FeaturePub>> featurePubs = getFeaturePubs(dao,
+ dao.getFeaturePubsBySrcFeature(srcFeature));
+
+ List<PubDbXRef> pubDbXRefs = dao.getPubDbXRef();
+
+ Hashtable<String, Feature> id_store = new Hashtable<String, Feature>(featuresInRange.size());
+
+ // build feature name store
+ for(int i = 0; i < featuresInRange.size(); i++)
+ {
+ Feature chadoFeature = (Feature)featuresInRange.get(i);
+ String featureId = Integer.toString(chadoFeature.getFeatureId());
+ id_store.put(featureId, chadoFeature);
+ }
+
+ for(int i=0; i<featuresInRange.size(); i++)
+ {
+ Feature chadoFeature = (Feature)featuresInRange.get(i);
+ id_store.put(Integer.toString(chadoFeature.getFeatureId()), chadoFeature);
+
+ chadoToGFF(chadoFeature, srcFeature.getUniqueName(), dbxrefs, synonym, featureCvTerms,
+ pubDbXRefs, featureCvTermDbXRefs, featureCvTermPubs, featurePubs,
+ id_store, dao, chadoFeature.getFeatureLoc(), buff, gene_builder);
+ if( i%10 == 0 || i == featuresInRange.size()-1)
+ progress_listener.progressMade("Read from database: " +
+ chadoFeature.getUniqueName());
+ }
+ return buff;
+ }
+
+ /**
+ * Use by the gene editor to retrieve the gene and related
+ * features
+ * @param search_gene gene uniquename
+ * @param schema_search schema list to search
+ * @param dao data access method
+ * @return GFF byte buffer
+ * @throws SQLException
+ * @throws ReadFormatException
+ * @throws ConnectException
+ */
+ private ByteBuffer getGeneFeature(final String search_gene,
+ final List<String> schema_search,
+ GmodDAO dao,
+ final boolean readChildren)
+ throws SQLException, ReadFormatException, ConnectException, IOException
+ {
+ CvTermThread cvThread = null;
+ if(DatabaseDocument.cvterms == null)
+ {
+ cvThread = new CvTermThread(dao);
+ cvThread.start();
+ }
+
+ final Hashtable<String, Feature> id_store = new Hashtable<String, Feature>();
+
+ boolean singleSchema = true;
+ final List<String> pg_schemas = dao.getSchema();
+ Iterator<String> schemasIt = pg_schemas.iterator();
+ while(schemasIt.hasNext())
+ {
+ String thisSchema = schemasIt.next();
+ if( thisSchema.equalsIgnoreCase(schema) )
+ {
+ singleSchema = false;
+ break;
+ }
+ }
+ if(singleSchema)
+ logger4j.debug("SINGLE SCHEMA");
+ else
+ reset((String)getLocation(), schema_search.get(0));
+ dao = getDAO();
+
+ List<Feature> features = dao.getFeaturesByUniqueName(search_gene);
+ if(features == null || features.size() == 0)
+ throw new IOException();
+ Feature chadoFeature = features.get(0);
+
+ ChadoCanonicalGene chado_gene = new ChadoCanonicalGene();
+ id_store.put(Integer.toString(chadoFeature.getFeatureId()),
+ chadoFeature);
+
+ List<FeatureLoc> featurelocs = new Vector<FeatureLoc>(chadoFeature.getFeatureLocsForFeatureId());
+ FeatureLoc featureloc = featurelocs.get(0);
+ int src_id = featureloc.getSrcFeatureId();
+ srcFeatureId = Integer.toString(src_id);
+
+ Feature parent = new Feature();
+ parent.setFeatureId(src_id);
+
+ logger4j.debug("GET PARENT FEATURE");
+ parent = dao.getLazyFeatureNoResiduesById(new Integer(src_id));
+
+ chado_gene.setSeqlen(parent.getSeqLen());
+ chado_gene.setSrcfeature_id(src_id);
+
+ final ByteBuffer buff = new ByteBuffer();
+
+ logger4j.debug("BUILD GENE GFF LINE");
+ buildGffLineFromId(dao, chadoFeature.getFeatureId(),
+ id_store, parent.getUniqueName(), src_id, buff, chadoFeature);
+
+
+ if(!readChildren)
+ {
+ logger4j.debug( new String(buff.getBytes()) );
+ return buff;
+ }
+
+ // get children of gene
+ List<FeatureRelationship> relations = new Vector<FeatureRelationship>(chadoFeature.getFeatureRelationshipsForObjectId());
+ Set<Integer> idsSeen = new HashSet<Integer>();
+ for(int i = 0; i < relations.size(); i++)
+ {
+ //Feature transcript = new Feature();
+ int id = relations.get(i).getFeatureBySubjectId().getFeatureId();
+ Integer idInt = new Integer(id);
+ if(idsSeen.contains(idInt))
+ continue;
+ idsSeen.add(idInt);
+ Feature transcript = buildGffLineFromId(dao, id, id_store, parent.getUniqueName(),
+ src_id, buff, null);
+
+ if( transcript == null || transcript.getCvTerm() == null ||
+ transcript.getCvTerm().getName() == null ||
+ (transcript.getCvTerm().getName().indexOf("RNA") < 0 &&
+ transcript.getCvTerm().getName().indexOf("transcript") < 0 ) )
+ continue;
+ // get children of transcript - exons and pp
+ logger4j.debug("GET CHILDREN OF "+transcript.getName());
+ List<FeatureRelationship> transcipt_relations = new Vector<FeatureRelationship>(
+ transcript.getFeatureRelationshipsForObjectId());
+
+ for(int j = 0; j < transcipt_relations.size(); j++)
+ {
+ id = transcipt_relations.get(j).getFeatureBySubjectId().getFeatureId();
+
+ buildGffLineFromId(dao, id, id_store, parent.getUniqueName(),
+ src_id, buff, null);
+ }
+ }
+
+ logger4j.debug( "GFF:\n"+new String(buff.getBytes()) );
+
+ // now wait for cvterm to be loaded
+ if(cvThread != null)
+ {
+ while(cvThread.isAlive())
+ try
+ {
+ Thread.sleep(10);
+ }
+ catch(InterruptedException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ return buff;
+ }
+
+ /**
+ * @param dao
+ * @param featureId
+ * @param id_store
+ * @param parentName
+ * @param srcFeatureId
+ * @param this_buff
+ * @param chadoFeature
+ * @return
+ */
+ private Feature buildGffLineFromId(final GmodDAO dao,
+ final int featureId,
+ final Hashtable<String, Feature> id_store,
+ final String parentName,
+ final int srcFeatureId,
+ final ByteBuffer this_buff,
+ Feature chadoFeature)
+ {
+ if(chadoFeature == null)
+ chadoFeature = (Feature)dao.getFeatureById(featureId);
+
+ id_store.put(Integer.toString(chadoFeature.getFeatureId()),
+ chadoFeature);
+
+ final FeatureLoc loc = getFeatureLoc(new Vector(
+ chadoFeature.getFeatureLocsForFeatureId()), srcFeatureId);
+
+ if(loc == null)
+ {
+ logger4j.debug("FEATURELOC NOT FOUND :: "+chadoFeature.getUniqueName());
+ return null;
+ }
+ final Hashtable<Integer, List<String>> dbxrefs = IBatisDAO.mergeDbXRef(
+ dao.getFeatureDbXRefsByFeatureUniquename(chadoFeature.getUniqueName()));
+
+ final Hashtable<Integer, List<FeatureSynonym>> synonym = getAllFeatureSynonyms(
+ dao.getFeatureSynonymsByFeatureUniquename(chadoFeature.getUniqueName()));
+
+ final Hashtable<Integer, List<FeatureCvTerm>> featureCvTerms = getFeatureCvTermsByFeature(dao,
+ dao.getFeatureCvTermsByFeature(chadoFeature));
+
+ final Hashtable<Integer, List<FeatureCvTermDbXRef>> featureCvTermDbXRefs = getFeatureCvTermDbXRef(dao,
+ dao.getFeatureCvTermDbXRefByFeature(chadoFeature));
+
+ Hashtable<Integer, List<FeatureCvTermPub>> featureCvTermPubs = null;
+
+ try
+ {
+ featureCvTermPubs = getFeatureCvTermPub(dao,
+ dao.getFeatureCvTermPubByFeature(chadoFeature));
+ }
+ catch(RuntimeException re){re.printStackTrace();}
+
+ final Hashtable<Integer, List<FeaturePub>> featurePubs = getFeaturePubs(dao,
+ dao.getFeaturePubsByFeature(chadoFeature));
+ List<PubDbXRef> pubDbXRefs= new Vector<PubDbXRef>(); //dao.getPubDbXRef();
+ chadoToGFF(chadoFeature, parentName, dbxrefs, synonym, featureCvTerms,
+ pubDbXRefs, featureCvTermDbXRefs, featureCvTermPubs, featurePubs,
+ id_store, dao, loc, this_buff, gene_builder);
+ return chadoFeature;
+ }
+
+ /**
+ * Convert the chado feature into a GFF line
+ * @param feat Chado feature
+ * @param parentFeature parent of this feature
+ * @param dbxrefs hashtable containing dbxrefs
+ * @param synonym hashtable containing synonynms
+ * @param featureCvTerms
+ * @param pubDbXRefs
+ * @param featureCvTermDbXRefs
+ * @param id_store id store for looking up parent names
+ * @param dao chado data access
+ * @param featureloc feature location for this chado feature
+ * @param this_buff byte buffer of GFF line
+ */
+ private static void chadoToGFF(
+ final Feature feat,
+ final String parentFeature,
+ final Hashtable<Integer, List<String>> dbxrefs,
+ final Hashtable<Integer, List<FeatureSynonym>> synonym,
+ final Hashtable<Integer, List<FeatureCvTerm>> featureCvTerms,
+ final List<PubDbXRef> pubDbXRefs,
+ final Hashtable<Integer, List<FeatureCvTermDbXRef>> featureCvTermDbXRefs,
+ final Hashtable<Integer, List<FeatureCvTermPub>> featureCvTermPubs,
+ final Hashtable<Integer, List<FeaturePub>> featurePubs,
+ final Hashtable<String, Feature> id_store,
+ final GmodDAO dao,
+ final FeatureLoc featureloc,
+ final ByteBuffer this_buff,
+ final boolean gene_builder)
+ {
+ String gff_source = null;
+
+ final int fmin = featureloc.getFmin().intValue() + 1;
+ final int fmax = featureloc.getFmax().intValue();
+ final int type_id = feat.getCvTerm().getCvTermId();
+ final Short strand = featureloc.getStrand();
+ final Integer phase = featureloc.getPhase();
+ final String name = feat.getUniqueName();
+ final String typeName = getCvtermName(type_id, dao, gene_builder);
+ final Integer featureId = new Integer(feat.getFeatureId());
+ final String timelastmodified = Long.toString(feat.getTimeLastModified().getTime());
+
+ String parent_id = null;
+ String parent_relationship = null;
+ int rank = -1;
+/* if(feat.getFeatureRelationship() != null)
+ {
+ FeatureRelationship feat_relationship = feat.getFeatureRelationship();
+ parent_id = Integer.toString(feat_relationship.getFeatureByObjectId().getFeatureId());
+ long parent_type_id = feat_relationship.getCvTerm().getCvTermId();
+
+ parent_relationship = feat_relationship.getCvTerm().getName();
+
+ rank= feat_relationship.getRank();
+ if(parent_relationship == null)
+ parent_relationship = getCvtermName(parent_type_id, dao);
+ }
+ else */
+
+ ByteBuffer clusterOrthoParalog = null;
+ if(feat.getFeatureRelationshipsForSubjectId() != null)
+ {
+ Collection<FeatureRelationship> relations = feat.getFeatureRelationshipsForSubjectId();
+ Iterator<FeatureRelationship> it = relations.iterator();
+ Set<Integer> featureRelationshipIds = new HashSet<Integer>();
+ //Set duplicates = new HashSet();
+
+ while(it.hasNext())
+ {
+ final FeatureRelationship fr = it.next();
+ final Integer featureRelationShipId = new Integer( fr.getFeatureRelationshipId() );
+
+ if(featureRelationshipIds.contains( featureRelationShipId ))
+ continue;
+
+ featureRelationshipIds.add(featureRelationShipId);
+ final String cvTermName;
+ if( fr.getCvTerm().getName() == null )
+ {
+ int parent_type_id = fr.getCvTerm().getCvTermId();
+ cvTermName = getCvtermName(parent_type_id, dao, gene_builder);
+ }
+ else
+ cvTermName = fr.getCvTerm().getName();
+
+ if(cvTermName.equals("derives_from") || cvTermName.equals("part_of") ||
+ cvTermName.equals("proper_part_of") ||
+ cvTermName.equals("partof") || cvTermName.equals("producedby")) // flybase
+ {
+ parent_relationship = cvTermName;
+ parent_id = Integer.toString(fr.getFeatureByObjectId().getFeatureId());
+ rank = fr.getRank();
+ }
+ else
+ {
+ if(clusterOrthoParalog == null)
+ clusterOrthoParalog = new ByteBuffer();
+ // ortholog/paralog/cluster data
+ int orthologueFeature = fr.getFeatureByObjectId().getFeatureId();
+ clusterOrthoParalog.append(cvTermName+"="+
+ GFFStreamFeature.encode("object_id="+orthologueFeature+"; rank="+fr.getRank())+";");
+ }
+ }
+ }
+
+ // look up parent name
+ if(parent_id != null && id_store != null && id_store.containsKey(parent_id))
+ parent_id = id_store.get(parent_id).getUniqueName();
+
+ // make gff format
+
+ Vector<String> dbxref = null;
+ // append dbxrefs
+ if(dbxrefs != null &&
+ dbxrefs.containsKey(featureId))
+ {
+ dbxref = (Vector<String>)dbxrefs.get(featureId);
+ for(int j=0; j<dbxref.size(); j++)
+ {
+ if(dbxref.get(j).startsWith("GFF_source:"))
+ {
+ gff_source = dbxref.get(j).substring(11);
+ dbxref.removeElementAt(j);
+ }
+ }
+ }
+
+ this_buff.append(parentFeature + "\t"); // seqid
+
+ if(gff_source != null)
+ this_buff.append(gff_source+"\t"); // source
+ else
+ this_buff.append("chado\t");
+
+ if(typeName.equals("exon"))
+ this_buff.append(EXONMODEL + "\t"); // type
+ else
+ this_buff.append(typeName + "\t"); // type
+ this_buff.append(fmin + "\t"); // start
+ this_buff.append(fmax + "\t"); // end
+ this_buff.append(".\t"); // score
+ if(strand.equals( new Short((short)-1)) ) // strand
+ this_buff.append("-\t");
+ else if(strand.equals( new Short((short)1)) )
+ this_buff.append("+\t");
+ else
+ this_buff.append(".\t");
+
+ if(phase == null)
+ this_buff.append(".\t"); // phase
+ else
+ this_buff.append(phase+"\t");
+
+ this_buff.append("ID=" + name + ";");
+ this_buff.append("feature_id=" + featureId.toString() + ";");
+
+ if(feat.getName() != null)
+ this_buff.append("Name=" + feat.getName() + ";");
+
+ if(parent_id != null && !parent_id.equals("0"))
+ {
+ if(parent_relationship.equals("derives_from"))
+ this_buff.append("Derives_from=" + parent_id + ";");
+ else
+ this_buff.append("Parent=" + parent_id + ";");
+ }
+
+ this_buff.append("timelastmodified=" + timelastmodified + ";");
+ this_buff.append("isObsolete=" + Boolean.toString(feat.isObsolete()) + ";");
+
+ if(featureloc.isFminPartial())
+ this_buff.append("Start_range=.,.;");
+ if(featureloc.isFmaxPartial())
+ this_buff.append("End_range=.,.;");
+ // this is the chado feature_relationship.rank used
+ // to order joined features e.g. exons
+ if(rank > -1)
+ this_buff.append("feature_relationship_rank="+rank+";");
+
+ //this_buff.append("feature_id="+feature_id+";");
+
+ // attributes
+ if(feat.getFeatureProps() != null &&
+ feat.getFeatureProps().size() > 0)
+ {
+ Collection<FeatureProp> featureprops = feat.getFeatureProps();
+ for(FeatureProp featprop : featureprops)
+ {
+ String qualifier_name = getCvtermName(featprop.getCvTerm().getCvTermId(), dao, gene_builder);
+ if(qualifier_name == null)
+ continue;
+ if(featprop.getValue() != null)
+ this_buff.append(GFFStreamFeature.encode(qualifier_name)+ "=" +
+ GFFStreamFeature.encode(featprop.getValue())+";");
+ else
+ this_buff.append(GFFStreamFeature.encode(qualifier_name)+";");
+ }
+ }
+
+ if(clusterOrthoParalog != null)
+ this_buff.append(clusterOrthoParalog);
+
+ // append dbxrefs
+ boolean foundPrimaryDbXRef = false;
+ if(feat.getDbXRef() != null)
+ {
+ this_buff.append("Dbxref=");
+ this_buff.append(GFFStreamFeature.encode(
+ feat.getDbXRef().getDb().getName()+":"+feat.getDbXRef().getAccession()));
+ foundPrimaryDbXRef = true;
+ if(dbxref == null || dbxref.size() == 0)
+ this_buff.append(";");
+ }
+
+ if(dbxref != null && dbxref.size() > 0)
+ {
+ if(foundPrimaryDbXRef)
+ this_buff.append(",");
+ else
+ this_buff.append("Dbxref=");
+ for(int j=0; j<dbxref.size(); j++)
+ {
+ this_buff.append(GFFStreamFeature.encode(dbxref.get(j)));
+ if(j<dbxref.size()-1)
+ this_buff.append(",");
+ }
+ this_buff.append(";");
+ }
+
+ // append synonyms
+ if(synonym != null &&
+ synonym.containsKey(featureId))
+ {
+ List<FeatureSynonym> v_synonyms = synonym.get(featureId);
+ for(FeatureSynonym alias: v_synonyms)
+ {
+ this_buff.append( getCvtermName(alias.getSynonym().getCvTerm().getCvTermId(), dao, gene_builder) + "=" );
+ //this_buff.append(alias.getSynonym().getCvterm().getName()+"=");
+ this_buff.append(alias.getSynonym().getName());
+
+ if(!alias.isCurrent())
+ this_buff.append(GFFStreamFeature.encode(";current=false"));
+
+ //if(j<v_synonyms.size()-1)
+ this_buff.append(";");
+ }
+ }
+
+ // /literature
+ if(featurePubs != null &&
+ featurePubs.containsKey(featureId))
+ {
+ List<FeaturePub> v_featurePubs = featurePubs.get(featureId);
+ for(FeaturePub featurePub: v_featurePubs)
+ {
+ this_buff.append( "literature=" );
+ this_buff.append(featurePub.getPub().getUniqueName());
+ this_buff.append(";");
+ }
+ }
+
+ // GO, controlled_curation, product
+ if(featureCvTerms != null &&
+ featureCvTerms.containsKey(featureId))
+ {
+ List<FeatureCvTerm> v_feature_cvterms = featureCvTerms.get(featureId);
+ for(FeatureCvTerm feature_cvterm: v_feature_cvterms)
+ {
+ Integer featureCvTermId = new Integer( feature_cvterm.getFeatureCvTermId() );
+
+ List<FeatureCvTermDbXRef> featureCvTermDbXRefList = null;
+ if(featureCvTermDbXRefs != null)
+ featureCvTermDbXRefList = featureCvTermDbXRefs.get(featureCvTermId);
+
+ List<FeatureCvTermPub> featureCvTermPubList = null;
+ if(featureCvTermPubs != null)
+ featureCvTermPubList = featureCvTermPubs.get(featureCvTermId);
+
+ appendControlledVocabulary(this_buff, dao, feature_cvterm,
+ featureCvTermDbXRefList,featureCvTermPubList, pubDbXRefs, gene_builder);
+ }
+ //System.out.println(new String(this_buff.getBytes()));
+ }
+ this_buff.append("\n");
+ }
+
+ /**
+ * Appends controlled vocabulary terms to the buffer
+ * @param attr_buff
+ * @param dao
+ * @param feature_cvterm
+ * @param featureCvTermDbXRef
+ */
+ public static void appendControlledVocabulary(
+ final ByteBuffer attr_buff,
+ final GmodDAO dao,
+ final FeatureCvTerm feature_cvterm,
+ final List<FeatureCvTermDbXRef> featureCvTermDbXRefs,
+ final List<FeatureCvTermPub> featureCvTermPubs,
+ final List<PubDbXRef> pubDbXRefs,
+ final boolean gene_builder)
+ {
+ CvTerm cvterm = getCvTerm( feature_cvterm.getCvTerm().getCvTermId(), dao, gene_builder);
+ DbXRef dbXRef = feature_cvterm.getCvTerm().getDbXRef();
+
+ if(cvterm.getCv().getName().startsWith(DatabaseDocument.CONTROLLED_CURATION_TAG_CVNAME))
+ {
+ attr_buff.append("controlled_curation=");
+
+ attr_buff.append("term="+
+ GFFStreamFeature.encode(feature_cvterm.getCvTerm().getName())+"%3B");
+ attr_buff.append("cv="+
+ GFFStreamFeature.encode(feature_cvterm.getCvTerm().getCv().getName())+"%3B");
+
+ // N.B. the db_xref may be a FeatureCvTermDbXRef or a Pub for /controlled_curation
+ int nfound_dbxref = 0;
+ if(feature_cvterm.getPub().getUniqueName() != null &&
+ !feature_cvterm.getPub().getUniqueName().equalsIgnoreCase("NULL"))
+ {
+ // PMID
+ Pub pub = feature_cvterm.getPub();
+ // internal check
+ checkPubDbXRef(pubDbXRefs, pub.getPubId(), pub, feature_cvterm);
+
+ attr_buff.append("db_xref="+ pub.getUniqueName());
+ nfound_dbxref++;
+ }
+
+ if(featureCvTermPubs != null &&
+ featureCvTermPubs.size() > 0)
+ {
+ for(FeatureCvTermPub featureCvTermPub: featureCvTermPubs)
+ {
+ if(feature_cvterm.getFeatureCvTermId() !=
+ featureCvTermPub.getFeatureCvTerm().getFeatureCvTermId())
+ continue;
+
+ if(nfound_dbxref == 0)
+ attr_buff.append("db_xref=");
+ else if(nfound_dbxref > 0)
+ attr_buff.append("|");
+
+ attr_buff.append(featureCvTermPub.getPub().getUniqueName());
+ nfound_dbxref++;
+ }
+ }
+ if(nfound_dbxref > 0)
+ attr_buff.append("%3B");
+
+ if(featureCvTermDbXRefs != null &&
+ featureCvTermDbXRefs.size() > 0 )
+ {
+ int nfound = 0;
+ for(FeatureCvTermDbXRef featureCvTermDbXRef: featureCvTermDbXRefs)
+ {
+ if(feature_cvterm.getFeatureCvTermId() !=
+ featureCvTermDbXRef.getFeatureCvTerm().getFeatureCvTermId())
+ {
+ continue;
+ }
+
+ if(nfound == 0)
+ attr_buff.append("with=");
+ else if(nfound > 0)
+ attr_buff.append("|");
+
+ DbXRef fc_dbXRef = featureCvTermDbXRef.getDbXRef();
+ attr_buff.append(fc_dbXRef.getDb().getName()+":");
+ attr_buff.append(fc_dbXRef.getAccession());
+ nfound++;
+ }
+
+ if(nfound > 0)
+ attr_buff.append("%3B");
+ }
+
+ List<FeatureCvTermProp> feature_cvtermprops = (List<FeatureCvTermProp>) feature_cvterm.getFeatureCvTermProps();
+ for(int i = 0; i < feature_cvtermprops.size(); i++)
+ {
+ FeatureCvTermProp feature_cvtermprop = feature_cvtermprops.get(i);
+ attr_buff.append(getCvtermName(feature_cvtermprop.getCvTerm()
+ .getCvTermId(), dao, gene_builder));
+ attr_buff.append("=");
+ attr_buff.append(GFFStreamFeature.encode(feature_cvtermprop.getValue()));
+ if(i < feature_cvtermprops.size()-1)
+ attr_buff.append("%3B");
+ }
+
+ attr_buff.append(";");
+ }
+ else if(cvterm.getCv().getName().equals(DatabaseDocument.PRODUCTS_TAG_CVNAME))
+ {
+ attr_buff.append("product=");
+
+ // use the rank=1 to specify an alternative product where there is more than one
+ if(feature_cvterm.getRank() > 0)
+ attr_buff.append("rank="+feature_cvterm.getRank()+"%3B");
+ constructCvTermString(attr_buff, dao, feature_cvterm, featureCvTermDbXRefs,
+ featureCvTermPubs, dbXRef, false, gene_builder);
+ }
+ else if(cvterm.getCv().getName().equals(ChadoTransactionManager.HISTORY_CV))
+ {
+ attr_buff.append("history=");
+ constructCvTermString(attr_buff, dao, feature_cvterm, featureCvTermDbXRefs,
+ featureCvTermPubs, dbXRef, false, gene_builder);
+ }
+ else if(cvterm.getCv().getName().equals(DatabaseDocument.RILEY_TAG_CVNAME))
+ {
+ // class include the cvTermId as a convenience for looking up the term
+ attr_buff.append("class=");
+ attr_buff.append(dbXRef.getAccession()+"::"+
+ feature_cvterm.getCvTerm().getCvTermId()+";");
+ }
+ else
+ {
+ attr_buff.append("GO=");
+
+ if(cvterm.getCv().getName().equals("molecular_function"))
+ attr_buff.append("aspect=F%3B");
+ else if(cvterm.getCv().getName().equals("cellular_component"))
+ attr_buff.append("aspect=C%3B");
+ else if(cvterm.getCv().getName().equals("biological_process"))
+ attr_buff.append("aspect=P%3B");
+ constructCvTermString(attr_buff, dao, feature_cvterm, featureCvTermDbXRefs,
+ featureCvTermPubs, dbXRef, true, gene_builder);
+ }
+ }
+
+ /**
+ * Construct the GFF qualifier string for a FeatureCvTerm
+ * @param attr_buff
+ * @param dao
+ * @param feature_cvterm
+ * @param featureCvTermDbXRefs
+ * @param featureCvTermPubs
+ * @param dbXRef
+ * @param showDbId
+ */
+ private static void constructCvTermString(
+ final ByteBuffer attr_buff,
+ final GmodDAO dao,
+ final FeatureCvTerm feature_cvterm,
+ final List<FeatureCvTermDbXRef> featureCvTermDbXRefs,
+ final List<FeatureCvTermPub> featureCvTermPubs,
+ final DbXRef dbXRef,
+ final boolean showDbId,
+ final boolean gene_builder)
+ {
+ if(feature_cvterm.isNot())
+ attr_buff.append("qualifier=NOT%3B");
+
+ if(showDbId)
+ attr_buff.append("GOid="+dbXRef.getDb().getName() + ":"
+ + dbXRef.getAccession() + "%3B");
+
+ attr_buff.append("term="+
+ GFFStreamFeature.encode(feature_cvterm.getCvTerm().getName())+"%3B");
+
+ // PMID
+ int nfound_pub = 0;
+ if(feature_cvterm.getPub() != null &&
+ feature_cvterm.getPub().getUniqueName() != null &&
+ !feature_cvterm.getPub().getUniqueName().equalsIgnoreCase("NULL"))
+ {
+ Pub pub = feature_cvterm.getPub();
+ attr_buff.append("db_xref="+
+ pub.getUniqueName());
+ nfound_pub++;
+ }
+
+ if(featureCvTermPubs != null &&
+ featureCvTermPubs.size() > 0)
+ {
+ for(FeatureCvTermPub featureCvTermPub: featureCvTermPubs)
+ {
+ if(feature_cvterm.getFeatureCvTermId() !=
+ featureCvTermPub.getFeatureCvTerm().getFeatureCvTermId())
+ continue;
+
+ if(nfound_pub == 0)
+ attr_buff.append("db_xref=");
+ else if(nfound_pub > 0)
+ attr_buff.append("|");
+
+ attr_buff.append(featureCvTermPub.getPub().getUniqueName());
+ nfound_pub++;
+ }
+ }
+
+ if(nfound_pub > 0)
+ attr_buff.append("%3B");
+
+ if(featureCvTermDbXRefs != null &&
+ featureCvTermDbXRefs.size() > 0 )
+ {
+ int nfound = 0;
+ for(FeatureCvTermDbXRef featureCvTermDbXRef : featureCvTermDbXRefs)
+ {
+ if(feature_cvterm.getFeatureCvTermId() !=
+ featureCvTermDbXRef.getFeatureCvTerm().getFeatureCvTermId())
+ {
+ continue;
+ }
+
+ if(nfound == 0)
+ attr_buff.append("with=");
+ else if(nfound > 0)
+ attr_buff.append("|");
+
+ DbXRef fc_dbXRef = featureCvTermDbXRef.getDbXRef();
+ attr_buff.append(fc_dbXRef.getDb().getName()+":");
+ attr_buff.append(fc_dbXRef.getAccession());
+ nfound++;
+ }
+
+ if(nfound > 0)
+ attr_buff.append("%3B");
+
+ }
+
+ List<FeatureCvTermProp> feature_cvtermprops = (List<FeatureCvTermProp>)feature_cvterm.getFeatureCvTermProps();
+ for(int i = 0; i < feature_cvtermprops.size(); i++)
+ {
+ FeatureCvTermProp feature_cvtermprop = feature_cvtermprops.get(i);
+ if(feature_cvtermprop.getValue() == null)
+ continue;
+
+ attr_buff.append(getCvtermName(feature_cvtermprop.getCvTerm()
+ .getCvTermId(), dao, gene_builder));
+ attr_buff.append("=");
+ attr_buff.append(GFFStreamFeature.encode(feature_cvtermprop.getValue()));
+ if(i < feature_cvtermprops.size()-1)
+ attr_buff.append("%3B");
+ }
+
+ attr_buff.append(";");
+ }
+
+ /**
+ * Check the PubDbXref contains the Pub in FeatureCvTerm
+ * @param pubDbXRefs
+ * @param pubId
+ * @param pub
+ * @param feature_cvterm
+ */
+ private static void checkPubDbXRef(final List<PubDbXRef> pubDbXRefs, final int pubId,
+ final Pub pub, final FeatureCvTerm feature_cvterm)
+ {
+ PubDbXRef pubDbXRef = null;
+ for(int i = 0; i < pubDbXRefs.size(); i++)
+ {
+ pubDbXRef = pubDbXRefs.get(i);
+ if(pubDbXRef.getPub().getPubId() == pubId)
+ {
+ DbXRef dbxref = pubDbXRef.getDbXRef();
+ Splash.logger4j.debug("Checking PubDbXRef and found Pub "+dbxref.getDb().getName()+
+ ":"+dbxref.getAccession());
+ break;
+ }
+ }
+
+ if(pubDbXRef == null ||
+ !pub.getUniqueName().endsWith(pubDbXRef.getDbXRef().getAccession()))
+ {
+ Splash.logger4j.debug("Checking PubDbXRef and not found Pub "+
+ feature_cvterm.getPub().getUniqueName());
+ }
+ }
+
+ /**
+ * Look up the cvterm_id for a controlled vocabulary name.
+ * @param name
+ * @return
+ */
+ public static Integer getCvtermID(final String name)
+ {
+ Enumeration<Integer> enum_cvterm = cvterms.keys();
+ while(enum_cvterm.hasMoreElements())
+ {
+ Integer key = enum_cvterm.nextElement();
+ if(name.equalsIgnoreCase( cvterms.get(key).getName() ))
+ return key;
+ }
+ return null;
+ }
+
+ /**
+ * Look up a cvterm name from the collection of cvterms.
+ * @param id a cvterm_id
+ * @return the cvterm name
+ */
+ private static String getCvtermName(final int id,
+ final GmodDAO dao,
+ final boolean gene_builder)
+ {
+ if(gene_builder)
+ return dao.getCvTermById(id).getName();
+
+ return getCvTerm(id, dao, gene_builder).getName();
+ }
+
+ private static CvTerm getCvTerm(final int id,
+ final GmodDAO dao,
+ final boolean gene_builder)
+ {
+ if(gene_builder)
+ return dao.getCvTermById(id);
+ if(cvterms == null)
+ getCvterms(dao);
+
+ return cvterms.get(new Integer(id));
+ }
+
+ /**
+ * Use the CvTerm name to return a CvTerm.
+ * @param cvTermId
+ * @return
+ */
+ public static CvTerm getCvTermByCvTermName(final String cvterm_name)
+ {
+ Enumeration<CvTerm> enum_cvterm = cvterms.elements();
+ while(enum_cvterm.hasMoreElements())
+ {
+ CvTerm cvterm = enum_cvterm.nextElement();
+ if(cvterm_name.equalsIgnoreCase( cvterm.getName() ))
+ return cvterm;
+ }
+
+ return null;
+ }
+
+ /**
+ * Use the CvTermId to return a CvTerm.
+ * @param cvTermId
+ * @return
+ */
+ public static CvTerm getCvTermByCvTermId(final int cvTermId,
+ final uk.ac.sanger.artemis.io.Feature feature)
+ {
+ if(cvterms == null)
+ {
+ try
+ {
+ DatabaseDocument doc =
+ (DatabaseDocument) ((DocumentEntry)feature.getEntry()).getDocument();
+ return doc.getDAO().getCvTermById(cvTermId);
+ }
+ catch(ConnectException e)
+ {
+ logger4j.warn(e.getMessage());
+ }
+ catch(SQLException e)
+ {
+ logger4j.warn(e.getMessage());
+ }
+ }
+
+ Enumeration<CvTerm> enum_cvterm = cvterms.elements();
+ while(enum_cvterm.hasMoreElements())
+ {
+ CvTerm cvterm = enum_cvterm.nextElement();
+ if(cvterm.getCvTermId() == cvTermId)
+ return cvterm;
+ }
+
+ return null;
+ }
+
+ /**
+ * Use the Cv name and CvTerm name to return a CvTerm.
+ * @param cvterm_name
+ * @param cvName
+ * @return
+ */
+ public static CvTerm getCvTermByCvAndCvTerm(final String cvterm_name,
+ final String cvName)
+ {
+ final Enumeration<CvTerm> enum_cvterm = cvterms.elements();
+ while(enum_cvterm.hasMoreElements())
+ {
+ final CvTerm cvterm = enum_cvterm.nextElement();
+ if(cvName.equals( cvterm.getCv().getName() ) &&
+ cvterm_name.equals( cvterm.getName() ))
+ return cvterm;
+ }
+ return null;
+ }
+
+ /**
+ * This is different from getCvTermByCvAndCvTerm as it makes sure
+ * the Cv name part matches the name supplied to the function, i.e.
+ * by matching just the start.
+ * @param cvterm_name
+ * @param cvName
+ * @return
+ */
+ public static CvTerm getCvTermByCvPartAndCvTerm(final String cvterm_name,
+ final String cvName)
+ {
+ Enumeration<CvTerm> enum_cvterm = cvterms.elements();
+ while(enum_cvterm.hasMoreElements())
+ {
+ CvTerm cvterm = enum_cvterm.nextElement();
+ if(cvterm.getCv().getName().startsWith( cvName ) &&
+ cvterm_name.equals( cvterm.getName() ))
+ return cvterm;
+ }
+ return null;
+ }
+ /**
+ * Look up cvterms names and id and return in a hashtable.
+ * @param dao the data access object
+ * @return the cvterm <code>Hashtable</code>
+ */
+ private static Hashtable<Integer, CvTerm> getCvterms(final GmodDAO dao)
+ {
+ try
+ {
+ final List<CvTerm> cvterm_list = dao.getCvTerms();
+ final Iterator<CvTerm> it = cvterm_list.iterator();
+ cvterms = new Hashtable<Integer, CvTerm>(cvterm_list.size());
+
+ while(it.hasNext())
+ {
+ final CvTerm cvterm = it.next();
+ cvterms.put(new Integer(cvterm.getCvTermId()), cvterm);
+ }
+ }
+ catch(RuntimeException sqle)
+ {
+ System.err.println("SQLException retrieving CvTerms");
+ System.err.println(sqle);
+ }
+ logger4j.debug("LOADED CvTerms");
+
+ return cvterms;
+ }
+
+ /**
+ * Get CvTerm's in a given CV
+ * @param cvName
+ * @return
+ */
+ public Vector getCvTermsByCvName(final String cvName)
+ {
+ if(cvterms == null)
+ {
+ logger4j.debug("getCvTermsByCvName LOADING CVTERMS");
+ getCvterms(getDAOOnly());
+ }
+
+ return getCvterms("", cvName, false);
+ }
+
+ public List<String> getDatabaseNames()
+ {
+ GmodDAO dao = getDAOOnly();
+ List dbs = dao.getDbs();
+ List<String> names = new Vector<String>();
+ Iterator it = dbs.iterator();
+ while(it.hasNext())
+ {
+ Db db = (Db)it.next();
+ names.add(db.getName());
+ }
+ return names;
+ }
+
+ public Organism getOrganismByCommonName(final String commonName)
+ {
+ GmodDAO dao = getDAOOnly();
+ return dao.getOrganismByCommonName(commonName);
+ }
+
+ public List<String> getOrganismNames()
+ {
+ if(organismNames != null && organismNames.size() > 0)
+ return organismNames;
+
+ GmodDAO dao = getDAOOnly();
+ List organisms = dao.getOrganisms();
+ organismNames = new Vector<String>();
+ Iterator it = organisms.iterator();
+ while(it.hasNext())
+ {
+ Organism organism = (Organism)it.next();
+ organismNames.add(organism.getCommonName());
+ }
+ return organismNames;
+ }
+
+ public static Vector<CvTerm> getCvterms(final String search_str,
+ final String cv_name,
+ final boolean ignoreCase)
+ {
+ final Vector<CvTerm> cvterm_match = new Vector<CvTerm>();
+ Enumeration<Integer> enum_cvterm = cvterms.keys();
+ while(enum_cvterm.hasMoreElements())
+ {
+ Integer key = enum_cvterm.nextElement();
+ CvTerm cvterm = cvterms.get(key);
+
+ if(cvterm.getCv().getName().startsWith(cv_name))
+ {
+ if(ignoreCase)
+ {
+ if(indexOfIgnoreCase(cvterm.getName(),search_str,0) > -1)
+ cvterm_match.add(cvterm);
+ }
+ else
+ {
+ if(cvterm.getName().indexOf(search_str) > -1)
+ cvterm_match.add(cvterm);
+ }
+ }
+ }
+ return cvterm_match;
+ }
+
+
+ public static CvTerm getCvtermFromGoId(final String goId)
+ {
+ Enumeration<Integer> enum_cvterm = cvterms.keys();
+ while (enum_cvterm.hasMoreElements())
+ {
+ Integer key = enum_cvterm.nextElement();
+ CvTerm cvTerm = cvterms.get(key);
+
+ if(cvTerm.getCv().getName().equals("molecular_function") ||
+ cvTerm.getCv().getName().equals("biological_process") ||
+ cvTerm.getCv().getName().equals("cellular_component"))
+ {
+ if(cvTerm.getDbXRef().getAccession().equals(goId))
+ return cvTerm;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Similar to <code>String.indexOf(String, int)</code>, but it ignores
+ * case
+ */
+ private static int indexOfIgnoreCase(String textToSearch,
+ String pattern,
+ int fromIndex)
+ {
+ int n = pattern.length();
+ while(textToSearch.length() > ((fromIndex + n) - 1))
+ {
+ if(textToSearch.regionMatches(true, fromIndex, pattern, 0, n))
+ return fromIndex;
+ fromIndex++;
+ }
+ return -1;
+ }
+
+
+ /**
+ * Get a list of the CV names
+ *
+ * @return
+ */
+ public static List<String> getCvControledCurationNames()
+ {
+ if(cvControledCuratioNames != null)
+ return cvControledCuratioNames;
+ cvControledCuratioNames = new Vector<String>();
+ final Enumeration<CvTerm> enum_cvterm = cvterms.elements();
+ while(enum_cvterm.hasMoreElements())
+ {
+ final CvTerm cvTerm = enum_cvterm.nextElement();
+ final String cvNameStr = cvTerm.getCv().getName();
+
+ if(cvNameStr.startsWith(DatabaseDocument.CONTROLLED_CURATION_TAG_CVNAME) &&
+ !cvControledCuratioNames.contains(cvNameStr))
+ cvControledCuratioNames.add(cvNameStr);
+ }
+
+ return cvControledCuratioNames;
+ }
+
+ /**
+ * Look up synonym type names e.g. synonym, systematic_id.
+ * @return the synonym tag names
+ */
+ public static String[] getSynonymTypeNames(final String cv_name,
+ final GFFStreamFeature feature)
+ {
+ if(cvterms == null)
+ {
+ DatabaseDocument doc = (DatabaseDocument)feature.getDocumentEntry().getDocument();
+ try
+ {
+ Cv cv = new Cv();
+ cv.setName(cv_name);
+ List<CvTerm> synonymCvTerms = doc.getDAO().getCvTermByNameInCv(null, cv);
+ String synonymNames[] = new String[synonymCvTerms.size()];
+ for(int i=0; i<synonymCvTerms.size(); i++)
+ synonymNames[i] = synonymCvTerms.get(i).getName();
+
+ return synonymNames;
+ }
+ catch(ConnectException e){}
+ catch(SQLException e){}
+ }
+
+ Vector<String> synonym_names = new Vector<String>();
+ Enumeration<CvTerm> cvterm_enum = cvterms.elements();
+ while(cvterm_enum.hasMoreElements())
+ {
+ CvTerm cvterm = cvterm_enum.nextElement();
+ if(cvterm.getCv().getName().equals(cv_name))
+ synonym_names.add(cvterm.getName());
+ }
+
+ return synonym_names.toArray(
+ new String[synonym_names.size()]);
+ }
+
+ public void insertCvTerm(CvTerm cvTerm)
+ {
+ final GmodDAO dao = getDAOOnly();
+ dao.persist(cvTerm);
+ cvTerm = dao.getCvTermByNameAndCvName(cvTerm.getName(), cvTerm.getCv().getName());
+ cvterms.put(new Integer(cvTerm.getCvTermId()), cvTerm);
+ }
+
+ /**
+ * Get the sequence for a feature.
+ * @param dao the data access object
+ * @param buff the buffer to add the sequence to
+ * @return the resulting buffer
+ * @throws java.sql.SQLException
+ */
+ private Feature getChadoSequence(GmodDAO dao, ByteBuffer buff)
+ {
+ Feature feature = dao.getFeatureById(Integer.parseInt(srcFeatureId));
+ getChadoSequence(feature, buff);
+ return feature;
+ }
+
+ /**
+ * Find the gene by the ID or synonym on the gene or transcript.
+ * @param id
+ * @return
+ */
+ public Feature getChadoGeneByAnyCurrentName(String id)
+ {
+ Feature chadoFeature =
+ (Feature)(getDAOOnly().getFeaturesByAnyCurrentName(id).get(0));
+
+ if(!chadoFeature.getCvTerm().equals("gene"))
+ {
+ Iterator<FeatureRelationship> parents =
+ chadoFeature.getFeatureRelationshipsForSubjectId().iterator();
+ while(parents.hasNext())
+ {
+ FeatureRelationship fr = parents.next();
+ Feature parent = fr.getFeatureByObjectId();
+ if(parent.getCvTerm().getName().equals("gene"))
+ chadoFeature = parent;
+ }
+ }
+ return chadoFeature;
+ }
+
+
+ /**
+ * Get the sequence for a feature.
+ * @param dao the data access object
+ * @param buff the buffer to add the sequence to
+ * @return the resulting buffer
+ * @throws java.sql.SQLException
+ */
+ private void getChadoSequence(final Feature feature, ByteBuffer buff)
+ {
+ buff.append("##FASTA\n>");
+ buff.append(feature.getUniqueName());
+ buff.append("\n");
+ buff.append(feature.getResidues());
+ }
+
+ /**
+ * Get the CDS FeatureLoc's associated with a give protein
+ * @param peptideName
+ * @return
+ */
+ public List<FeatureLoc> getCdsFeatureLocsByPeptideName(final String peptideName)
+ {
+ Feature peptideFeature = getFeatureByUniquename(peptideName);
+ Collection<FeatureRelationship> frs = peptideFeature.getFeatureRelationshipsForSubjectId();
+ Iterator<FeatureRelationship> it = frs.iterator();
+ Feature transcriptFeature = null;
+ while(it.hasNext())
+ {
+ FeatureRelationship fr = it.next();
+ if(fr.getCvTerm().getName().equalsIgnoreCase("derives_from"))
+ {
+ transcriptFeature = fr.getFeatureByObjectId();
+ logger4j.debug("TRANSCRIPT :: "+transcriptFeature.getUniqueName());
+ break;
+ }
+ }
+
+ if(transcriptFeature == null)
+ return null;
+ return getCdsFeatureLocsByTranscriptName(transcriptFeature.getUniqueName());
+ }
+
+ /**
+ * Get the CDS FeatureLoc's associated with a given transcript
+ * @param transcriptName
+ * @return
+ */
+ public List<FeatureLoc> getCdsFeatureLocsByTranscriptName(final String transcriptName)
+ {
+ Feature transcriptFeature = getFeatureByUniquename(transcriptName);
+ if(transcriptFeature == null)
+ return null;
+
+ Collection<FeatureRelationship> frs = transcriptFeature.getFeatureRelationshipsForObjectId();
+ Iterator<FeatureRelationship> it = frs.iterator();
+ List<FeatureLoc> cdsFeatureLocs = new Vector<FeatureLoc>();
+ while(it.hasNext())
+ {
+ FeatureRelationship fr = it.next();
+ org.gmod.schema.sequence.Feature child = fr.getFeatureBySubjectId();
+ if(child.getCvTerm().getName().equals("exon") ||
+ child.getCvTerm().getName().equals("pseudogenic_exon"))
+ {
+ Collection<FeatureLoc> featureLocs = child.getFeatureLocsForFeatureId();
+ Iterator<FeatureLoc> it2 = featureLocs.iterator();
+ while(it2.hasNext())
+ cdsFeatureLocs.add(it2.next());
+ }
+ }
+ Collections.sort(cdsFeatureLocs, new LocationComarator());
+ return cdsFeatureLocs;
+ }
+
+ /**
+ * Get the sequence for a feature.
+ * @param uniqueName the feature
+ * @return the resulting buffer
+ */
+ public PartialSequence getChadoSequence(final String uniqueName)
+ {
+ Feature feature = (Feature) getDAOOnly().getResiduesByUniqueName(uniqueName).get(0);
+ char[] c = getChars(feature.getResidues());
+
+ PartialSequence ps = new PartialSequence(c, feature.getSeqLen(),
+ feature.getFeatureLoc().getFmin().intValue()+1,
+ feature.getFeatureLoc().getStrand(),
+ feature.getFeatureLoc().getPhase());
+ return ps;
+ }
+
+ /**
+ * Convert byte array to char array
+ * @param b byte array
+ * @return char array
+ */
+ private char[] getChars(final byte b[])
+ {
+ char[] c = new char[b.length];
+
+ for(int i = 0; i < b.length; i++)
+ c[i] = (char)b[i];
+ return c;
+ }
+
+ /**
+ * Get the <code>List</code> of available schemas.
+ * @return the <code>List</code> of available schemas
+ */
+/* public List getSchema()
+ {
+ return schema_list;
+ }*/
+
+ public Feature getFeatureByUniquename(final String uniqueName)
+ {
+ GmodDAO dao = getDAOOnly();
+ return (Feature) dao.getFeatureByUniqueName(uniqueName, null);
+ }
+
+ /**
+ * Given a feature uniqueName return the features that are part_of
+ * that gene
+ * @param geneName
+ * @return
+ */
+ public Vector<Feature> getPartOfFeatures(final String uniqueName)
+ {
+ Feature feature = getFeatureByUniquename(uniqueName);
+ if(feature == null)
+ return null;
+
+ Collection frs = feature.getFeatureRelationshipsForObjectId();
+ Iterator it = frs.iterator();
+ Vector<Feature> partOfFeatures = new Vector<Feature>(frs.size());
+ while(it.hasNext())
+ {
+ FeatureRelationship fr = (FeatureRelationship)it.next();
+ if(fr.getCvTerm().getName().equalsIgnoreCase("part_of"))
+ partOfFeatures.add(fr.getFeatureBySubjectId());
+ }
+
+ return partOfFeatures;
+ }
+
+ /**
+ * Given a gene unique name return the poplypeptide chado features that belong
+ * to that gene
+ * @param geneName
+ * @return
+ */
+ public Vector<Feature> getPolypeptideFeatures(final String geneName)
+ {
+ Feature geneFeature = getFeatureByUniquename(geneName);
+ if(geneFeature == null)
+ return null;
+
+ Collection<FeatureRelationship> frs = geneFeature.getFeatureRelationshipsForObjectId();
+ Iterator<FeatureRelationship> it = frs.iterator();
+ List<Feature> transcripts = new Vector<Feature>(frs.size());
+ while(it.hasNext())
+ {
+ FeatureRelationship fr = it.next();
+ transcripts.add(fr.getFeatureBySubjectId());
+ }
+
+ Vector<Feature> polypep = new Vector<Feature>();
+ for(int i=0; i<transcripts.size(); i++)
+ {
+ org.gmod.schema.sequence.Feature transcript =
+ transcripts.get(i);
+ frs = transcript.getFeatureRelationshipsForObjectId();
+ it = frs.iterator();
+ while(it.hasNext())
+ {
+ FeatureRelationship fr = it.next();
+ if(fr.getCvTerm().getName().equalsIgnoreCase("derives_from"))
+ if(fr.getFeatureBySubjectId().getCvTerm().getName().equalsIgnoreCase("polypeptide"))
+ polypep.add(fr.getFeatureBySubjectId());
+ }
+ }
+ return polypep;
+ }
+
+ /**
+ * Given a gene unique name return the poplypeptides that belong
+ * to that gene
+ * @param geneName
+ * @return
+ */
+ /*public Vector getPolypeptideNames(final String geneName)
+ {
+ Vector polypeptides = getPolypeptideFeatures(geneName);
+ Vector polypeptideNames = new Vector(polypeptides.size());
+ for(int i=0; i<polypeptides.size(); i++)
+ {
+ Feature feature = (Feature)polypeptides.get(i);
+ polypeptideNames.add(feature.getUniqueName());
+ }
+ return polypeptideNames;
+ }*/
+
+ public List getClustersByFeatureIds(final List featureIds)
+ {
+ GmodDAO dao = getDAOOnly();
+ return dao.getClustersByFeatureIds(featureIds);
+ }
+
+ public List getParentFeaturesByChildFeatureIds(final List subjectIds)
+ {
+ GmodDAO dao = getDAOOnly();
+ return dao.getParentFeaturesByChildFeatureIds(subjectIds);
+ }
+
+ public List getFeatureDbXRefsByFeatureId(final List featureIds)
+ {
+ GmodDAO dao = getDAOOnly();
+ return dao.getFeatureDbXRefsByFeatureId(featureIds);
+ }
+
+
+ /**
+ * Used by SimilarityLazyQualifierValue.bulkRetrieve() to get the match features
+ * @param featureIds the <code>List</code> of feature_id's
+ * @return the corresponding features
+ */
+ public List getFeaturesByListOfIds(final List featureIds)
+ {
+ GmodDAO dao = getDAOOnly();
+ return dao.getFeaturesByListOfIds(featureIds);
+ }
+
+ public List getFeaturePropByFeatureIds(final List featureIds)
+ {
+ GmodDAO dao = getDAOOnly();
+ return dao.getFeaturePropByFeatureIds(featureIds);
+ }
+
+ public List getSimilarityMatches(List featureIds)
+ {
+ GmodDAO dao = getDAOOnly();
+ if(featureIds == null)
+ return dao.getSimilarityMatches(new Integer(srcFeatureId));
+ else
+ return dao.getSimilarityMatchesByFeatureIds(featureIds);
+ }
+
+ public List getFeatureLocsByListOfIds(List featureIds)
+ {
+ GmodDAO dao = getDAOOnly();
+ return dao.getFeatureLocsByListOfIds(featureIds);
+ }
+
+ /**
+ * Test the database connection.
+ * @throws ConnectException
+ * @throws SQLException
+ */
+ public void ping() throws ConnectException, SQLException
+ {
+ getDAO().getAllCvs();
+ }
+
+ /**
+ * Load the cvterms
+ * @throws ConnectException
+ * @throws SQLException
+ */
+ public void loadCvTerms() throws ConnectException, SQLException
+ {
+ GmodDAO dao = getDAO();
+ cvThread = new CvTermThread(dao);
+ cvThread.start();
+ }
+
+ /**
+ * Get a list of the organisms with sequences
+ * @return
+ * @throws ConnectException
+ * @throws java.sql.SQLException
+ */
+ public List<Organism> getOrganismsContainingSrcFeatures()
+ throws ConnectException, java.sql.SQLException
+ {
+ List<Organism> list = null;
+ try
+ {
+ GmodDAO dao = getDAO();
+ cvThread = new CvTermThread(dao);
+ cvThread.start();
+
+ list = dao.getOrganismsContainingSrcFeatures();
+
+ Collections.sort(list, new Comparator<Organism>()
+ {
+ public int compare(final Organism o1, final Organism o2)
+ {
+ String name1 = o1.getCommonName();
+ String name2 = o2.getCommonName();
+
+ if(name1 == null)
+ name1 = o1.getGenus() + "." + o1.getSpecies();
+
+ if(name2 == null)
+ name2 = o2.getGenus() + "." + o2.getSpecies();
+ return name1.compareToIgnoreCase( name2 );
+ }
+ });
+ }
+ catch(RuntimeException runExp)
+ {
+ runExp.printStackTrace();
+ JOptionPane.showMessageDialog(null, "Runtime Problems...\n"+
+ getLocation()+"\n"+
+ runExp.getMessage(),
+ "Runtime Error",
+ JOptionPane.ERROR_MESSAGE);
+
+ logger4j.debug(runExp.getMessage());
+ }
+ catch(ConnectException exp)
+ {
+ exp.printStackTrace();
+ JOptionPane.showMessageDialog(null, "Connection Problems...\n"+
+ exp.getMessage(),
+ "Connection Error",
+ JOptionPane.ERROR_MESSAGE);
+ logger4j.debug(exp.getMessage());
+ throw exp;
+ }
+ catch(java.sql.SQLException sqlExp)
+ {
+ sqlExp.printStackTrace();
+ JOptionPane.showMessageDialog(null, "SQL Problems....\n"+
+ getLocation()+"\n"+
+ sqlExp.getMessage(),
+ "SQL Error",
+ JOptionPane.ERROR_MESSAGE);
+ logger4j.debug(sqlExp.getMessage());
+ throw sqlExp;
+ }
+
+ return list;
+ }
+
+ public static boolean isCvThreadAlive()
+ {
+ if(cvThread == null)
+ return false;
+
+ if(cvThread.isAlive())
+ return true;
+ else
+ return false;
+ }
+
+ public List getResidueFeatures(Integer organismId)
+ {
+ return getDAOOnly().getResidueFeatures(organismId);
+ }
+
+ /**
+ *
+ */
+ public void showCvTermLookUp()
+ {
+ try
+ {
+ new ChadoCvTermView( getDAO() );
+ }
+ catch(ConnectException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ catch(SQLException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Get a list of the available graphs
+ * @return
+ */
+ public List getGraphs()
+ {
+ GmodDAO dao = getDAOOnly();
+
+ List list = dao.getTableColumns("graph");
+ if(list == null || list.size() == 0)
+ return null;
+ return dao.getGraphs(Integer.parseInt(srcFeatureId));
+ }
+
+ /**
+ * Get the graph data from the database.
+ * This uses JDBC and the org.postgresql.largeobject API.
+ * @param graphId
+ * @param name
+ * @return
+ */
+ public LargeObjectDocument getGraphData(int graphId, String name)
+ {
+ LargeObjectDocument doc = null;
+ try
+ {
+ // this causes class cast problems probably because it gets
+ // a pool connection rather than the underlying real connection
+ // Connection conn = ((SimpleDataSource)connIB.getDataSource()).getConnection();
+ SimpleDataSource ds = (SimpleDataSource)connIB.getDataSource();
+ Connection conn = DriverManager.getConnection(
+ ds.getJdbcUrl(), ds.getJdbcUsername(), ds.getJdbcPassword());
+
+
+ // All LargeObject API calls must be within a transaction
+ conn.setAutoCommit(false);
+
+ // Get the Large Object Manager to perform operations with
+ LargeObjectManager lobj =
+ ((org.postgresql.PGConnection)conn).getLargeObjectAPI();
+
+ PreparedStatement pstmt =
+ conn.prepareStatement("SELECT * FROM graph.graph WHERE graph_id=?");
+ pstmt.setInt(1, graphId);
+ ResultSet rs = pstmt.executeQuery();
+
+ if (rs != null)
+ {
+ while(rs.next())
+ {
+ // open the large object for reading
+ int oid = rs.getInt(5);
+ doc = new LargeObjectDocument(ds.getJdbcUrl(), name,
+ lobj.open(oid, LargeObjectManager.READ));
+ }
+ rs.close();
+ }
+ pstmt.close();
+ }
+ catch (SQLException e)
+ {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return doc;
+ }
+
+ /**
+ * Get the data access object (DAO).
+ * @return data access object
+ */
+ private GmodDAO getDAO()
+ throws java.net.ConnectException, SQLException
+ {
+ if(!iBatis)
+ {
+ if(jdbcDAO == null)
+ jdbcDAO = new JdbcDAO((String)getLocation(), pfield);
+ return jdbcDAO;
+ }
+ else
+ {
+ if(connIB == null)
+ {
+ System.setProperty("chado", (String)getLocation());
+ connIB = new IBatisDAO(pfield);
+ }
+ return connIB;
+ }
+ }
+
+ /**
+ * Get the username for this connection
+ * @return
+ */
+ public String getUserName()
+ {
+ // "localhost:10001/backup?chado"
+
+ String url = (String)getLocation();
+ DatabaseLocationParser dlp = new DatabaseLocationParser(url);
+
+//
+// int index = url.indexOf("?");
+//
+// String userName = url.substring(index+1).trim();
+// if(userName.startsWith("user="))
+// userName = userName.substring(5);
+//
+ return dlp.getUsername();
+ }
+
+ private GmodDAO getDAOOnly()
+ {
+ GmodDAO dao = null;
+ try
+ {
+ dao = getDAO();
+ }
+ catch(RuntimeException sqlExp)
+ {
+ JOptionPane.showMessageDialog(null, "SQL Problems...\n"+
+ sqlExp.getMessage(),
+ "SQL Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ catch(ConnectException exp)
+ {
+ JOptionPane.showMessageDialog(null, "Connection Problems...\n"+
+ exp.getMessage(),
+ "Connection Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ catch(java.sql.SQLException sqlExp)
+ {
+ JOptionPane.showMessageDialog(null, "SQL Problems....\n"+
+ sqlExp.getMessage(),
+ "SQL Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ return dao;
+ }
+
+
+ /**
+ * Create a new OutputStream object from this Document. The contents of the
+ * Document can be written from the stream.
+ *
+ * @exception IOException
+ * Thrown if the Document can't be written.
+ */
+ public OutputStream getOutputStream() throws IOException
+ {
+ final File write_file = new File(System.getProperty("user.dir")+
+ System.getProperty("file.separator")+
+ getName());
+
+ final FileOutputStream file_output_stream =
+ new FileOutputStream(write_file);
+
+ if(write_file.getName().endsWith(".gz"))
+ {
+ // assume this file should be gzipped
+ return new java.util.zip.GZIPOutputStream (file_output_stream);
+ }
+ else
+ return file_output_stream;
+ }
+
+ /**
+ * Commit the <code>ChadoTransaction</code> SQL back to the
+ * database.
+ * @param sql the collection of <code>ChadoTransaction</code> objects
+ * @return
+ */
+ public int commit(final Vector<ChadoTransaction> sql,
+ final boolean force)
+ {
+ GmodDAO dao = null;
+ int ncommit = -1;
+ final Hashtable<String, Feature> featureIdStore = new Hashtable<String, Feature>();
+ boolean useTransactions = false;
+
+ try
+ {
+ dao = getDAO();
+
+ if(!force && dao instanceof IBatisDAO)
+ useTransactions = true;
+
+ if(useTransactions)
+ {
+ ((IBatisDAO) dao).startTransaction();
+ logger4j.debug("START TRANSACTION");
+ }
+ boolean unchanged;
+
+ //
+ // check feature timestamps have not changed
+ Vector<String> names_checked = new Vector<String>();
+ for(int i = 0; i < sql.size(); i++)
+ {
+ final ChadoTransaction tsn = sql.get(i);
+ final Object uniquenames[] = getUniqueNames(tsn);
+
+ if(uniquenames == null)
+ continue;
+
+ for(int j=0; j<uniquenames.length; j++)
+ {
+ final String uniquename = (String) uniquenames[j];
+
+ if(uniquename == null || names_checked.contains(uniquename))
+ continue;
+
+ names_checked.add(uniquename);
+ final String keyName = tsn.getFeatureKey();
+
+ unchanged = checkFeatureTimestamp(schema, uniquename,
+ dao, keyName, featureIdStore, tsn);
+ if(!unchanged)
+ {
+ if(useTransactions)
+ ((IBatisDAO) dao).endTransaction();
+ return 0;
+ }
+ }
+ }
+
+ final Timestamp ts = new Timestamp(new java.util.Date().getTime());
+ //
+ // commit to database
+ for(ncommit = 0; ncommit < sql.size(); ncommit++)
+ {
+ try
+ {
+ ChadoTransaction tsn = (ChadoTransaction) sql.get(ncommit);
+ commitChadoTransaction(tsn, dao, ts);
+ }
+ catch (RuntimeException re)
+ {
+ if(!force)
+ throw re;
+ logger4j.warn(constructExceptionMessage(re, sql, ncommit));
+ logger4j.warn("NOW TRYING TO CONTINUE TO COMMIT");
+ }
+ }
+
+ //
+ // update timelastmodified timestamp
+ names_checked = new Vector<String>();
+
+
+ //
+ // add all the UPDATE Feature transactions to the names_checked list
+ // these time stamps should already have been updated
+ for(int i = 0; i < sql.size(); i++)
+ {
+ final ChadoTransaction tsn = sql.get(i);
+ if(tsn.getType() == ChadoTransaction.UPDATE &&
+ tsn.getFeatureObject() instanceof Feature)
+ {
+ final Object uniquenames[] = getUniqueNames(tsn);
+ if(uniquenames == null)
+ continue;
+
+ for(int j=0; j<uniquenames.length; j++)
+ names_checked.add((String) uniquenames[j]);
+ }
+ }
+
+
+ for(int i = 0; i < sql.size(); i++)
+ {
+ final ChadoTransaction tsn = sql.get(i);
+ final Object uniquenames[] = getUniqueNames(tsn);
+
+ if(uniquenames == null)
+ continue;
+
+ for(int j=0; j<uniquenames.length; j++)
+ {
+ final String uniquename = (String) uniquenames[j];
+ if(uniquename == null || names_checked.contains(uniquename))
+ continue;
+
+ names_checked.add(uniquename);
+
+ final Feature feature;
+
+ // retrieve from featureId store
+ if(featureIdStore != null && featureIdStore.containsKey(uniquename))
+ {
+ Feature f = featureIdStore.get(uniquename);
+
+ feature = new Feature();
+ feature.setFeatureId(f.getFeatureId());
+ feature.setUniqueName(uniquename);
+ feature.setObsolete(f.isObsolete());
+ }
+ else
+ feature = dao.getFeatureByUniqueName(uniquename,
+ tsn.getFeatureKey());
+ if(feature != null)
+ {
+ feature.setTimeLastModified(ts);
+ feature.setName("0"); // do not change name
+ dao.merge(feature);
+ }
+ }
+
+ GFFStreamFeature gff_feature = (GFFStreamFeature) tsn.getGff_feature();
+ gff_feature.setLastModified(ts);
+ }
+
+ final String nocommit = System.getProperty("nocommit");
+ if( useTransactions &&
+ (nocommit == null || nocommit.equals("false")))
+ {
+ ((IBatisDAO) dao).commitTransaction();
+ logger4j.debug("TRANSACTION COMPLETE");
+ }
+ else if(useTransactions &&
+ (nocommit != null && nocommit.equals("true")))
+ logger4j.debug("TRANSACTION NOT COMMITTED : nocommit property set to true");
+ }
+ catch (java.sql.SQLException sqlExp)
+ {
+ JOptionPane.showMessageDialog(null, "Problems Writing...\n" +
+ sqlExp.getMessage(),
+ "Problems Writing to Database ",
+ JOptionPane.ERROR_MESSAGE);
+ sqlExp.printStackTrace();
+ }
+ catch (java.net.ConnectException conn_ex)
+ {
+ JOptionPane.showMessageDialog(null, "Problems connecting..."+
+ conn_ex.getMessage(),
+ "Database Connection Error - Check Server",
+ JOptionPane.ERROR_MESSAGE);
+ conn_ex.printStackTrace();
+ }
+ catch (RuntimeException re)
+ {
+ final String msg = constructExceptionMessage(re, sql, ncommit);
+ JOptionPane.showMessageDialog(null, msg,
+ "Problems Writing to Database ",
+ JOptionPane.ERROR_MESSAGE);
+ logger4j.error(msg);
+ //re.printStackTrace();
+ }
+ finally
+ {
+ if(useTransactions)
+ try
+ {
+ ((IBatisDAO) dao).endTransaction();
+ logger4j.debug("END TRANSACTION");
+ }
+ catch(SQLException e){ e.printStackTrace(); }
+ }
+
+ if(featureIdStore != null)
+ featureIdStore.clear();
+
+ return ncommit;
+ }
+
+ /**
+ * Get the uniquenames involved in a transaction
+ * @param tsn
+ * @return
+ */
+ private Object[] getUniqueNames(final ChadoTransaction tsn)
+ {
+ if(tsn.getGff_feature() == null)
+ return null;
+ if(tsn.getGff_feature().getSegmentRangeStore() == null ||
+ tsn.getGff_feature().getSegmentRangeStore().size() < 2 ||
+ tsn.getFeatureObject() instanceof FeatureProp)
+ return new Object[]{ tsn.getUniquename() };
+ else
+ return tsn.getGff_feature().getSegmentRangeStore().keySet().toArray();
+ }
+
+ /**
+ * Construct an exeption message from the ChadoTransaction
+ * @param re
+ * @param sql
+ * @param ncommit
+ * @return
+ */
+ private String constructExceptionMessage(final RuntimeException re,
+ final Vector<ChadoTransaction> sql,
+ final int ncommit)
+ {
+ String msg = "";
+ if(ncommit > -1 && ncommit < sql.size())
+ {
+ final ChadoTransaction t_failed = sql.get(ncommit);
+ if(t_failed.getType() == ChadoTransaction.DELETE)
+ msg = "DELETE failed ";
+ else if(t_failed.getType() == ChadoTransaction.INSERT)
+ msg = "INSERT failed ";
+ else if(t_failed.getType() == ChadoTransaction.UPDATE)
+ msg = "UPDATE failed ";
+
+ if(t_failed.getUniquename() != null)
+ msg = msg + "for " + t_failed.getUniquename()+":";
+ else if(t_failed.getFeatureObject() != null &&
+ t_failed.getFeatureObject() instanceof Feature)
+ {
+ final Feature chadoFeature = (Feature)t_failed.getFeatureObject();
+ if(chadoFeature.getUniqueName() != null)
+ msg = msg + "for " + chadoFeature.getUniqueName() +":";
+ }
+
+ msg = msg+"\n";
+ }
+ return msg + re.getMessage();
+ }
+
+ /**
+ * Commit a single chado transaction
+ * @param tsn
+ * @param dao
+ */
+ private void commitChadoTransaction(final ChadoTransaction tsn,
+ final GmodDAO dao,
+ final Timestamp ts)
+ {
+ if(tsn.getType() == ChadoTransaction.UPDATE)
+ {
+ if(tsn.getFeatureObject() instanceof Feature)
+ {
+ Feature feature = (Feature)tsn.getFeatureObject();
+
+ if(feature.getUniqueName() != null)
+ {
+ final String uniquename;
+ if(tsn.getOldUniquename() != null)
+ uniquename = (String)tsn.getOldUniquename();
+ else
+ uniquename = feature.getUniqueName();
+
+ Feature old_feature
+ = dao.getFeatureByUniqueName(uniquename, tsn.getFeatureKey());
+
+ if(old_feature != null)
+ feature.setFeatureId( old_feature.getFeatureId() );
+
+ tsn.setOldUniquename(feature.getUniqueName());
+ }
+ feature.setTimeLastModified(ts);
+ }
+
+ if(tsn.getFeatureObject() instanceof FeatureLoc)
+ {
+ // update any featurelocs on the polypeptide
+ String keyStr = tsn.getGff_feature().getKey().getKeyString();
+ if(keyStr.equals("polypeptide"))
+ adjustChildCoordinates((FeatureLoc)tsn.getFeatureObject(), dao);
+ }
+
+ dao.merge(tsn.getFeatureObject());
+
+ if(tsn.getFeatureObject() instanceof FeatureLoc)
+ {
+ // if CDS featureloc is changed update residues on
+ // associated features
+ List<ChadoTransaction> tsns = getUpdateResiduesColumnTransactions(tsn);
+ if(tsns != null)
+ {
+ for(int i=0; i<tsns.size(); i++)
+ dao.merge( tsns.get(i).getFeatureObject() );
+ }
+ }
+ }
+ else if(tsn.getType() == ChadoTransaction.INSERT)
+ {
+ if(tsn.getFeatureObject() instanceof FeatureCvTerm)
+ ArtemisUtils.inserFeatureCvTerm(dao, (FeatureCvTerm)tsn.getFeatureObject());
+ else
+ {
+ // set srcfeature_id
+ if(tsn.getFeatureObject() instanceof Feature &&
+ ((Feature) tsn.getFeatureObject()).getFeatureLoc() != null)
+ {
+ FeatureLoc featureloc = ((Feature) tsn.getFeatureObject()).getFeatureLoc();
+ Feature featureBySrcFeatureId = new Feature();
+ featureBySrcFeatureId.setFeatureId(Integer.parseInt(srcFeatureId));
+ featureloc.setFeatureBySrcFeatureId(featureBySrcFeatureId);
+ }
+
+ if(tsn.getFeatureObject() instanceof Feature &&
+ tsn.getGff_feature() != null)
+ {
+ String keyStr = tsn.getGff_feature().getKey().getKeyString();
+ if(GeneUtils.isFeatureToUpdateResidues(keyStr))
+ {
+ String residues = GeneUtils.deriveResidues(tsn.getGff_feature());
+ if(residues != null)
+ ((Feature)tsn.getFeatureObject()).setResidues(residues.getBytes());
+ }
+ }
+ dao.persist(tsn.getFeatureObject());
+ }
+
+ }
+ else if(tsn.getType() == ChadoTransaction.DELETE)
+ {
+ if(tsn.getFeatureObject() instanceof FeatureCvTerm)
+ ArtemisUtils.deleteFeatureCvTerm(dao, (FeatureCvTerm)tsn.getFeatureObject());
+ else
+ dao.delete(tsn.getFeatureObject());
+ }
+ }
+
+ /**
+ * Check the <code>Timestamp</code> on a feature (for versioning).
+ * @param schema the schema
+ * @param uniquename the feature uniquename
+ * @param timestamp the last read feature timestamp
+ */
+ public boolean checkFeatureTimestamp(final String schema,
+ final String uniquename,
+ final GmodDAO dao,
+ final String keyName,
+ final Hashtable<String, Feature> featureIdStore,
+ final ChadoTransaction tsn)
+ {
+ final Timestamp timestamp = tsn.getLastModified();
+ final Object featureObject = tsn.getFeatureObject();
+
+ final Feature feature = dao.getFeatureByUniqueName(uniquename, keyName);
+ if(feature == null)
+ return true;
+
+ featureIdStore.put(uniquename, feature);
+
+ if(featureObject instanceof FeatureProp)
+ ((FeatureProp)featureObject).setFeature(feature);
+ else if(featureObject instanceof FeatureLoc)
+ {
+ if(((FeatureLoc)featureObject).getFeatureByFeatureId().getUniqueName().equals(uniquename))
+ {
+ logger4j.debug("Setting featureId for:" + uniquename );
+ ((FeatureLoc)featureObject).setFeatureByFeatureId(feature);
+ }
+ }
+
+ final Timestamp now = feature.getTimeLastModified();
+
+ if(now != null && timestamp != null)
+ {
+ now.setNanos(0);
+ timestamp.setNanos(0);
+
+ if(now.compareTo(timestamp) != 0)
+ {
+ final SimpleDateFormat date_format =
+ new SimpleDateFormat("dd.MM.yyyy hh:mm:ss z");
+
+ //System.out.println(date_format.format(now)+" "+
+ // date_format.format(timestamp));
+ int select = JOptionPane.showConfirmDialog(null, uniquename +
+ " has been altered at :\n"+
+ date_format.format(now)+"\nOverwite?",
+ "Feature Changed",
+ JOptionPane.OK_CANCEL_OPTION);
+ if(select == JOptionPane.OK_OPTION)
+ return true;
+ else
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Adjust the coordinates of features located on the feature which
+ * has coordinates being changed.
+ * @param newFeatureLoc
+ * @param dao
+ */
+ private void adjustChildCoordinates(final FeatureLoc newFeatureLoc,
+ final GmodDAO dao)
+ {
+ int featureId = newFeatureLoc.getFeatureByFeatureId().getFeatureId();
+ int srcFeatureId = newFeatureLoc.getFeatureBySrcFeatureId().getFeatureId();
+ List<FeatureLoc> oldFeatureLocs = dao.getFeatureLocsByFeatureId(featureId);
+ FeatureLoc oldFeatureLoc = null;
+
+ for(int i=0; i<oldFeatureLocs.size(); i++)
+ {
+ FeatureLoc fl = oldFeatureLocs.get(i);
+ if(srcFeatureId == fl.getFeatureBySrcFeatureId().getFeatureId())
+ {
+ oldFeatureLoc = fl;
+ break;
+ }
+ }
+
+ if(oldFeatureLoc == null)
+ return;
+
+ if(oldFeatureLoc.getFmin() != newFeatureLoc.getFmin())
+ {
+ List<FeatureLoc> featureLocsBySrcFeatureId =
+ dao.getFeatureLocsBySrcFeatureId(featureId);
+ for(int i=0; i<featureLocsBySrcFeatureId.size(); i++)
+ {
+ FeatureLoc onPolyPepLoc = featureLocsBySrcFeatureId.get(i);
+ onPolyPepLoc.setFmin(onPolyPepLoc.getFmin() -
+ (newFeatureLoc.getFmin() - oldFeatureLoc.getFmin()));
+
+ onPolyPepLoc.setFmax(onPolyPepLoc.getFmax() -
+ (newFeatureLoc.getFmin() - oldFeatureLoc.getFmin()));
+ dao.merge(onPolyPepLoc);
+ }
+ }
+ }
+
+ /**
+ * Update residues column when the CDS featureloc is updated
+ * @param tsn
+ */
+ public static List<ChadoTransaction> getUpdateResiduesColumnTransactions(
+ final ChadoTransaction tsn)
+ {
+ String keyStr = tsn.getGff_feature().getKey().getKeyString();
+ List<ChadoTransaction> transactions = null;
+ if(DatabaseDocument.EXONMODEL.equals(keyStr) &&
+ Options.getOptions().getOptionValues("sequence_update_features") != null)
+ {
+ ChadoCanonicalGene chadoGene = tsn.getGff_feature().getChadoGene();
+ if(chadoGene == null)
+ return null;
+
+ uk.ac.sanger.artemis.io.Feature transcript =
+ chadoGene.getTranscriptFeatureFromName(
+ GeneUtils.getUniqueName(tsn.getGff_feature()));
+
+ if(transcript != null)
+ {
+ FeatureForUpdatingResidues featureForUpdatingResidues =
+ GeneUtils.getFeatureForUpdatingResidues((GFFStreamFeature) transcript);
+ if(featureForUpdatingResidues != null)
+ {
+ ChadoTransaction tsnResidue =
+ new ChadoTransaction(ChadoTransaction.UPDATE,
+ featureForUpdatingResidues,
+ null, null, null, "RESIDUE SEQUENCE UPDATE "+
+ GeneUtils.getUniqueName(transcript));
+ if(transactions == null)
+ transactions = new Vector<ChadoTransaction>();
+ transactions.add(tsnResidue);
+ logger4j.debug(tsnResidue.getLogComment());
+ }
+
+ uk.ac.sanger.artemis.io.Feature pp =
+ chadoGene.getProteinOfTranscript(GeneUtils.getUniqueName(transcript));
+ if(pp != null)
+ {
+ FeatureForUpdatingResidues ppForUpdatingResidues =
+ GeneUtils.getFeatureForUpdatingResidues((GFFStreamFeature) pp);
+ if(ppForUpdatingResidues != null)
+ {
+ ChadoTransaction tsnResidue =
+ new ChadoTransaction(ChadoTransaction.UPDATE,
+ ppForUpdatingResidues,
+ null, null, null, "RESIDUE SEQUENCE UPDATE "+
+ GeneUtils.getUniqueName(pp));
+ if(transactions == null)
+ transactions = new Vector<ChadoTransaction>();
+ transactions.add(tsnResidue);
+ logger4j.debug(tsnResidue.getLogComment());
+ }
+ }
+ }
+ }
+ return transactions;
+ }
+
+
+ public static void main(String args[])
+ {
+ try
+ {
+ GmodDAO dao;
+ DatabaseEntrySource src = new DatabaseEntrySource();
+ src.setLocation(true);
+
+ if(System.getProperty("ibatis") == null)
+ dao = new JdbcDAO(src.getLocation(), src.getPfield());
+ else
+ dao = new IBatisDAO(src.getPfield());
+
+ Feature feature = new Feature();
+ feature.setUniqueName(args[0]);
+ List<String> schemas = new Vector<String>();
+ schemas.add(args[1]);
+ List<Feature> featureList = new Vector<Feature>();
+ featureList.add(dao.getFeatureByUniqueName(args[0], "polypeptide"));
+ System.out.println("FINISHED getFeature()");
+ for(int i = 0; i < featureList.size(); i++)
+ {
+ feature = featureList.get(i);
+
+ String abb = feature.getOrganism().getAbbreviation();
+ String type = feature.getCvTerm().getName();
+ int fmin = feature.getFeatureLoc().getFmin().intValue() + 1;
+ int fmax = feature.getFeatureLoc().getFmax().intValue();
+ String featprop =
+ ((new Vector<FeatureProp>(feature.getFeatureProps()).get(0))).getCvTerm().getName();
+
+ System.out.print(fmin+".."+fmax);
+ System.out.print(" "+type);
+ System.out.print(" "+featprop);
+ System.out.print(" "+feature.getFeatureLoc().getStrand());
+ System.out.print(" "+feature.getUniqueName());
+ System.out.print(" "+abb);
+ System.out.println(" "+Integer.toString(feature.getFeatureId()));
+
+/* Hashtable synonyms = getAllFeatureSynonyms(dao, null);
+ Vector syns = (Vector)synonyms.get(new Integer(feature.getId()));
+ for(int j=0; j<syns.size(); j++)
+ {
+ FeatureSynonym alias = (FeatureSynonym)syns.get(j);
+ System.out.print(" "+alias.getSynonym().getCvterm().getName()+
+ "="+alias.getSynonym().getName());
+ }*/
+
+ System.out.println(" ");
+ }
+ }
+ catch(SQLException sqle)
+ {
+ sqle.printStackTrace();
+ }
+ catch(RuntimeException re)
+ {
+ re.printStackTrace();
+ }
+ catch(ConnectException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public Document getParent()
+ {
+ return null;
+ }
+
+ /**
+ * Find from a list the FeatureLoc with a given srcFeature
+ * @param locs
+ * @param srcfeature_id
+ * @return
+ */
+ public static FeatureLoc getFeatureLoc(List<FeatureLoc> locs, int srcfeature_id)
+ {
+ for(int i=0; i<locs.size(); i++)
+ {
+ FeatureLoc loc = locs.get(i);
+ if(loc.getFeatureBySrcFeatureId().getFeatureId() == srcfeature_id)
+ return loc;
+ }
+ return null;
+ }
+
+
+ public String getSrcFeatureId()
+ {
+ return srcFeatureId;
+ }
+
+
+ private JPasswordField getPfield()
+ {
+ return pfield;
+ }
+
+
+ /**
+ * Return true if this looks like a single schema postgres
+ * database
+ * @return
+ */
+ public boolean isSingleSchema()
+ {
+ return singleSchema;
+ }
+
+ /**
+ * Ensure exon featurelocs are in the correct order
+ */
+ class LocationComarator implements Comparator<FeatureLoc>
+ {
+ public int compare(FeatureLoc o1, FeatureLoc o2)
+ {
+ int loc1 = o1.getFmin().intValue();
+ int loc2 = o2.getFmin().intValue();
+
+ if(loc2 == loc1)
+ return 0;
+ int strand = o1.getStrand().intValue();
+
+ if(strand < 0)
+ {
+ if(loc2 > loc1)
+ return 1;
+ else
+ return -1;
+ }
+ else
+ {
+ if(loc2 > loc1)
+ return -1;
+ else
+ return 1;
+ }
+ }
+ }
+
+ class CvTermThread extends Thread
+ {
+ private GmodDAO dao;
+ CvTermThread(final GmodDAO dao)
+ {
+ this.dao = dao;
+ }
+
+ public void run()
+ {
+ getCvterms(dao);
+ }
+ }
+
+ public void setRange(Range range)
+ {
+ this.range = range;
+ }
+
+
+ public Hashtable<String, Feature> getIdFeatureStore()
+ {
+ return idFeatureStore;
+ }
+
+
+ public boolean isLazyFeatureLoad()
+ {
+ return lazyFeatureLoad;
+ }
+
+
+ public void setLazyFeatureLoad(boolean lazyFeatureLoad)
+ {
+ this.lazyFeatureLoad = lazyFeatureLoad;
+ }
+
+ /**
+ * Set the types that define what entries are created. Each is given an
+ * entry name and the features within that entry.
+ * @param types
+ */
+ public static void setTYPES(String[][][] types)
+ {
+ TYPES = types;
+ }
+
+ /**
+ * Get the types that define what entries are created.
+ * @return
+ */
+ public static String[][][] getTYPES()
+ {
+ return TYPES;
+ }
+}
diff --git a/uk/ac/sanger/artemis/util/DatabaseLocationParser.java b/uk/ac/sanger/artemis/util/DatabaseLocationParser.java
new file mode 100644
index 0000000..4179fa2
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/DatabaseLocationParser.java
@@ -0,0 +1,374 @@
+/* DatabaseLocationParser.java
+ *
+ * created: Jun 2013
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+package uk.ac.sanger.artemis.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *
+ * @author Eric Rasche <rasche.eric at yandex.ru>
+ */
+public class DatabaseLocationParser {
+
+ private String host;
+ private String database;
+ private int port = 0;
+ private String db_engine = "postgresql";
+ private String protocol = "jdbc";
+ private static org.apache.log4j.Logger logger4j =
+ org.apache.log4j.Logger.getLogger(DatabaseLocationParser.class);
+ /**
+ * Desire use of Protocol in the final URL. i.e., "jdbc:"
+ */
+ public static final int PROTOCOL = 1;
+ /**
+ * Desire use of scheme in final url. i.e., "postgres://"
+ */
+ public static final int SCHEME = 2;
+ /**
+ * Desire use of database name in final url.
+ *
+ */
+ public static final int DATABASE_NAME = 4;
+ /**
+ * Desire listing of query parameters in final url. e.g.,
+ * "user=name&ssl=true"
+ */
+ public static final int QUERY_PARAMS = 8;
+ private Map<String, String> params = new HashMap<String, String>();
+
+ /**
+ * Empty initializer
+ */
+ public DatabaseLocationParser() {
+ }
+
+ /**
+ * Create a new DLP object from a given URL
+ *
+ * @param url
+ */
+ public DatabaseLocationParser(String url) {
+ setFromURLString(url);
+ }
+
+ /**
+ * Set the URL internally and parse out important portions.
+ *
+ * @param url
+ */
+ public void setFromURLString(String url) {
+ logger4j.debug("DLP was called with a URL of [" + url + "]");
+ try {
+ //"jdbc:postgres://localhost:5432/drupal6?user=drupal6&ssl=true";
+
+ //If it's prefixed, remove that so URI parsing is correct
+ if (url.startsWith("jdbc:")) {
+ url = url.substring(5);
+ }
+ if (!url.startsWith(db_engine + "://")) {
+ url = db_engine + "://" + url;
+ }
+
+
+ URI db_loc = new URI(url);
+
+ logger4j.debug("URI " + db_loc.toString());
+ logger4j.debug("Host: " + db_loc.getHost());
+ logger4j.debug("Port: " + db_loc.getPort());
+ logger4j.debug("Engine: " + db_loc.getScheme());
+ logger4j.debug("DB: " + db_loc.getPath());
+
+ host = db_loc.getHost();
+
+ database = db_loc.getPath().substring(1);
+
+ port = db_loc.getPort();
+
+ db_engine = db_loc.getScheme();
+
+ //Split on '&' and parse each subunit
+ String[] query_params = db_loc.getQuery().split("&");
+ for (int i = 0; i < query_params.length; i++) {
+ //Split based on the equals sign
+ logger4j.debug("Given a parameter:" + query_params[i]);
+ String[] parts = query_params[i].split("=");
+
+
+ //This will fail for input like user=chad=o&ssl=true
+ //As we'll only grab user=chad
+ // Then again, who has an equals sign in their username
+ if (parts.length > 1) {
+ params.put(parts[0], parts[1]);
+ logger4j.debug("[" + parts[0] + "," + parts[1] + "]");
+
+ } else {
+ // This might fail strangely, but then again they're providing funky URLs
+ params.put("user", parts[0]);
+ logger4j.debug("[user," + parts[0] + "]");
+ }
+ }
+ } catch (URISyntaxException ex) {
+ logger4j.warn("Error parsing URL [" + url + "]" + ex);
+ }
+ logger4j.debug("This has a complete_url of [" + getCompleteURL() + "]");
+ }
+
+ /**
+ * Returns the complete URL
+ *
+ * @return complete url
+ */
+ public String getCompleteURL() {
+ return getURLWithFixes(PROTOCOL | SCHEME
+ | DATABASE_NAME | QUERY_PARAMS);
+ }
+
+ /**
+ * Returns the URL as required for a DriverManager.getConnection object
+ *
+ * @return url as required for a SQL connection
+ */
+ public String getConnectionString() {
+ return getURLWithFixes(PROTOCOL | SCHEME
+ | DATABASE_NAME | QUERY_PARAMS);
+ }
+
+ /**
+ * Returns the unprefixed URL, for classes that automatically prepend
+ * 'jdbc:postgres://'.
+ *
+ * This is important for uk/ac/sanger/artemis/chado/DbSqlConfig.java
+ *
+ * @return unprefixed url with database and query parameters appended.
+ */
+ public String getUnprefixedURL() {
+ return getURLWithFixes(DATABASE_NAME | QUERY_PARAMS);
+ }
+
+ /**
+ * Returns a URL with a selection of modifications.
+ *
+ * Using a binary OR, one can select which modifications should be applied
+ * to create the final URL. These modifications consist of:
+ *
+ * - PROTOCOL: "jdbc:" - SCHEME: "postgresql://" - DATABASE_NAME: The
+ * supplied database name - QUERY_PARAMS: The supplied query parameters
+ * (user, ssl, etc)
+ *
+ * @param modifications, a binary OR'd selection of PROTOCOL, SCHEME,
+ * DATABASE_NAME, QUERY_PARAMS, all of which are available as public final
+ * static integers from this class
+ * @return String version of a URL, modified according to the rules
+ * supplied.
+ */
+ private String getURLWithFixes(int modifications) {
+ try {
+ String scheme = new String(db_engine);
+ String userInfo = null;
+ int db_port = new Integer(port);
+ String db_name = "/" + database;
+ String query_params = "";
+ String fragment = null;
+
+ String result = "";
+ if ((modifications & PROTOCOL) == PROTOCOL) {
+ result += protocol + ":";
+ }
+ if ((modifications & SCHEME) != SCHEME) {
+ scheme = null;
+ }
+ if ((modifications & DATABASE_NAME) != DATABASE_NAME) {
+ db_name = null;
+ }
+
+ // Query Parameters
+ // "user=chado_user&ssl=true"
+ if ((modifications & QUERY_PARAMS) == QUERY_PARAMS) {
+ if (params.size() > 0) {
+ /**
+ * Handling of the query parameters. There are other ways to
+ * do this, but it's probably never going to be more than a
+ * "user" and an "ssl" parameter, which means that in the
+ * grand scheme of things, joining a couple strings together
+ * isn't a big issue
+ */
+ Set<String> keys = params.keySet();
+ java.util.Iterator<String> it = keys.iterator();
+ while (it.hasNext()) {
+ String key = it.next();
+ query_params += key + "=" + params.get(key) + "&";
+ }
+ query_params = query_params.substring(0, query_params.length() - 1);
+ }
+ }
+ URI uri_result = new URI(scheme, userInfo, host, db_port, db_name, query_params, fragment);
+ logger4j.debug("Pre-final URL: " + uri_result.toString());
+
+ result = result + uri_result.toString();
+ //Bugfix. Even if SCHEME is null, // is still prepended. so we remove
+ if ((modifications & SCHEME) != SCHEME && (modifications & PROTOCOL) != PROTOCOL) {
+ result = result.substring(2);
+ }
+ return result;
+ } catch (URISyntaxException ex) {
+ logger4j.error("Could not construct URL. This will likely cause an "
+ + "SQL connection failure. ");
+ }
+ return null;
+ }
+
+ /**
+ * Checks whether SSL is enabled
+ *
+ * @return true if SSL is enabled
+ */
+ public boolean isSSLEnabled() {
+ if (params.containsKey("ssl")) {
+ return params.get("ssl").equals("true");
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the hostname
+ *
+ * @return the hostname
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Returns the database
+ *
+ * @return the database name
+ */
+ public String getDatabase() {
+ return database;
+ }
+
+ /**
+ * Returns the port number
+ *
+ * @return port number
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Returns the username of the user connecting to the database
+ *
+ * @return username
+ */
+ public String getUsername() {
+ if (params.containsKey("user")) {
+ return params.get("user");
+ } else {
+ return "chado"; //That's the default u/n afaik
+ }
+ }
+
+ /**
+ * Returns the database engine
+ *
+ * @return database engine (usu. postgresql)
+ */
+ public String getDBEngine() {
+ return db_engine;
+ }
+
+ /**
+ * Sets the hostname
+ *
+ * @param hostname
+ */
+ public void setHost(String hostname) {
+ host = hostname.trim();
+ }
+
+ /**
+ * Sets the database name
+ *
+ * @param new_db_name
+ */
+ public void setDatabase(String new_db_name) {
+ database = new_db_name.trim();
+ }
+
+ /**
+ * Sets the port number
+ *
+ * @param new_port_number
+ */
+ public void setPort(String new_port_number) {
+ port = Integer.parseInt(new_port_number.trim());
+ }
+
+ /**
+ * Sets the port number
+ *
+ * @param new_port_number
+ */
+ public void setPort(int new_port_number) {
+ port = new_port_number;
+ }
+
+ /**
+ * Sets the username to connect with
+ *
+ * @param new_username
+ */
+ public void setUsername(String new_username) {
+ params.put("user", new_username.trim());
+ }
+
+ /**
+ * Enables or disables SSL in connection URL
+ *
+ * @param is_enabled
+ */
+ public void setSSL(boolean is_enabled) {
+ if (is_enabled) {
+ params.put("ssl", "true");
+ } else {
+ params.remove("ssl");
+ }
+ }
+
+ /**
+ * Sets the Database Engine
+ *
+ * @param new_db_engine
+ */
+ public void setDBEngine(String new_db_engine) {
+ db_engine = new_db_engine.trim();
+ }
+}
diff --git a/uk/ac/sanger/artemis/util/Document.java b/uk/ac/sanger/artemis/util/Document.java
new file mode 100644
index 0000000..1e72b4e
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/Document.java
@@ -0,0 +1,209 @@
+/* Document.java
+ *
+ * created: Thu Dec 17 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/Document.java,v 1.3 2007-03-01 15:42:00 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.*;
+
+/**
+ * Each object of this class represents a file/directory on the server (when
+ * the program is running as an applet) or a file/directory in the current
+ * directory (when the program is running stand alone). Each object
+ * encapsulates a File or URL object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: Document.java,v 1.3 2007-03-01 15:42:00 tjc Exp $
+ **/
+
+public abstract class Document
+{
+ /**
+ * Create a new Document from a (directory) location and a file name.
+ * @param location A directory Document which will be appended to the file
+ * argument
+ * @param name The name of the document to access.
+ * @exception IOException If the directory_document is a URL Document and
+ * the name argument isn't well formed then a MalformedURLException
+ * exception is thrown.
+ **/
+ public Document(Document directory_document, String name)
+ throws IOException
+ {
+ location = directory_document.append (name).getLocation ();
+ }
+
+ /**
+ * Append a String to the Document location with the correct separator.
+ * @param name The name to append.
+ **/
+ public abstract Document append(String name) throws IOException;
+
+ /**
+ * Return the name of this Document (the last element of the Document
+ * location).
+ **/
+ public abstract String getName();
+
+ /**
+ * Return a Document with the last element stripped off.
+ **/
+ public abstract Document getParent();
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and is readable.
+ **/
+ public abstract boolean readable();
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and can be written to.
+ **/
+ public abstract boolean writable();
+
+ /**
+ * Create a new InputStream object from this Document. The contents of the
+ * Document can be read from the stream.
+ * @exception IOException Thrown if the Document can't be read from
+ * (for example if it doesn't exist).
+ **/
+ public abstract InputStream getInputStream() throws IOException;
+
+ /**
+ * Create a new Reader object from this Document.
+ * @exception IOException An exception is thrown is the Document can't be
+ * read from (for example if it doesn't exist).
+ **/
+ public Reader getReader() throws IOException
+ {
+ return new InputStreamReader(getInputStream ());
+ }
+
+ /**
+ * Create a new LinePushBackReader object from this Document.
+ * @exception IOException An exception is thrown is the Document can't be
+ * read from (for example if it doesn't exist).
+ * @return A LinePushBackReader object. One object is created when
+ * getLinePushBackReader() is first called, subsequent calls will get the
+ * same object.
+ **/
+ public LinePushBackReader getLinePushBackReader() throws IOException
+ {
+ if (line_push_push_reader == null)
+ {
+ line_push_push_reader =
+ new LinePushBackReader(new InputStreamReader(getInputStream()));
+ }
+
+ return line_push_push_reader;
+ }
+
+ /**
+ * Add listener that will be passed to the ProgressInputStream constructor
+ * when getInputStream () is called.
+ **/
+ public void addInputStreamProgressListener(final InputStreamProgressListener listener)
+ {
+ listeners.add (listener);
+ }
+
+ /**
+ * Return all the InputStreamProgressListener objects for this Document.
+ **/
+ protected InputStreamProgressListenerVector getProgressListeners ()
+ {
+ return listeners;
+ }
+
+ /**
+ * Create a new OutputStream object from this Document. The contents of the
+ * Document can be written from the stream.
+ * @exception IOException Thrown if the Document can't be written.
+ **/
+ public abstract OutputStream getOutputStream() throws IOException;
+
+ /**
+ * Create a new Writer object from this Document. The Document can then be
+ * written to using the new Writer object. The old centents of the
+ * Document will be lost.
+ * @exception ReadOnlyException is thrown if the Document is read only (a
+ * URLDocument will always be read only).
+ **/
+ public Writer getWriter() throws IOException
+ {
+ final int BUFFER_SIZE = 100000;
+ return new BufferedWriter (new OutputStreamWriter (getOutputStream ()),
+ BUFFER_SIZE);
+ }
+
+ /**
+ * Return a String representation of this Document.
+ **/
+ public String toString()
+ {
+ return getLocation().toString();
+ }
+
+ /**
+ * Create a new Document with the given location.
+ **/
+ protected Document (Object location)
+ {
+ if(location == null)
+ throw new Error ("internal error - created a null Document");
+
+ this.location = location;
+ }
+
+ /**
+ * Return the raw location object from this Document - eg a File or a URL.
+ **/
+ public Object getLocation()
+ {
+ return location;
+ }
+
+ protected void setLocation(Object location)
+ {
+ this.location = location;
+ }
+
+ /**
+ * The actual location object representing this Document - eg a File or URL
+ * object.
+ **/
+ private Object location = null;
+
+ /**
+ * Returned by getLinePushBackReader().
+ **/
+ private LinePushBackReader line_push_push_reader = null;
+
+ /**
+ * InputStreamProgressEvents are sent to this objects.
+ **/
+ private final InputStreamProgressListenerVector listeners =
+ new InputStreamProgressListenerVector ();
+}
diff --git a/uk/ac/sanger/artemis/util/DocumentFactory.java b/uk/ac/sanger/artemis/util/DocumentFactory.java
new file mode 100644
index 0000000..f4c0fbe
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/DocumentFactory.java
@@ -0,0 +1,74 @@
+/* DocumentFactory.java
+ *
+ * created: Fri Jul 11 2003
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2003 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/DocumentFactory.java,v 1.3 2006-03-02 19:46:41 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.net.*;
+import java.io.*;
+
+import uk.ac.sanger.artemis.components.filetree.RemoteFileNode;
+
+/**
+ * A Factory for Document objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: DocumentFactory.java,v 1.3 2006-03-02 19:46:41 tjc Exp $
+ **/
+
+public class DocumentFactory {
+ /**
+ * Create a new Document of the appropriate type from the given String.
+ * eg. FileDocument for files or a URLDocument for a URL.
+ **/
+ public static Document makeDocument (final String source_string) {
+ if (source_string.indexOf ("://") != -1) {
+ try {
+ return new URLDocument (new URL (source_string));
+ } catch (MalformedURLException e) {
+ return new FileDocument (new File (source_string));
+ }
+ }
+ else
+ {
+ File file = new File (source_string);
+ if(file.exists()) // assume a local file
+ return new FileDocument(file);
+ else // assume a remote file
+ {
+ int index;
+ String parent = source_string;
+
+ if( (index = parent.lastIndexOf("/")) > -1)
+ parent = parent.substring(0,index);
+ else
+ parent = file.getParent();
+
+ RemoteFileNode node = new RemoteFileNode("", file.getName(), null,
+ parent, false);
+ return new RemoteFileDocument(node);
+ }
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/util/FTPSeekableStream.java b/uk/ac/sanger/artemis/util/FTPSeekableStream.java
new file mode 100644
index 0000000..5f44482
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/FTPSeekableStream.java
@@ -0,0 +1,331 @@
+package uk.ac.sanger.artemis.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+
+import org.apache.commons.net.ftp.FTP;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPConnectionClosedException;
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.commons.net.ftp.FTPReply;
+import org.apache.log4j.Logger;
+
+import net.sf.samtools.seekablestream.SeekableStream;
+
+/**
+ * Written independently to, but bugfixed by looking at the Savant
+ * SeekableFTPStream.
+ *
+ * @author gv1
+ *
+ */
+public class FTPSeekableStream extends SeekableStream {
+
+ private static final Logger logger = Logger
+ .getLogger(FTPSeekableStream.class);
+
+ private static final String defaultUser = "anonymous";
+ private static final String defaultPassword = "";
+
+ private URL url;
+ private String host;
+
+ private String user;
+ private String password;
+
+ private String remoteFilePath;
+ private String remoteFileName;
+
+ private FTPClient _client;
+
+ private long position = 0;
+ private long length = -1;
+
+ private File tmpFolder;
+ private File index;
+
+ public FTPSeekableStream(URL url) throws SocketException, IOException {
+ this(url, defaultUser, defaultPassword);
+ }
+
+ public FTPSeekableStream(URL url, String user, String password)
+ throws SocketException, IOException {
+
+ this.url = url;
+ this.user = user;
+ this.password = password;
+
+ host = url.getHost();
+ remoteFilePath = url.getPath();
+
+ String[] split = remoteFilePath.split("/");
+ if (split.length > 0) {
+ remoteFileName = split[split.length - 1];
+ } else {
+ remoteFileName = remoteFilePath;
+ }
+
+ logger.info(String.format("Setup a stream for %s %s %s %s", host,
+ remoteFilePath, this.user, this.password));
+
+ }
+
+ private FTPClient getClient() throws SocketException, IOException {
+
+ if (_client == null) {
+
+ FTPClient client = new FTPClient();
+
+ client.connect(host);
+ client.login(this.user, this.password);
+
+ logger.debug(client.getReplyString());
+
+ client.setFileType(FTP.BINARY_FILE_TYPE);
+ client.enterLocalPassiveMode();
+ client.setSoTimeout(10000);
+
+ int reply = client.getReplyCode();
+ logger.info(reply);
+
+ if (!FTPReply.isPositiveCompletion(reply)) {
+ close();
+ throw new IOException("FTP server refused connection.");
+ }
+
+ _client = client;
+ }
+ return _client;
+ }
+
+ @Override
+ public long length() {
+ logger.info("length " + length);
+
+ if (length != -1) {
+ return length;
+ }
+
+ try {
+ for (FTPFile f : getClient().listFiles(remoteFilePath)) {
+ if (f.getName().equals(remoteFilePath)) {
+ length = f.getSize();
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return length;
+
+ }
+
+ @Override
+ public int read(byte[] bytes, int offset, int length)
+ throws IOException {
+
+ InputStream in = initStream();
+
+ if (in == null) {
+ throw new IOException("Could not get stream");
+ }
+
+ int i = 0;
+
+ while (i < length) {
+ int bytesRead = in.read(bytes, offset + i, length - i);
+ if (bytesRead < 0) {
+ if (i == 0) {
+ return -1;
+ } else {
+ break;
+ }
+ }
+
+ i += bytesRead;
+ }
+
+ finishStream(in);
+
+ position += i;
+
+ return i;
+ }
+
+ @Override
+ public void close() throws IOException {
+
+ if (_client != null) {
+
+ try {
+ _client.completePendingCommand();
+ } catch (IOException e) {
+ logger.error(e);
+ }
+ try {
+ _client.logout();
+ } catch (IOException e) {
+ logger.error(e);
+ }
+
+ _client.disconnect();
+ _client = null;
+ }
+ }
+
+ @Override
+ public boolean eof() throws IOException {
+ if (position >= length()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String getSource() {
+ return url.toString();
+ }
+
+ @Override
+ public void seek(long position) throws IOException {
+ logger.info("seek " + position);
+ this.position = position;
+
+ }
+
+ @Override
+ public int read() throws IOException {
+ logger.info("read");
+
+ InputStream in = initStream();
+
+ int read = in.read();
+ position++;
+
+ finishStream(in);
+
+ return read;
+
+ }
+
+ private InputStream initStream() throws SocketException, IOException {
+ FTPClient client = getClient();
+ client.setRestartOffset(position);
+ InputStream in = client.retrieveFileStream(remoteFilePath);
+ return in;
+ }
+
+ private void finishStream(InputStream in) throws IOException {
+ in.close();
+ try {
+ getClient().completePendingCommand();
+ } catch (FTPConnectionClosedException suppressed) {
+ } catch (SocketTimeoutException stx) {
+ close();
+ }
+
+ }
+
+ public File getTmpFolder() {
+ if (tmpFolder == null) {
+ tmpFolder = new File("/tmp/");
+ }
+ return tmpFolder;
+ }
+
+ public void setTmpFolder(File tmpFolder) throws IOException {
+ if (!tmpFolder.isDirectory()) {
+ throw new IOException("File " + tmpFolder.getName()
+ + " is not a folder");
+ }
+ this.tmpFolder = tmpFolder;
+ }
+
+ public File getIndexFile() throws IOException {
+
+ if (index == null) {
+
+ String indexFileName = remoteFileName + ".bai";
+ String localPath = getTmpFolder().getAbsolutePath() + "/"
+ + indexFileName;
+
+ File localFile = new File(localPath);
+
+ if (!localFile.isFile()) {
+
+ String remotePath = remoteFilePath + ".bai";
+ logger.info(String.format("Downloading from %s to %s",
+ remotePath, localPath));
+
+ FTPClient client = getClient();
+ client.setRestartOffset(0);
+
+
+ InputStream in = null;
+ FileOutputStream out = null;
+
+ try {
+
+ in = client.retrieveFileStream(remotePath);
+ out = new FileOutputStream(localPath);
+
+ byte[] buffer = new byte[1024];
+
+ int len;
+ int total = 0;
+
+ while ((len = in.read(buffer)) > 0) {
+ total += len;
+ out.write(buffer, 0, len);
+ }
+
+ logger.info("Index Size in bytes : " + total);
+
+ client.completePendingCommand();
+
+ index = new File(localPath);
+
+ if (!index.isFile()) {
+ throw new IOException(
+ "Could not save the index file locally");
+ }
+
+ logger.info("Saved " + index.getAbsolutePath());
+
+ } finally {
+
+ if (in != null) {
+ in.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+
+ }
+
+ } else {
+ logger.info("Using cached index " + localFile.getAbsolutePath());
+ index = localFile;
+ }
+
+ logger.info("File size " + index.length());
+
+ }
+
+ return index;
+
+ }
+
+ @Override
+ public long position() throws IOException
+ {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/util/FastVector.java b/uk/ac/sanger/artemis/util/FastVector.java
new file mode 100644
index 0000000..b9d1712
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/FastVector.java
@@ -0,0 +1,101 @@
+/* FastVector.java
+ *
+ * created: Sun Feb 20 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/FastVector.java,v 1.4 2006-08-09 16:35:31 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Collections;
+
+/**
+ * This class implements a Vector of Objects with a fast version of
+ * contains().
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: FastVector.java,v 1.4 2006-08-09 16:35:31 tjc Exp $
+ *
+ **/
+
+public class FastVector extends ArrayList
+{
+ /**
+ * Performs the same function as Vector.addElement()
+ */
+ public boolean add(Object object)
+ {
+ if(object == null)
+ throw new Error("internal error - adding a null object");
+ else if(contains(object))
+ throw new Error("internal error - object added a second time");
+
+ return super.add(object);
+ }
+
+ /**
+ * Performs the same function as Vector.lastElement()
+ **/
+ public Object lastElement()
+ {
+ return (Object)get(size() - 1);
+ }
+
+ /**
+ * Insert an Object after another.
+ * @param old_object The new_object will be inserted after this object
+ * or at the start if old_object isn't in the vector.
+ * @param new_object The new object to insert.
+ **/
+ public void insertElementAfter(Object old_object, Object new_object)
+ {
+ final int old_object_index = indexOf(old_object);
+
+ if(old_object_index == -1)
+ add(0, new_object);
+ else
+ add(old_object_index + 1, new_object);
+ }
+
+ /**
+ * Replace the Object at the given index. (Performs the same function as
+ * Vector.elementAt())
+ **/
+ public void setElementAt(final Object object, final int index)
+ {
+ remove(index);
+ add(index, object);
+ }
+
+ /**
+ * Return a sorted copy of this vector.
+ * @param cmp The returned vector will be sorted with this Comparator.
+ **/
+ public FastVector sort(final Comparator cmp)
+ {
+ final FastVector return_vector = (FastVector)clone();
+ Collections.sort(return_vector, cmp);
+ return return_vector;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/util/FileDocument.java b/uk/ac/sanger/artemis/util/FileDocument.java
new file mode 100644
index 0000000..361396c
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/FileDocument.java
@@ -0,0 +1,176 @@
+/* FileDocument.java
+ *
+ * created: Fri Dec 18 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/FileDocument.java,v 1.1 2004-06-09 09:53:01 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.*;
+
+/**
+ * Objects of this class are Documents created from a file.
+ *
+ * @author Kim Rutherford
+ * @version $Id: FileDocument.java,v 1.1 2004-06-09 09:53:01 tjc Exp $
+ **/
+
+public class FileDocument extends Document {
+ /**
+ * Create a new FileDocument from a File.
+ * @param location This should be a file or directory name.
+ **/
+ public FileDocument (File location) {
+ super (location);
+ }
+
+ /**
+ * Append a String to the Document location with the correct separator.
+ * @param name The name to append.
+ **/
+ public Document append (String name) throws IOException {
+ return new FileDocument (new File (getFile (), name));
+ }
+
+ /**
+ * Return the name of this Document (the last element of the Document
+ * location).
+ **/
+ public String getName () {
+ return getFile ().getName ();
+ }
+
+ /**
+ * Return a Document with the last element stripped off.
+ **/
+ public Document getParent () {
+ try {
+ final File canonical_file = new File (getFile ().getCanonicalPath ());
+ return new FileDocument (new File (canonical_file.getParent ()));
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and is readable.
+ **/
+ public boolean readable () {
+ if (getFile ().exists () && getFile ().canRead ()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and can be written to.
+ **/
+ public boolean writable () {
+ if (getFile ().exists () && getFile ().canWrite ()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Create a new InputStream object from this Document. The contents of the
+ * Document can be read from the InputStream.
+ * @exception IOException Thrown if the Document can't be read from
+ * (for example if it doesn't exist).
+ **/
+ public InputStream getInputStream ()
+ throws IOException {
+ final File read_file = (File) getLocation ();
+
+ final InputStream file_input_stream =
+ new ProgressInputStream (new FileInputStream (read_file),
+ getProgressListeners ());
+
+ if (read_file.getName ().endsWith (".gz")) {
+ final BufferedInputStream ins = new BufferedInputStream(file_input_stream);
+
+ if (! System.getProperty("java.version").startsWith("1.5."))
+ {
+ if(DocumentBlockCompressed.isValidFile(ins)) // BGZIP
+ return DocumentBlockCompressed.getBlockCompressedInputStream(ins);
+ }
+
+ ins.close();
+ file_input_stream.close();
+
+ // assume GZIP
+ return new WorkingGZIPInputStream (
+ new ProgressInputStream (new FileInputStream (read_file),
+ getProgressListeners ()));
+ } else
+ return file_input_stream;
+ }
+
+ /**
+ * Create a new OutputStream object from this Document. The Document can
+ * then be written to using the new object. The old centents of the
+ * Document will be lost.
+ * @exception ReadOnlyException is thrown if the Document is read only.
+ **/
+ public OutputStream getOutputStream () throws IOException {
+ final File write_file = (File) getLocation ();
+
+ final FileOutputStream file_output_stream =
+ new FileOutputStream (write_file);
+
+ if (write_file.getName ().endsWith (".gz")) {
+ // assume this file should be gzipped
+ return new java.util.zip.GZIPOutputStream (file_output_stream);
+ } else {
+ return file_output_stream;
+ }
+ }
+
+ /**
+ * Return the File object that this FileDocument is encapsulating.
+ **/
+ public File getFile () {
+ return (File) getLocation ();
+ }
+
+}
+
+/**
+ * Requires Java 1.6
+ */
+class DocumentBlockCompressed
+{
+ protected static boolean isValidFile(BufferedInputStream ins) throws IOException
+ {
+ return net.sf.samtools.util.BlockCompressedInputStream.isValidFile(ins);
+ }
+
+ protected static InputStream getBlockCompressedInputStream(BufferedInputStream ins) throws IOException
+ {
+ return new net.sf.samtools.util.BlockCompressedInputStream(ins);
+ }
+}
diff --git a/uk/ac/sanger/artemis/util/InputStreamProgressEvent.java b/uk/ac/sanger/artemis/util/InputStreamProgressEvent.java
new file mode 100644
index 0000000..f01a375
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/InputStreamProgressEvent.java
@@ -0,0 +1,64 @@
+/* InputStreamProgressEvent.java
+ *
+ * created: Thu Jun 8 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/InputStreamProgressEvent.java,v 1.1 2004-06-09 09:53:02 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+/**
+ * This event is sent when progress is made while reading from an
+ * InputStream. "progress" means some more bytes have been read.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: InputStreamProgressEvent.java,v 1.1 2004-06-09 09:53:02 tjc Exp $
+ **/
+
+public class InputStreamProgressEvent {
+ /**
+ * Call the constructor with the char_count parameter set to this if the
+ * end of stream has been reached.
+ **/
+ public final static int EOF = -1;
+
+ /**
+ * Create a new InputStreamProgressEvent.
+ * @param char_count The total number of chars that have been read so far.
+ * Call the constructor with this parameter set to EOF at end of stream.
+ **/
+ public InputStreamProgressEvent (final int char_count) {
+ this.char_count = char_count;
+ }
+
+ /**
+ * Return the char_count that was passed to the constructor. A return
+ * value of EOF means that end of file has been hit.
+ **/
+ public int getCharCount () {
+ return char_count;
+ }
+
+ /**
+ * The count that was passed to the constructor.
+ **/
+ private int char_count;
+}
diff --git a/uk/ac/sanger/artemis/util/InputStreamProgressListener.java b/uk/ac/sanger/artemis/util/InputStreamProgressListener.java
new file mode 100644
index 0000000..3ce627f
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/InputStreamProgressListener.java
@@ -0,0 +1,41 @@
+/* InputStreamProgressListener.java
+ *
+ * created: Thu Jun 8 2000
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/InputStreamProgressListener.java,v 1.2 2005-04-01 16:08:23 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+/**
+ * This interface is implemented by classes which need to know when progress
+ * is made while reading from an InputStream. "progress" means some more
+ * bytes have been read.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: InputStreamProgressListener.java,v 1.2 2005-04-01 16:08:23 tjc Exp $
+ **/
+
+public interface InputStreamProgressListener
+{
+ void progressMade(final InputStreamProgressEvent event);
+ void progressMade(final String progress);
+}
diff --git a/uk/ac/sanger/artemis/util/InputStreamProgressListenerVector.java b/uk/ac/sanger/artemis/util/InputStreamProgressListenerVector.java
new file mode 100644
index 0000000..91d182e
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/InputStreamProgressListenerVector.java
@@ -0,0 +1,70 @@
+/* InputStreamProgressListenerVector.java
+ *
+ * created: Wed Aug 7 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/InputStreamProgressListenerVector.java,v 1.1 2004-06-09 09:53:04 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.util.Vector;
+
+/**
+ * A Vector of InputStreamProgressListener objects.
+ *
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: InputStreamProgressListenerVector.java,v 1.1 2004-06-09 09:53:04 tjc Exp $
+ **/
+
+public class InputStreamProgressListenerVector {
+ /**
+ * Create a new (empty) vector of InputStreamProgressListener objects.
+ **/
+ public InputStreamProgressListenerVector () {
+ vector = new Vector ();
+ }
+
+ /**
+ * Performs the same function as Vector.addElement ()
+ **/
+ public void add (final InputStreamProgressListener node) {
+ vector.addElement (node);
+ }
+
+ /**
+ * Performs the same function as Vector.elementAt ()
+ **/
+ public InputStreamProgressListener elementAt (final int index) {
+ return (InputStreamProgressListener) vector.elementAt (index);
+ }
+
+ /**
+ * Performs the same function as Vector.size ()
+ **/
+ public int size () {
+ return vector.size ();
+ }
+
+ /**
+ * Storage for InputStreamProgressListener objects.
+ **/
+ private Vector vector;
+}
diff --git a/uk/ac/sanger/artemis/util/LargeObjectDocument.java b/uk/ac/sanger/artemis/util/LargeObjectDocument.java
new file mode 100644
index 0000000..92f10fc
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/LargeObjectDocument.java
@@ -0,0 +1,121 @@
+/* LargeObjectDocument.java
+ *
+ * created: 2009
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2009 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.sql.SQLException;
+
+import org.postgresql.largeobject.LargeObject;
+
+/**
+ * Objects of this class are Documents created from a database
+ * and for reading blobs as a LargeObject.
+ **/
+public class LargeObjectDocument extends Document
+{
+ private String name;
+ private LargeObject obj;
+
+ /**
+ * Create a new FileDocument from a File.
+ * @param location This should be a file or directory name.
+ **/
+ public LargeObjectDocument (String location, String name, LargeObject obj)
+ {
+ super (location);
+ this.obj = obj;
+ this.name = name;
+ }
+
+ /**
+ * Return the name of this Document (the last element of the Document
+ * location).
+ **/
+ public String getName ()
+ {
+ return name;
+ }
+
+ /**
+ * Create a new InputStream object from this Document. The contents of the
+ * Document can be read from the InputStream.
+ * @exception IOException Thrown if the Document can't be read from
+ * (for example if it doesn't exist).
+ **/
+ public InputStream getInputStream ()
+ throws IOException
+ {
+ try
+ {
+ try
+ {
+ // assume zipped
+ return new WorkingGZIPInputStream (obj.getInputStream());
+ }
+ catch(IOException e)
+ {
+ obj.seek(0);
+ return obj.getInputStream();
+ }
+ }
+ catch (SQLException e)
+ {
+ e.printStackTrace();
+ throw new IOException( e.getMessage() );
+ }
+ }
+
+ /**
+ * Create a new OutputStream object from this Document. The Document can
+ * then be written to using the new object. The old centents of the
+ * Document will be lost.
+ * @exception ReadOnlyException is thrown if the Document is read only.
+ **/
+ public OutputStream getOutputStream () throws IOException
+ {
+ return null;
+ }
+
+ public Document append(String name) throws IOException
+ {
+ return null;
+ }
+
+ public Document getParent()
+ {
+ return null;
+ }
+
+ public boolean readable()
+ {
+ return false;
+ }
+
+ public boolean writable()
+ {
+ return false;
+ }
+}
diff --git a/uk/ac/sanger/artemis/util/LinePushBackReader.java b/uk/ac/sanger/artemis/util/LinePushBackReader.java
new file mode 100644
index 0000000..c2a6e33
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/LinePushBackReader.java
@@ -0,0 +1,126 @@
+/* LinePushBackReader.java
+ *
+ * created: Mon Oct 12 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/LinePushBackReader.java,v 1.1 2004-06-09 09:53:05 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.Reader;
+import java.io.LineNumberReader;
+import java.io.IOException;
+
+/**
+ * This class buffers exactly one line of input, which can be pushed back if
+ * needed. All methods in Reader can be called on objects of this class, but
+ * only readLine () understands about pushBack (). This class also counts
+ * lines using LineNumberReader (see the getLineNumber () method).
+ *
+ * @author Kim Rutherford
+ * @version $Id: LinePushBackReader.java,v 1.1 2004-06-09 09:53:05 tjc Exp $
+ */
+
+public class LinePushBackReader extends Reader {
+ /**
+ * Create a new LinePushBackReader object.
+ * @param in_stream the stream to create the object from
+ */
+ public LinePushBackReader (Reader in_stream) {
+ buffered_reader = new LineNumberReader (in_stream);
+ }
+
+
+ /**
+ * Does the same thing as Reader.readLine (), but understands about
+ * pushBack ()
+ * @return the next line from the input stream or null if we are at the end
+ * of file
+ */
+ public String readLine () throws IOException {
+ if (line_buffer != null) {
+ String tmp_string = line_buffer;
+ line_buffer = null;
+ return tmp_string;
+ } else {
+ String line = buffered_reader.readLine ();
+ return line;
+ }
+ }
+
+
+ /**
+ * Does the same thing as LineNumberReader.getLineNumber ()
+ */
+ public int getLineNumber () {
+ return buffered_reader.getLineNumber ();
+ }
+
+
+ /**
+ * Does the same thing as Reader.read ().
+ */
+ public int read(char [] cbuf, int off, int len)
+ throws IOException {
+ return buffered_reader.read (cbuf, off, len);
+ }
+
+
+ /**
+ * Does the same thing as Reader.close ().
+ */
+ public void close ()
+ throws IOException {
+ buffered_reader.close ();
+ }
+
+ /**
+ * Push a String back into the LinePushBackReader stream. This can be
+ * called once between calls to readLine (). Note that you don't have to
+ * push back the any String can pushed back.
+ * @param line The string to push back
+ * @exception PushBackException Thrown if this method is called twice before
+ * a call to readLine ()
+ */
+ public void pushBack (String line)
+ throws PushBackException {
+
+ if (line_buffer != null) {
+ throw new PushBackException ("LinePushBackReader.pushBack () " +
+ "called twice before calling readLine ()");
+ } else {
+ line_buffer = line;
+ }
+ }
+
+ /**
+ * A single line buffer that is used only when the user invokes
+ * pushBack () or calls readLine () after pushBack ().
+ */
+ private String line_buffer;
+
+ /**
+ * This is the stream that was passed to the constructor.
+ */
+ private LineNumberReader buffered_reader;
+}
+
+
diff --git a/uk/ac/sanger/artemis/util/OutOfRangeException.java b/uk/ac/sanger/artemis/util/OutOfRangeException.java
new file mode 100644
index 0000000..0297ed9
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/OutOfRangeException.java
@@ -0,0 +1,51 @@
+/* OutOfRangeException.java
+ *
+ * created: Sun Jan 10 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/OutOfRangeException.java,v 1.1 2004-06-09 09:53:06 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+/**
+ * This Exception is thrown when a value passed to a method is out of range
+ * for the given operation. For example if a base position in a Location is
+ * less than 1 or greater than the length of the sequence.
+ *
+ * @author Kim Rutherford
+ * @version $Id: OutOfRangeException.java,v 1.1 2004-06-09 09:53:06 tjc Exp $
+ **/
+
+public class OutOfRangeException extends Exception {
+ /**
+ * Create a new OutOfRangeException object.
+ **/
+ public OutOfRangeException () {
+
+ }
+
+ /**
+ * Create a new OutOfRangeException object with the given message.
+ **/
+ public OutOfRangeException (final String message) {
+ super (message);
+ }
+}
diff --git a/uk/ac/sanger/artemis/util/ProgressInputStream.java b/uk/ac/sanger/artemis/util/ProgressInputStream.java
new file mode 100644
index 0000000..015b329
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/ProgressInputStream.java
@@ -0,0 +1,170 @@
+/* ProgressInputStream.java
+ *
+ * created: Mon Sep 20 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1999 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/ProgressInputStream.java,v 1.1 2004-06-09 09:53:08 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.*;
+
+/**
+ * This is a FileInputStream which has the readLine () and close () methods
+ * overridden to show the progress in a Label component.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ProgressInputStream.java,v 1.1 2004-06-09 09:53:08 tjc Exp $
+ **/
+
+public class ProgressInputStream extends FilterInputStream {
+ /**
+ * Creates an InputStream to read from the specified InputStream object and
+ * show the progress in a Label as it goes.
+ * @param input_stream the InputStream to be read from
+ * @param listener InputStreamProgressEvent objects will be sent to this
+ * listener as progress on reading is made.
+ * @exception IOException if an I/O error occurs.
+ **/
+ public
+ ProgressInputStream (final InputStream input_stream,
+ final InputStreamProgressListenerVector listeners) {
+ super (input_stream);
+
+ this.listeners = listeners;
+ }
+
+ /**
+ * Reads up to <code>b.length</code> bytes of data from this input
+ * stream into an array of bytes. This method blocks until some input
+ * is available.
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or
+ * <code>-1</code> if there is no more data because the end of
+ * the file has been reached.
+ * @exception IOException if an I/O error occurs.
+ */
+ public int read(byte b[]) throws IOException {
+ final int result = super.read (b);
+
+ if (result > 0) {
+ byte_count += b.length;
+ maybeFireEvent ();
+ } else {
+ if (result == -1) {
+ final InputStreamProgressEvent event =
+ new InputStreamProgressEvent (InputStreamProgressEvent.EOF);
+ fireEvent (event);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Reads up to <code>len</code> bytes of data from this input stream
+ * into an array of bytes. This method blocks until some input is
+ * available.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset of the data.
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * <code>-1</code> if there is no more data because the end of
+ * the file has been reached.
+ * @exception IOException if an I/O error occurs.
+ */
+ public int read(byte b[], int off, int len) throws IOException {
+ final int result = super.read (b, off, len);
+
+ if (result > 0) {
+ byte_count += b.length;
+ maybeFireEvent ();
+ } else {
+ if (result == -1) {
+ final InputStreamProgressEvent event =
+ new InputStreamProgressEvent (InputStreamProgressEvent.EOF);
+ fireEvent (event);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Close this file input stream and release any system resources associated
+ * with the stream.
+ *
+ * @exception IOException if an I/O error occurs.
+ **/
+ public void close ()
+ throws IOException {
+ super.close ();
+
+ final InputStreamProgressEvent event =
+ new InputStreamProgressEvent (InputStreamProgressEvent.EOF);
+ fireEvent (event);
+ }
+
+ /**
+ * If we haven't sent a InputStreamProgressEvent to the listener recently,
+ * then send one.
+ **/
+ private void maybeFireEvent () {
+ if (byte_count > 50000) {
+ // do some rounding (we divide by 2 because chars are twice the size
+ // of bytes)
+ final int char_count = (byte_count / 10000) * 10000;
+
+ if (last_count + 10000 < char_count) {
+ fireEvent (new InputStreamProgressEvent (char_count));
+ last_count = char_count;
+ }
+ }
+ }
+
+ /**
+ * Send the event to all the listeners.
+ **/
+ private void fireEvent (final InputStreamProgressEvent event) {
+ if (listeners != null) {
+ for (int i = 0 ; i < listeners.size () ; ++i) {
+ listeners.elementAt (i).progressMade (event);
+ }
+ }
+ }
+
+ /**
+ * InputStreamProgressEvents are sent to these object.
+ **/
+ private final InputStreamProgressListenerVector listeners;
+
+ /**
+ * The number of bytes that have been read so far.
+ **/
+ private int byte_count = 0;
+
+ /**
+ * This is the char count that was sent to the listener last time.
+ **/
+ private int last_count = 0;
+}
diff --git a/uk/ac/sanger/artemis/util/PushBackException.java b/uk/ac/sanger/artemis/util/PushBackException.java
new file mode 100644
index 0000000..8c9d2ba
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/PushBackException.java
@@ -0,0 +1,46 @@
+/* PushBackException.java
+ *
+ * created: Mon Oct 12 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/PushBackException.java,v 1.1 2004-06-09 09:53:09 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.IOException;
+
+/**
+ * This exception is thrown if LinePushBackReader.pushBack () is called twice
+ * before a call to LinePushBackReader.readLine ().
+ *
+ * @author Kim Rutherford
+ * @version $Id: PushBackException.java,v 1.1 2004-06-09 09:53:09 tjc Exp $
+ * */
+
+public class PushBackException extends IOException {
+ /**
+ * This constructor creates a PushBackException with the given String as
+ * the message.
+ **/
+ public PushBackException (String message) {
+ super (message);
+ }
+}
diff --git a/uk/ac/sanger/artemis/util/ReadOnlyException.java b/uk/ac/sanger/artemis/util/ReadOnlyException.java
new file mode 100644
index 0000000..e80503a
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/ReadOnlyException.java
@@ -0,0 +1,54 @@
+/* ReadOnlyException.java
+ *
+ * created: Mon Nov 30 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/ReadOnlyException.java,v 1.1 2004-06-09 09:53:10 tjc Exp $
+ **/
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.IOException;
+
+/**
+ * An object of this type is thrown if an attempt is made to change a read
+ * only object.
+ *
+ * @author Kim Rutherford
+ * @version $Id: ReadOnlyException.java,v 1.1 2004-06-09 09:53:10 tjc Exp $
+ **/
+public class ReadOnlyException extends IOException {
+ /**
+ * This constructor creates a ReadOnlyException with no detail message.
+ **/
+ public ReadOnlyException () {
+ super ();
+ }
+
+ /**
+ * This constructor creates a ReadOnlyException with the given String
+ * as the message.
+ **/
+ public ReadOnlyException (String message) {
+ super (message);
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/util/RemoteFileDocument.java b/uk/ac/sanger/artemis/util/RemoteFileDocument.java
new file mode 100644
index 0000000..e619c3a
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/RemoteFileDocument.java
@@ -0,0 +1,204 @@
+/* RemoteFileDocument.java
+ *
+ * created: Aug 2005
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2005 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.*;
+import javax.swing.JOptionPane;
+import uk.ac.sanger.artemis.components.filetree.*;
+import uk.ac.sanger.artemis.j2ssh.FileTransferProgressMonitor;
+import uk.ac.sanger.artemis.j2ssh.FTProgress;
+
+/**
+ * Objects of this class are Documents created from a file.
+ *
+ **/
+
+public class RemoteFileDocument extends Document
+{
+ private String str;
+ /**
+ *
+ * Create a new RemoteFileDocument from a File.
+ * @param location This should be a file or directory name.
+ * @param remote_file File on server
+ *
+ **/
+ public RemoteFileDocument(RemoteFileNode location)
+ {
+ super(location);
+ }
+
+ /**
+ *
+ * Append a String to the Document location with the correct separator.
+ * @param name The name to append.
+ *
+ **/
+ public Document append(String name) throws IOException
+ {
+ return null;
+ }
+
+ /**
+ *
+ * Return the name of this Document (the last element of the Document
+ * location).
+ *
+ **/
+ public String getName()
+ {
+ return getRemoteFileNode().getFile();
+ }
+
+ /**
+ * Return a Document with the last element stripped off.
+ **/
+ public Document getParent()
+ {
+ return null;
+ }
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and is readable.
+ **/
+ public boolean readable()
+ {
+ return true;
+ }
+
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and can be written to.
+ **/
+ public boolean writable()
+ {
+ return true;
+ }
+
+ /**
+ * Create a new InputStream object from this Document. The contents of the
+ * Document can be read from the InputStream.
+ * @exception IOException Thrown if the Document can't be read from
+ * (for example if it doesn't exist).
+ **/
+ public InputStream getInputStream ()
+ throws IOException
+ {
+ final RemoteFileNode node = getRemoteFileNode();
+
+ FileTransferProgressMonitor monitor = new FileTransferProgressMonitor(null);
+ FTProgress progress = monitor.add(node.getFile());
+ final byte[] bytes = node.getFileContents(progress);
+ monitor.close();
+
+ final InputStream file_input_stream =
+ new ProgressInputStream(new ByteArrayInputStream(bytes),
+ getProgressListeners());
+
+ if(getName().endsWith(".gz"))
+ return new WorkingGZIPInputStream(file_input_stream);
+ else
+ return file_input_stream;
+ }
+
+ /**
+ * Create a new OutputStream object from this Document. The Document can
+ * then be written to using the new object. The old centents of the
+ * Document will be lost.
+ * @exception ReadOnlyException is thrown if the Document is read only.
+ **/
+ public OutputStream getOutputStream() throws IOException
+ {
+ final File write_file = new File(System.getProperty("user.dir")+
+ System.getProperty("file.separator")+
+ getName());
+
+ if(write_file.exists())
+ {
+ int n = JOptionPane.showConfirmDialog(null,
+ "The file :\n"+System.getProperty("user.dir")+
+ System.getProperty("file.separator")+
+ getName()+
+ "\nalready exists on the local disk.\nOverwrite?",
+ "Overwrite "+getName(),
+ JOptionPane.YES_NO_OPTION);
+ if(n == JOptionPane.NO_OPTION)
+ return null;
+ }
+
+ final FileOutputStream file_output_stream =
+ new FileOutputStream(write_file);
+
+ if(write_file.getName().endsWith(".gz"))
+ return new java.util.zip.GZIPOutputStream(file_output_stream);
+ else
+ return file_output_stream;
+ }
+
+ /**
+ *
+ * Save the entry back to the ssh server
+ *
+ */
+ public void saveEntry(final File local_file)
+ {
+ FileTransferProgressMonitor monitor = null;
+ try
+ {
+ monitor = new FileTransferProgressMonitor(null);
+ FTProgress progress = monitor.add(local_file.getName());
+
+ getRemoteFileNode().put(local_file, progress);
+ getRemoteFileNode().stat();
+ monitor.close();
+ }
+ finally
+ {
+ if(monitor != null)
+ monitor.close();
+ }
+ }
+
+ /**
+ * Return the RemoteFileNode object that this RemoteFileDocument is encapsulating.
+ **/
+ public RemoteFileNode getRemoteFileNode()
+ {
+ return (RemoteFileNode)getLocation();
+ }
+
+
+ public String toString()
+ {
+ return str;
+ }
+
+ public void setString(final String str)
+ {
+ this.str = str;
+ }
+}
diff --git a/uk/ac/sanger/artemis/util/StringVector.java b/uk/ac/sanger/artemis/util/StringVector.java
new file mode 100644
index 0000000..95f560d
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/StringVector.java
@@ -0,0 +1,225 @@
+/* StringVector.java
+ *
+ * created: Fri Jan 1 1999
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/StringVector.java,v 1.5 2005-10-13 12:06:12 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.util.Vector;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * This class implements a Vector of String objects.
+ *
+ * @author Kim Rutherford
+ * @version $Id: StringVector.java,v 1.5 2005-10-13 12:06:12 tjc Exp $
+ **/
+
+public class StringVector extends Vector<String>
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Create a new vector of String objects.
+ **/
+ public StringVector()
+ {
+ super();
+ }
+
+ /**
+ * Create a new vector which contains the given Strings.
+ **/
+ public StringVector(final String[] new_strings)
+ {
+ super(new_strings.length);
+ int len = new_strings.length;
+ for(int i = 0; i < len; ++i)
+ add(new_strings[i]);
+ }
+
+ public StringVector(final String new_string)
+ {
+ super();
+ add(new_string);
+ }
+
+ /**
+ * Create a new vector which contains the given Strings.
+ **/
+ public StringVector(final StringVector new_strings)
+ {
+ super(new_strings);
+ }
+
+ /**
+ * Call add() on each of the String objects in the given StringVector.
+ **/
+ public void add(final StringVector new_strings)
+ {
+ for (int i = 0; i < new_strings.size(); ++i)
+ add (new_strings.elementAt(i));
+ }
+
+ /**
+ * Sorts the elements of the vector using quicksort from the collections
+ * package.
+ */
+ public void sort()
+ {
+ final Comparator<String> comparator = new Comparator<String>()
+ {
+ public int compare(String fst, String snd)
+ {
+ if(fst == null)
+ {
+ if(snd == null)
+ return 0;
+ else
+ return -1;
+ }
+ else
+ {
+ if(snd == null)
+ return 1;
+ }
+ return fst.compareTo(snd);
+ }
+ };
+
+ Collections.sort(this, comparator);
+ }
+
+ /**
+ * Return a new copy of this object.
+ **/
+ public StringVector copy()
+ {
+ return new StringVector(this);
+ }
+
+ /**
+ * Return a StringVector containing the values of the given String after
+ * splitting using the given characters. If the argument String is zero
+ * length or it consists only of the characters used to split, the return
+ * vector will be zero length.
+ * @param keep_zero_char_toks If true then zero width tokens will be
+ * returned. eg. when splitting on tabs if this parameter is true then
+ * splitting this "\t\tfoo" will return "" and "foo". If this flag is
+ * false then the split_characters will be treated as a block (and "foo"
+ * would be returned in the example.
+ **/
+ public static StringVector getStrings(final String argument,
+ final String delim,
+ final boolean keep_zero_char_toks)
+ {
+ final StringVector strVector = new StringVector();
+
+ String tok;
+ String lastTok = null;
+ int idx1 = 0;
+ int idx2;
+ final int argLen = argument.length();
+
+ while(idx1 < argLen)
+ {
+ idx2 = argument.indexOf(delim,idx1);
+ if(idx2 == idx1)
+ {
+ idx1++;
+ continue;
+ }
+
+ if(idx2 < 0)
+ idx2 = argLen;
+
+ tok = argument.substring(idx1,idx2);
+ idx1 = idx2+1;
+
+ if(tok.length() == 1 &&
+ delim.indexOf(tok.charAt(0)) != -1)
+ {
+ // ignore the split characters
+ if(keep_zero_char_toks &&
+ (lastTok == null ||
+ lastTok != null && lastTok.length () == 1 &&
+ delim.indexOf (lastTok) != -1))
+ {
+ // add a space because of two split_characters in a row
+ strVector.add("");
+ }
+ }
+ else
+ strVector.add(tok);
+
+ lastTok = tok;
+ }
+
+ return strVector;
+ }
+
+ /**
+ * Return a StringVector containing the values of the given String after
+ * splitting using the given characters. If the argument String is zero
+ * length or it consists only of the characters used to split, the return
+ * vector will be zero length.
+ **/
+ public static StringVector getStrings(final String argument,
+ final String split_characters)
+ {
+ return getStrings(argument, split_characters, false);
+ }
+
+ /**
+ * Return a StringVector containing the values of the given String after
+ * splitting on whitespace. The return object contains one String for each
+ * sequence of non-whitespace characters in the argument. If the argument
+ * String is zero length or it consists only of whitespace, the return
+ * vector will be zero length.
+ **/
+ public static StringVector getStrings(final String argument)
+ {
+ return getStrings(argument, " ", false);
+ }
+
+ public static void main(String args[])
+ {
+ String argument = "a a g g g c a c g t c g c a t c g a c t c";
+ long startTime = System.currentTimeMillis();
+ for(int i=0; i<10000000; i++)
+ getStrings(argument, " ", true);
+
+ System.out.println("TIME TAKEN "+ Long.toString(System.currentTimeMillis()-startTime));
+
+ startTime = System.currentTimeMillis();
+ for(int i=0; i<10000000; i++)
+ {
+ java.util.StringTokenizer st = new java.util.StringTokenizer(argument, " ", true);
+ while(st.hasMoreTokens())
+ st.nextToken();
+ }
+ System.out.println("TIME TAKEN "+ Long.toString(System.currentTimeMillis()-startTime));
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/util/TextDocument.java b/uk/ac/sanger/artemis/util/TextDocument.java
new file mode 100644
index 0000000..2ceee5b
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/TextDocument.java
@@ -0,0 +1,175 @@
+/* TextDocument.java
+ *
+ * created: 2006
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2006 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import javax.swing.JOptionPane;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Objects of this class are Documents created from a file.
+ **/
+public class TextDocument extends Document
+{
+ /**
+ * Create a new TextDocument from a String.
+ * @param location This should be a file or directory name.
+ * @param remote_file File on server
+ **/
+ public TextDocument()
+ {
+ super("Copy and Paste");
+ }
+
+ /**
+ *
+ * Append a String to the Document location with the correct separator.
+ * @param name The name to append.
+ *
+ **/
+ public Document append(String name) throws IOException
+ {
+ return null;
+ }
+
+ /**
+ *
+ * Return the name of this Document (the last element of the Document
+ * location).
+ *
+ **/
+ public String getName()
+ {
+ return null;
+ }
+
+ /**
+ * Return a Document with the last element stripped off.
+ **/
+ public Document getParent()
+ {
+ return null;
+ }
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and is readable.
+ **/
+ public boolean readable()
+ {
+ return true;
+ }
+
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and can be written to.
+ **/
+ public boolean writable()
+ {
+ return true;
+ }
+
+ /**
+ * Create a new InputStream object from this Document. The contents of the
+ * Document can be read from the InputStream.
+ * @exception IOException Thrown if the Document can't be read from
+ * (for example if it doesn't exist).
+ **/
+ public InputStream getInputStream ()
+ throws IOException
+ {
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ //odd: the Object param of getContents is not currently used
+ Transferable contents = clipboard.getContents(null);
+
+ boolean hasTransferableText = (contents != null) &&
+ contents.isDataFlavorSupported(DataFlavor.stringFlavor);
+ byte[] bytes = null;
+
+ if(hasTransferableText)
+ {
+ try
+ {
+ bytes = ((String)contents.getTransferData(DataFlavor.stringFlavor)).getBytes();
+ }
+ catch(UnsupportedFlavorException ex)
+ {
+ //highly unlikely since we are using a standard DataFlavor
+ System.out.println(ex);
+ }
+ catch(IOException ex)
+ {
+ System.out.println(ex);
+ }
+ }
+
+ final InputStream file_input_stream =
+ new ProgressInputStream(new ByteArrayInputStream(bytes),
+ getProgressListeners());
+
+ return file_input_stream;
+ }
+
+ /**
+ * Create a new OutputStream object from this Document. The Document can
+ * then be written to using the new object. The old centents of the
+ * Document will be lost.
+ * @exception ReadOnlyException is thrown if the Document is read only.
+ **/
+ public OutputStream getOutputStream() throws IOException
+ {
+ final File write_file = new File(getName());
+
+ if(write_file.exists())
+ {
+ int n = JOptionPane.showConfirmDialog(null,
+ "The file :\n"+getName()+
+ "\nalready exists on the local disk.\nOverwrite?",
+ "Overwrite "+getName(),
+ JOptionPane.YES_NO_OPTION);
+ if(n == JOptionPane.NO_OPTION)
+ return null;
+ }
+
+ final FileOutputStream file_output_stream =
+ new FileOutputStream(write_file);
+
+ if(write_file.getName().endsWith(".gz"))
+ return new java.util.zip.GZIPOutputStream(file_output_stream);
+ else
+ return file_output_stream;
+ }
+
+}
diff --git a/uk/ac/sanger/artemis/util/URLDocument.java b/uk/ac/sanger/artemis/util/URLDocument.java
new file mode 100644
index 0000000..00857a7
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/URLDocument.java
@@ -0,0 +1,146 @@
+/* URLDocument.java
+ *
+ * created: Fri Dec 18 1998
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 1998,1999,2000 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/URLDocument.java,v 1.1 2004-06-09 09:53:16 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.net.*;
+import java.io.*;
+
+/**
+ * Objects of this class are Documents created from a URL.
+ *
+ * @author Kim Rutherford
+ * @version $Id: URLDocument.java,v 1.1 2004-06-09 09:53:16 tjc Exp $
+ **/
+
+public class URLDocument extends Document {
+ /**
+ * Create a new Document from a URL.
+ * @param location This should be a URL string giving the location on the
+ * server where a file or directory is to be found (for example
+ * getDocumentBase ()).
+ **/
+ public URLDocument (URL location) {
+ super (location);
+ }
+
+ /**
+ * Append a String to the Document location with the correct separator.
+ * @param name The name to append.
+ **/
+ public Document append (String name) throws IOException {
+ return new URLDocument (new URL (getURL ().toString () + '/' + name));
+ }
+
+ /**
+ * Return the name of this Document (the last element of the Document
+ * location).
+ **/
+ public String getName () {
+ final String url_string = getURL ().toString ();
+ final String file_url_string =
+ url_string.substring (url_string.lastIndexOf ('/') + 1);
+
+ return file_url_string;
+ }
+
+ /**
+ * Return a Document with the last element stripped off.
+ **/
+ public Document getParent () {
+ final String url_string = getURL ().toString ();
+ final String directory_url_string =
+ url_string.substring (0, url_string.lastIndexOf ('/'));
+
+ try {
+ return new URLDocument (new URL (directory_url_string));
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and is readable. Always returns true.
+ **/
+ public boolean readable () {
+ // XXX
+ return true;
+ }
+
+
+ /**
+ * Return true if and only if the Document refered to by this object exists
+ * and can be written to. Always returns false.
+ **/
+ public boolean writable () {
+ return false;
+ }
+
+
+ /**
+ * Create a new InputStream object from this Document. The contents of the
+ * Document can be read from the InputStream.
+ * @exception IOException Thrown if the Document can't be read from
+ * (for example if it doesn't exist).
+ **/
+ public InputStream getInputStream () throws IOException {
+ // location must be a URL object
+ final URL url = (URL) getLocation ();
+ final URLConnection connection = url.openConnection ();
+
+ connection.connect ();
+
+ final InputStream in_stream =
+ new ProgressInputStream (connection.getInputStream (),
+ getProgressListeners ());
+
+ if (getURL ().toString ().endsWith (".gz")) {
+ // assume this file is gzipped
+ System.out.println (getName ());
+ return new java.util.zip.GZIPInputStream (in_stream);
+ } else {
+ return in_stream;
+ }
+ }
+
+ /**
+ * Create a new OutputStream object from this Document. The contents of the
+ * Document can be written from the stream.
+ * @exception IOException Thrown if the Document can't be written.
+ **/
+ public OutputStream getOutputStream () throws IOException {
+ throw new ReadOnlyException ("this Document can not be written to");
+ }
+
+ /**
+ * Return the URL object that this URLDocument is encapsulating.
+ **/
+ private URL getURL () {
+ return (URL) getLocation ();
+ }
+}
+
+
diff --git a/uk/ac/sanger/artemis/util/WorkingGZIPInputStream.java b/uk/ac/sanger/artemis/util/WorkingGZIPInputStream.java
new file mode 100644
index 0000000..3849d62
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/WorkingGZIPInputStream.java
@@ -0,0 +1,78 @@
+/* WorkingGZIPInputStream.java
+ *
+ * created: Thu May 30 2002
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2001 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/util/WorkingGZIPInputStream.java,v 1.1 2004-06-09 09:53:17 tjc Exp $
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.*;
+
+/**
+ * A wrapper class to work around a bug in GZIPInputStream. The read()
+ * method sometimes throws a IOException complaining about a Corrupt GZIP
+ * trailer on the jdk 1.1.8-13 on the Alphas. This wrapper catches and
+ * ignores that exception.
+ * @author Kim Rutherford <kmr at sanger.ac.uk>
+ * @version $Id: WorkingGZIPInputStream.java,v 1.1 2004-06-09 09:53:17 tjc Exp $
+ **/
+
+public class WorkingGZIPInputStream extends java.util.zip.GZIPInputStream {
+ /**
+ * Creates a new input stream with the specified buffer size.
+ * @param in the input stream
+ * @param size the input buffer size
+ * @exception IOException if an I/O error has occurred
+ **/
+
+ public WorkingGZIPInputStream (InputStream in, int size)
+ throws IOException {
+ super (in, size);
+ }
+
+ /**
+ * Creates a new input stream with a default buffer size.
+ * @param in the input stream
+ * @exception IOException if an I/O error has occurred
+ **/
+ public WorkingGZIPInputStream (InputStream in)
+ throws IOException {
+ super (in);
+ }
+
+ /**
+ * Calls super.read() and then catch and ignore any IOExceptions that
+ * mention "Corrupt GZIP trailer".
+ **/
+ public int read (byte buf[], int off, int len)
+ throws IOException {
+ try {
+ return super.read (buf, off, len);
+ } catch (IOException e) {
+ if (e.getMessage ().indexOf ("Corrupt GZIP trailer") != -1) {
+ return -1;
+ } else {
+ throw e;
+ }
+ }
+ }
+}
diff --git a/uk/ac/sanger/artemis/util/ZipFileDocument.java b/uk/ac/sanger/artemis/util/ZipFileDocument.java
new file mode 100644
index 0000000..8f1bd20
--- /dev/null
+++ b/uk/ac/sanger/artemis/util/ZipFileDocument.java
@@ -0,0 +1,193 @@
+/* ZipFileDocument
+ *
+ * This file is part of Artemis
+ *
+ * Copyright (C) 2010 Genome Research Limited
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package uk.ac.sanger.artemis.util;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class ZipFileDocument extends FileDocument
+{
+ private File zipFile;
+ private String zipEntryName;
+ private byte b[] = null;
+
+ public ZipFileDocument(File zipFile, String zipEntryName)
+ {
+ super(zipFile);
+ this.zipFile = zipFile;
+ this.zipEntryName = zipEntryName;
+ }
+
+ /**
+ * Return the name of this Document.
+ **/
+ public String getName ()
+ {
+ return zipEntryName;
+ }
+
+ /**
+ * Create a new InputStream object from this Document. The contents of the
+ * Document can be read from the InputStream.
+ * @exception IOException Thrown if the Document can't be read from
+ * (for example if it doesn't exist).
+ **/
+ public InputStream getInputStream() throws IOException
+ {
+ if(!zipFile.getName().endsWith(".zip"))
+ return super.getInputStream();
+
+ if(b == null)
+ b = getEntryContent(zipFile, zipEntryName);
+
+ if(b == null)
+ {
+ if(!zipEntryName.endsWith(".gz"))
+ {
+ zipEntryName = zipEntryName + ".gz";
+ b = getEntryContent(zipFile, zipEntryName);
+ }
+
+ if(b == null)
+ return null;
+ }
+ if(zipEntryName.endsWith(".gz"))
+ return new WorkingGZIPInputStream(new ByteArrayInputStream(b));
+ return new ByteArrayInputStream(b);
+ }
+
+ public String writeTmpFile(String txt) throws IOException
+ {
+ final File tmpFile = File.createTempFile(zipEntryName, "tmp");
+ tmpFile.deleteOnExit();
+
+ FileWriter out = new FileWriter(tmpFile);
+ out.write(txt);
+ out.close();
+ return tmpFile.getAbsolutePath();
+ }
+
+ /**
+ * Returns true if the zip file contains the entry name.
+ * @return
+ */
+ private boolean containsEntry()
+ {
+ try
+ {
+ FileInputStream fis = new FileInputStream(zipFile);
+ ZipInputStream zis = new ZipInputStream(
+ new BufferedInputStream(fis));
+ ZipEntry ze;
+
+ while ((ze=zis.getNextEntry())!=null)
+ {
+ if( (ze.getName().equals(zipEntryName) || ze.getName().equals(zipEntryName+".gz")) &&
+ !ze.isDirectory())
+ {
+ zipEntryName = ze.getName();
+
+ ByteBuffer buff = new ByteBuffer();
+ b = new byte[1];
+ while(zis.read(b,0,1) != -1)
+ buff.append(b);
+
+ b = buff.getBytes();
+
+ fis.close();
+ zis.close();
+ return true;
+ }
+ }
+ fis.close();
+ zis.close();
+ }
+ catch (IOException e){}
+
+ return false;
+ }
+
+ /**
+ * Return true if and only if the Document referred to by this object exists
+ * and is readable.
+ **/
+ public boolean readable()
+ {
+ if(getFile().exists() &&
+ getFile ().canRead() &&
+ zipFile.getName().endsWith(".zip"))
+ {
+ if(containsEntry())
+ return true;
+ }
+ return false;
+ }
+
+ private static byte[] getEntryContent(File zipFile, String zipEntryName) throws IOException
+ {
+ ZipInputStream zis= new ZipInputStream(
+ new FileInputStream(zipFile));
+
+ ZipEntry ze;
+ while ((ze=zis.getNextEntry())!=null)
+ {
+ if(ze.isDirectory() || !ze.getName().equals(zipEntryName))
+ continue;
+
+ ByteBuffer buff = new ByteBuffer();
+ byte[] b = new byte[1];
+ while(zis.read(b,0,1) != -1)
+ buff.append(b);
+ return buff.getBytes();
+ }
+ return null;
+ }
+
+ public static void main(String[] args)
+ {
+ System.out.println(args[1]);
+
+ try
+ {
+ ZipFileDocument document = new ZipFileDocument(new File(args[0]), args[1]);
+ InputStream in = document.getInputStream();
+ StringBuffer out = new StringBuffer();
+ byte[] b = new byte[4096];
+ for (int n; (n = in.read(b)) != -1;) {
+ out.append(new String(b, 0, n));
+ }
+ System.out.println(new String(out));
+
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/artemis.git
More information about the debian-med-commit
mailing list